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