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