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