mhlist, mhbuild: Always evaluate realsizes of the MIME parts.
[mmh] / uip / mhbuild.c
1 /*
2 ** mhbuild.c -- expand/translate MIME composition files
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 /*
10 ** This code was originally part of mhn.c.  I split it into
11 ** a separate program (mhbuild.c). But the code still has some of
12 ** the mhn.c code in it.  This program needs additional
13 ** streamlining and removal of unneeded code.
14 */
15
16 #include <h/mh.h>
17 #include <fcntl.h>
18 #include <h/signals.h>
19 #include <errno.h>
20 #include <signal.h>
21 #include <h/tws.h>
22 #include <h/mime.h>
23 #include <h/mhparse.h>
24 #include <h/utils.h>
25
26 #ifdef HAVE_SYS_TIME_H
27 # include <sys/time.h>
28 #endif
29 #include <time.h>
30
31 static struct swit switches[] = {
32 #define EBCDICSW  0
33         { "ebcdicsafe", 0 },
34 #define NEBCDICSW  1
35         { "noebcdicsafe", 0 },
36 #define HEADSW  2
37         { "headers", 0 },
38 #define NHEADSW  3
39         { "noheaders", 0 },
40 #define LISTSW  4
41         { "list", 0 },
42 #define NLISTSW  5
43         { "nolist", 0 },
44 #define RFC934SW  6
45         { "rfc934mode", 0 },
46 #define NRFC934SW  7
47         { "norfc934mode", 0 },
48 #define VERBSW  8
49         { "verbose", 0 },
50 #define NVERBSW  9
51         { "noverbose", 0 },
52 #define CONTENTIDSW  10
53         { "contentid", 0 },
54 #define NCONTENTIDSW  11
55         { "nocontentid", 0 },
56 #define VERSIONSW  12
57         { "version", 0 },
58 #define HELPSW  13
59         { "help", 0 },
60 #define DEBUGSW  14
61         { "debug", -5 },
62         { NULL, 0 }
63 };
64
65
66 /*
67 ** Directory to place tmp files.  This must
68 ** be set before these routines are called.
69 */
70 char *tmp;
71
72 pid_t xpid = 0;
73
74 static char prefix[] = "----- =_aaaaaaaaaa";
75
76
77 /*
78 ** prototypes
79 */
80
81 /* mhmisc.c */
82 int make_intermediates(char *);
83 void content_error(char *, CT, char *, ...);
84
85 /* mhfree.c */
86 void free_content(CT);
87 void free_ctinfo(CT);
88 void free_encoding(CT, int);
89
90 /*
91 ** static prototypes
92 */
93 static int init_decoded_content(CT);
94 static char *fgetstr(char *, int, FILE *);
95 static int user_content(FILE *, char *, char *, CT *);
96 static void set_id(CT, int);
97 static int compose_content(CT);
98 static int scan_content(CT);
99 static int build_headers(CT);
100 static CT build_mime(char *);
101
102
103 int debugsw = 0;
104 int verbosw = 0;
105
106 int ebcdicsw = 0;
107 int listsw   = 0;
108 int rfc934sw = 0;
109 int contentidsw = 1;
110
111 /*
112 ** Temporary files
113 */
114 static char infile[BUFSIZ];
115 static int unlink_infile  = 0;
116
117 static char outfile[BUFSIZ];
118 static int unlink_outfile = 0;
119
120 static void unlink_done(int) NORETURN;
121
122 /* mhoutsbr.c */
123 int output_message(CT, char *);
124 int output_message_fp(CT, FILE *, char*);
125
126 /* mhlistsbr.c */
127 int list_all_messages(CT *, int, int, int);
128
129 /* mhmisc.c */
130 void set_endian(void);
131
132 /* mhfree.c */
133 void free_content(CT);
134
135
136 int
137 main(int argc, char **argv)
138 {
139         int headsw = 1;
140         char *cp, buf[BUFSIZ];
141         char buffer[BUFSIZ], *compfile = NULL;
142         char **argp, **arguments;
143         CT ct, cts[2];
144         FILE *fp = NULL;
145         FILE *fp_out = NULL;
146
147         done = unlink_done;
148
149 #ifdef LOCALE
150         setlocale(LC_ALL, "");
151 #endif
152         invo_name = mhbasename(argv[0]);
153
154         /* read user profile/context */
155         context_read();
156
157         arguments = getarguments(invo_name, argc, argv, 1);
158         argp = arguments;
159
160         while ((cp = *argp++)) {
161                 if (cp[0] == '-' && cp[1] == '\0') {
162                         if (compfile)
163                                 adios(NULL, "cannot specify both standard input and a file");
164                         else
165                                 compfile = cp;
166                         listsw = 0;  /* turn off -list if using std in/out */
167                         verbosw = 0;  /* turn off -verbose listings */
168                         break;
169                 }
170                 if (*cp == '-') {
171                         switch (smatch(++cp, switches)) {
172                         case AMBIGSW:
173                                 ambigsw(cp, switches);
174                                 done(1);
175                         case UNKWNSW:
176                                 adios(NULL, "-%s unknown", cp);
177
178                         case HELPSW:
179                                 snprintf(buf, sizeof(buf), "%s [switches] file", invo_name);
180                                 print_help(buf, switches, 1);
181                                 done(1);
182                         case VERSIONSW:
183                                 print_version(invo_name);
184                                 done(1);
185
186                         case EBCDICSW:
187                                 ebcdicsw++;
188                                 continue;
189                         case NEBCDICSW:
190                                 ebcdicsw = 0;
191                                 continue;
192
193                         case HEADSW:
194                                 headsw++;
195                                 continue;
196                         case NHEADSW:
197                                 headsw = 0;
198                                 continue;
199
200                         case LISTSW:
201                                 listsw++;
202                                 continue;
203                         case NLISTSW:
204                                 listsw = 0;
205                                 continue;
206
207                         case RFC934SW:
208                                 rfc934sw++;
209                                 continue;
210                         case NRFC934SW:
211                                 rfc934sw = 0;
212                                 continue;
213
214                         case CONTENTIDSW:
215                                 contentidsw = 1;
216                                 continue;
217                         case NCONTENTIDSW:
218                                 contentidsw = 0;
219                                 continue;
220
221                         case VERBSW:
222                                 verbosw++;
223                                 continue;
224                         case NVERBSW:
225                                 verbosw = 0;
226                                 continue;
227                         case DEBUGSW:
228                                 debugsw = 1;
229                                 continue;
230                         }
231                 }
232                 if (compfile)
233                         adios(NULL, "only one composition file allowed");
234                 else
235                         compfile = cp;
236         }
237
238         set_endian();
239
240         if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0)
241                 listsw  = 0;
242
243         /*
244         ** Check if we've specified an additional profile
245         */
246         if ((cp = getenv("MHBUILD"))) {
247                 if ((fp = fopen(cp, "r"))) {
248                         readconfig((struct node **) 0, fp, cp, 0);
249                         fclose(fp);
250                 } else {
251                         admonish("", "unable to read $MHBUILD profile (%s)",
252                                         cp);
253                 }
254         }
255
256         /*
257         ** Read the standard profile setup
258         */
259         if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
260                 readconfig((struct node **) 0, fp, cp, 0);
261                 fclose(fp);
262         }
263
264         /*
265         ** Check for storage directory.  If defined, we
266         ** will store temporary files there.  Else we
267         ** store them in standard nmh directory.
268         */
269         if ((cp = context_find(nmhstorage)) && *cp)
270                 tmp = concat(cp, "/", invo_name, NULL);
271         else
272                 tmp = getcpy(toabsdir(invo_name));
273
274         /* Check if we have a file to process */
275         if (!compfile)
276                 adios(NULL, "need to specify a %s composition file",
277                                 invo_name);
278
279         /*
280         ** Process the composition file from standard input.
281         */
282         if (compfile[0] == '-' && compfile[1] == '\0') {
283                 /* copy standard input to temporary file */
284                 strncpy(infile, m_mktemp(invo_name, NULL, &fp),
285                                 sizeof(infile));
286                 while (fgets(buffer, BUFSIZ, stdin))
287                         fputs(buffer, fp);
288                 fclose(fp);
289                 unlink_infile = 1;
290
291                 /* build the content structures for MIME message */
292                 ct = build_mime(infile);
293                 cts[0] = ct;
294                 cts[1] = NULL;
295
296                 /* output MIME message to this temporary file */
297                 strncpy(outfile, m_mktemp(invo_name, NULL, &fp_out),
298                                 sizeof(outfile));
299                 unlink_outfile = 1;
300
301                 /* output the message */
302                 output_message_fp(ct, fp_out, outfile);
303                 fclose(fp_out);
304
305                 /* output the temp file to standard output */
306                 if ((fp = fopen(outfile, "r")) == NULL)
307                         adios(outfile, "unable to open");
308                 while (fgets(buffer, BUFSIZ, fp))
309                         fputs(buffer, stdout);
310                 fclose(fp);
311
312                 unlink(infile);
313                 unlink_infile = 0;
314
315                 unlink(outfile);
316                 unlink_outfile = 0;
317
318                 free_content(ct);
319                 done(0);
320         }
321
322         /*
323         ** Process the composition file from a file.
324         */
325
326         /* build the content structures for MIME message */
327         ct = build_mime(compfile);
328         cts[0] = ct;
329         cts[1] = NULL;
330
331         /* output MIME message to this temporary file */
332         strncpy(outfile, m_mktemp2(compfile, invo_name, NULL, &fp_out),
333                         sizeof(outfile));
334         unlink_outfile = 1;
335
336         /* output the message */
337         output_message_fp(ct, fp_out, outfile);
338         fclose(fp_out);
339
340         /*
341         ** List the message info
342         */
343         if (listsw)
344                 list_all_messages(cts, headsw, verbosw, debugsw);
345
346         /* Rename composition draft */
347         snprintf(buffer, sizeof(buffer), "%s.orig", m_backup(compfile));
348         if (rename(compfile, buffer) == NOTOK) {
349                 adios(compfile, "unable to rename comp draft %s to", buffer);
350         }
351
352         /* Rename output file to take its place */
353         if (rename(outfile, compfile) == NOTOK) {
354                 advise(outfile, "unable to rename output %s to", compfile);
355                 rename(buffer, compfile);
356                 done(1);
357         }
358         unlink_outfile = 0;
359
360         free_content(ct);
361         done(0);
362         return 1;
363 }
364
365
366 static void
367 unlink_done(int status)
368 {
369         /*
370         ** Check if we need to remove stray temporary files.
371         */
372         if (unlink_infile)
373                 unlink(infile);
374         if (unlink_outfile)
375                 unlink(outfile);
376
377         exit(status);
378 }
379
380 /*
381 ** Main routine for translating composition file
382 ** into valid MIME message.  It translates the draft
383 ** into a content structure (actually a tree of content
384 ** structures).  This message then can be manipulated
385 ** in various ways, including being output via
386 ** output_message().
387 */
388 static CT
389 build_mime(char *infile)
390 {
391         int compnum, state;
392         char buf[BUFSIZ], name[NAMESZ];
393         char *cp, *np, *vp;
394         struct multipart *m;
395         struct part **pp;
396         CT ct;
397         FILE *in;
398
399         umask(~m_gmprot());
400
401         /* open the composition draft */
402         if ((in = fopen(infile, "r")) == NULL)
403                 adios(infile, "unable to open for reading");
404
405         /*
406         ** Allocate space for primary (outside) content
407         */
408         if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
409                 adios(NULL, "out of memory");
410
411         /*
412         ** Allocate structure for handling decoded content
413         ** for this part.  We don't really need this, but
414         ** allocate it to remain consistent.
415         */
416         init_decoded_content(ct);
417
418         /*
419         ** Parse some of the header fields in the composition
420         ** draft into the linked list of header fields for
421         ** the new MIME message.
422         */
423         for (compnum = 1, state = FLD;;) {
424                 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
425                 case FLD:
426                 case FLDPLUS:
427                 case FLDEOF:
428                         compnum++;
429
430                         /* abort if draft has Mime-Version header field */
431                         if (!mh_strcasecmp(name, VRSN_FIELD))
432                                 adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
433
434                         /*
435                         ** abort if draft has Content-Transfer-Encoding
436                         ** header field
437                         */
438                         if (!mh_strcasecmp(name, ENCODING_FIELD))
439                                 adios(NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
440
441                         /* ignore any Content-Type fields in the header */
442                         if (!mh_strcasecmp(name, TYPE_FIELD)) {
443                                 while (state == FLDPLUS)
444                                         state = m_getfld(state, name, buf,
445                                                         sizeof(buf), in);
446                                 goto finish_field;
447                         }
448
449                         /* get copies of the buffers */
450                         np = getcpy(name);
451                         vp = getcpy(buf);
452
453                         /* if necessary, get rest of field */
454                         while (state == FLDPLUS) {
455                                 state = m_getfld(state, name, buf,
456                                                 sizeof(buf), in);
457                                 vp = add(buf, vp);  /* add to prev value */
458                         }
459
460                         /* Now add the header data to the list */
461                         add_header(ct, np, vp);
462
463 finish_field:
464                         /* if this wasn't the last hdr field, then continue */
465                         if (state != FLDEOF)
466                                 continue;
467                         /* else fall... */
468
469                 case FILEEOF:
470                         adios(NULL, "draft has empty body -- no directives!");
471                         /* NOTREACHED */
472
473                 case BODY:
474                 case BODYEOF:
475                         fseek(in, (long) (-strlen(buf)), SEEK_CUR);
476                         break;
477
478                 case LENERR:
479                 case FMTERR:
480                         adios(NULL, "message format error in component #%d",
481                                         compnum);
482
483                 default:
484                         adios(NULL, "getfld() returned %d", state);
485                 }
486                 break;
487         }
488
489         /*
490         ** Now add the MIME-Version header field
491         ** to the list of header fields.
492         */
493         np = getcpy(VRSN_FIELD);
494         vp = concat(" ", VRSN_VALUE, "\n", NULL);
495         add_header(ct, np, vp);
496
497         /*
498         ** We initally assume we will find multiple contents in the
499         ** draft.  So create a multipart/mixed content to hold everything.
500         ** We can remove this later, if it is not needed.
501         */
502         if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK)
503                 done(1);
504         ct->c_type = CT_MULTIPART;
505         ct->c_subtype = MULTI_MIXED;
506         ct->c_file = getcpy(infile);
507
508         if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
509                 adios(NULL, "out of memory");
510         ct->c_ctparams = (void *) m;
511         pp = &m->mp_parts;
512
513         /*
514         ** read and parse the composition file
515         ** and the directives it contains.
516         */
517         while (fgetstr(buf, sizeof(buf) - 1, in)) {
518                 struct part *part;
519                 CT p;
520
521                 if (user_content(in, infile, buf, &p) == DONE) {
522                         admonish(NULL, "ignoring spurious #end");
523                         continue;
524                 }
525                 if (!p)
526                         continue;
527
528                 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
529                         adios(NULL, "out of memory");
530                 *pp = part;
531                 pp = &part->mp_next;
532                 part->mp_part = p;
533         }
534
535         /*
536         ** close the composition draft since
537         ** it's not needed any longer.
538         */
539         fclose(in);
540
541         /* check if any contents were found */
542         if (!m->mp_parts)
543                 adios(NULL, "no content directives found");
544
545         /*
546         ** If only one content was found, then remove and
547         ** free the outer multipart content.
548         */
549         if (!m->mp_parts->mp_next) {
550                 CT p;
551
552                 p = m->mp_parts->mp_part;
553                 m->mp_parts->mp_part = NULL;
554
555                 /* move header fields */
556                 p->c_first_hf = ct->c_first_hf;
557                 p->c_last_hf = ct->c_last_hf;
558                 ct->c_first_hf = NULL;
559                 ct->c_last_hf = NULL;
560
561                 free_content(ct);
562                 ct = p;
563         } else {
564                 set_id(ct, 1);
565         }
566
567         /*
568         ** Fill out, or expand directives.  Parse and execute
569         ** commands specified by profile composition strings.
570         */
571         compose_content(ct);
572
573         if ((cp = strchr(prefix, 'a')) == NULL)
574                 adios(NULL, "internal error(4)");
575
576         /*
577         ** Scan the contents.  Choose a transfer encoding, and
578         ** check if prefix for multipart boundary clashes with
579         ** any of the contents.
580         */
581         while (scan_content(ct) == NOTOK) {
582                 if (*cp < 'z') {
583                         (*cp)++;
584                 } else {
585                         if (*++cp == 0)
586                                 adios(NULL, "giving up trying to find a unique delimiter string");
587                                 else
588                                 (*cp)++;
589                 }
590         }
591
592         /* Build the rest of the header field structures */
593         build_headers(ct);
594
595         return ct;
596 }
597
598
599 /*
600 ** Set up structures for placing unencoded
601 ** content when building parts.
602 */
603
604 static int
605 init_decoded_content(CT ct)
606 {
607         CE ce;
608
609         if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL)
610                 adios(NULL, "out of memory");
611
612         ct->c_cefile     = ce;
613         ct->c_ceopenfnx  = open7Bit;  /* since unencoded */
614         ct->c_ceclosefnx = close_encoding;
615         ct->c_cesizefnx  = NULL;  /* since unencoded */
616
617         return OK;
618 }
619
620
621 static char *
622 fgetstr(char *s, int n, FILE *stream)
623 {
624         char *cp, *ep;
625
626         for (ep = (cp = s) + n; cp < ep; ) {
627                 int i;
628
629                 if (!fgets(cp, n, stream))
630                         return (cp != s ? s : NULL);
631                 if (cp == s && *cp != '#')
632                         return s;
633
634                 cp += (i = strlen(cp)) - 1;
635                 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
636                         break;
637                 *cp = '\0';
638                 n -= (i - 2);
639         }
640
641         return s;
642 }
643
644
645 /*
646 ** Parse the composition draft for text and directives.
647 ** Do initial setup of Content structure.
648 */
649
650 static int
651 user_content(FILE *in, char *file, char *buf, CT *ctp)
652 {
653         int vrsn;
654         unsigned char *cp;
655         char **ap;
656         char buffer[BUFSIZ];
657         struct multipart *m;
658         struct part **pp;
659         struct stat st;
660         struct str2init *s2i;
661         CI ci;
662         CT ct;
663         CE ce;
664
665         if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
666                 *ctp = NULL;
667                 return OK;
668         }
669
670         /* allocate basic Content structure */
671         if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
672                 adios(NULL, "out of memory");
673         *ctp = ct;
674
675         /* allocate basic structure for handling decoded content */
676         init_decoded_content(ct);
677         ce = ct->c_cefile;
678
679         ci = &ct->c_ctinfo;
680         set_id(ct, 0);
681
682         /*
683         ** Handle inline text.  Check if line
684         ** is one of the following forms:
685         **
686         ** 1) doesn't begin with '#'  (implicit directive)
687         ** 2) begins with "##"        (implicit directive)
688         ** 3) begins with "#<"
689         */
690         if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
691                 int headers;
692                 int inlineD;
693                 long pos;
694                 char content[BUFSIZ];
695                 FILE *out;
696                 char *cp;
697
698                 cp = m_mktemp2(NULL, invo_name, NULL, &out);
699                 if (cp == NULL)
700                         adios("mhbuild", "unable to create temporary file");
701
702                 /* use a temp file to collect the plain text lines */
703                 ce->ce_file = getcpy(cp);
704                 ce->ce_unlink = 1;
705
706                 if (buf[0] == '#' && buf[1] == '<') {
707                         strncpy(content, buf + 2, sizeof(content));
708                         inlineD = 1;
709                         goto rock_and_roll;
710                 } else {
711                         inlineD = 0;
712                 }
713
714                 /* the directive is implicit */
715                 strncpy(content, "text/plain", sizeof(content));
716                 headers = 0;
717                 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
718                 for (;;) {
719                         int i;
720
721                         if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
722                                         buffer[i=strlen(DESCR_FIELD)] == ':') {
723                                 headers = 1;
724
725 again_descr:
726                                 ct->c_descr = add(buffer + i + 1, ct->c_descr);
727                                 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
728                                         adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
729                                 switch (buffer[0]) {
730                                 case ' ':
731                                 case '\t':
732                                         i = -1;
733                                         goto again_descr;
734
735                                 case '#':
736                                         adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
737                                         /* NOTREACHED */
738
739                                 default:
740                                         break;
741                                 }
742                         }
743
744                         if (headers >= 0 && uprf(buffer, DISPO_FIELD)
745                                 && buffer[i = strlen(DISPO_FIELD)] == ':') {
746                                 headers = 1;
747
748 again_dispo:
749                                 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
750                                 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
751                                         adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
752                                 switch (buffer[0]) {
753                                 case ' ':
754                                 case '\t':
755                                         i = -1;
756                                         goto again_dispo;
757
758                                 case '#':
759                                         adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
760                                         /* NOTREACHED */
761
762                                 default:
763                                         break;
764                                 }
765                         }
766
767                         if (headers != 1 || buffer[0] != '\n')
768                                 fputs(buffer, out);
769
770 rock_and_roll:
771                         headers = -1;
772                         pos = ftell(in);
773                         if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
774                                         == NULL)
775                                 break;
776                         if (buffer[0] == '#') {
777                                 char *bp;
778
779                                 if (buffer[1] != '#')
780                                         break;
781                                 for (cp = (bp = buffer) + 1; *cp; cp++)
782                                         *bp++ = *cp;
783                                 *bp = '\0';
784                         }
785                 }
786
787                 if (listsw)
788                         ct->c_end = ftell(out);
789                 fclose(out);
790
791                 /* parse content type */
792                 if (get_ctinfo(content, ct, inlineD) == NOTOK)
793                         done(1);
794
795                 for (s2i = str2cts; s2i->si_key; s2i++)
796                         if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
797                                 break;
798                 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
799                         s2i++;
800
801                 /*
802                 ** check type specified (possibly implicitly)
803                 */
804                 switch (ct->c_type = s2i->si_val) {
805                 case CT_MESSAGE:
806                         if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
807                                 ct->c_encoding = CE_7BIT;
808                                 goto call_init;
809                         }
810                         /* else fall... */
811                 case CT_MULTIPART:
812                         adios(NULL, "it doesn't make sense to define an in-line %s content",
813                                         ct->c_type == CT_MESSAGE ? "message" :
814                                         "multipart");
815                         /* NOTREACHED */
816
817                 default:
818 call_init:
819                         if ((ct->c_ctinitfnx = s2i->si_init))
820                                 (*ct->c_ctinitfnx) (ct);
821                         break;
822                 }
823
824                 if (cp)
825                         fseek(in, pos, SEEK_SET);
826                 return OK;
827         }
828
829         /*
830         ** If we've reached this point, the next line
831         ** must be some type of explicit directive.
832         */
833
834         if (buf[1] == '@') {
835                 adios(NULL, "The #@ directive i.e. message/external-body "
836                                 "is not supported anymore.");
837         }
838
839         /* parse directive */
840         if (get_ctinfo(buf+1, ct, 1) == NOTOK)
841                 done(1);
842
843         /* check directive against the list of MIME types */
844         for (s2i = str2cts; s2i->si_key; s2i++)
845                 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
846                         break;
847
848         /*
849         ** Check if the directive specified a valid type.
850         ** This will happen if it was one of the following forms:
851         **
852         **    #type/subtype
853         */
854         if (s2i->si_key) {
855                 if (!ci->ci_subtype)
856                         adios(NULL, "missing subtype in \"#%s\"", ci->ci_type);
857
858                 switch (ct->c_type = s2i->si_val) {
859                 case CT_MULTIPART:
860                         adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
861                         /* NOTREACHED */
862
863                 case CT_MESSAGE:
864                         if (!mh_strcasecmp(ci->ci_subtype, "partial") ||
865                                         !mh_strcasecmp(ci->ci_subtype,
866                                         "external-body")) {
867                                 adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
868                         }
869 use_forw:
870                         adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
871                         /* NOTREACHED */
872
873                 default:
874                         if ((ct->c_ctinitfnx = s2i->si_init))
875                                 (*ct->c_ctinitfnx) (ct);
876                         break;
877                 }
878
879                 /* Handle [file] argument */
880                 if (ci->ci_magic) {
881                         /* check if specifies command to execute */
882                         if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
883                                 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
884                                         continue;
885                                 if (!*cp)
886                                         adios(NULL, "empty pipe command for #%s directive", ci->ci_type);
887                                 cp = getcpy(cp);
888                                 free(ci->ci_magic);
889                                 ci->ci_magic = cp;
890                         } else {
891                                 /* record filename of decoded contents */
892                                 ce->ce_file = ci->ci_magic;
893                                 if (access(ce->ce_file, R_OK) == NOTOK)
894                                         adios("reading", "unable to access %s for", ce->ce_file);
895                                 if (listsw && stat(ce->ce_file, &st) != NOTOK)
896                                         ct->c_end = (long) st.st_size;
897                                 ci->ci_magic = NULL;
898                         }
899                         return OK;
900                 }
901
902                 /*
903                 ** No [file] argument, so check profile for
904                 ** method to compose content.
905                 */
906                 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
907                                 invo_name, ci->ci_type, ci->ci_subtype);
908                 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
909                         snprintf(buffer, sizeof(buffer), "%s-compose-%s",
910                                         invo_name, ci->ci_type);
911                         if ((cp = context_find(buffer)) == NULL ||
912                                         *cp == '\0') {
913                                 content_error(NULL, ct, "don't know how to compose content");
914                                 done(1);
915                         }
916                 }
917                 ci->ci_magic = getcpy(cp);
918                 return OK;
919         }
920
921         /*
922         ** Message directive
923         ** #forw [+folder] [msgs]
924         */
925         if (!mh_strcasecmp(ci->ci_type, "forw")) {
926                 int msgnum;
927                 char *folder, *arguments[MAXARGS];
928                 struct msgs *mp;
929
930                 if (ci->ci_magic) {
931                         int i;
932
933                         ap = brkstring(ci->ci_magic, " ", "\n");
934                         for (i=0; ap[i] && i<MAXARGS-1; i++) {
935                                 arguments[i] = ap[i];
936                         }
937                         arguments[i] = NULL;
938
939                 } else {
940                         arguments[0] = seq_cur;
941                         arguments[1] = NULL;
942                 }
943                 folder = NULL;
944
945                 /* search the arguments for a folder name */
946                 for (ap = arguments; *ap; ap++) {
947                         cp = *ap;
948                         if (*cp == '+' || *cp == '@') {
949                                 if (folder)
950                                         adios(NULL, "only one folder per #forw directive");
951                                 else
952                                         folder = getcpy(expandfol(cp));
953                         }
954                 }
955
956                 /* else, use the current folder */
957                 if (!folder)
958                         folder = getcpy(getcurfol());
959
960                 if (!(mp = folder_read(folder)))
961                         adios(NULL, "unable to read folder %s", folder);
962                 for (ap = arguments; *ap; ap++) {
963                         cp = *ap;
964                         if (*cp != '+' && *cp != '@')
965                                 if (!m_convert(mp, cp))
966                                         done(1);
967                 }
968                 free(folder);
969                 free_ctinfo(ct);
970
971                 /*
972                 ** If there is more than one message to include, make this
973                 ** a content of type "multipart/digest" and insert each message
974                 ** as a subpart.  If there is only one message, then make this
975                 ** a content of type "message/rfc822".
976                 */
977                 if (mp->numsel > 1) {
978                         /* we are forwarding multiple messages */
979                         if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
980                                 done(1);
981                         ct->c_type = CT_MULTIPART;
982                         ct->c_subtype = MULTI_DIGEST;
983
984                         if ((m = (struct multipart *)
985                                         calloc(1, sizeof(*m))) == NULL)
986                                 adios(NULL, "out of memory");
987                         ct->c_ctparams = (void *) m;
988                         pp = &m->mp_parts;
989
990                         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
991                                 if (is_selected(mp, msgnum)) {
992                                         struct part *part;
993                                         CT p;
994                                         CE pe;
995
996                                         if ((p = (CT) calloc(1, sizeof(*p)))
997                                                         == NULL)
998                                                 adios(NULL, "out of memory");
999                                         init_decoded_content(p);
1000                                         pe = p->c_cefile;
1001                                         if (get_ctinfo("message/rfc822", p, 0)
1002                                                         == NOTOK)
1003                                                 done(1);
1004                                         p->c_type = CT_MESSAGE;
1005                                         p->c_subtype = MESSAGE_RFC822;
1006
1007                                         snprintf(buffer, sizeof(buffer),
1008                                                         "%s/%d", mp->foldpath,
1009                                                         msgnum);
1010                                         pe->ce_file = getcpy(buffer);
1011                                         if (listsw && stat(pe->ce_file, &st)
1012                                                         != NOTOK)
1013                                                 p->c_end = (long) st.st_size;
1014
1015                                         if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
1016                                                 adios(NULL, "out of memory");
1017                                         *pp = part;
1018                                         pp = &part->mp_next;
1019                                         part->mp_part = p;
1020                                 }
1021                         }
1022                 } else {
1023                         /* we are forwarding one message */
1024                         if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
1025                                 done(1);
1026                         ct->c_type = CT_MESSAGE;
1027                         ct->c_subtype = MESSAGE_RFC822;
1028
1029                         msgnum = mp->lowsel;
1030                         snprintf(buffer, sizeof(buffer), "%s/%d",
1031                                         mp->foldpath, msgnum);
1032                         ce->ce_file = getcpy(buffer);
1033                         if (listsw && stat(ce->ce_file, &st) != NOTOK)
1034                                 ct->c_end = (long) st.st_size;
1035                 }
1036
1037                 folder_free(mp);  /* free folder/message structure */
1038                 return OK;
1039         }
1040
1041         /*
1042         ** #end
1043         */
1044         if (!mh_strcasecmp(ci->ci_type, "end")) {
1045                 free_content(ct);
1046                 *ctp = NULL;
1047                 return DONE;
1048         }
1049
1050         /*
1051         ** #begin [ alternative | parallel ]
1052         */
1053         if (!mh_strcasecmp(ci->ci_type, "begin")) {
1054                 if (!ci->ci_magic) {
1055                         vrsn = MULTI_MIXED;
1056                         cp = SubMultiPart[vrsn - 1].kv_key;
1057                 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
1058                         vrsn = MULTI_ALTERNATE;
1059                         cp = SubMultiPart[vrsn - 1].kv_key;
1060                 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
1061                         vrsn = MULTI_PARALLEL;
1062                         cp = SubMultiPart[vrsn - 1].kv_key;
1063                 } else if (uprf(ci->ci_magic, "digest")) {
1064                         goto use_forw;
1065                 } else {
1066                         vrsn = MULTI_UNKNOWN;
1067                         cp = ci->ci_magic;
1068                 }
1069
1070                 free_ctinfo(ct);
1071                 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
1072                 if (get_ctinfo(buffer, ct, 0) == NOTOK)
1073                         done(1);
1074                 ct->c_type = CT_MULTIPART;
1075                 ct->c_subtype = vrsn;
1076
1077                 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
1078                         adios(NULL, "out of memory");
1079                 ct->c_ctparams = (void *) m;
1080
1081                 pp = &m->mp_parts;
1082                 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
1083                         struct part *part;
1084                         CT p;
1085
1086                         if (user_content(in, file, buffer, &p) == DONE) {
1087                                 if (!m->mp_parts)
1088                                         adios(NULL, "empty \"#begin ... #end\" sequence");
1089                                 return OK;
1090                         }
1091                         if (!p)
1092                                 continue;
1093
1094                         if ((part = (struct part *)
1095                                         calloc(1, sizeof(*part))) == NULL)
1096                                 adios(NULL, "out of memory");
1097                         *pp = part;
1098                         pp = &part->mp_next;
1099                         part->mp_part = p;
1100                 }
1101                 admonish(NULL, "premature end-of-file, missing #end");
1102                 return OK;
1103         }
1104
1105         /*
1106         ** Unknown directive
1107         */
1108         adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
1109         return NOTOK;  /* NOT REACHED */
1110 }
1111
1112
1113 static void
1114 set_id(CT ct, int top)
1115 {
1116         char msgid[BUFSIZ];
1117         static int partno;
1118         static time_t clock = 0;
1119         static char *msgfmt;
1120
1121         if (clock == 0) {
1122                 time(&clock);
1123                 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1124                                 (int) getpid(), (long) clock, LocalName());
1125                 partno = 0;
1126                 msgfmt = getcpy(msgid);
1127         }
1128         snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1129         ct->c_id = getcpy(msgid);
1130 }
1131
1132
1133 static char ebcdicsafe[0x100] = {
1134         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1135         0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1136         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1137         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1138         0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
1139         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1140         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1141         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1142         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1143         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1144         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1145         0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
1146         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1147         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1148         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1149         0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1150         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1151         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1152         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1153         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1154         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1155         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1156         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1157         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1158         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1159         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1160         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1161         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1162         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1163         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1164         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1165         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1166 };
1167
1168
1169 /*
1170 ** Fill out, or expand the various contents in the composition
1171 ** draft.  Read-in any necessary files.  Parse and execute any
1172 ** commands specified by profile composition strings.
1173 */
1174
1175 static int
1176 compose_content(CT ct)
1177 {
1178         CE ce = ct->c_cefile;
1179
1180         switch (ct->c_type) {
1181         case CT_MULTIPART:
1182         {
1183                 int partnum;
1184                 char *pp;
1185                 char partnam[BUFSIZ];
1186                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1187                 struct part *part;
1188
1189                 if (ct->c_partno) {
1190                         snprintf(partnam, sizeof(partnam), "%s.",
1191                                         ct->c_partno);
1192                         pp = partnam + strlen(partnam);
1193                 } else {
1194                         pp = partnam;
1195                 }
1196
1197                 /* first, we call compose_content on all the subparts */
1198                 for (part = m->mp_parts, partnum = 1; part;
1199                                 part = part->mp_next, partnum++) {
1200                         CT p = part->mp_part;
1201
1202                         sprintf(pp, "%d", partnum);
1203                         p->c_partno = getcpy(partnam);
1204                         if (compose_content(p) == NOTOK)
1205                                 return NOTOK;
1206                 }
1207
1208                 /*
1209                 ** If the -rfc934mode switch is given, then check all
1210                 ** the subparts of a multipart/digest.  If they are all
1211                 ** message/rfc822, then mark this content and all
1212                 ** subparts with the rfc934 compatibility mode flag.
1213                 */
1214                 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1215                         int is934 = 1;
1216
1217                         for (part = m->mp_parts; part; part = part->mp_next) {
1218                                 CT p = part->mp_part;
1219
1220                                 if (p->c_subtype != MESSAGE_RFC822) {
1221                                         is934 = 0;
1222                                         break;
1223                                 }
1224                         }
1225                         ct->c_rfc934 = is934;
1226                         for (part = m->mp_parts; part; part = part->mp_next) {
1227                                 CT p = part->mp_part;
1228
1229                                 if ((p->c_rfc934 = is934))
1230                                         p->c_end++;
1231                         }
1232                 }
1233
1234                 if (listsw) {
1235                         ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1236                         if (ct->c_rfc934)
1237                                 ct->c_end += 1;
1238
1239                         for (part = m->mp_parts; part; part = part->mp_next)
1240                                 ct->c_end += part->mp_part->c_end + partnum;
1241                 }
1242         }
1243         break;
1244
1245         case CT_MESSAGE:
1246                 /* Nothing to do for type message */
1247                 break;
1248
1249         /*
1250         ** Discrete types (text/application/audio/image/video)
1251         */
1252         default:
1253                 if (!ce->ce_file) {
1254                         pid_t child_id;
1255                         int xstdout, len, buflen;
1256                         char *bp, **ap, *cp;
1257                         char *vec[4], buffer[BUFSIZ];
1258                         FILE *out;
1259                         CI ci = &ct->c_ctinfo;
1260                         char *tfile = NULL;
1261
1262                         if (!(cp = ci->ci_magic))
1263                                 adios(NULL, "internal error(5)");
1264
1265                         tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1266                         if (tfile == NULL) {
1267                                 adios("mhbuild", "unable to create temporary file");
1268                         }
1269                         ce->ce_file = getcpy(tfile);
1270                         ce->ce_unlink = 1;
1271
1272                         xstdout = 0;
1273
1274                         /* Get buffer ready to go */
1275                         bp = buffer;
1276                         bp[0] = '\0';
1277                         buflen = sizeof(buffer);
1278
1279                         /*
1280                         ** Parse composition string into buffer
1281                         */
1282                         for ( ; *cp; cp++) {
1283                                 if (*cp == '%') {
1284                                         switch (*++cp) {
1285                                         case 'a':
1286                                         {
1287                                                 /*
1288                                                 ** insert parameters from
1289                                                 ** directive
1290                                                 */
1291                                                 char **ep;
1292                                                 char *s = "";
1293
1294                                                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1295                                                         snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1296                                                         len = strlen(bp);
1297                                                         bp += len;
1298                                                         buflen -= len;
1299                                                         s = " ";
1300                                                 }
1301                                         }
1302                                         break;
1303
1304                                         case 'F':
1305                                                 /* %f, and stdout is not-redirected */
1306                                                 xstdout = 1;
1307                                                 /* and fall... */
1308
1309                                         case 'f':
1310                                                 /*
1311                                                 ** insert temporary filename
1312                                                 ** where content should be
1313                                                 ** written
1314                                                 */
1315                                                 snprintf(bp, buflen, "%s", ce->ce_file);
1316                                                 break;
1317
1318                                         case 's':
1319                                                 /* insert content subtype */
1320                                                 strncpy(bp, ci->ci_subtype, buflen);
1321                                                 break;
1322
1323                                         case '%':
1324                                                 /* insert character % */
1325                                                 goto raw;
1326
1327                                         default:
1328                                                 *bp++ = *--cp;
1329                                                 *bp = '\0';
1330                                                 buflen--;
1331                                                 continue;
1332                                         }
1333                                         len = strlen(bp);
1334                                         bp += len;
1335                                         buflen -= len;
1336                                 } else {
1337 raw:
1338                                         *bp++ = *cp;
1339                                         *bp = '\0';
1340                                         buflen--;
1341                                 }
1342                         }
1343
1344                         if (verbosw)
1345                                 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1346
1347                         fflush(stdout);  /* not sure if need for -noverbose */
1348
1349                         vec[0] = "/bin/sh";
1350                         vec[1] = "-c";
1351                         vec[2] = buffer;
1352                         vec[3] = NULL;
1353
1354                         if ((out = fopen(ce->ce_file, "w")) == NULL)
1355                                 adios(ce->ce_file, "unable to open for writing");
1356
1357                         switch (child_id = fork()) {
1358                         case NOTOK:
1359                                 adios("fork", "unable to fork");
1360                                 /* NOTREACHED */
1361
1362                         case OK:
1363                                 if (!xstdout)
1364                                         dup2(fileno(out), 1);
1365                                 close(fileno(out));
1366                                 execvp("/bin/sh", vec);
1367                                 fprintf(stderr, "unable to exec ");
1368                                 perror("/bin/sh");
1369                                 _exit(-1);
1370                                 /* NOTREACHED */
1371
1372                         default:
1373                                 fclose(out);
1374                                 if (pidXwait(child_id, NULL))
1375                                         done(1);
1376                                 break;
1377                         }
1378                 }
1379
1380                 /* Check size of file */
1381                 if (listsw && ct->c_end == 0L) {
1382                         struct stat st;
1383
1384                         if (stat(ce->ce_file, &st) != NOTOK)
1385                                 ct->c_end = (long) st.st_size;
1386                 }
1387                 break;
1388         }
1389
1390         return OK;
1391 }
1392
1393
1394 /*
1395 ** Scan the content.
1396 **
1397 **    1) choose a transfer encoding.
1398 **    2) check for clashes with multipart boundary string.
1399 **    3) for text content, figure out which character set is being used.
1400 **
1401 ** If there is a clash with one of the contents and the multipart boundary,
1402 ** this function will exit with NOTOK.  This will cause the scanning process
1403 ** to be repeated with a different multipart boundary.  It is possible
1404 ** (although highly unlikely) that this scan will be repeated multiple times.
1405 */
1406
1407 static int
1408 scan_content(CT ct)
1409 {
1410         int len;
1411         int check8bit = 0, contains8bit = 0;  /* check if contains 8bit data */
1412         int checklinelen = 0, linelen = 0;  /* check for long lines */
1413         int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary   */
1414         int checklinespace = 0, linespace = 0;  /* check if any line ends with space */
1415         int checkebcdic = 0, ebcdicunsafe = 0;  /* check if contains ebcdic unsafe characters */
1416         unsigned char *cp = NULL, buffer[BUFSIZ];
1417         struct text *t = NULL;
1418         FILE *in = NULL;
1419         CE ce = ct->c_cefile;
1420
1421         /*
1422         ** handle multipart by scanning all subparts
1423         ** and then checking their encoding.
1424         */
1425         if (ct->c_type == CT_MULTIPART) {
1426                 struct multipart *m = (struct multipart *) ct->c_ctparams;
1427                 struct part *part;
1428
1429                 /* initially mark the domain of enclosing multipart as 7bit */
1430                 ct->c_encoding = CE_7BIT;
1431
1432                 for (part = m->mp_parts; part; part = part->mp_next) {
1433                         CT p = part->mp_part;
1434
1435                         if (scan_content(p) == NOTOK) {
1436                                 /* choose encoding for subpart */
1437                                 return NOTOK;
1438                         }
1439
1440                         /*
1441                         ** if necessary, enlarge encoding for enclosing
1442                         ** multipart
1443                         */
1444                         if (p->c_encoding == CE_BINARY)
1445                                 ct->c_encoding = CE_BINARY;
1446                         if (p->c_encoding == CE_8BIT &&
1447                                         ct->c_encoding != CE_BINARY)
1448                                 ct->c_encoding = CE_8BIT;
1449                 }
1450
1451                 return OK;
1452         }
1453
1454         /*
1455         ** Decide what to check while scanning this content.
1456         */
1457         switch (ct->c_type) {
1458         case CT_TEXT:
1459                 check8bit = 1;
1460                 checkboundary = 1;
1461                 if (ct->c_subtype == TEXT_PLAIN) {
1462                         checkebcdic = 0;
1463                         checklinelen = 0;
1464                         checklinespace = 0;
1465                 } else {
1466                         checkebcdic = ebcdicsw;
1467                         checklinelen = 1;
1468                         checklinespace = 1;
1469                 }
1470                 break;
1471
1472         case CT_APPLICATION:
1473                 check8bit = 1;
1474                 checkebcdic = ebcdicsw;
1475                 checklinelen = 1;
1476                 checklinespace = 1;
1477                 checkboundary = 1;
1478                 break;
1479
1480         case CT_MESSAGE:
1481                 check8bit = 0;
1482                 checkebcdic = 0;
1483                 checklinelen = 0;
1484                 checklinespace = 0;
1485                 checkboundary = 1;
1486                 break;
1487
1488         case CT_AUDIO:
1489         case CT_IMAGE:
1490         case CT_VIDEO:
1491                 /*
1492                 ** Don't check anything for these types,
1493                 ** since we are forcing use of base64.
1494                 */
1495                 check8bit = 0;
1496                 checkebcdic = 0;
1497                 checklinelen = 0;
1498                 checklinespace = 0;
1499                 checkboundary = 0;
1500                 break;
1501         }
1502
1503         /*
1504         ** Scan the unencoded content
1505         */
1506         if (check8bit || checklinelen || checklinespace || checkboundary) {
1507                 if ((in = fopen(ce->ce_file, "r")) == NULL)
1508                         adios(ce->ce_file, "unable to open for reading");
1509                 len = strlen(prefix);
1510
1511                 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1512                         /*
1513                         ** Check for 8bit data.
1514                         */
1515                         if (check8bit) {
1516                                 for (cp = buffer; *cp; cp++) {
1517                                         if (!isascii(*cp)) {
1518                                                 contains8bit = 1;
1519                                                 /* no need to keep checking */
1520                                                 check8bit = 0;
1521                                         }
1522                                         /*
1523                                         ** Check if character is ebcdic-safe.
1524                                         ** We only check this if also checking
1525                                         ** for 8bit data.
1526                                         */
1527                                         if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1528                                                 ebcdicunsafe = 1;
1529                                                 /* no need to keep checking */
1530                                                 checkebcdic = 0;
1531                                         }
1532                                 }
1533                         }
1534
1535                         /*
1536                         ** Check line length.
1537                         */
1538                         if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1539                                 linelen = 1;
1540                                 checklinelen = 0;  /* no need to keep checking */
1541                         }
1542
1543                         /*
1544                         ** Check if line ends with a space.
1545                         */
1546                         if (checklinespace &&
1547                                         (cp = buffer + strlen(buffer) - 2) >
1548                                         buffer && isspace(*cp)) {
1549                                 linespace = 1;
1550                                 /* no need to keep checking */
1551                                 checklinespace = 0;
1552                         }
1553
1554                         /*
1555                         ** Check if content contains a line that clashes
1556                         ** with our standard boundary for multipart messages.
1557                         */
1558                         if (checkboundary && buffer[0] == '-' &&
1559                                         buffer[1] == '-') {
1560                                 for (cp = buffer + strlen(buffer) - 1;
1561                                                 cp >= buffer; cp--)
1562                                         if (!isspace(*cp))
1563                                                 break;
1564                                 *++cp = '\0';
1565                                 if (strncmp(buffer + 2, prefix, len)==0 &&
1566                                                 isdigit(buffer[2 + len])) {
1567                                         boundaryclash = 1;
1568                                         /* no need to keep checking */
1569                                         checkboundary = 0;
1570                                 }
1571                         }
1572                 }
1573                 fclose(in);
1574         }
1575
1576         /*
1577         ** Decide which transfer encoding to use.
1578         */
1579         switch (ct->c_type) {
1580         case CT_TEXT:
1581                 /*
1582                 ** If the text content didn't specify a character
1583                 ** set, we need to figure out which one was used.
1584                 */
1585                 t = (struct text *) ct->c_ctparams;
1586                 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1587                         CI ci = &ct->c_ctinfo;
1588                         char **ap, **ep;
1589
1590                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1591                                 continue;
1592
1593                         if (contains8bit) {
1594                                 t->tx_charset = CHARSET_UNKNOWN;
1595                                 *ap = concat("charset=", write_charset_8bit(),
1596                                                 NULL);
1597                         } else {
1598                                 t->tx_charset = CHARSET_USASCII;
1599                                 *ap = getcpy("charset=us-ascii");
1600                         }
1601
1602                         cp = strchr(*ap++, '=');
1603                         *ap = NULL;
1604                         *cp++ = '\0';
1605                         *ep = cp;
1606                 }
1607
1608                 if (contains8bit || ebcdicunsafe || linelen || linespace)
1609                         ct->c_encoding = CE_QUOTED;
1610                 else
1611                         ct->c_encoding = CE_7BIT;
1612                 break;
1613
1614         case CT_APPLICATION:
1615                 /* For application type, use base64, except when postscript */
1616                 if (contains8bit || ebcdicunsafe || linelen || linespace)
1617                         ct->c_encoding = (ct->c_subtype ==
1618                                         APPLICATION_POSTSCRIPT) ?
1619                                         CE_QUOTED : CE_BASE64;
1620                 else
1621                         ct->c_encoding = CE_7BIT;
1622                 break;
1623
1624         case CT_MESSAGE:
1625                 ct->c_encoding = CE_7BIT;
1626                 break;
1627
1628         case CT_AUDIO:
1629         case CT_IMAGE:
1630         case CT_VIDEO:
1631                 /* For audio, image, and video contents, just use base64 */
1632                 ct->c_encoding = CE_BASE64;
1633                 break;
1634         }
1635
1636         return (boundaryclash ? NOTOK : OK);
1637 }
1638
1639
1640 /*
1641 ** Scan the content structures, and build header
1642 ** fields that will need to be output into the
1643 ** message.
1644 */
1645
1646 static int
1647 build_headers(CT ct)
1648 {
1649         int cc, len;
1650         char **ap, **ep;
1651         char *np, *vp, buffer[BUFSIZ];
1652         CI ci = &ct->c_ctinfo;
1653
1654         /*
1655         ** If message is type multipart, then add the multipart
1656         ** boundary to the list of attribute/value pairs.
1657         */
1658         if (ct->c_type == CT_MULTIPART) {
1659                 char *cp;
1660                 static int level = 0;  /* store nesting level */
1661
1662                 ap = ci->ci_attrs;
1663                 ep = ci->ci_values;
1664                 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1665                                 prefix, level++);
1666                 cp = strchr(*ap++ = getcpy(buffer), '=');
1667                 *ap = NULL;
1668                 *cp++ = '\0';
1669                 *ep = cp;
1670         }
1671
1672         /*
1673         ** Skip the output of Content-Type, parameters, content
1674         ** description and disposition, and Content-ID if the
1675         ** content is of type "message" and the rfc934 compatibility
1676         ** flag is set (which means we are inside multipart/digest
1677         ** and the switch -rfc934mode was given).
1678         */
1679         if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1680                 goto skip_headers;
1681
1682         /*
1683         ** output the content type and subtype
1684         */
1685         np = getcpy(TYPE_FIELD);
1686         vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1687
1688         /* keep track of length of line */
1689         len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1690                         strlen(ci->ci_subtype) + 3;
1691
1692         /*
1693         ** Append the attribute/value pairs to
1694         ** the end of the Content-Type line.
1695         */
1696         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1697                 vp = add(";", vp);
1698                 len++;
1699
1700                 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1701                 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1702                         vp = add("\n\t", vp);
1703                         len = 8;
1704                 } else {
1705                         vp = add(" ", vp);
1706                         len++;
1707                 }
1708                 vp = add(buffer, vp);
1709                 len += cc;
1710         }
1711
1712         /*
1713         ** Append any RFC-822 comment to the end of
1714         ** the Content-Type line.
1715         */
1716         if (ci->ci_comment) {
1717                 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1718                 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1719                         vp = add("\n\t", vp);
1720                         len = 8;
1721                 } else {
1722                         vp = add(" ", vp);
1723                         len++;
1724                 }
1725                 vp = add(buffer, vp);
1726                 len += cc;
1727         }
1728         vp = add("\n", vp);
1729         add_header(ct, np, vp);
1730
1731         /*
1732         ** output the Content-ID, unless disabled by -nocontentid
1733         */
1734         if (contentidsw && ct->c_id) {
1735                 np = getcpy(ID_FIELD);
1736                 vp = concat(" ", ct->c_id, NULL);
1737                 add_header(ct, np, vp);
1738         }
1739
1740         /*
1741         ** output the Content-Description
1742         */
1743         if (ct->c_descr) {
1744                 np = getcpy(DESCR_FIELD);
1745                 vp = concat(" ", ct->c_descr, NULL);
1746                 add_header(ct, np, vp);
1747         }
1748
1749         /*
1750         ** output the Content-Disposition
1751         */
1752         if (ct->c_dispo) {
1753                 np = getcpy(DISPO_FIELD);
1754                 vp = concat(" ", ct->c_dispo, NULL);
1755                 add_header(ct, np, vp);
1756         }
1757
1758 skip_headers:
1759         /*
1760         ** output the Content-Transfer-Encoding
1761         */
1762         switch (ct->c_encoding) {
1763         case CE_7BIT:
1764                 /* Nothing to output */
1765                 break;
1766
1767         case CE_8BIT:
1768                 if (ct->c_type == CT_MESSAGE)
1769                         adios(NULL, "internal error, invalid encoding");
1770
1771                 np = getcpy(ENCODING_FIELD);
1772                 vp = concat(" ", "8bit", "\n", NULL);
1773                 add_header(ct, np, vp);
1774                 break;
1775
1776         case CE_QUOTED:
1777                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1778                         adios(NULL, "internal error, invalid encoding");
1779
1780                 np = getcpy(ENCODING_FIELD);
1781                 vp = concat(" ", "quoted-printable", "\n", NULL);
1782                 add_header(ct, np, vp);
1783                 break;
1784
1785         case CE_BASE64:
1786                 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1787                         adios(NULL, "internal error, invalid encoding");
1788
1789                 np = getcpy(ENCODING_FIELD);
1790                 vp = concat(" ", "base64", "\n", NULL);
1791                 add_header(ct, np, vp);
1792                 break;
1793
1794         case CE_BINARY:
1795                 if (ct->c_type == CT_MESSAGE)
1796                         adios(NULL, "internal error, invalid encoding");
1797
1798                 np = getcpy(ENCODING_FIELD);
1799                 vp = concat(" ", "binary", "\n", NULL);
1800                 add_header(ct, np, vp);
1801                 break;
1802
1803         default:
1804                 adios(NULL, "unknown transfer encoding in content");
1805                 break;
1806         }
1807
1808         /*
1809         ** Additional content specific header processing
1810         */
1811         switch (ct->c_type) {
1812         case CT_MULTIPART:
1813         {
1814                 struct multipart *m;
1815                 struct part *part;
1816
1817                 m = (struct multipart *) ct->c_ctparams;
1818                 for (part = m->mp_parts; part; part = part->mp_next) {
1819                         CT p;
1820
1821                         p = part->mp_part;
1822                         build_headers(p);
1823                 }
1824         }
1825                 break;
1826
1827         default:
1828                 /* Nothing to do */
1829                 break;
1830         }
1831
1832         return OK;
1833 }