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