* man/mhbuild.man: wrapped one appearance of "Content-Disposition"
[mmh] / uip / mhbuildsbr.c
1
2 /*
3  * mhbuildsbr.c -- routines to expand/translate MIME composition files
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 /*
13  * This code was originally part of mhn.c.  I split it into
14  * a separate program (mhbuild.c) and then later split it
15  * again (mhbuildsbr.c).  But the code still has some of
16  * the mhn.c code in it.  This program needs additional
17  * streamlining and removal of unneeded code.
18  */
19
20 #include <h/mh.h>
21 #include <fcntl.h>
22 #include <h/signals.h>
23 #include <h/md5.h>
24 #include <errno.h>
25 #include <signal.h>
26 #include <h/mts.h>
27 #include <h/tws.h>
28 #include <h/mime.h>
29 #include <h/mhparse.h>
30 #include <h/utils.h>
31
32 #ifdef TIME_WITH_SYS_TIME
33 # include <sys/time.h>
34 # include <time.h>
35 #else
36 # ifdef TM_IN_SYS_TIME
37 #  include <sys/time.h>
38 # else
39 #  include <time.h>
40 # endif
41 #endif
42
43 #ifdef HAVE_SYS_WAIT_H
44 # include <sys/wait.h>
45 #endif
46
47
48 extern int debugsw;
49 extern int verbosw;
50
51 extern int ebcdicsw;
52 extern int listsw;
53 extern int rfc934sw;
54 extern int contentidsw;
55
56 extern int endian;      /* mhmisc.c */
57
58 /* cache policies */
59 extern int rcachesw;    /* mhcachesbr.c */
60 extern int wcachesw;    /* mhcachesbr.c */
61
62 int checksw = 0;        /* Add Content-MD5 field */
63
64 /*
65  * Directory to place tmp files.  This must
66  * be set before these routines are called.
67  */
68 char *tmp;
69
70 pid_t xpid = 0;
71
72 static char prefix[] = "----- =_aaaaaaaaaa";
73
74 /*
75  * Structure for mapping types to their internal flags
76  */
77 struct k2v {
78     char *kv_key;
79     int   kv_value;
80 };
81
82 /*
83  * Structures for TEXT messages
84  */
85 static struct k2v SubText[] = {
86     { "plain",    TEXT_PLAIN },
87     { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341    */
88     { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896    */
89     { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
90 };
91
92 static struct k2v Charset[] = {
93     { "us-ascii",   CHARSET_USASCII },
94     { "iso-8859-1", CHARSET_LATIN },
95     { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
96 };
97
98 /*
99  * Structures for MULTIPART messages
100  */
101 static struct k2v SubMultiPart[] = {
102     { "mixed",       MULTI_MIXED },
103     { "alternative", MULTI_ALTERNATE },
104     { "digest",      MULTI_DIGEST },
105     { "parallel",    MULTI_PARALLEL },
106     { NULL,          MULTI_UNKNOWN }    /* this one must be last! */
107 };
108
109 /*
110  * Structures for MESSAGE messages
111  */
112 static struct k2v SubMessage[] = {
113     { "rfc822",        MESSAGE_RFC822 },
114     { "partial",       MESSAGE_PARTIAL },
115     { "external-body", MESSAGE_EXTERNAL },
116     { NULL,            MESSAGE_UNKNOWN }        /* this one must be last! */
117 };
118
119 /*
120  * Structure for APPLICATION messages
121  */
122 static struct k2v SubApplication[] = {
123     { "octet-stream", APPLICATION_OCTETS },
124     { "postscript",   APPLICATION_POSTSCRIPT },
125     { NULL,           APPLICATION_UNKNOWN }     /* this one must be last! */
126 };
127
128
129 /* mhmisc.c */
130 int make_intermediates (char *);
131 void content_error (char *, CT, char *, ...);
132
133 /* mhcachesbr.c */
134 int find_cache (CT, int, int *, char *, char *, int);
135
136 /* ftpsbr.c */
137 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
138
139 /* mhfree.c */
140 void free_content (CT);
141 void free_ctinfo (CT);
142 void free_encoding (CT, int);
143
144 /*
145  * prototypes
146  */
147 CT build_mime (char *);
148 int pidcheck (int);
149
150 /*
151  * static prototypes
152  */
153 static CT get_content (FILE *, char *, int);
154 static int add_header (CT, char *, char *);
155 static int get_ctinfo (char *, CT, int);
156 static int get_comment (CT, char **, int);
157 static int InitGeneric (CT);
158 static int InitText (CT);
159 static int InitMultiPart (CT);
160 static void reverse_parts (CT);
161 static int InitMessage (CT);
162 static int params_external (CT, int);
163 static int InitApplication (CT);
164 static int init_decoded_content (CT);
165 static int init_encoding (CT, OpenCEFunc);
166 static void close_encoding (CT);
167 static unsigned long size_encoding (CT);
168 static int InitBase64 (CT);
169 static int openBase64 (CT, char **);
170 static int InitQuoted (CT);
171 static int openQuoted (CT, char **);
172 static int Init7Bit (CT);
173 static int open7Bit (CT, char **);
174 static int openExternal (CT, CT, CE, char **, int *);
175 static int InitFile (CT);
176 static int openFile (CT, char **);
177 static int InitFTP (CT);
178 static int openFTP (CT, char **);
179 static int InitMail (CT);
180 static int openMail (CT, char **);
181 static char *fgetstr (char *, int, FILE *);
182 static int user_content (FILE *, char *, char *, CT *);
183 static void set_id (CT, int);
184 static int compose_content (CT);
185 static int scan_content (CT);
186 static int build_headers (CT);
187 static char *calculate_digest (CT, int);
188 static int readDigest (CT, char *);
189 static char *incl_name_value (char *, char *, char *);
190 static char *extract_name_value (char *, char *);
191
192 /*
193  * Structures for mapping (content) types to
194  * the functions to handle them.
195  */
196 struct str2init {
197     char *si_key;
198     int   si_val;
199     InitFunc si_init;
200 };
201
202 static struct str2init str2cts[] = {
203     { "application", CT_APPLICATION, InitApplication },
204     { "audio",       CT_AUDIO,       InitGeneric },
205     { "image",       CT_IMAGE,       InitGeneric },
206     { "message",     CT_MESSAGE,     InitMessage },
207     { "multipart",   CT_MULTIPART,   InitMultiPart },
208     { "text",        CT_TEXT,        InitText },
209     { "video",       CT_VIDEO,       InitGeneric },
210     { NULL,          CT_EXTENSION,   NULL },  /* these two must be last! */
211     { NULL,          CT_UNKNOWN,     NULL },
212 };
213
214 static struct str2init str2ces[] = {
215     { "base64",           CE_BASE64,    InitBase64 },
216     { "quoted-printable", CE_QUOTED,    InitQuoted },
217     { "8bit",             CE_8BIT,      Init7Bit },
218     { "7bit",             CE_7BIT,      Init7Bit },
219     { "binary",           CE_BINARY,    NULL },
220     { NULL,               CE_EXTENSION, NULL },  /* these two must be last! */
221     { NULL,               CE_UNKNOWN,   NULL },
222 };
223
224 /*
225  * NOTE WELL: si_key MUST NOT have value of NOTOK
226  *
227  * si_key is 1 if access method is anonymous.
228  */
229 static struct str2init str2methods[] = {
230     { "afs",         1, InitFile },
231     { "anon-ftp",    1, InitFTP },
232     { "ftp",         0, InitFTP },
233     { "local-file",  0, InitFile },
234     { "mail-server", 0, InitMail },
235     { NULL,          0, NULL }
236 };
237
238
239 int
240 pidcheck (int status)
241 {
242     if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
243         return status;
244
245     fflush (stdout);
246     fflush (stderr);
247     return done (1);
248 }
249
250
251 /*
252  * Main routine for translating composition file
253  * into valid MIME message.  It translates the draft
254  * into a content structure (actually a tree of content
255  * structures).  This message then can be manipulated
256  * in various ways, including being output via
257  * output_message().
258  */
259
260 CT
261 build_mime (char *infile)
262 {
263     int compnum, state;
264     char buf[BUFSIZ], name[NAMESZ];
265     char *cp, *np, *vp;
266     struct multipart *m;
267     struct part **pp;
268     CT ct;
269     FILE *in;
270
271     umask (~m_gmprot ());
272
273     /* open the composition draft */
274     if ((in = fopen (infile, "r")) == NULL)
275         adios (infile, "unable to open for reading");
276
277     /*
278      * Allocate space for primary (outside) content
279      */
280     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
281         adios (NULL, "out of memory");
282
283     /*
284      * Allocate structure for handling decoded content
285      * for this part.  We don't really need this, but
286      * allocate it to remain consistent.
287      */
288     init_decoded_content (ct);
289
290     /*
291      * Parse some of the header fields in the composition
292      * draft into the linked list of header fields for
293      * the new MIME message.
294      */
295     for (compnum = 1, state = FLD;;) {
296         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
297         case FLD:
298         case FLDPLUS:
299         case FLDEOF:
300             compnum++;
301
302             /* abort if draft has Mime-Version header field */
303             if (!strcasecmp (name, VRSN_FIELD))
304                 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
305
306             /* abort if draft has Content-Transfer-Encoding header field */
307             if (!strcasecmp (name, ENCODING_FIELD))
308                 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
309
310             /* ignore any Content-Type fields in the header */
311             if (!strcasecmp (name, TYPE_FIELD)) {
312                 while (state == FLDPLUS)
313                     state = m_getfld (state, name, buf, sizeof(buf), in);
314                 goto finish_field;
315             }
316
317             /* get copies of the buffers */
318             np = add (name, NULL);
319             vp = add (buf, NULL);
320
321             /* if necessary, get rest of field */
322             while (state == FLDPLUS) {
323                 state = m_getfld (state, name, buf, sizeof(buf), in);
324                 vp = add (buf, vp);     /* add to previous value */
325             }
326
327             /* Now add the header data to the list */
328             add_header (ct, np, vp);
329
330 finish_field:
331             /* if this wasn't the last header field, then continue */
332             if (state != FLDEOF)
333                 continue;
334             /* else fall... */
335
336         case FILEEOF:
337             adios (NULL, "draft has empty body -- no directives!");
338             /* NOTREACHED */
339
340         case BODY:
341         case BODYEOF:
342             fseek (in, (long) (-strlen (buf)), SEEK_CUR);
343             break;
344
345         case LENERR:
346         case FMTERR:
347             adios (NULL, "message format error in component #%d", compnum);
348
349         default:
350             adios (NULL, "getfld() returned %d", state);
351         }
352         break;
353     }
354
355     /*
356      * Now add the MIME-Version header field
357      * to the list of header fields.
358      */
359     np = add (VRSN_FIELD, NULL);
360     vp = concat (" ", VRSN_VALUE, "\n", NULL);
361     add_header (ct, np, vp);
362
363     /*
364      * We initally assume we will find multiple contents in the
365      * draft.  So create a multipart/mixed content to hold everything.
366      * We can remove this later, if it is not needed.
367      */
368     if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
369         done (1);
370     ct->c_type = CT_MULTIPART;
371     ct->c_subtype = MULTI_MIXED;
372     ct->c_file = add (infile, NULL);
373
374     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
375         adios (NULL, "out of memory");
376     ct->c_ctparams = (void *) m;
377     pp = &m->mp_parts;
378
379     /*
380      * read and parse the composition file
381      * and the directives it contains.
382      */
383     while (fgetstr (buf, sizeof(buf) - 1, in)) {
384         struct part *part;
385         CT p;
386
387         if (user_content (in, infile, buf, &p) == DONE) {
388             admonish (NULL, "ignoring spurious #end");
389             continue;
390         }
391         if (!p)
392             continue;
393
394         if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
395             adios (NULL, "out of memory");
396         *pp = part;
397         pp = &part->mp_next;
398         part->mp_part = p;
399     }
400
401     /*
402      * close the composition draft since
403      * it's not needed any longer.
404      */
405     fclose (in);
406
407     /* check if any contents were found */
408     if (!m->mp_parts)
409         adios (NULL, "no content directives found");
410
411     /*
412      * If only one content was found, then remove and
413      * free the outer multipart content.
414      */
415     if (!m->mp_parts->mp_next) {
416         CT p;
417
418         p = m->mp_parts->mp_part;
419         m->mp_parts->mp_part = NULL;
420
421         /* move header fields */
422         p->c_first_hf = ct->c_first_hf;
423         p->c_last_hf = ct->c_last_hf;
424         ct->c_first_hf = NULL;
425         ct->c_last_hf = NULL;
426
427         free_content (ct);
428         ct = p;
429     } else {
430         set_id (ct, 1);
431     }
432
433     /*
434      * Fill out, or expand directives.  Parse and execute
435      * commands specified by profile composition strings.
436      */
437     compose_content (ct);
438
439     if ((cp = strchr(prefix, 'a')) == NULL)
440         adios (NULL, "internal error(4)");
441
442     /*
443      * Scan the contents.  Choose a transfer encoding, and
444      * check if prefix for multipart boundary clashes with
445      * any of the contents.
446      */
447     while (scan_content (ct) == NOTOK) {
448         if (*cp < 'z') {
449             (*cp)++;
450         } else {
451             if (*++cp == 0)
452                 adios (NULL, "giving up trying to find a unique delimiter string");
453             else
454                 (*cp)++;
455         }
456     }
457
458     /* Build the rest of the header field structures */
459     build_headers (ct);
460
461     return ct;
462 }
463
464
465 /*
466  * Main routine for reading/parsing the headers
467  * of a message content.
468  *
469  * toplevel =  1   # we are at the top level of the message
470  * toplevel =  0   # we are inside message type or multipart type
471  *                 # other than multipart/digest
472  * toplevel = -1   # we are inside multipart/digest
473  */
474
475 static CT
476 get_content (FILE *in, char *file, int toplevel)
477 {
478     int compnum, state;
479     char buf[BUFSIZ], name[NAMESZ];
480     CT ct;
481
482     if (!(ct = (CT) calloc (1, sizeof(*ct))))
483         adios (NULL, "out of memory");
484
485     ct->c_fp = in;
486     ct->c_file = add (file, NULL);
487     ct->c_begin = ftell (ct->c_fp) + 1;
488
489     /*
490      * Read the content headers
491      */
492     for (compnum = 1, state = FLD;;) {
493         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
494         case FLD:
495         case FLDPLUS:
496         case FLDEOF:
497             compnum++;
498
499             /* Get MIME-Version field */
500             if (!strcasecmp (name, VRSN_FIELD)) {
501                 int ucmp;
502                 char c, *cp, *dp;
503
504                 cp = add (buf, NULL);
505                 while (state == FLDPLUS) {
506                     state = m_getfld (state, name, buf, sizeof(buf), in);
507                     cp = add (buf, cp);
508                 }
509
510                 if (ct->c_vrsn) {
511                     advise (NULL, "message %s has multiple %s: fields (%s)",
512                             ct->c_file, VRSN_FIELD, dp = trimcpy (cp));
513                     free (dp);
514                     free (cp);
515                     goto out;
516                 }
517
518                 ct->c_vrsn = cp;
519                 while (isspace (*cp))
520                     cp++;
521                 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
522                     *dp++ = ' ';
523                 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
524                     if (!isspace (*dp))
525                         break;
526                 *++dp = '\0';
527                 if (debugsw)
528                     fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
529
530                 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
531                     goto out;
532
533                 for (dp = cp; istoken (*dp); dp++)
534                     continue;
535                 c = *dp, *dp = '\0';
536                 ucmp = !strcasecmp (cp, VRSN_VALUE);
537                 *dp = c;
538                 if (!ucmp)
539                     admonish (NULL,
540                               "message %s has unknown value for %s: field (%s)",
541                               ct->c_file, VRSN_FIELD, cp);
542                 goto got_header;
543             }
544
545             /* Get Content-Type field */
546             if (!strcasecmp (name, TYPE_FIELD)) {
547                 char *cp;
548                 struct str2init *s2i;
549                 CI ci = &ct->c_ctinfo;
550
551                 cp = add (buf, NULL);
552                 while (state == FLDPLUS) {
553                     state = m_getfld (state, name, buf, sizeof(buf), in);
554                     cp = add (buf, cp);
555                 }
556
557                 /* Check if we've already seen a Content-Type header */
558                 if (ct->c_ctline) {
559                     char *dp = trimcpy (cp);
560
561                     advise (NULL, "message %s has multiple %s: fields (%s)",
562                             ct->c_file, TYPE_FIELD, dp);
563                     free (dp);
564                     free (cp);
565                     goto out;
566                 }
567
568                 /* Parse the Content-Type field */
569                 if (get_ctinfo (cp, ct, 0) == NOTOK)
570                     goto out;
571
572                 /*
573                  * Set the Init function and the internal
574                  * flag for this content type.
575                  */
576                 for (s2i = str2cts; s2i->si_key; s2i++)
577                     if (!strcasecmp (ci->ci_type, s2i->si_key))
578                         break;
579                 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
580                     s2i++;
581                 ct->c_type = s2i->si_val;
582                 ct->c_ctinitfnx = s2i->si_init;
583                 goto got_header;
584             }
585
586             /* Get Content-Transfer-Encoding field */
587             if (!strcasecmp (name, ENCODING_FIELD)) {
588                 char *cp, *dp;
589                 char c;
590                 struct str2init *s2i;
591
592                 cp = add (buf, NULL);
593                 while (state == FLDPLUS) {
594                     state = m_getfld (state, name, buf, sizeof(buf), in);
595                     cp = add (buf, cp);
596                 }
597
598                 /*
599                  * Check if we've already seen the
600                  * Content-Transfer-Encoding field
601                  */
602                 if (ct->c_celine) {
603                     advise (NULL, "message %s has multiple %s: fields (%s)",
604                             ct->c_file, ENCODING_FIELD, dp = trimcpy (cp));
605                     free (dp);
606                     free (cp);
607                     goto out;
608                 }
609
610                 ct->c_celine = cp;      /* Save copy of this field */
611                 while (isspace (*cp))
612                     cp++;
613                 for (dp = cp; istoken (*dp); dp++)
614                     continue;
615                 c = *dp;
616                 *dp = '\0';
617
618                 /*
619                  * Find the internal flag and Init function
620                  * for this transfer encoding.
621                  */
622                 for (s2i = str2ces; s2i->si_key; s2i++)
623                     if (!strcasecmp (cp, s2i->si_key))
624                         break;
625                 if (!s2i->si_key && !uprf (cp, "X-"))
626                     s2i++;
627                 *dp = c;
628                 ct->c_encoding = s2i->si_val;
629
630                 /* Call the Init function for this encoding */
631                 if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
632                     goto out;
633                 goto got_header;
634             }
635
636             /* Get Content-ID field */
637             if (!strcasecmp (name, ID_FIELD)) {
638                 ct->c_id = add (buf, ct->c_id);
639                 while (state == FLDPLUS) {
640                     state = m_getfld (state, name, buf, sizeof(buf), in);
641                     ct->c_id = add (buf, ct->c_id);
642                 }
643                 goto got_header;
644             }
645
646             /* Get Content-Description field */
647             if (!strcasecmp (name, DESCR_FIELD)) {
648                 ct->c_descr = add (buf, ct->c_descr);
649                 while (state == FLDPLUS) {
650                     state = m_getfld (state, name, buf, sizeof(buf), in);
651                     ct->c_descr = add (buf, ct->c_descr);
652                 }
653                 goto got_header;
654             }
655
656             /* Get Content-Disposition field */
657             if (!strcasecmp (name, DISPO_FIELD)) {
658                 ct->c_dispo = add (buf, ct->c_dispo);
659                 while (state == FLDPLUS) {
660                     state = m_getfld (state, name, buf, sizeof(buf), in);
661                     ct->c_dispo = add (buf, ct->c_dispo);
662                 }
663                 goto got_header;
664             }
665
666             /* Get Content-MD5 field */
667             if (!strcasecmp (name, MD5_FIELD)) {
668                 char *cp, *dp, *ep;
669
670                 cp = add (buf, NULL);
671                 while (state == FLDPLUS) {
672                     state = m_getfld (state, name, buf, sizeof(buf), in);
673                     cp = add (buf, cp);
674                 }
675
676                 if (!checksw) {
677                     free (cp);
678                     goto got_header;
679                 }
680
681                 if (ct->c_digested) {
682                     advise (NULL, "message %s has multiple %s: fields (%s)",
683                             ct->c_file, MD5_FIELD, dp = trimcpy (cp));
684                     free (dp);
685                     free (cp);
686                     goto out;
687                 }
688
689                 ep = cp;
690                 while (isspace (*cp))
691                     cp++;
692                 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
693                     *dp++ = ' ';
694                 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
695                     if (!isspace (*dp))
696                         break;
697                 *++dp = '\0';
698                 if (debugsw)
699                     fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
700
701                 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
702                     free (ep);
703                     goto out;
704                 }
705
706                 for (dp = cp; *dp && !isspace (*dp); dp++)
707                     continue;
708                 *dp = '\0';
709
710                 readDigest (ct, cp);
711                 free (ep);
712                 ct->c_digested++;
713                 goto got_header;
714             }
715
716 #if 0
717             if (uprf (name, XXX_FIELD_PRF))
718                 advise (NULL, "unknown field (%s) in message %s",
719                         name, ct->c_file);
720             /* and fall... */
721 #endif
722
723             while (state == FLDPLUS)
724                 state = m_getfld (state, name, buf, sizeof(buf), in);
725
726 got_header:
727             if (state != FLDEOF) {
728                 ct->c_begin = ftell (in) + 1;
729                 continue;
730             }
731             /* else fall... */
732
733         case BODY:
734         case BODYEOF:
735             ct->c_begin = ftell (in) - strlen (buf);
736             break;
737
738         case FILEEOF:
739             ct->c_begin = ftell (in);
740             break;
741
742         case LENERR:
743         case FMTERR:
744             adios (NULL, "message format error in component #%d", compnum);
745
746         default:
747             adios (NULL, "getfld() returned %d", state);
748         }
749         break;
750     }
751
752     /*
753      * Check if we saw a Content-Type field.
754      * If not, then assign a default value for
755      * it, and the Init function.
756      */
757     if (!ct->c_ctline) {
758         /*
759          * If we are inside a multipart/digest message,
760          * so default type is message/rfc822
761          */
762         if (toplevel < 0) {
763             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
764                 goto out;
765             ct->c_type = CT_MESSAGE;
766             ct->c_ctinitfnx = InitMessage;
767         } else {
768             /*
769              * Else default type is text/plain
770              */
771             if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
772                 goto out;
773             ct->c_type = CT_TEXT;
774             ct->c_ctinitfnx = InitText;
775         }
776     }
777
778     /* Use default Transfer-Encoding, if necessary */
779     if (!ct->c_celine) {
780         ct->c_encoding = CE_7BIT;
781         Init7Bit (ct);
782     }
783
784     return ct;
785
786 out:
787     free_content (ct);
788     return NULL;
789 }
790
791
792 /*
793  * small routine to add header field to list
794  */
795
796 static int
797 add_header (CT ct, char *name, char *value)
798 {
799     HF hp;
800
801     /* allocate header field structure */
802     hp = mh_xmalloc (sizeof(*hp));
803
804     /* link data into header structure */
805     hp->name = name;
806     hp->value = value;
807     hp->next = NULL;
808
809     /* link header structure into the list */
810     if (ct->c_first_hf == NULL) {
811         ct->c_first_hf = hp;            /* this is the first */
812         ct->c_last_hf = hp;
813     } else {
814         ct->c_last_hf->next = hp;       /* add it to the end */
815         ct->c_last_hf = hp;
816     }
817
818     return 0;
819 }
820
821
822 /*
823  * Used to parse both:
824  *   1) Content-Type line
825  *   2) composition directives
826  *
827  * and fills in the information of the CTinfo structure.
828  */
829
830 static int
831 get_ctinfo (char *cp, CT ct, int magic)
832 {
833     int i;
834     char *dp, **ap, **ep;
835     char c;
836     CI ci;
837
838     ci = &ct->c_ctinfo;
839     i = strlen (invo_name) + 2;
840
841     /* store copy of Content-Type line */
842     cp = ct->c_ctline = add (cp, NULL);
843
844     while (isspace (*cp))       /* trim leading spaces */
845         cp++;
846
847     /* change newlines to spaces */
848     for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
849         *dp++ = ' ';
850
851     /* trim trailing spaces */
852     for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
853         if (!isspace (*dp))
854             break;
855     *++dp = '\0';
856
857     if (debugsw)
858         fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
859
860     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
861         return NOTOK;
862
863     for (dp = cp; istoken (*dp); dp++)
864         continue;
865     c = *dp, *dp = '\0';
866     ci->ci_type = add (cp, NULL);       /* store content type */
867     *dp = c, cp = dp;
868
869     if (!*ci->ci_type) {
870         advise (NULL, "invalid %s: field in message %s (empty type)", 
871                 TYPE_FIELD, ct->c_file);
872         return NOTOK;
873     }
874
875     /* down case the content type string */
876     for (dp = ci->ci_type; *dp; dp++)
877         if (isalpha(*dp) && isupper (*dp))
878             *dp = tolower (*dp);
879
880     while (isspace (*cp))
881         cp++;
882
883     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
884         return NOTOK;
885
886     if (*cp != '/') {
887         if (!magic)
888             ci->ci_subtype = add ("", NULL);
889         goto magic_skip;
890     }
891
892     cp++;
893     while (isspace (*cp))
894         cp++;
895
896     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
897         return NOTOK;
898
899     for (dp = cp; istoken (*dp); dp++)
900         continue;
901     c = *dp, *dp = '\0';
902     ci->ci_subtype = add (cp, NULL);    /* store the content subtype */
903     *dp = c, cp = dp;
904
905     if (!*ci->ci_subtype) {
906         advise (NULL,
907                 "invalid %s: field in message %s (empty subtype for \"%s\")",
908                 TYPE_FIELD, ct->c_file, ci->ci_type);
909         return NOTOK;
910     }
911
912     /* down case the content subtype string */
913     for (dp = ci->ci_subtype; *dp; dp++)
914         if (isalpha(*dp) && isupper (*dp))
915             *dp = tolower (*dp);
916
917 magic_skip:
918     while (isspace (*cp))
919         cp++;
920
921     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
922         return NOTOK;
923
924     /*
925      * Parse attribute/value pairs given with Content-Type
926      */
927     ep = (ap = ci->ci_attrs) + NPARMS;
928     while (*cp == ';') {
929         char *vp, *up;
930
931         if (ap >= ep) {
932             advise (NULL,
933                     "too many parameters in message %s's %s: field (%d max)",
934                     ct->c_file, TYPE_FIELD, NPARMS);
935             return NOTOK;
936         }
937
938         cp++;
939         while (isspace (*cp))
940             cp++;
941
942         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
943             return NOTOK;
944
945         if (*cp == 0) {
946             advise (NULL,
947                     "extraneous trailing ';' in message %s's %s: parameter list",
948                     ct->c_file, TYPE_FIELD);
949             return OK;
950         }
951
952         /* down case the attribute name */
953         for (dp = cp; istoken (*dp); dp++)
954             if (isalpha(*dp) && isupper (*dp))
955                 *dp = tolower (*dp);
956
957         for (up = dp; isspace (*dp); )
958             dp++;
959         if (dp == cp || *dp != '=') {
960             advise (NULL,
961                     "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
962                     ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
963             return NOTOK;
964         }
965
966         vp = (*ap = add (cp, NULL)) + (up - cp);
967         *vp = '\0';
968         for (dp++; isspace (*dp); )
969             dp++;
970
971         /* now add the attribute value */
972         ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
973
974         if (*dp == '"') {
975             for (cp = ++dp, dp = vp;;) {
976                 switch (c = *cp++) {
977                     case '\0':
978 bad_quote:
979                         advise (NULL,
980                                 "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
981                                 ct->c_file, TYPE_FIELD, i, i, "", *ap);
982                         return NOTOK;
983
984                     case '\\':
985                         *dp++ = c;
986                         if ((c = *cp++) == '\0')
987                             goto bad_quote;
988                         /* else fall... */
989
990                     default:
991                         *dp++ = c;
992                         continue;
993
994                     case '"':
995                         *dp = '\0';
996                         break;
997                 }
998                 break;
999             }
1000         } else {
1001             for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
1002                 continue;
1003             *dp = '\0';
1004         }
1005         if (!*vp) {
1006             advise (NULL,
1007                     "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
1008                     ct->c_file, TYPE_FIELD, i, i, "", *ap);
1009             return NOTOK;
1010         }
1011         ap++;
1012
1013         while (isspace (*cp))
1014             cp++;
1015
1016         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
1017             return NOTOK;
1018     }
1019
1020     /*
1021      * Get any <Content-Id> given in buffer
1022      */
1023     if (magic && *cp == '<') {
1024         if (ct->c_id) {
1025             free (ct->c_id);
1026             ct->c_id = NULL;
1027         }
1028         if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
1029             advise (NULL, "invalid ID in message %s", ct->c_file);
1030             return NOTOK;
1031         }
1032         c = *dp;
1033         *dp = '\0';
1034         if (*ct->c_id)
1035             ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
1036         else
1037             ct->c_id = NULL;
1038         *dp++ = c;
1039         cp = dp;
1040
1041         while (isspace (*cp))
1042             cp++;
1043     }
1044
1045     /*
1046      * Get any [Content-Description] given in buffer.
1047      */
1048     if (magic && *cp == '[') {
1049         ct->c_descr = ++cp;
1050         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
1051             if (*dp == ']')
1052                 break;
1053         if (dp < cp) {
1054             advise (NULL, "invalid description in message %s", ct->c_file);
1055             ct->c_descr = NULL;
1056             return NOTOK;
1057         }
1058         
1059         c = *dp;
1060         *dp = '\0';
1061         if (*ct->c_descr)
1062             ct->c_descr = concat (ct->c_descr, "\n", NULL);
1063         else
1064             ct->c_descr = NULL;
1065         *dp++ = c;
1066         cp = dp;
1067
1068         while (isspace (*cp))
1069             cp++;
1070     }
1071
1072     /*
1073      * Get any {Content-Disposition} given in buffer.
1074      */
1075     if (magic && *cp == '{') {
1076         ct->c_dispo = ++cp;
1077         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
1078             if (*dp == '}')
1079                 break;
1080         if (dp < cp) {
1081             advise (NULL, "invalid disposition in message %s", ct->c_file);
1082             ct->c_dispo = NULL;
1083             return NOTOK;
1084         }
1085         
1086         c = *dp;
1087         *dp = '\0';
1088         if (*ct->c_dispo)
1089             ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
1090         else
1091             ct->c_dispo = NULL;
1092         *dp++ = c;
1093         cp = dp;
1094
1095         while (isspace (*cp))
1096             cp++;
1097     }
1098
1099     /*
1100      * Check if anything is left over
1101      */
1102     if (*cp) {
1103         if (magic) {
1104             ci->ci_magic = add (cp, NULL);
1105
1106             /* If there is a Content-Disposition header and it doesn't
1107                have a *filename=, extract it from the magic contents.
1108                The r1bindex call skips any leading directory
1109                components. */
1110             if (ct->c_dispo)
1111               ct->c_dispo =
1112                 incl_name_value (ct->c_dispo,
1113                                  "filename",
1114                                  r1bindex (extract_name_value ("name",
1115                                                                ci->ci_magic),
1116                                            '/'));
1117         }
1118         else
1119             advise (NULL,
1120                     "extraneous information in message %s's %s: field\n%*.*s(%s)",
1121                 ct->c_file, TYPE_FIELD, i, i, "", cp);
1122     }
1123
1124     return OK;
1125 }
1126
1127
1128 static int
1129 get_comment (CT ct, char **ap, int istype)
1130 {
1131     int i;
1132     char *bp, *cp;
1133     char c, buffer[BUFSIZ], *dp;
1134     CI ci;
1135
1136     ci = &ct->c_ctinfo;
1137     cp = *ap;
1138     bp = buffer;
1139     cp++;
1140
1141     for (i = 0;;) {
1142         switch (c = *cp++) {
1143         case '\0':
1144 invalid:
1145         advise (NULL, "invalid comment in message %s's %s: field",
1146                 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
1147         return NOTOK;
1148
1149         case '\\':
1150             *bp++ = c;
1151             if ((c = *cp++) == '\0')
1152                 goto invalid;
1153             *bp++ = c;
1154             continue;
1155
1156         case '(':
1157             i++;
1158             /* and fall... */
1159         default:
1160             *bp++ = c;
1161             continue;
1162
1163         case ')':
1164             if (--i < 0)
1165                 break;
1166             *bp++ = c;
1167             continue;
1168         }
1169         break;
1170     }
1171     *bp = '\0';
1172
1173     if (istype) {
1174         if ((dp = ci->ci_comment)) {
1175             ci->ci_comment = concat (dp, " ", buffer, NULL);
1176             free (dp);
1177         } else {
1178             ci->ci_comment = add (buffer, NULL);
1179         }
1180     }
1181
1182     while (isspace (*cp))
1183         cp++;
1184
1185     *ap = cp;
1186     return OK;
1187 }
1188
1189
1190 /*
1191  * CONTENTS
1192  *
1193  * Handles content types audio, image, and video.
1194  * There's not much to do right here.
1195  */
1196
1197 static int
1198 InitGeneric (CT ct)
1199 {
1200     return OK;          /* not much to do here */
1201 }
1202
1203
1204 /*
1205  * TEXT
1206  */
1207
1208 static int
1209 InitText (CT ct)
1210 {
1211     char **ap, **ep;
1212     struct k2v *kv;
1213     struct text *t;
1214     CI ci = &ct->c_ctinfo;
1215
1216     /* check for missing subtype */
1217     if (!*ci->ci_subtype)
1218         ci->ci_subtype = add ("plain", ci->ci_subtype);
1219
1220     /* match subtype */
1221     for (kv = SubText; kv->kv_key; kv++)
1222         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1223             break;
1224     ct->c_subtype = kv->kv_value;
1225
1226     /* allocate text character set structure */
1227     if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
1228         adios (NULL, "out of memory");
1229     ct->c_ctparams = (void *) t;
1230
1231     /* initially mark character set as unspecified */
1232     t->tx_charset = CHARSET_UNSPECIFIED;
1233
1234     /* scan for charset parameter */
1235     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1236         if (!strcasecmp (*ap, "charset"))
1237             break;
1238
1239     /* check if content specified a character set */
1240     if (*ap) {
1241         /* match character set or set to CHARSET_UNKNOWN */
1242         for (kv = Charset; kv->kv_key; kv++)
1243             if (!strcasecmp (*ep, kv->kv_key))
1244                 break;
1245         t->tx_charset = kv->kv_value;
1246     }
1247
1248     return OK;
1249 }
1250
1251
1252 /*
1253  * MULTIPART
1254  */
1255
1256 static int
1257 InitMultiPart (CT ct)
1258 {
1259     int inout;
1260     long last, pos;
1261     char *cp, *dp, **ap, **ep;
1262     char *bp, buffer[BUFSIZ];
1263     struct multipart *m;
1264     struct k2v *kv;
1265     struct part *part, **next;
1266     CI ci = &ct->c_ctinfo;
1267     CT p;
1268     FILE *fp;
1269
1270     /*
1271      * The encoding for multipart messages must be either
1272      * 7bit, 8bit, or binary (per RFC2045).
1273      */
1274     if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1275         && ct->c_encoding != CE_BINARY) {
1276         admonish (NULL,
1277                   "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1278                   ci->ci_type, ci->ci_subtype, ct->c_file);
1279         return NOTOK;
1280     }
1281
1282     /* match subtype */
1283     for (kv = SubMultiPart; kv->kv_key; kv++)
1284         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1285             break;
1286     ct->c_subtype = kv->kv_value;
1287
1288     /*
1289      * Check for "boundary" parameter, which is
1290      * required for multipart messages.
1291      */
1292     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1293         if (!strcasecmp (*ap, "boundary")) {
1294             bp = *ep;
1295             break;
1296         }
1297     }
1298
1299     /* complain if boundary parameter is missing */
1300     if (!*ap) {
1301         advise (NULL,
1302                 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1303                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1304         return NOTOK;
1305     }
1306
1307     /* allocate primary structure for multipart info */
1308     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1309         adios (NULL, "out of memory");
1310     ct->c_ctparams = (void *) m;
1311
1312     /* check if boundary parameter contains only whitespace characters */
1313     for (cp = bp; isspace (*cp); cp++)
1314         continue;
1315     if (!*cp) {
1316         advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1317                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1318         return NOTOK;
1319     }
1320
1321     /* remove trailing whitespace from boundary parameter */
1322     for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1323         if (!isspace (*dp))
1324             break;
1325     *++dp = '\0';
1326
1327     /* record boundary separators */
1328     m->mp_start = concat (bp, "\n", NULL);
1329     m->mp_stop = concat (bp, "--\n", NULL);
1330
1331     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1332         advise (ct->c_file, "unable to open for reading");
1333         return NOTOK;
1334     }
1335
1336     fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1337     last = ct->c_end;
1338     next = &m->mp_parts;
1339     part = NULL;
1340     inout = 1;
1341
1342     while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1343         if (pos > last)
1344             break;
1345
1346         pos += strlen (buffer);
1347         if (buffer[0] != '-' || buffer[1] != '-')
1348             continue;
1349         if (inout) {
1350             if (strcmp (buffer + 2, m->mp_start))
1351                 continue;
1352 next_part:
1353             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1354                 adios (NULL, "out of memory");
1355             *next = part;
1356             next = &part->mp_next;
1357
1358             if (!(p = get_content (fp, ct->c_file,
1359                 rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1360                 fclose (ct->c_fp);
1361                 ct->c_fp = NULL;
1362                 return NOTOK;
1363             }
1364             p->c_fp = NULL;
1365             part->mp_part = p;
1366             pos = p->c_begin;
1367             fseek (fp, pos, SEEK_SET);
1368             inout = 0;
1369         } else {
1370             if (strcmp (buffer + 2, m->mp_start) == 0) {
1371                 inout = 1;
1372 end_part:
1373                 p = part->mp_part;
1374                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1375                 if (p->c_end < p->c_begin)
1376                     p->c_begin = p->c_end;
1377                 if (inout)
1378                     goto next_part;
1379                 goto last_part;
1380             } else {
1381                 if (strcmp (buffer + 2, m->mp_stop) == 0)
1382                     goto end_part;
1383             }
1384         }
1385     }
1386
1387     advise (NULL, "bogus multipart content in message %s", ct->c_file);
1388     if (!inout && part) {
1389         p = part->mp_part;
1390         p->c_end = ct->c_end;
1391
1392         if (p->c_begin >= p->c_end) {
1393             for (next = &m->mp_parts; *next != part;
1394                      next = &((*next)->mp_next))
1395                 continue;
1396             *next = NULL;
1397             free_content (p);
1398             free ((char *) part);
1399         }
1400     }
1401
1402 last_part:
1403     /* reverse the order of the parts for multipart/alternative */
1404     if (ct->c_subtype == MULTI_ALTERNATE)
1405         reverse_parts (ct);
1406
1407     /*
1408      * label all subparts with part number, and
1409      * then initialize the content of the subpart.
1410      */
1411     {
1412         int partnum;
1413         char *pp;
1414         char partnam[BUFSIZ];
1415
1416         if (ct->c_partno) {
1417             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1418             pp = partnam + strlen (partnam);
1419         } else {
1420             pp = partnam;
1421         }
1422
1423         for (part = m->mp_parts, partnum = 1; part;
1424                  part = part->mp_next, partnum++) {
1425             p = part->mp_part;
1426
1427             sprintf (pp, "%d", partnum);
1428             p->c_partno = add (partnam, NULL);
1429
1430             /* initialize the content of the subparts */
1431             if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1432                 fclose (ct->c_fp);
1433                 ct->c_fp = NULL;
1434                 return NOTOK;
1435             }
1436         }
1437     }
1438
1439     fclose (ct->c_fp);
1440     ct->c_fp = NULL;
1441     return OK;
1442 }
1443
1444
1445 /*
1446  * reverse the order of the parts of a multipart
1447  */
1448
1449 static void
1450 reverse_parts (CT ct)
1451 {
1452     int i;
1453     struct multipart *m;
1454     struct part **base, **bmp, **next, *part;
1455
1456     m = (struct multipart *) ct->c_ctparams;
1457
1458     /* if only one part, just return */
1459     if (!m->mp_parts || !m->mp_parts->mp_next)
1460         return;
1461
1462     /* count number of parts */
1463     i = 0;
1464     for (part = m->mp_parts; part; part = part->mp_next)
1465         i++;
1466
1467     /* allocate array of pointers to the parts */
1468     if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1469         adios (NULL, "out of memory");
1470     bmp = base;
1471
1472     /* point at all the parts */
1473     for (part = m->mp_parts; part; part = part->mp_next)
1474         *bmp++ = part;
1475     *bmp = NULL;
1476
1477     /* reverse the order of the parts */
1478     next = &m->mp_parts;
1479     for (bmp--; bmp >= base; bmp--) {
1480         part = *bmp;
1481         *next = part;
1482         next = &part->mp_next;
1483     }
1484     *next = NULL;
1485
1486     /* free array of pointers */
1487     free ((char *) base);
1488 }
1489
1490
1491 /*
1492  * MESSAGE
1493  */
1494
1495 static int
1496 InitMessage (CT ct)
1497 {
1498     struct k2v *kv;
1499     CI ci = &ct->c_ctinfo;
1500
1501     if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1502         admonish (NULL,
1503                   "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1504                   ci->ci_type, ci->ci_subtype, ct->c_file);
1505         return NOTOK;
1506     }
1507
1508     /* check for missing subtype */
1509     if (!*ci->ci_subtype)
1510         ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1511
1512     /* match subtype */
1513     for (kv = SubMessage; kv->kv_key; kv++)
1514         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1515             break;
1516     ct->c_subtype = kv->kv_value;
1517
1518     switch (ct->c_subtype) {
1519         case MESSAGE_RFC822:
1520             break;
1521
1522         case MESSAGE_PARTIAL:
1523             {
1524                 char **ap, **ep;
1525                 struct partial *p;
1526
1527                 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1528                     adios (NULL, "out of memory");
1529                 ct->c_ctparams = (void *) p;
1530
1531                 /* scan for parameters "id", "number", and "total" */
1532                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1533                     if (!strcasecmp (*ap, "id")) {
1534                         p->pm_partid = add (*ep, NULL);
1535                         continue;
1536                     }
1537                     if (!strcasecmp (*ap, "number")) {
1538                         if (sscanf (*ep, "%d", &p->pm_partno) != 1
1539                                 || p->pm_partno < 1) {
1540 invalid_param:
1541                             advise (NULL,
1542                                     "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1543                                     *ap, ci->ci_type, ci->ci_subtype,
1544                                     ct->c_file, TYPE_FIELD);
1545                             return NOTOK;
1546                         }
1547                         continue;
1548                     }
1549                     if (!strcasecmp (*ap, "total")) {
1550                         if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1551                                 || p->pm_maxno < 1)
1552                             goto invalid_param;
1553                         continue;
1554                     }
1555                 }
1556
1557                 if (!p->pm_partid
1558                         || !p->pm_partno
1559                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1560                     advise (NULL,
1561                             "invalid parameters for \"%s/%s\" type in message %s's %s field",
1562                             ci->ci_type, ci->ci_subtype,
1563                             ct->c_file, TYPE_FIELD);
1564                     return NOTOK;
1565                 }
1566             }
1567             break;
1568
1569         case MESSAGE_EXTERNAL:
1570             {
1571                 int exresult;
1572                 struct exbody *e;
1573                 CT p;
1574                 FILE *fp;
1575
1576                 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1577                     adios (NULL, "out of memory");
1578                 ct->c_ctparams = (void *) e;
1579
1580                 if (!ct->c_fp
1581                         && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1582                     advise (ct->c_file, "unable to open for reading");
1583                     return NOTOK;
1584                 }
1585
1586                 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1587
1588                 if (!(p = get_content (fp, ct->c_file, 0))) {
1589                     fclose (ct->c_fp);
1590                     ct->c_fp = NULL;
1591                     return NOTOK;
1592                 }
1593
1594                 e->eb_parent = ct;
1595                 e->eb_content = p;
1596                 p->c_ctexbody = e;
1597                 if ((exresult = params_external (ct, 0)) != NOTOK
1598                         && p->c_ceopenfnx == openMail) {
1599                     int cc, size;
1600                     char *bp;
1601                     
1602                     if ((size = ct->c_end - p->c_begin) <= 0) {
1603                         if (!e->eb_subject)
1604                             content_error (NULL, ct,
1605                                            "empty body for access-type=mail-server");
1606                         goto no_body;
1607                     }
1608                     
1609                     e->eb_body = bp = mh_xmalloc ((unsigned) size);
1610                     fseek (p->c_fp, p->c_begin, SEEK_SET);
1611                     while (size > 0)
1612                         switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1613                             case NOTOK:
1614                                 adios ("failed", "fread");
1615
1616                             case OK:
1617                                 adios (NULL, "unexpected EOF from fread");
1618
1619                             default:
1620                                 bp += cc, size -= cc;
1621                                 break;
1622                         }
1623                     *bp = 0;
1624                 }
1625 no_body:
1626                 p->c_fp = NULL;
1627                 p->c_end = p->c_begin;
1628
1629                 fclose (ct->c_fp);
1630                 ct->c_fp = NULL;
1631
1632                 if (exresult == NOTOK)
1633                     return NOTOK;
1634                 if (e->eb_flags == NOTOK)
1635                     return OK;
1636
1637                 switch (p->c_type) {
1638                     case CT_MULTIPART:
1639                         break;
1640
1641                     case CT_MESSAGE:
1642                         if (p->c_subtype != MESSAGE_RFC822)
1643                             break;
1644                         /* else fall... */
1645                     default:
1646                         e->eb_partno = ct->c_partno;
1647                         if (p->c_ctinitfnx)
1648                             (*p->c_ctinitfnx) (p);
1649                         break;
1650                 }
1651             }
1652             break;
1653
1654         default:
1655             break;
1656     }
1657
1658     return OK;
1659 }
1660
1661
1662 static int
1663 params_external (CT ct, int composing)
1664 {
1665     char **ap, **ep;
1666     struct exbody *e = (struct exbody *) ct->c_ctparams;
1667     CI ci = &ct->c_ctinfo;
1668
1669     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1670         if (!strcasecmp (*ap, "access-type")) {
1671             struct str2init *s2i;
1672             CT p = e->eb_content;
1673
1674             for (s2i = str2methods; s2i->si_key; s2i++)
1675                 if (!strcasecmp (*ep, s2i->si_key))
1676                     break;
1677
1678             if (!s2i->si_key) {
1679                 e->eb_access = *ep;
1680                 e->eb_flags = NOTOK;
1681                 p->c_encoding = CE_EXTERNAL;
1682                 continue;
1683             }
1684             e->eb_access = s2i->si_key;
1685             e->eb_flags = s2i->si_val;
1686             p->c_encoding = CE_EXTERNAL;
1687
1688             /* Call the Init function for this external type */
1689             if ((*s2i->si_init)(p) == NOTOK)
1690                 return NOTOK;
1691             continue;
1692         }
1693         if (!strcasecmp (*ap, "name")) {
1694             e->eb_name = *ep;
1695             continue;
1696         }
1697         if (!strcasecmp (*ap, "permission")) {
1698             e->eb_permission = *ep;
1699             continue;
1700         }
1701         if (!strcasecmp (*ap, "site")) {
1702             e->eb_site = *ep;
1703             continue;
1704         }
1705         if (!strcasecmp (*ap, "directory")) {
1706             e->eb_dir = *ep;
1707             continue;
1708         }
1709         if (!strcasecmp (*ap, "mode")) {
1710             e->eb_mode = *ep;
1711             continue;
1712         }
1713         if (!strcasecmp (*ap, "size")) {
1714             sscanf (*ep, "%lu", &e->eb_size);
1715             continue;
1716         }
1717         if (!strcasecmp (*ap, "server")) {
1718             e->eb_server = *ep;
1719             continue;
1720         }
1721         if (!strcasecmp (*ap, "subject")) {
1722             e->eb_subject = *ep;
1723             continue;
1724         }
1725         if (composing && !strcasecmp (*ap, "body")) {
1726             e->eb_body = getcpy (*ep);
1727             continue;
1728         }
1729     }
1730
1731     if (!e->eb_access) {
1732         advise (NULL,
1733                 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1734                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1735         return NOTOK;
1736     }
1737
1738     return OK;
1739 }
1740
1741
1742 /*
1743  * APPLICATION
1744  */
1745
1746 static int
1747 InitApplication (CT ct)
1748 {
1749     struct k2v *kv;
1750     CI ci = &ct->c_ctinfo;
1751
1752     /* match subtype */
1753     for (kv = SubApplication; kv->kv_key; kv++)
1754         if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1755             break;
1756     ct->c_subtype = kv->kv_value;
1757
1758     return OK;
1759 }
1760
1761
1762 /*
1763  * Set up structures for placing unencoded
1764  * content when building parts.
1765  */
1766
1767 static int
1768 init_decoded_content (CT ct)
1769 {
1770     CE ce;
1771
1772     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1773         adios (NULL, "out of memory");
1774
1775     ct->c_cefile     = ce;
1776     ct->c_ceopenfnx  = open7Bit;        /* since unencoded */
1777     ct->c_ceclosefnx = close_encoding;
1778     ct->c_cesizefnx  = NULL;            /* since unencoded */
1779
1780     return OK;
1781 }
1782
1783
1784 /*
1785  * TRANSFER ENCODINGS
1786  */
1787
1788 static int
1789 init_encoding (CT ct, OpenCEFunc openfnx)
1790 {
1791     CE ce;
1792
1793     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1794         adios (NULL, "out of memory");
1795
1796     ct->c_cefile     = ce;
1797     ct->c_ceopenfnx  = openfnx;
1798     ct->c_ceclosefnx = close_encoding;
1799     ct->c_cesizefnx  = size_encoding;
1800
1801     return OK;
1802 }
1803
1804
1805 static void
1806 close_encoding (CT ct)
1807 {
1808     CE ce;
1809
1810     if (!(ce = ct->c_cefile))
1811         return;
1812
1813     if (ce->ce_fp) {
1814         fclose (ce->ce_fp);
1815         ce->ce_fp = NULL;
1816     }
1817 }
1818
1819
1820 static unsigned long
1821 size_encoding (CT ct)
1822 {
1823     int fd;
1824     unsigned long size;
1825     char *file;
1826     CE ce;
1827     struct stat st;
1828
1829     if (!(ce = ct->c_cefile))
1830         return (ct->c_end - ct->c_begin);
1831
1832     if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1833         return (long) st.st_size;
1834
1835     if (ce->ce_file) {
1836         if (stat (ce->ce_file, &st) != NOTOK)
1837             return (long) st.st_size;
1838         else
1839             return 0L;
1840     }
1841
1842     if (ct->c_encoding == CE_EXTERNAL)
1843         return (ct->c_end - ct->c_begin);       
1844
1845     file = NULL;
1846     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1847         return (ct->c_end - ct->c_begin);
1848
1849     if (fstat (fd, &st) != NOTOK)
1850         size = (long) st.st_size;
1851     else
1852         size = 0L;
1853
1854     (*ct->c_ceclosefnx) (ct);
1855     return size;
1856 }
1857
1858
1859 /*
1860  * BASE64
1861  */
1862
1863 static unsigned char b642nib[0x80] = {
1864     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1865     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1866     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1867     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1868     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1869     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1870     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1871     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1872     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
1873     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1874     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1875     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1876     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
1877     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1878     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1879     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1880 };
1881
1882
1883 static int
1884 InitBase64 (CT ct)
1885 {
1886     return init_encoding (ct, openBase64);
1887 }
1888
1889
1890 static int
1891 openBase64 (CT ct, char **file)
1892 {
1893     int bitno, cc, digested;
1894     int fd, len, skip;
1895     unsigned long bits;
1896     unsigned char value, *b, *b1, *b2, *b3;
1897     char *cp, *ep, buffer[BUFSIZ];
1898     CE ce;
1899     MD5_CTX mdContext;
1900
1901     b  = (unsigned char *) &bits;
1902     b1 = &b[endian > 0 ? 1 : 2];
1903     b2 = &b[endian > 0 ? 2 : 1];
1904     b3 = &b[endian > 0 ? 3 : 0];
1905
1906     ce = ct->c_cefile;
1907     if (ce->ce_fp) {
1908         fseek (ce->ce_fp, 0L, SEEK_SET);
1909         goto ready_to_go;
1910     }
1911
1912     if (ce->ce_file) {
1913         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1914             content_error (ce->ce_file, ct, "unable to fopen for reading");
1915             return NOTOK;
1916         }
1917         goto ready_to_go;
1918     }
1919
1920     if (*file == NULL) {
1921         ce->ce_file = add (m_scratch ("", tmp), NULL);
1922         ce->ce_unlink = 1;
1923     } else {
1924         ce->ce_file = add (*file, NULL);
1925         ce->ce_unlink = 0;
1926     }
1927
1928     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1929         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1930         return NOTOK;
1931     }
1932
1933     if ((len = ct->c_end - ct->c_begin) < 0)
1934         adios (NULL, "internal error(1)");
1935
1936     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1937         content_error (ct->c_file, ct, "unable to open for reading");
1938         return NOTOK;
1939     }
1940     
1941     if ((digested = ct->c_digested))
1942         MD5Init (&mdContext);
1943
1944     bitno = 18;
1945     bits = 0L;
1946     skip = 0;
1947
1948     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1949     while (len > 0) {
1950         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1951         case NOTOK:
1952             content_error (ct->c_file, ct, "error reading from");
1953             goto clean_up;
1954
1955         case OK:
1956             content_error (NULL, ct, "premature eof");
1957             goto clean_up;
1958
1959         default:
1960             if (cc > len)
1961                 cc = len;
1962             len -= cc;
1963
1964             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1965                 switch (*cp) {
1966                 default:
1967                     if (isspace (*cp))
1968                         break;
1969                     if (skip || (*cp & 0x80)
1970                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1971                         if (debugsw) {
1972                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1973                                 *cp,
1974                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1975                                 skip);
1976                         }
1977                         content_error (NULL, ct,
1978                                        "invalid BASE64 encoding -- continuing");
1979                         continue;
1980                     }
1981
1982                     bits |= value << bitno;
1983 test_end:
1984                     if ((bitno -= 6) < 0) {
1985                         putc ((char) *b1, ce->ce_fp);
1986                         if (digested)
1987                             MD5Update (&mdContext, b1, 1);
1988                         if (skip < 2) {
1989                             putc ((char) *b2, ce->ce_fp);
1990                             if (digested)
1991                                 MD5Update (&mdContext, b2, 1);
1992                             if (skip < 1) {
1993                                 putc ((char) *b3, ce->ce_fp);
1994                                 if (digested)
1995                                     MD5Update (&mdContext, b3, 1);
1996                             }
1997                         }
1998
1999                         if (ferror (ce->ce_fp)) {
2000                             content_error (ce->ce_file, ct,
2001                                            "error writing to");
2002                             goto clean_up;
2003                         }
2004                         bitno = 18, bits = 0L, skip = 0;
2005                     }
2006                     break;
2007
2008                 case '=':
2009                     if (++skip > 3)
2010                         goto self_delimiting;
2011                     goto test_end;
2012                 }
2013             }
2014         }
2015     }
2016
2017     if (bitno != 18) {
2018         if (debugsw)
2019             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2020
2021         content_error (NULL, ct, "invalid BASE64 encoding");
2022         goto clean_up;
2023     }
2024
2025 self_delimiting:
2026     fseek (ct->c_fp, 0L, SEEK_SET);
2027
2028     if (fflush (ce->ce_fp)) {
2029         content_error (ce->ce_file, ct, "error writing to");
2030         goto clean_up;
2031     }
2032
2033     if (digested) {
2034         unsigned char digest[16];
2035
2036         MD5Final (digest, &mdContext);
2037         if (memcmp((char *) digest, (char *) ct->c_digest,
2038                    sizeof(digest) / sizeof(digest[0])))
2039             content_error (NULL, ct,
2040                            "content integrity suspect (digest mismatch) -- continuing");
2041         else
2042             if (debugsw)
2043                 fprintf (stderr, "content integrity confirmed\n");
2044     }
2045
2046     fseek (ce->ce_fp, 0L, SEEK_SET);
2047
2048 ready_to_go:
2049     *file = ce->ce_file;
2050     return fileno (ce->ce_fp);
2051
2052 clean_up:
2053     free_encoding (ct, 0);
2054     return NOTOK;
2055 }
2056
2057
2058 /*
2059  * QUOTED PRINTABLE
2060  */
2061
2062 static char hex2nib[0x80] = {
2063     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2064     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2065     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2066     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2067     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2068     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2069     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
2070     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2071     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
2072     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2073     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2074     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2075     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
2076     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2077     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2078     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
2079 };
2080
2081
2082 static int 
2083 InitQuoted (CT ct)
2084 {
2085     return init_encoding (ct, openQuoted);
2086 }
2087
2088
2089 static int
2090 openQuoted (CT ct, char **file)
2091 {
2092     int cc, digested, len, quoted;
2093     char *cp, *ep;
2094     char buffer[BUFSIZ];
2095     unsigned char mask;
2096     CE ce;
2097     MD5_CTX mdContext;
2098
2099     ce = ct->c_cefile;
2100     if (ce->ce_fp) {
2101         fseek (ce->ce_fp, 0L, SEEK_SET);
2102         goto ready_to_go;
2103     }
2104
2105     if (ce->ce_file) {
2106         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2107             content_error (ce->ce_file, ct, "unable to fopen for reading");
2108             return NOTOK;
2109         }
2110         goto ready_to_go;
2111     }
2112
2113     if (*file == NULL) {
2114         ce->ce_file = add (m_scratch ("", tmp), NULL);
2115         ce->ce_unlink = 1;
2116     } else {
2117         ce->ce_file = add (*file, NULL);
2118         ce->ce_unlink = 0;
2119     }
2120
2121     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2122         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2123         return NOTOK;
2124     }
2125
2126     if ((len = ct->c_end - ct->c_begin) < 0)
2127         adios (NULL, "internal error(2)");
2128
2129     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2130         content_error (ct->c_file, ct, "unable to open for reading");
2131         return NOTOK;
2132     }
2133
2134     if ((digested = ct->c_digested))
2135         MD5Init (&mdContext);
2136
2137     quoted = 0;
2138 #ifdef lint
2139     mask = 0;
2140 #endif
2141
2142     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2143     while (len > 0) {
2144         char *dp;
2145
2146         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2147             content_error (NULL, ct, "premature eof");
2148             goto clean_up;
2149         }
2150
2151         if ((cc = strlen (buffer)) > len)
2152             cc = len;
2153         len -= cc;
2154
2155         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2156             if (!isspace (*ep))
2157                 break;
2158         *++ep = '\n', ep++;
2159
2160         for (; cp < ep; cp++) {
2161             if (quoted) {
2162                 if (quoted > 1) {
2163                     if (!isxdigit (*cp)) {
2164 invalid_hex:
2165                         dp = "expecting hexidecimal-digit";
2166                         goto invalid_encoding;
2167                     }
2168                     mask <<= 4;
2169                     mask |= hex2nib[*cp & 0x7f];
2170                     putc (mask, ce->ce_fp);
2171                     if (digested)
2172                         MD5Update (&mdContext, &mask, 1);
2173                 } else {
2174                     switch (*cp) {
2175                     case ':':
2176                         putc (*cp, ce->ce_fp);
2177                         if (digested)
2178                             MD5Update (&mdContext, (unsigned char *) ":", 1);
2179                         break;
2180
2181                     default:
2182                         if (!isxdigit (*cp))
2183                             goto invalid_hex;
2184                         mask = hex2nib[*cp & 0x7f];
2185                         quoted = 2;
2186                         continue;
2187                     }
2188                 }
2189
2190                 if (ferror (ce->ce_fp)) {
2191                     content_error (ce->ce_file, ct, "error writing to");
2192                     goto clean_up;
2193                 }
2194                 quoted = 0;
2195                 continue;
2196             }
2197
2198             switch (*cp) {
2199             default:
2200                 if (*cp < '!' || *cp > '~') {
2201                     int i;
2202                     dp = "expecting character in range [!..~]";
2203
2204 invalid_encoding:
2205                     i = strlen (invo_name) + 2;
2206                     content_error (NULL, ct,
2207                                    "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
2208                                    dp, i, i, "", *cp);
2209                     goto clean_up;
2210                 }
2211                 /* and fall...*/
2212             case ' ':
2213             case '\t':
2214             case '\n':
2215                 putc (*cp, ce->ce_fp);
2216                 if (digested) {
2217                     if (*cp == '\n')
2218                         MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2219                     else
2220                         MD5Update (&mdContext, (unsigned char *) cp, 1);
2221                 }
2222                 if (ferror (ce->ce_fp)) {
2223                     content_error (ce->ce_file, ct, "error writing to");
2224                     goto clean_up;
2225                 }
2226                 break;
2227
2228             case '=':
2229                 if (*++cp != '\n') {
2230                     quoted = 1;
2231                     cp--;
2232                 }
2233                 break;
2234             }
2235         }
2236     }
2237     if (quoted) {
2238         content_error (NULL, ct,
2239                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2240         goto clean_up;
2241     }
2242
2243     fseek (ct->c_fp, 0L, SEEK_SET);
2244
2245     if (fflush (ce->ce_fp)) {
2246         content_error (ce->ce_file, ct, "error writing to");
2247         goto clean_up;
2248     }
2249
2250     if (digested) {
2251         unsigned char digest[16];
2252
2253         MD5Final (digest, &mdContext);
2254         if (memcmp((char *) digest, (char *) ct->c_digest,
2255                    sizeof(digest) / sizeof(digest[0])))
2256             content_error (NULL, ct,
2257                            "content integrity suspect (digest mismatch) -- continuing");
2258         else
2259             if (debugsw)
2260                 fprintf (stderr, "content integrity confirmed\n");
2261     }
2262
2263     fseek (ce->ce_fp, 0L, SEEK_SET);
2264
2265 ready_to_go:
2266     *file = ce->ce_file;
2267     return fileno (ce->ce_fp);
2268
2269 clean_up:
2270     free_encoding (ct, 0);
2271     return NOTOK;
2272 }
2273
2274
2275 /*
2276  * 7BIT
2277  */
2278
2279 static int
2280 Init7Bit (CT ct)
2281 {
2282     if (init_encoding (ct, open7Bit) == NOTOK)
2283         return NOTOK;
2284
2285     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
2286     return OK;
2287 }
2288
2289
2290 static int
2291 open7Bit (CT ct, char **file)
2292 {
2293     int cc, fd, len;
2294     char buffer[BUFSIZ];
2295     CE ce;
2296
2297     ce = ct->c_cefile;
2298     if (ce->ce_fp) {
2299         fseek (ce->ce_fp, 0L, SEEK_SET);
2300         goto ready_to_go;
2301     }
2302
2303     if (ce->ce_file) {
2304         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2305             content_error (ce->ce_file, ct, "unable to fopen for reading");
2306             return NOTOK;
2307         }
2308         goto ready_to_go;
2309     }
2310
2311     if (*file == NULL) {
2312         ce->ce_file = add (m_scratch ("", tmp), NULL);
2313         ce->ce_unlink = 1;
2314     } else {
2315         ce->ce_file = add (*file, NULL);
2316         ce->ce_unlink = 0;
2317     }
2318
2319     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2320         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2321         return NOTOK;
2322     }
2323
2324     if (ct->c_type == CT_MULTIPART) {
2325         char **ap, **ep;
2326         CI ci = &ct->c_ctinfo;
2327
2328         len = 0;
2329         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2330         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2331             + 1 + strlen (ci->ci_subtype);
2332         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2333             putc (';', ce->ce_fp);
2334             len++;
2335
2336             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2337
2338             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2339                 fputs ("\n\t", ce->ce_fp);
2340                 len = 8;
2341             } else {
2342                 putc (' ', ce->ce_fp);
2343                 len++;
2344             }
2345             fprintf (ce->ce_fp, "%s", buffer);
2346             len += cc;
2347         }
2348
2349         if (ci->ci_comment) {
2350             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2351                 fputs ("\n\t", ce->ce_fp);
2352                 len = 8;
2353             }
2354             else {
2355                 putc (' ', ce->ce_fp);
2356                 len++;
2357             }
2358             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2359             len += cc;
2360         }
2361         fprintf (ce->ce_fp, "\n");
2362         if (ct->c_id)
2363             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2364         if (ct->c_descr)
2365             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2366         if (ct->c_dispo)
2367             fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2368         fprintf (ce->ce_fp, "\n");
2369     }
2370
2371     if ((len = ct->c_end - ct->c_begin) < 0)
2372         adios (NULL, "internal error(3)");
2373
2374     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2375         content_error (ct->c_file, ct, "unable to open for reading");
2376         return NOTOK;
2377     }
2378
2379     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2380     while (len > 0)
2381         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2382         case NOTOK:
2383             content_error (ct->c_file, ct, "error reading from");
2384             goto clean_up;
2385
2386         case OK:
2387             content_error (NULL, ct, "premature eof");
2388             goto clean_up;
2389
2390         default:
2391             if (cc > len)
2392                 cc = len;
2393             len -= cc;
2394
2395             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2396             if (ferror (ce->ce_fp)) {
2397                 content_error (ce->ce_file, ct, "error writing to");
2398                 goto clean_up;
2399             }
2400         }
2401
2402     fseek (ct->c_fp, 0L, SEEK_SET);
2403
2404     if (fflush (ce->ce_fp)) {
2405         content_error (ce->ce_file, ct, "error writing to");
2406         goto clean_up;
2407     }
2408
2409     fseek (ce->ce_fp, 0L, SEEK_SET);
2410
2411 ready_to_go:
2412     *file = ce->ce_file;
2413     return fileno (ce->ce_fp);
2414
2415 clean_up:
2416     free_encoding (ct, 0);
2417     return NOTOK;
2418 }
2419
2420
2421 /*
2422  * External
2423  */
2424
2425 static int
2426 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2427 {
2428     char cachefile[BUFSIZ];
2429
2430     if (ce->ce_fp) {
2431         fseek (ce->ce_fp, 0L, SEEK_SET);
2432         goto ready_already;
2433     }
2434
2435     if (ce->ce_file) {
2436         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2437             content_error (ce->ce_file, ct, "unable to fopen for reading");
2438             return NOTOK;
2439         }
2440         goto ready_already;
2441     }
2442
2443     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2444                 cachefile, sizeof(cachefile)) != NOTOK) {
2445         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2446             ce->ce_file = getcpy (cachefile);
2447             ce->ce_unlink = 0;
2448             goto ready_already;
2449         } else {
2450             admonish (cachefile, "unable to fopen for reading");
2451         }
2452     }
2453
2454     return OK;
2455
2456 ready_already:
2457     *file = ce->ce_file;
2458     *fd = fileno (ce->ce_fp);
2459     return DONE;
2460 }
2461
2462 /*
2463  * File
2464  */
2465
2466 static int
2467 InitFile (CT ct)
2468 {
2469     return init_encoding (ct, openFile);
2470 }
2471
2472
2473 static int
2474 openFile (CT ct, char **file)
2475 {
2476     int fd, cachetype;
2477     char cachefile[BUFSIZ];
2478     struct exbody *e = ct->c_ctexbody;
2479     CE ce = ct->c_cefile;
2480
2481     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2482         case NOTOK:
2483             return NOTOK;
2484
2485         case OK:
2486             break;
2487
2488         case DONE:
2489             return fd;
2490     }
2491
2492     if (!e->eb_name) {
2493         content_error (NULL, ct, "missing name parameter");
2494         return NOTOK;
2495     }
2496
2497     ce->ce_file = getcpy (e->eb_name);
2498     ce->ce_unlink = 0;
2499
2500     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2501         content_error (ce->ce_file, ct, "unable to fopen for reading");
2502         return NOTOK;
2503     }
2504
2505     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2506             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2507                 cachefile, sizeof(cachefile)) != NOTOK) {
2508         int mask;
2509         FILE *fp;
2510
2511         mask = umask (cachetype ? ~m_gmprot () : 0222);
2512         if ((fp = fopen (cachefile, "w"))) {
2513             int cc;
2514             char buffer[BUFSIZ];
2515             FILE *gp = ce->ce_fp;
2516
2517             fseek (gp, 0L, SEEK_SET);
2518
2519             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2520                        > 0)
2521                 fwrite (buffer, sizeof(*buffer), cc, fp);
2522             fflush (fp);
2523
2524             if (ferror (gp)) {
2525                 admonish (ce->ce_file, "error reading");
2526                 unlink (cachefile);
2527             }
2528             else
2529                 if (ferror (fp)) {
2530                     admonish (cachefile, "error writing");
2531                     unlink (cachefile);
2532                 }
2533             fclose (fp);
2534         }
2535         umask (mask);
2536     }
2537
2538     fseek (ce->ce_fp, 0L, SEEK_SET);
2539     *file = ce->ce_file;
2540     return fileno (ce->ce_fp);
2541 }
2542
2543 /*
2544  * FTP
2545  */
2546
2547 static int
2548 InitFTP (CT ct)
2549 {
2550     return init_encoding (ct, openFTP);
2551 }
2552
2553
2554 static int
2555 openFTP (CT ct, char **file)
2556 {
2557     int cachetype, caching, fd;
2558     int len, buflen;
2559     char *bp, *ftp, *user, *pass;
2560     char buffer[BUFSIZ], cachefile[BUFSIZ];
2561     struct exbody *e;
2562     CE ce;
2563     static char *username = NULL;
2564     static char *password = NULL;
2565
2566     e  = ct->c_ctexbody;
2567     ce = ct->c_cefile;
2568
2569     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2570         ftp = NULL;
2571
2572 #ifndef BUILTIN_FTP
2573     if (!ftp)
2574         return NOTOK;
2575 #endif
2576
2577     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2578         case NOTOK:
2579             return NOTOK;
2580
2581         case OK:
2582             break;
2583
2584         case DONE:
2585             return fd;
2586     }
2587
2588     if (!e->eb_name || !e->eb_site) {
2589         content_error (NULL, ct, "missing %s parameter",
2590                        e->eb_name ? "site": "name");
2591         return NOTOK;
2592     }
2593
2594     if (xpid) {
2595         if (xpid < 0)
2596             xpid = -xpid;
2597         pidcheck (pidwait (xpid, NOTOK));
2598         xpid = 0;
2599     }
2600
2601     /* Get the buffer ready to go */
2602     bp = buffer;
2603     buflen = sizeof(buffer);
2604
2605     /*
2606      * Construct the query message for user
2607      */
2608     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2609     len = strlen (bp);
2610     bp += len;
2611     buflen -= len;
2612
2613     if (e->eb_partno) {
2614         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2615         len = strlen (bp);
2616         bp += len;
2617         buflen -= len;
2618     }
2619
2620     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2621                     e->eb_flags ? "anonymous " : "", e->eb_site);
2622     len = strlen (bp);
2623     bp += len;
2624     buflen -= len;
2625
2626     if (e->eb_size > 0) {
2627         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2628         len = strlen (bp);
2629         bp += len;
2630         buflen -= len;
2631     }
2632     snprintf (bp, buflen, "? ");
2633
2634     /*
2635      * Now, check the answer
2636      */
2637     if (!getanswer (buffer))
2638         return NOTOK;
2639
2640     if (e->eb_flags) {
2641         user = "anonymous";
2642         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2643         pass = buffer;
2644     } else {
2645         ruserpass (e->eb_site, &username, &password);
2646         user = username;
2647         pass = password;
2648     }
2649
2650     ce->ce_unlink = (*file == NULL);
2651     caching = 0;
2652     cachefile[0] = '\0';
2653     if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2654             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2655                 cachefile, sizeof(cachefile)) != NOTOK) {
2656         if (*file == NULL) {
2657             ce->ce_unlink = 0;
2658             caching = 1;
2659         }
2660     }
2661
2662     if (*file)
2663         ce->ce_file = add (*file, NULL);
2664     else if (caching)
2665         ce->ce_file = add (cachefile, NULL);
2666     else
2667         ce->ce_file = add (m_scratch ("", tmp), NULL);
2668
2669     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2670         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2671         return NOTOK;
2672     }
2673
2674 #ifdef BUILTIN_FTP
2675     if (ftp)
2676 #endif
2677     {
2678         int child_id, i, vecp;
2679         char *vec[9];
2680
2681         vecp = 0;
2682         vec[vecp++] = r1bindex (ftp, '/');
2683         vec[vecp++] = e->eb_site;
2684         vec[vecp++] = user;
2685         vec[vecp++] = pass;
2686         vec[vecp++] = e->eb_dir;
2687         vec[vecp++] = e->eb_name;
2688         vec[vecp++] = ce->ce_file,
2689         vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2690                         ? "ascii" : "binary";
2691         vec[vecp] = NULL;
2692
2693         fflush (stdout);
2694
2695         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2696             sleep (5);
2697         switch (child_id) {
2698             case NOTOK:
2699                 adios ("fork", "unable to");
2700                 /* NOTREACHED */
2701
2702             case OK:
2703                 close (fileno (ce->ce_fp));
2704                 execvp (ftp, vec);
2705                 fprintf (stderr, "unable to exec ");
2706                 perror (ftp);
2707                 _exit (-1);
2708                 /* NOTREACHED */
2709
2710             default:
2711                 if (pidXwait (child_id, NULL)) {
2712 #ifdef BUILTIN_FTP
2713 losing_ftp:
2714 #endif
2715                     username = password = NULL;
2716                     ce->ce_unlink = 1;
2717                     return NOTOK;
2718                 }
2719                 break;
2720         }
2721     }
2722 #ifdef BUILTIN_FTP
2723     else
2724         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2725                      ce->ce_file,
2726                      e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0)
2727                 == NOTOK)
2728             goto losing_ftp;
2729 #endif
2730
2731     if (cachefile[0]) {
2732         if (caching)
2733             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2734         else {
2735             int mask;
2736             FILE *fp;
2737
2738             mask = umask (cachetype ? ~m_gmprot () : 0222);
2739             if ((fp = fopen (cachefile, "w"))) {
2740                 int cc;
2741                 FILE *gp = ce->ce_fp;
2742
2743                 fseek (gp, 0L, SEEK_SET);
2744
2745                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2746                            > 0)
2747                     fwrite (buffer, sizeof(*buffer), cc, fp);
2748                 fflush (fp);
2749
2750                 if (ferror (gp)) {
2751                     admonish (ce->ce_file, "error reading");
2752                     unlink (cachefile);
2753                 }
2754                 else
2755                     if (ferror (fp)) {
2756                         admonish (cachefile, "error writing");
2757                         unlink (cachefile);
2758                     }
2759                 fclose (fp);
2760             }
2761             umask (mask);
2762         }
2763     }
2764
2765     fseek (ce->ce_fp, 0L, SEEK_SET);
2766     *file = ce->ce_file;
2767     return fileno (ce->ce_fp);
2768 }
2769
2770
2771 /*
2772  * Mail
2773  */
2774
2775 static int
2776 InitMail (CT ct)
2777 {
2778     return init_encoding (ct, openMail);
2779 }
2780
2781
2782 static int
2783 openMail (CT ct, char **file)
2784 {
2785     int child_id, fd, i, vecp;
2786     int len, buflen;
2787     char *bp, buffer[BUFSIZ], *vec[7];
2788     struct exbody *e = ct->c_ctexbody;
2789     CE ce = ct->c_cefile;
2790
2791     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2792         case NOTOK:
2793             return NOTOK;
2794
2795         case OK:
2796             break;
2797
2798         case DONE:
2799             return fd;
2800     }
2801
2802     if (!e->eb_server) {
2803         content_error (NULL, ct, "missing server parameter");
2804         return NOTOK;
2805     }
2806
2807     if (xpid) {
2808         if (xpid < 0)
2809             xpid = -xpid;
2810         pidcheck (pidwait (xpid, NOTOK));
2811         xpid = 0;
2812     }
2813
2814     /* Get buffer ready to go */
2815     bp = buffer;
2816     buflen = sizeof(buffer);
2817
2818     /* Now construct query message */
2819     snprintf (bp, buflen, "Retrieve content");
2820     len = strlen (bp);
2821     bp += len;
2822     buflen -= len;
2823
2824     if (e->eb_partno) {
2825         snprintf (bp, buflen, " %s", e->eb_partno);
2826         len = strlen (bp);
2827         bp += len;
2828         buflen -= len;
2829     }
2830
2831     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2832                     e->eb_server,
2833                     e->eb_subject ? e->eb_subject : e->eb_body);
2834
2835     /* Now, check answer */
2836     if (!getanswer (buffer))
2837         return NOTOK;
2838
2839     vecp = 0;
2840     vec[vecp++] = r1bindex (mailproc, '/');
2841     vec[vecp++] = e->eb_server;
2842     vec[vecp++] = "-subject";
2843     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2844     vec[vecp++] = "-body";
2845     vec[vecp++] = e->eb_body;
2846     vec[vecp] = NULL;
2847
2848     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2849         sleep (5);
2850     switch (child_id) {
2851         case NOTOK:
2852             advise ("fork", "unable to");
2853             return NOTOK;
2854
2855         case OK:
2856             execvp (mailproc, vec);
2857             fprintf (stderr, "unable to exec ");
2858             perror (mailproc);
2859             _exit (-1);
2860             /* NOTREACHED */
2861
2862         default:
2863             if (pidXwait (child_id, NULL) == OK)
2864                 advise (NULL, "request sent");
2865             break;
2866     }
2867
2868     if (*file == NULL) {
2869         ce->ce_file = add (m_scratch ("", tmp), NULL);
2870         ce->ce_unlink = 1;
2871     } else {
2872         ce->ce_file = add (*file, NULL);
2873         ce->ce_unlink = 0;
2874     }
2875
2876     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2877         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2878         return NOTOK;
2879     }
2880
2881     fseek (ce->ce_fp, 0L, SEEK_SET);
2882     *file = ce->ce_file;
2883     return fileno (ce->ce_fp);
2884 }
2885
2886
2887 static char *
2888 fgetstr (char *s, int n, FILE *stream)
2889 {
2890     char *cp, *ep;
2891
2892     for (ep = (cp = s) + n; cp < ep; ) {
2893         int i;
2894
2895         if (!fgets (cp, n, stream))
2896             return (cp != s ? s : NULL);
2897         if (cp == s && *cp != '#')
2898             return s;
2899
2900         cp += (i = strlen (cp)) - 1;
2901         if (i <= 1 || *cp-- != '\n' || *cp != '\\')
2902             break;
2903         *cp = '\0';
2904         n -= (i - 2);
2905     }
2906
2907     return s;
2908 }
2909
2910
2911 /*
2912  * Parse the composition draft for text and directives.
2913  * Do initial setup of Content structure.
2914  */
2915
2916 static int
2917 user_content (FILE *in, char *file, char *buf, CT *ctp)
2918 {
2919     int extrnal, vrsn;
2920     char *cp, **ap;
2921     char buffer[BUFSIZ];
2922     struct multipart *m;
2923     struct part **pp;
2924     struct stat st;
2925     struct str2init *s2i;
2926     CI ci;
2927     CT ct;
2928     CE ce;
2929
2930     if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
2931         *ctp = NULL;
2932         return OK;
2933     }
2934
2935     /* allocate basic Content structure */
2936     if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
2937         adios (NULL, "out of memory");
2938     *ctp = ct;
2939
2940     /* allocate basic structure for handling decoded content */
2941     init_decoded_content (ct);
2942     ce = ct->c_cefile;
2943
2944     ci = &ct->c_ctinfo;
2945     set_id (ct, 0);
2946
2947     /*
2948      * Handle inline text.  Check if line
2949      * is one of the following forms:
2950      *
2951      * 1) doesn't begin with '#'        (implicit directive)
2952      * 2) begins with "##"              (implicit directive)
2953      * 3) begins with "#<"
2954      */
2955     if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
2956         int headers;
2957         int inlineD;
2958         long pos;
2959         char content[BUFSIZ];
2960         FILE *out;
2961
2962         /* use a temp file to collect the plain text lines */
2963         ce->ce_file = add (m_tmpfil (invo_name), NULL);
2964         ce->ce_unlink = 1;
2965
2966         if ((out = fopen (ce->ce_file, "w")) == NULL)
2967             adios (ce->ce_file, "unable to open for writing");
2968
2969         if (buf[0] == '#' && buf[1] == '<') {
2970             strncpy (content, buf + 2, sizeof(content));
2971             inlineD = 1;
2972             goto rock_and_roll;
2973         } else {
2974             inlineD = 0;
2975         }
2976
2977         /* the directive is implicit */
2978         strncpy (content, "text/plain", sizeof(content));
2979         headers = 0;
2980         strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
2981         for (;;) {
2982             int i;
2983
2984             if (headers >= 0 && uprf (buffer, DESCR_FIELD)
2985                 && buffer[i = strlen (DESCR_FIELD)] == ':') {
2986                 headers = 1;
2987
2988 again_descr:
2989                 ct->c_descr = add (buffer + i + 1, ct->c_descr);
2990                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
2991                     adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
2992                 switch (buffer[0]) {
2993                 case ' ':
2994                 case '\t':
2995                     i = -1;
2996                     goto again_descr;
2997
2998                 case '#':
2999                     adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
3000                     /* NOTREACHED */
3001
3002                 default:
3003                     break;
3004                 }
3005             }
3006
3007             if (headers >= 0 && uprf (buffer, DISPO_FIELD)
3008                 && buffer[i = strlen (DISPO_FIELD)] == ':') {
3009                 headers = 1;
3010
3011 again_dispo:
3012                 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
3013                 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
3014                     adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
3015                 switch (buffer[0]) {
3016                 case ' ':
3017                 case '\t':
3018                     i = -1;
3019                     goto again_dispo;
3020
3021                 case '#':
3022                     adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
3023                     /* NOTREACHED */
3024
3025                 default:
3026                     break;
3027                 }
3028             }
3029
3030             if (headers != 1 || buffer[0] != '\n')
3031                 fputs (buffer, out);
3032
3033 rock_and_roll:
3034             headers = -1;
3035             pos = ftell (in);
3036             if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
3037                 break;
3038             if (buffer[0] == '#') {
3039                 char *bp;
3040
3041                 if (buffer[1] != '#')
3042                     break;
3043                 for (cp = (bp = buffer) + 1; *cp; cp++)
3044                     *bp++ = *cp;
3045                 *bp = '\0';
3046             }
3047         }
3048
3049         if (listsw)
3050             ct->c_end = ftell (out);
3051         fclose (out);
3052
3053         /* parse content type */
3054         if (get_ctinfo (content, ct, inlineD) == NOTOK)
3055             done (1);
3056
3057         for (s2i = str2cts; s2i->si_key; s2i++)
3058             if (!strcasecmp (ci->ci_type, s2i->si_key))
3059                 break;
3060         if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
3061             s2i++;
3062
3063         /*
3064          * check type specified (possibly implicitly)
3065          */
3066         switch (ct->c_type = s2i->si_val) {
3067         case CT_MESSAGE:
3068             if (!strcasecmp (ci->ci_subtype, "rfc822")) {
3069                 ct->c_encoding = CE_7BIT;
3070                 goto call_init;
3071             }
3072             /* else fall... */
3073         case CT_MULTIPART:
3074             adios (NULL, "it doesn't make sense to define an in-line %s content",
3075                    ct->c_type == CT_MESSAGE ? "message" : "multipart");
3076             /* NOTREACHED */
3077
3078         default:
3079 call_init:
3080             if ((ct->c_ctinitfnx = s2i->si_init))
3081                 (*ct->c_ctinitfnx) (ct);
3082             break;
3083         }
3084
3085         if (cp)
3086             fseek (in, pos, SEEK_SET);
3087         return OK;
3088     }
3089
3090     /*
3091      * If we've reached this point, the next line
3092      * must be some type of explicit directive.
3093      */
3094
3095     /* check if directive is external-type */
3096     extrnal = (buf[1] == '@');
3097
3098     /* parse directive */
3099     if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
3100         done (1);
3101
3102     /* check directive against the list of MIME types */
3103     for (s2i = str2cts; s2i->si_key; s2i++)
3104         if (!strcasecmp (ci->ci_type, s2i->si_key))
3105             break;
3106
3107     /*
3108      * Check if the directive specified a valid type.
3109      * This will happen if it was one of the following forms:
3110      *
3111      *    #type/subtype  (or)
3112      *    #@type/subtype
3113      */
3114     if (s2i->si_key) {
3115         if (!ci->ci_subtype)
3116             adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
3117
3118         switch (ct->c_type = s2i->si_val) {
3119         case CT_MULTIPART:
3120             adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
3121                    ci->ci_type, ci->ci_subtype);
3122             /* NOTREACHED */
3123
3124         case CT_MESSAGE:
3125             if (!strcasecmp (ci->ci_subtype, "partial"))
3126                 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
3127                        ci->ci_type, ci->ci_subtype);
3128             if (!strcasecmp (ci->ci_subtype, "external-body"))
3129                 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
3130                        ci->ci_type, ci->ci_subtype);
3131 use_forw:
3132             adios (NULL,
3133                    "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
3134                    ci->ci_type, ci->ci_subtype);
3135             /* NOTREACHED */
3136
3137         default:
3138             if ((ct->c_ctinitfnx = s2i->si_init))
3139                 (*ct->c_ctinitfnx) (ct);
3140             break;
3141         }
3142
3143         /*
3144          * #@type/subtype (external types directive)
3145          */
3146         if (extrnal) {
3147             struct exbody *e;
3148             CT p;
3149
3150             if (!ci->ci_magic)
3151                 adios (NULL, "need external information for \"#@%s/%s\"",
3152                        ci->ci_type, ci->ci_subtype);
3153             p = ct;
3154
3155             snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
3156             free (ci->ci_magic);
3157             ci->ci_magic = NULL;
3158
3159             /*
3160              * Since we are using the current Content structure to
3161              * hold information about the type of the external
3162              * reference, we need to create another Content structure
3163              * for the message/external-body to wrap it in.
3164              */
3165             if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
3166                 adios (NULL, "out of memory");
3167             *ctp = ct;
3168             ci = &ct->c_ctinfo;
3169             if (get_ctinfo (buffer, ct, 0) == NOTOK)
3170                 done (1);
3171             ct->c_type = CT_MESSAGE;
3172             ct->c_subtype = MESSAGE_EXTERNAL;
3173
3174             if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
3175                 adios (NULL, "out of memory");
3176             ct->c_ctparams = (void *) e;
3177
3178             e->eb_parent = ct;
3179             e->eb_content = p;
3180             p->c_ctexbody = e;
3181
3182             if (params_external (ct, 1) == NOTOK)
3183                 done (1);
3184
3185             return OK;
3186         }
3187
3188         /* Handle [file] argument */
3189         if (ci->ci_magic) {
3190             /* check if specifies command to execute */
3191             if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
3192                 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
3193                     continue;
3194                 if (!*cp)
3195                     adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
3196                 cp = add (cp, NULL);
3197                 free (ci->ci_magic);
3198                 ci->ci_magic = cp;
3199             } else {
3200                 /* record filename of decoded contents */
3201                 ce->ce_file = ci->ci_magic;
3202                 if (access (ce->ce_file, R_OK) == NOTOK)
3203                     adios ("reading", "unable to access %s for", ce->ce_file);
3204                 if (listsw && stat (ce->ce_file, &st) != NOTOK)
3205                     ct->c_end = (long) st.st_size;
3206                 ci->ci_magic = NULL;
3207             }
3208             return OK;
3209         }
3210
3211         /*
3212          * No [file] argument, so check profile for
3213          * method to compose content.
3214          */
3215         snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
3216                 invo_name, ci->ci_type, ci->ci_subtype);
3217         if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3218             snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
3219             if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3220                 content_error (NULL, ct, "don't know how to compose content");
3221                 done (1);
3222             }
3223         }
3224         ci->ci_magic = add (cp, NULL);
3225         return OK;
3226     }
3227
3228     if (extrnal)
3229         adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
3230
3231     /*
3232      * Message directive
3233      * #forw [+folder] [msgs]
3234      */
3235     if (!strcasecmp (ci->ci_type, "forw")) {
3236         int msgnum;
3237         char *folder, *arguments[MAXARGS];
3238         struct msgs *mp;
3239
3240         if (ci->ci_magic) {
3241             ap = brkstring (ci->ci_magic, " ", "\n");
3242             copyip (ap, arguments, MAXARGS);
3243         } else {
3244             arguments[0] = "cur";
3245             arguments[1] = NULL;
3246         }
3247         folder = NULL;
3248
3249         /* search the arguments for a folder name */
3250         for (ap = arguments; *ap; ap++) {
3251             cp = *ap;
3252             if (*cp == '+' || *cp == '@') {
3253                 if (folder)
3254                     adios (NULL, "only one folder per #forw directive");
3255                 else
3256                     folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
3257             }
3258         }
3259
3260         /* else, use the current folder */
3261         if (!folder)
3262             folder = add (getfolder (1), NULL);
3263
3264         if (!(mp = folder_read (folder)))
3265             adios (NULL, "unable to read folder %s", folder);
3266         for (ap = arguments; *ap; ap++) {
3267             cp = *ap;
3268             if (*cp != '+' && *cp != '@')
3269                 if (!m_convert (mp, cp))
3270                     done (1);
3271         }
3272         free (folder);
3273         free_ctinfo (ct);
3274
3275         /*
3276          * If there is more than one message to include, make this
3277          * a content of type "multipart/digest" and insert each message
3278          * as a subpart.  If there is only one message, then make this
3279          * a content of type "message/rfc822".
3280          */
3281         if (mp->numsel > 1) {
3282             /* we are forwarding multiple messages */
3283             if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
3284                 done (1);
3285             ct->c_type = CT_MULTIPART;
3286             ct->c_subtype = MULTI_DIGEST;
3287
3288             if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3289                 adios (NULL, "out of memory");
3290             ct->c_ctparams = (void *) m;
3291             pp = &m->mp_parts;
3292
3293             for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
3294                 if (is_selected(mp, msgnum)) {
3295                     struct part *part;
3296                     CT p;
3297                     CE pe;
3298
3299                     if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
3300                         adios (NULL, "out of memory");
3301                     init_decoded_content (p);
3302                     pe = p->c_cefile;
3303                     if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
3304                         done (1);
3305                     p->c_type = CT_MESSAGE;
3306                     p->c_subtype = MESSAGE_RFC822;
3307
3308                     snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3309                     pe->ce_file = add (buffer, NULL);
3310                     if (listsw && stat (pe->ce_file, &st) != NOTOK)
3311                         p->c_end = (long) st.st_size;
3312
3313                     if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3314                         adios (NULL, "out of memory");
3315                     *pp = part;
3316                     pp = &part->mp_next;
3317                     part->mp_part = p;
3318                 }
3319             }
3320         } else {
3321             /* we are forwarding one message */
3322             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
3323                 done (1);
3324             ct->c_type = CT_MESSAGE;
3325             ct->c_subtype = MESSAGE_RFC822;
3326
3327             msgnum = mp->lowsel;
3328             snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3329             ce->ce_file = add (buffer, NULL);
3330             if (listsw && stat (ce->ce_file, &st) != NOTOK)
3331                 ct->c_end = (long) st.st_size;
3332         }
3333
3334         folder_free (mp);       /* free folder/message structure */
3335         return OK;
3336     }
3337
3338     /*
3339      * #end
3340      */
3341     if (!strcasecmp (ci->ci_type, "end")) {
3342         free_content (ct);
3343         *ctp = NULL;
3344         return DONE;
3345     }
3346
3347     /*
3348      * #begin [ alternative | parallel ]
3349      */
3350     if (!strcasecmp (ci->ci_type, "begin")) {
3351         if (!ci->ci_magic) {
3352             vrsn = MULTI_MIXED;
3353             cp = SubMultiPart[vrsn - 1].kv_key;
3354         } else if (!strcasecmp (ci->ci_magic, "alternative")) {
3355             vrsn = MULTI_ALTERNATE;
3356             cp = SubMultiPart[vrsn - 1].kv_key;
3357         } else if (!strcasecmp (ci->ci_magic, "parallel")) {
3358             vrsn = MULTI_PARALLEL;
3359             cp = SubMultiPart[vrsn - 1].kv_key;
3360         } else if (uprf (ci->ci_magic, "digest")) {
3361             goto use_forw;
3362         } else {
3363             vrsn = MULTI_UNKNOWN;
3364             cp = ci->ci_magic;
3365         }
3366
3367         free_ctinfo (ct);
3368         snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
3369         if (get_ctinfo (buffer, ct, 0) == NOTOK)
3370             done (1);
3371         ct->c_type = CT_MULTIPART;
3372         ct->c_subtype = vrsn;
3373
3374         if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3375             adios (NULL, "out of memory");
3376         ct->c_ctparams = (void *) m;
3377
3378         pp = &m->mp_parts;
3379         while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
3380             struct part *part;
3381             CT p;
3382
3383             if (user_content (in, file, buffer, &p) == DONE) {
3384                 if (!m->mp_parts)
3385                     adios (NULL, "empty \"#begin ... #end\" sequence");
3386                 return OK;
3387             }
3388             if (!p)
3389                 continue;
3390
3391             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3392                 adios (NULL, "out of memory");
3393             *pp = part;
3394             pp = &part->mp_next;
3395             part->mp_part = p;
3396         }
3397         admonish (NULL, "premature end-of-file, missing #end");
3398         return OK;
3399     }
3400
3401     /*
3402      * Unknown directive
3403      */
3404     adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
3405     return NOTOK;       /* NOT REACHED */
3406 }
3407
3408
3409 static void
3410 set_id (CT ct, int top)
3411 {
3412     char msgid[BUFSIZ];
3413     static int partno;
3414     static time_t clock = 0;
3415     static char *msgfmt;
3416
3417     if (clock == 0) {
3418         time (&clock);
3419         snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
3420                 (int) getpid(), (long) clock, LocalName());
3421         partno = 0;
3422         msgfmt = getcpy(msgid);
3423     }
3424     snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
3425     ct->c_id = getcpy (msgid);
3426 }
3427
3428
3429 static char ebcdicsafe[0x100] = {
3430     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3431     0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3432     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3433     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3434     0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
3435     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3436     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3437     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3438     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3439     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3440     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3441     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
3442     0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3443     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3444     0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3445     0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3446     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3447     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3448     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3449     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3450     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3451     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3452     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3453     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3454     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3455     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3456     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3457     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3458     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3459     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3460     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3461     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
3462 };
3463
3464
3465 /*
3466  * Fill out, or expand the various contents in the composition
3467  * draft.  Read-in any necessary files.  Parse and execute any
3468  * commands specified by profile composition strings.
3469  */
3470
3471 static int
3472 compose_content (CT ct)
3473 {
3474     CE ce = ct->c_cefile;
3475
3476     switch (ct->c_type) {
3477     case CT_MULTIPART:
3478     {
3479         int partnum;
3480         char *pp;
3481         char partnam[BUFSIZ];
3482         struct multipart *m = (struct multipart *) ct->c_ctparams;
3483         struct part *part;
3484
3485         if (ct->c_partno) {
3486             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
3487             pp = partnam + strlen (partnam);
3488         } else {
3489             pp = partnam;
3490         }
3491
3492         /* first, we call compose_content on all the subparts */
3493         for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
3494             CT p = part->mp_part;
3495
3496             sprintf (pp, "%d", partnum);
3497             p->c_partno = add (partnam, NULL);
3498             if (compose_content (p) == NOTOK)
3499                 return NOTOK;
3500         }
3501
3502         /*
3503          * If the -rfc934mode switch is given, then check all
3504          * the subparts of a multipart/digest.  If they are all
3505          * message/rfc822, then mark this content and all
3506          * subparts with the rfc934 compatibility mode flag.
3507          */
3508         if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
3509             int is934 = 1;
3510
3511             for (part = m->mp_parts; part; part = part->mp_next) {
3512                 CT p = part->mp_part;
3513
3514                 if (p->c_subtype != MESSAGE_RFC822) {
3515                     is934 = 0;
3516                     break;
3517                 }
3518             }
3519             ct->c_rfc934 = is934;
3520             for (part = m->mp_parts; part; part = part->mp_next) {
3521                 CT p = part->mp_part;
3522
3523                 if ((p->c_rfc934 = is934))
3524                     p->c_end++;
3525             }
3526         }
3527
3528         if (listsw) {
3529             ct->c_end = (partnum = strlen (prefix) + 2) + 2;
3530             if (ct->c_rfc934)
3531                 ct->c_end += 1;
3532
3533             for (part = m->mp_parts; part; part = part->mp_next)
3534                 ct->c_end += part->mp_part->c_end + partnum;
3535         }
3536     }
3537     break;
3538
3539     case CT_MESSAGE:
3540         /* Nothing to do for type message */
3541         break;
3542
3543     /*
3544      * Discrete types (text/application/audio/image/video)
3545      */
3546     default:
3547         if (!ce->ce_file) {
3548             pid_t child_id;
3549             int i, xstdout, len, buflen;
3550             char *bp, **ap, *cp;
3551             char *vec[4], buffer[BUFSIZ];
3552             FILE *out;
3553             CI ci = &ct->c_ctinfo;
3554
3555             if (!(cp = ci->ci_magic))
3556                 adios (NULL, "internal error(5)");
3557
3558             ce->ce_file = add (m_tmpfil (invo_name), NULL);
3559             ce->ce_unlink = 1;
3560
3561             xstdout = 0;
3562
3563             /* Get buffer ready to go */
3564             bp = buffer;
3565             bp[0] = '\0';
3566             buflen = sizeof(buffer);
3567
3568             /*
3569              * Parse composition string into buffer
3570              */
3571             for ( ; *cp; cp++) {
3572                 if (*cp == '%') {
3573                     switch (*++cp) {
3574                     case 'a':
3575                     {
3576                         /* insert parameters from directive */
3577                         char **ep;
3578                         char *s = "";
3579
3580                         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3581                             snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
3582                             len = strlen (bp);
3583                             bp += len;
3584                             buflen -= len;
3585                             s = " ";
3586                         }
3587                     }
3588                     break;
3589
3590                     case 'F':
3591                         /* %f, and stdout is not-redirected */
3592                         xstdout = 1;
3593                         /* and fall... */
3594
3595                     case 'f':
3596                         /*
3597                          * insert temporary filename where
3598                          * content should be written
3599                          */
3600                         snprintf (bp, buflen, "%s", ce->ce_file);
3601                         break;
3602
3603                     case 's':
3604                         /* insert content subtype */
3605                         strncpy (bp, ci->ci_subtype, buflen);
3606                         break;
3607
3608                     case '%':
3609                         /* insert character % */
3610                         goto raw;
3611
3612                     default:
3613                         *bp++ = *--cp;
3614                         *bp = '\0';
3615                         buflen--;
3616                         continue;
3617                     }
3618                     len = strlen (bp);
3619                     bp += len;
3620                     buflen -= len;
3621                 } else {
3622 raw:
3623                 *bp++ = *cp;
3624                 *bp = '\0';
3625                 buflen--;
3626                 }
3627             }
3628
3629             if (verbosw)
3630                 printf ("composing content %s/%s from command\n\t%s\n",
3631                         ci->ci_type, ci->ci_subtype, buffer);
3632
3633             fflush (stdout);    /* not sure if need for -noverbose */
3634
3635             vec[0] = "/bin/sh";
3636             vec[1] = "-c";
3637             vec[2] = buffer;
3638             vec[3] = NULL;
3639
3640             if ((out = fopen (ce->ce_file, "w")) == NULL)
3641                 adios (ce->ce_file, "unable to open for writing");
3642
3643             for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
3644                 sleep (5);
3645             switch (child_id) {
3646             case NOTOK:
3647                 adios ("fork", "unable to fork");
3648                 /* NOTREACHED */
3649
3650             case OK:
3651                 if (!xstdout)
3652                     dup2 (fileno (out), 1);
3653                 close (fileno (out));
3654                 execvp ("/bin/sh", vec);
3655                 fprintf (stderr, "unable to exec ");
3656                 perror ("/bin/sh");
3657                 _exit (-1);
3658                 /* NOTREACHED */
3659
3660             default:
3661                 fclose (out);
3662                 if (pidXwait(child_id, NULL))
3663                     done (1);
3664                 break;
3665             }
3666         }
3667
3668         /* Check size of file */
3669         if (listsw && ct->c_end == 0L) {
3670             struct stat st;
3671
3672             if (stat (ce->ce_file, &st) != NOTOK)
3673                 ct->c_end = (long) st.st_size;
3674         }
3675         break;
3676     }
3677
3678     return OK;
3679 }
3680
3681
3682 /*
3683  * Scan the content.
3684  *
3685  *    1) choose a transfer encoding.
3686  *    2) check for clashes with multipart boundary string.
3687  *    3) for text content, figure out which character set is being used.
3688  *
3689  * If there is a clash with one of the contents and the multipart boundary,
3690  * this function will exit with NOTOK.  This will cause the scanning process
3691  * to be repeated with a different multipart boundary.  It is possible
3692  * (although highly unlikely) that this scan will be repeated multiple times.
3693  */
3694
3695 static int
3696 scan_content (CT ct)
3697 {
3698     int len;
3699     int check8bit, contains8bit = 0;      /* check if contains 8bit data                */
3700     int checklinelen, linelen = 0;        /* check for long lines                       */
3701     int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary   */
3702     int checklinespace, linespace = 0;    /* check if any line ends with space          */
3703     int checkebcdic, ebcdicunsafe = 0;    /* check if contains ebcdic unsafe characters */
3704     char *cp, buffer[BUFSIZ];
3705     struct text *t;
3706     FILE *in;
3707     CE ce = ct->c_cefile;
3708
3709     /*
3710      * handle multipart by scanning all subparts
3711      * and then checking their encoding.
3712      */
3713     if (ct->c_type == CT_MULTIPART) {
3714         struct multipart *m = (struct multipart *) ct->c_ctparams;
3715         struct part *part;
3716
3717         /* initially mark the domain of enclosing multipart as 7bit */
3718         ct->c_encoding = CE_7BIT;
3719
3720         for (part = m->mp_parts; part; part = part->mp_next) {
3721             CT p = part->mp_part;
3722
3723             if (scan_content (p) == NOTOK)      /* choose encoding for subpart */
3724                 return NOTOK;
3725
3726             /* if necessary, enlarge encoding for enclosing multipart */
3727             if (p->c_encoding == CE_BINARY)
3728                 ct->c_encoding = CE_BINARY;
3729             if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
3730                 ct->c_encoding = CE_8BIT;
3731         }
3732
3733         return OK;
3734     }
3735
3736     /*
3737      * Decide what to check while scanning this content.
3738      */
3739     switch (ct->c_type) {
3740     case CT_TEXT:
3741         check8bit = 1;
3742         checkboundary = 1;
3743         if (ct->c_subtype == TEXT_PLAIN) {
3744             checkebcdic = 0;
3745             checklinelen = 0;
3746             checklinespace = 0;
3747         } else {
3748             checkebcdic = ebcdicsw;
3749             checklinelen = 1;
3750             checklinespace = 1;
3751         }
3752         break;
3753
3754     case CT_APPLICATION:
3755         check8bit = 1;
3756         checkebcdic = ebcdicsw;
3757         checklinelen = 1;
3758         checklinespace = 1;
3759         checkboundary = 1;
3760         break;
3761
3762     case CT_MESSAGE:
3763         check8bit = 0;
3764         checkebcdic = 0;
3765         checklinelen = 0;
3766         checklinespace = 0;
3767
3768         /* don't check anything for message/external */
3769         if (ct->c_subtype == MESSAGE_EXTERNAL)
3770             checkboundary = 0;
3771         else
3772             checkboundary = 1;
3773         break;
3774
3775     case CT_AUDIO:
3776     case CT_IMAGE:
3777     case CT_VIDEO:
3778         /*
3779          * Don't check anything for these types,
3780          * since we are forcing use of base64.
3781          */
3782         check8bit = 0;
3783         checkebcdic = 0;
3784         checklinelen = 0;
3785         checklinespace = 0;
3786         checkboundary = 0;
3787         break;
3788     }
3789
3790     /*
3791      * Scan the unencoded content
3792      */
3793     if (check8bit || checklinelen || checklinespace || checkboundary) {
3794         if ((in = fopen (ce->ce_file, "r")) == NULL)
3795             adios (ce->ce_file, "unable to open for reading");
3796         len = strlen (prefix);
3797
3798         while (fgets (buffer, sizeof(buffer) - 1, in)) {
3799             /*
3800              * Check for 8bit data.
3801              */
3802             if (check8bit) {
3803                 for (cp = buffer; *cp; cp++) {
3804                     if (!isascii (*cp)) {
3805                         contains8bit = 1;
3806                         check8bit = 0;  /* no need to keep checking */
3807                     }
3808                     /*
3809                      * Check if character is ebcdic-safe.  We only check
3810                      * this if also checking for 8bit data.
3811                      */
3812                     if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
3813                         ebcdicunsafe = 1;
3814                         checkebcdic = 0; /* no need to keep checking */
3815                     }
3816                 }
3817             }
3818
3819             /*
3820              * Check line length.
3821              */
3822             if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
3823                 linelen = 1;
3824                 checklinelen = 0;       /* no need to keep checking */
3825             }
3826
3827             /*
3828              * Check if line ends with a space.
3829              */
3830             if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
3831                 linespace = 1;
3832                 checklinespace = 0;     /* no need to keep checking */
3833             }
3834
3835             /*
3836              * Check if content contains a line that clashes
3837              * with our standard boundary for multipart messages.
3838              */
3839             if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
3840                 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
3841                     if (!isspace (*cp))
3842                         break;
3843                 *++cp = '\0';
3844                 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
3845                     boundaryclash = 1;
3846                     checkboundary = 0;  /* no need to keep checking */
3847                 }
3848             }
3849         }
3850         fclose (in);
3851     }
3852
3853     /*
3854      * Decide which transfer encoding to use.
3855      */
3856     switch (ct->c_type) {
3857     case CT_TEXT:
3858         /*
3859          * If the text content didn't specify a character
3860          * set, we need to figure out which one was used.
3861          */
3862         t = (struct text *) ct->c_ctparams;
3863         if (t->tx_charset == CHARSET_UNSPECIFIED) {
3864             CI ci = &ct->c_ctinfo;
3865             char **ap, **ep;
3866
3867             for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
3868                 continue;
3869
3870             if (contains8bit) {
3871                 t->tx_charset = CHARSET_UNKNOWN;
3872                 *ap = concat ("charset=", write_charset_8bit(), NULL);
3873             } else {
3874                 t->tx_charset = CHARSET_USASCII;
3875                 *ap = add ("charset=us-ascii", NULL);
3876             }
3877
3878             cp = strchr(*ap++, '=');
3879             *ap = NULL;
3880             *cp++ = '\0';
3881             *ep = cp;
3882         }
3883
3884         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3885             ct->c_encoding = CE_QUOTED;
3886         else
3887             ct->c_encoding = CE_7BIT;
3888         break;
3889
3890     case CT_APPLICATION:
3891         /* For application type, use base64, except when postscript */
3892         if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3893             ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
3894                 ? CE_QUOTED : CE_BASE64;
3895         else
3896             ct->c_encoding = CE_7BIT;
3897         break;
3898
3899     case CT_MESSAGE:
3900         ct->c_encoding = CE_7BIT;
3901         break;
3902
3903     case CT_AUDIO:
3904     case CT_IMAGE:
3905     case CT_VIDEO:
3906         /* For audio, image, and video contents, just use base64 */
3907         ct->c_encoding = CE_BASE64;
3908         break;
3909     }
3910
3911     return (boundaryclash ? NOTOK : OK);
3912 }
3913
3914
3915 /*
3916  * Scan the content structures, and build header
3917  * fields that will need to be output into the
3918  * message.
3919  */
3920
3921 static int
3922 build_headers (CT ct)
3923 {
3924     int cc, mailbody, len;
3925     char **ap, **ep;
3926     char *np, *vp, buffer[BUFSIZ];
3927     CI ci = &ct->c_ctinfo;
3928
3929     /*
3930      * If message is type multipart, then add the multipart
3931      * boundary to the list of attribute/value pairs.
3932      */
3933     if (ct->c_type == CT_MULTIPART) {
3934         char *cp;
3935         static int level = 0;   /* store nesting level */
3936
3937         ap = ci->ci_attrs;
3938         ep = ci->ci_values;
3939         snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
3940         cp = strchr(*ap++ = add (buffer, NULL), '=');
3941         *ap = NULL;
3942         *cp++ = '\0';
3943         *ep = cp;
3944     }
3945
3946     /*
3947      * Skip the output of Content-Type, parameters, content
3948      * description and disposition, and Content-ID if the
3949      * content is of type "message" and the rfc934 compatibility
3950      * flag is set (which means we are inside multipart/digest
3951      * and the switch -rfc934mode was given).
3952      */
3953     if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
3954         goto skip_headers;
3955
3956     /*
3957      * output the content type and subtype
3958      */
3959     np = add (TYPE_FIELD, NULL);
3960     vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
3961
3962     /* keep track of length of line */
3963     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
3964                 + strlen (ci->ci_subtype) + 3;
3965
3966     mailbody = ct->c_type == CT_MESSAGE
3967         && ct->c_subtype == MESSAGE_EXTERNAL
3968         && ((struct exbody *) ct->c_ctparams)->eb_body;
3969
3970     /*
3971      * Append the attribute/value pairs to
3972      * the end of the Content-Type line.
3973      */
3974     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3975         if (mailbody && !strcasecmp (*ap, "body"))
3976             continue;
3977
3978         vp = add (";", vp);
3979         len++;
3980
3981         snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
3982         if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
3983             vp = add ("\n\t", vp);
3984             len = 8;
3985         } else {
3986             vp = add (" ", vp);
3987             len++;
3988         }
3989         vp = add (buffer, vp);
3990         len += cc;
3991     }
3992
3993     /*
3994      * Append any RFC-822 comment to the end of
3995      * the Content-Type line.
3996      */
3997     if (ci->ci_comment) {
3998         snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
3999         if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
4000             vp = add ("\n\t", vp);
4001             len = 8;
4002         } else {
4003             vp = add (" ", vp);
4004             len++;
4005         }
4006         vp = add (buffer, vp);
4007         len += cc;
4008     }
4009     vp = add ("\n", vp);
4010     add_header (ct, np, vp);
4011
4012     /*
4013      * output the Content-ID, unless disabled by -nocontentid
4014      */
4015     if (contentidsw && ct->c_id) {
4016         np = add (ID_FIELD, NULL);
4017         vp = concat (" ", ct->c_id, NULL);
4018         add_header (ct, np, vp);
4019     }
4020
4021     /*
4022      * output the Content-Description
4023      */
4024     if (ct->c_descr) {
4025         np = add (DESCR_FIELD, NULL);
4026         vp = concat (" ", ct->c_descr, NULL);
4027         add_header (ct, np, vp);
4028     }
4029
4030     /*
4031      * output the Content-Disposition
4032      */
4033     if (ct->c_dispo) {
4034         np = add (DISPO_FIELD, NULL);
4035         vp = concat (" ", ct->c_dispo, NULL);
4036         add_header (ct, np, vp);
4037     }
4038
4039 skip_headers:
4040     /*
4041      * If this is the internal content structure for a
4042      * "message/external", then we are done with the
4043      * headers (since it has no body).
4044      */
4045     if (ct->c_ctexbody)
4046         return OK;
4047
4048     /*
4049      * output the Content-MD5
4050      */
4051     if (checksw) {
4052         np = add (MD5_FIELD, NULL);
4053         vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
4054         add_header (ct, np, vp);
4055     }
4056
4057     /*
4058      * output the Content-Transfer-Encoding
4059      */
4060     switch (ct->c_encoding) {
4061     case CE_7BIT:
4062         /* Nothing to output */
4063 #if 0
4064         np = add (ENCODING_FIELD, NULL);
4065         vp = concat (" ", "7bit", "\n", NULL);
4066         add_header (ct, np, vp);
4067 #endif
4068         break;
4069
4070     case CE_8BIT:
4071         if (ct->c_type == CT_MESSAGE)
4072             adios (NULL, "internal error, invalid encoding");
4073
4074         np = add (ENCODING_FIELD, NULL);
4075         vp = concat (" ", "8bit", "\n", NULL);
4076         add_header (ct, np, vp);
4077         break;
4078
4079     case CE_QUOTED:
4080         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
4081             adios (NULL, "internal error, invalid encoding");
4082
4083         np = add (ENCODING_FIELD, NULL);
4084         vp = concat (" ", "quoted-printable", "\n", NULL);
4085         add_header (ct, np, vp);
4086         break;
4087
4088     case CE_BASE64:
4089         if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
4090             adios (NULL, "internal error, invalid encoding");
4091
4092         np = add (ENCODING_FIELD, NULL);
4093         vp = concat (" ", "base64", "\n", NULL);
4094         add_header (ct, np, vp);
4095         break;
4096
4097     case CE_BINARY:
4098         if (ct->c_type == CT_MESSAGE)
4099             adios (NULL, "internal error, invalid encoding");
4100
4101         np = add (ENCODING_FIELD, NULL);
4102         vp = concat (" ", "binary", "\n", NULL);
4103         add_header (ct, np, vp);
4104         break;
4105
4106     default:
4107         adios (NULL, "unknown transfer encoding in content");
4108         break;
4109     }
4110
4111     /*
4112      * Additional content specific header processing
4113      */
4114     switch (ct->c_type) {
4115     case CT_MULTIPART:
4116     {
4117         struct multipart *m;
4118         struct part *part;
4119
4120         m = (struct multipart *) ct->c_ctparams;
4121         for (part = m->mp_parts; part; part = part->mp_next) {
4122             CT p;
4123
4124             p = part->mp_part;
4125             build_headers (p);
4126         }
4127     }
4128         break;
4129
4130     case CT_MESSAGE:
4131         if (ct->c_subtype == MESSAGE_EXTERNAL) {
4132             struct exbody *e;
4133
4134             e = (struct exbody *) ct->c_ctparams;
4135             build_headers (e->eb_content);
4136         }
4137         break;
4138
4139     default:
4140         /* Nothing to do */
4141         break;
4142     }
4143
4144     return OK;
4145 }
4146
4147
4148 static char nib2b64[0x40+1] =
4149         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4150
4151 static char *
4152 calculate_digest (CT ct, int asciiP)
4153 {
4154     int cc;
4155     char buffer[BUFSIZ], *vp, *op;
4156     unsigned char *dp;
4157     unsigned char digest[16];
4158     unsigned char outbuf[25];
4159     FILE *in;
4160     MD5_CTX mdContext;
4161     CE ce = ct->c_cefile;
4162
4163     /* open content */
4164     if ((in = fopen (ce->ce_file, "r")) == NULL)
4165         adios (ce->ce_file, "unable to open for reading");
4166
4167     /* Initialize md5 context */
4168     MD5Init (&mdContext);
4169
4170     /* calculate md5 message digest */
4171     if (asciiP) {
4172         while (fgets (buffer, sizeof(buffer) - 1, in)) {
4173             char c, *cp;
4174
4175             cp = buffer + strlen (buffer) - 1;
4176             if ((c = *cp) == '\n')
4177                 *cp = '\0';
4178
4179             MD5Update (&mdContext, (unsigned char *) buffer,
4180                        (unsigned int) strlen (buffer));
4181
4182             if (c == '\n')
4183                 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
4184         }
4185     } else {
4186         while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
4187             MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
4188     }
4189
4190     /* md5 finalization.  Write digest and zero md5 context */
4191     MD5Final (digest, &mdContext);
4192
4193     /* close content */
4194     fclose (in);
4195
4196     /* print debugging info */
4197     if (debugsw) {
4198         unsigned char *ep;
4199
4200         fprintf (stderr, "MD5 digest=");
4201         for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
4202                  dp < ep; dp++)
4203             fprintf (stderr, "%02x", *dp & 0xff);
4204         fprintf (stderr, "\n");
4205     }
4206
4207     /* encode the digest using base64 */
4208     for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
4209                 cc > 0; cc -= 3, op += 4) {
4210         unsigned long bits;
4211         char *bp;
4212
4213         bits = (*dp++ & 0xff) << 16;
4214         if (cc > 1) {
4215             bits |= (*dp++ & 0xff) << 8;
4216             if (cc > 2)
4217                 bits |= *dp++ & 0xff;
4218         }
4219
4220         for (bp = op + 4; bp > op; bits >>= 6)
4221             *--bp = nib2b64[bits & 0x3f];
4222         if (cc < 3) {
4223             *(op + 3) = '=';
4224             if (cc < 2)
4225                 *(op + 2) = '=';
4226         }
4227     }
4228
4229     /* null terminate string */
4230     outbuf[24] = '\0';
4231
4232     /* now make copy and return string */
4233     vp = concat (" ", outbuf, "\n", NULL);
4234     return vp;
4235 }
4236
4237
4238 static int
4239 readDigest (CT ct, char *cp)
4240 {
4241     int bitno, skip;
4242     unsigned long bits;
4243     char *bp = cp;
4244     unsigned char *dp, value, *ep;
4245     unsigned char *b, *b1, *b2, *b3;
4246
4247     b  = (unsigned char *) &bits,
4248     b1 = &b[endian > 0 ? 1 : 2],
4249     b2 = &b[endian > 0 ? 2 : 1],
4250     b3 = &b[endian > 0 ? 3 : 0];
4251     bitno = 18;
4252     bits = 0L;
4253     skip = 0;
4254
4255     for (ep = (dp = ct->c_digest)
4256                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
4257         switch (*cp) {
4258             default:
4259                 if (skip
4260                         || (*cp & 0x80)
4261                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
4262                     if (debugsw)
4263                         fprintf (stderr, "invalid BASE64 encoding\n");
4264                     return NOTOK;
4265                 }
4266
4267                 bits |= value << bitno;
4268 test_end:
4269                 if ((bitno -= 6) < 0) {
4270                     if (dp + (3 - skip) > ep)
4271                         goto invalid_digest;
4272                     *dp++ = *b1;
4273                     if (skip < 2) {
4274                         *dp++ = *b2;
4275                         if (skip < 1)
4276                             *dp++ = *b3;
4277                     }
4278                     bitno = 18;
4279                     bits = 0L;
4280                     skip = 0;
4281                 }
4282                 break;
4283
4284             case '=':
4285                 if (++skip > 3)
4286                     goto self_delimiting;
4287                 goto test_end;
4288         }
4289     if (bitno != 18) {
4290         if (debugsw)
4291             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
4292
4293         return NOTOK;
4294     }
4295 self_delimiting:
4296     if (dp != ep) {
4297 invalid_digest:
4298         if (debugsw) {
4299             while (*cp)
4300                 cp++;
4301             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
4302                      cp - bp);
4303         }
4304
4305         return NOTOK;
4306     }
4307
4308     if (debugsw) {
4309         fprintf (stderr, "MD5 digest=");
4310         for (dp = ct->c_digest; dp < ep; dp++)
4311             fprintf (stderr, "%02x", *dp & 0xff);
4312         fprintf (stderr, "\n");
4313     }
4314
4315     return OK;
4316 }
4317
4318
4319 /* Make sure that buf contains at least one appearance of name,
4320    followed by =.  If not, append both name and value.  Note that name
4321    should not contain a trailing =.  And quotes will be added around
4322    the value.  Typical usage:  make sure that a Content-Disposition
4323    header contains filename="foo".  If it doesn't and value does, use
4324    value from that. */
4325 static char *
4326 incl_name_value (char *buf, char *name, char *value) {
4327   char *newbuf = buf;
4328
4329   /* Assume that name is non-null. */
4330   if (buf && value) {
4331     char *name_plus_equal = concat (name, "=", NULL);
4332
4333     if (! strstr (buf, name_plus_equal)) {
4334       char *appendage;
4335       char *cp;
4336
4337       /* Trim trailing space, esp. newline. */
4338       for (cp = &buf[strlen (buf) - 1]; cp >= buf && isspace (*cp); --cp) {
4339         *cp = '\0';
4340       }
4341
4342       appendage = concat ("; ", name, "=", "\"", value, "\"\n", NULL);
4343       newbuf = add (appendage, buf);
4344       free (appendage);
4345     }
4346
4347     free (name_plus_equal);
4348   }
4349
4350   return newbuf;
4351 }
4352
4353
4354 /* Extract just name_suffix="foo", if any, from value.  If there isn't
4355    one, return the entire value.  Note that, for example, a name_suffix
4356    of name will match filename="foo", and return foo. */
4357 static char *
4358 extract_name_value (char *name_suffix, char *value) {
4359   char *extracted_name_value = value;
4360   char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
4361   char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
4362   char *cp;
4363
4364   free (name_suffix_plus_quote);
4365   if (name_suffix_equals) {
4366     char *name_suffix_begin;
4367
4368     /* Find first \". */
4369     for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
4370     name_suffix_begin = ++cp;
4371     /* Find second \". */
4372     for (; *cp != '"'; ++cp) /* empty */;
4373
4374     extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
4375     memcpy (extracted_name_value, name_suffix_begin, cp - name_suffix_begin);
4376     extracted_name_value[cp - name_suffix_begin] = '\0';
4377   }
4378
4379   return extracted_name_value;
4380 }