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