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