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