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