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