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