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