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