Part of the patch from bug #4301; clean up our prototypes, a lot. Still
[mmh] / uip / mhparse.c
1
2 /*
3  * mhparse.c -- routines to parse the contents of MIME messages
4  *
5  * This code is Copyright (c) 2002, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 #include <h/mh.h>
11 #include <fcntl.h>
12 #include <h/signals.h>
13 #include <h/md5.h>
14 #include <errno.h>
15 #include <signal.h>
16 #include <h/mts.h>
17 #include <h/tws.h>
18 #include <h/mime.h>
19 #include <h/mhparse.h>
20 #include <h/utils.h>
21
22
23 extern int debugsw;
24
25 extern int endian;      /* mhmisc.c     */
26
27 extern pid_t xpid;      /* mhshowsbr.c  */
28
29 /* cache policies */
30 extern int rcachesw;    /* mhcachesbr.c */
31 extern int wcachesw;    /* mhcachesbr.c */
32
33 int checksw = 0;        /* check Content-MD5 field */
34
35 /*
36  * Directory to place temp files.  This must
37  * be set before these routines are called.
38  */
39 char *tmp;
40
41 /*
42  * Structures for TEXT messages
43  */
44 struct k2v SubText[] = {
45     { "plain",    TEXT_PLAIN },
46     { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341    */
47     { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896    */
48     { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
49 };
50
51 struct k2v Charset[] = {
52     { "us-ascii",   CHARSET_USASCII },
53     { "iso-8859-1", CHARSET_LATIN },
54     { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
55 };
56
57 /*
58  * Structures for MULTIPART messages
59  */
60 struct k2v SubMultiPart[] = {
61     { "mixed",       MULTI_MIXED },
62     { "alternative", MULTI_ALTERNATE },
63     { "digest",      MULTI_DIGEST },
64     { "parallel",    MULTI_PARALLEL },
65     { NULL,          MULTI_UNKNOWN }    /* this one must be last! */
66 };
67
68 /*
69  * Structures for MESSAGE messages
70  */
71 struct k2v SubMessage[] = {
72     { "rfc822",        MESSAGE_RFC822 },
73     { "partial",       MESSAGE_PARTIAL },
74     { "external-body", MESSAGE_EXTERNAL },
75     { NULL,            MESSAGE_UNKNOWN }        /* this one must be last! */
76 };
77
78 /*
79  * Structure for APPLICATION messages
80  */
81 struct k2v SubApplication[] = {
82     { "octet-stream", APPLICATION_OCTETS },
83     { "postscript",   APPLICATION_POSTSCRIPT },
84     { NULL,           APPLICATION_UNKNOWN }     /* this one must be last! */
85 };
86
87
88 /* mhcachesbr.c */
89 int find_cache (CT, int, int *, char *, char *, int);
90
91 /* mhmisc.c */
92 int part_ok (CT, int);
93 int type_ok (CT, int);
94 void content_error (char *, CT, char *, ...);
95
96 /* mhfree.c */
97 void free_content (CT);
98 void free_encoding (CT, int);
99
100 /*
101  * static prototypes
102  */
103 static CT get_content (FILE *, char *, int);
104 static int get_comment (CT, unsigned char **, int);
105
106 static int InitGeneric (CT);
107 static int InitText (CT);
108 static int InitMultiPart (CT);
109 static void reverse_parts (CT);
110 static int InitMessage (CT);
111 static int InitApplication (CT);
112 static int init_encoding (CT, OpenCEFunc);
113 static unsigned long size_encoding (CT);
114 static int InitBase64 (CT);
115 static int openBase64 (CT, char **);
116 static int InitQuoted (CT);
117 static int openQuoted (CT, char **);
118 static int Init7Bit (CT);
119 static int openExternal (CT, CT, CE, char **, int *);
120 static int InitFile (CT);
121 static int openFile (CT, char **);
122 static int InitFTP (CT);
123 static int openFTP (CT, char **);
124 static int InitMail (CT);
125 static int openMail (CT, char **);
126 static int readDigest (CT, char *);
127
128 struct str2init str2cts[] = {
129     { "application", CT_APPLICATION, InitApplication },
130     { "audio",       CT_AUDIO,       InitGeneric },
131     { "image",       CT_IMAGE,       InitGeneric },
132     { "message",     CT_MESSAGE,     InitMessage },
133     { "multipart",   CT_MULTIPART,   InitMultiPart },
134     { "text",        CT_TEXT,        InitText },
135     { "video",       CT_VIDEO,       InitGeneric },
136     { NULL,          CT_EXTENSION,   NULL },  /* these two must be last! */
137     { NULL,          CT_UNKNOWN,     NULL },
138 };
139
140 struct str2init str2ces[] = {
141     { "base64",           CE_BASE64,    InitBase64 },
142     { "quoted-printable", CE_QUOTED,    InitQuoted },
143     { "8bit",             CE_8BIT,      Init7Bit },
144     { "7bit",             CE_7BIT,      Init7Bit },
145     { "binary",           CE_BINARY,    Init7Bit },
146     { NULL,               CE_EXTENSION, NULL },  /* these two must be last! */
147     { NULL,               CE_UNKNOWN,   NULL },
148 };
149
150 /*
151  * NOTE WELL: si_key MUST NOT have value of NOTOK
152  *
153  * si_key is 1 if access method is anonymous.
154  */
155 struct str2init str2methods[] = {
156     { "afs",         1, InitFile },
157     { "anon-ftp",    1, InitFTP },
158     { "ftp",         0, InitFTP },
159     { "local-file",  0, InitFile },
160     { "mail-server", 0, InitMail },
161     { NULL,          0, NULL }
162 };
163
164
165 int
166 pidcheck (int status)
167 {
168     if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
169         return status;
170
171     fflush (stdout);
172     fflush (stderr);
173     done (1);
174     return 1;
175 }
176
177
178 /*
179  * Main entry point for parsing a MIME message or file.
180  * It returns the Content structure for the top level
181  * entity in the file.
182  */
183
184 CT
185 parse_mime (char *file)
186 {
187     int is_stdin;
188     char buffer[BUFSIZ];
189     FILE *fp;
190     CT ct;
191
192     /*
193      * Check if file is actually standard input
194      */
195     if ((is_stdin = !(strcmp (file, "-")))) {
196         char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
197         if (tfile == NULL) {
198             advise("mhparse", "unable to create temporary file");
199             return NULL;
200         }
201         file = add (tfile, NULL);
202         chmod (file, 0600);
203
204         while (fgets (buffer, sizeof(buffer), stdin))
205             fputs (buffer, fp);
206         fflush (fp);
207
208         if (ferror (stdin)) {
209             unlink (file);
210             advise ("stdin", "error reading");
211             return NULL;
212         }
213         if (ferror (fp)) {
214             unlink (file);
215             advise (file, "error writing");
216             return NULL;
217         }
218         fseek (fp, 0L, SEEK_SET);
219     } else if ((fp = fopen (file, "r")) == NULL) {
220         advise (file, "unable to read");
221         return NULL;
222     }
223
224     if (!(ct = get_content (fp, file, 1))) {
225         if (is_stdin)
226             unlink (file);
227         advise (NULL, "unable to decode %s", file);
228         return NULL;
229     }
230
231     if (is_stdin)
232         ct->c_unlink = 1;       /* temp file to remove */
233
234     ct->c_fp = NULL;
235
236     if (ct->c_end == 0L) {
237         fseek (fp, 0L, SEEK_END);
238         ct->c_end = ftell (fp);
239     }
240
241     if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
242         fclose (fp);
243         free_content (ct);
244         return NULL;
245     }
246
247     fclose (fp);
248     return ct;
249 }
250
251
252 /*
253  * Main routine for reading/parsing the headers
254  * of a message content.
255  *
256  * toplevel =  1   # we are at the top level of the message
257  * toplevel =  0   # we are inside message type or multipart type
258  *                 # other than multipart/digest
259  * toplevel = -1   # we are inside multipart/digest
260  * NB: on failure we will fclose(in)!
261  */
262
263 static CT
264 get_content (FILE *in, char *file, int toplevel)
265 {
266     int compnum, state;
267     char buf[BUFSIZ], name[NAMESZ];
268     char *np, *vp;
269     CT ct;
270     HF hp;
271
272     /* allocate the content structure */
273     if (!(ct = (CT) calloc (1, sizeof(*ct))))
274         adios (NULL, "out of memory");
275
276     ct->c_fp = in;
277     ct->c_file = add (file, NULL);
278     ct->c_begin = ftell (ct->c_fp) + 1;
279
280     /*
281      * Parse the header fields for this
282      * content into a linked list.
283      */
284     for (compnum = 1, state = FLD;;) {
285         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
286         case FLD:
287         case FLDPLUS:
288         case FLDEOF:
289             compnum++;
290
291             /* get copies of the buffers */
292             np = add (name, NULL);
293             vp = add (buf, NULL);
294
295             /* if necessary, get rest of field */
296             while (state == FLDPLUS) {
297                 state = m_getfld (state, name, buf, sizeof(buf), in);
298                 vp = add (buf, vp);     /* add to previous value */
299             }
300
301             /* Now add the header data to the list */
302             add_header (ct, np, vp);
303
304             /* continue, if this isn't the last header field */
305             if (state != FLDEOF) {
306                 ct->c_begin = ftell (in) + 1;
307                 continue;
308             }
309             /* else fall... */
310
311         case BODY:
312         case BODYEOF:
313             ct->c_begin = ftell (in) - strlen (buf);
314             break;
315
316         case FILEEOF:
317             ct->c_begin = ftell (in);
318             break;
319
320         case LENERR:
321         case FMTERR:
322             adios (NULL, "message format error in component #%d", compnum);
323
324         default:
325             adios (NULL, "getfld() returned %d", state);
326         }
327
328         /* break out of the loop */
329         break;
330     }
331
332     /*
333      * Read the content headers.  We will parse the
334      * MIME related header fields into their various
335      * structures and set internal flags related to
336      * content type/subtype, etc.
337      */
338
339     hp = ct->c_first_hf;        /* start at first header field */
340     while (hp) {
341         /* Get MIME-Version field */
342         if (!mh_strcasecmp (hp->name, VRSN_FIELD)) {
343             int ucmp;
344             char c;
345             unsigned char *cp, *dp;
346
347             if (ct->c_vrsn) {
348                 advise (NULL, "message %s has multiple %s: fields",
349                         ct->c_file, VRSN_FIELD);
350                 goto next_header;
351             }
352             ct->c_vrsn = add (hp->value, NULL);
353
354             /* Now, cleanup this field */
355             cp = ct->c_vrsn;
356
357             while (isspace (*cp))
358                 cp++;
359             for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
360                 *dp++ = ' ';
361             for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
362                 if (!isspace (*dp))
363                     break;
364             *++dp = '\0';
365             if (debugsw)
366                 fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
367
368             if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
369                 goto out;
370
371             for (dp = cp; istoken (*dp); dp++)
372                 continue;
373             c = *dp;
374             *dp = '\0';
375             ucmp = !mh_strcasecmp (cp, VRSN_VALUE);
376             *dp = c;
377             if (!ucmp) {
378                 admonish (NULL, "message %s has unknown value for %s: field (%s)",
379                 ct->c_file, VRSN_FIELD, cp);
380             }
381         }
382         else if (!mh_strcasecmp (hp->name, TYPE_FIELD)) {
383         /* Get Content-Type field */
384             struct str2init *s2i;
385             CI ci = &ct->c_ctinfo;
386
387             /* Check if we've already seen a Content-Type header */
388             if (ct->c_ctline) {
389                 advise (NULL, "message %s has multiple %s: fields",
390                         ct->c_file, TYPE_FIELD);
391                 goto next_header;
392             }
393
394             /* Parse the Content-Type field */
395             if (get_ctinfo (hp->value, ct, 0) == NOTOK)
396                 goto out;
397
398             /*
399              * Set the Init function and the internal
400              * flag for this content type.
401              */
402             for (s2i = str2cts; s2i->si_key; s2i++)
403                 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
404                     break;
405             if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
406                 s2i++;
407             ct->c_type = s2i->si_val;
408             ct->c_ctinitfnx = s2i->si_init;
409         }
410         else if (!mh_strcasecmp (hp->name, ENCODING_FIELD)) {
411         /* Get Content-Transfer-Encoding field */
412             char c;
413             unsigned char *cp, *dp;
414             struct str2init *s2i;
415
416             /*
417              * Check if we've already seen the
418              * Content-Transfer-Encoding field
419              */
420             if (ct->c_celine) {
421                 advise (NULL, "message %s has multiple %s: fields",
422                         ct->c_file, ENCODING_FIELD);
423                 goto next_header;
424             }
425
426             /* get copy of this field */
427             ct->c_celine = cp = add (hp->value, NULL);
428
429             while (isspace (*cp))
430                 cp++;
431             for (dp = cp; istoken (*dp); dp++)
432                 continue;
433             c = *dp;
434             *dp = '\0';
435
436             /*
437              * Find the internal flag and Init function
438              * for this transfer encoding.
439              */
440             for (s2i = str2ces; s2i->si_key; s2i++)
441                 if (!mh_strcasecmp (cp, s2i->si_key))
442                     break;
443             if (!s2i->si_key && !uprf (cp, "X-"))
444                 s2i++;
445             *dp = c;
446             ct->c_encoding = s2i->si_val;
447
448             /* Call the Init function for this encoding */
449             if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
450                 goto out;
451         }
452         else if (!mh_strcasecmp (hp->name, MD5_FIELD)) {
453         /* Get Content-MD5 field */
454             unsigned char *cp, *dp;
455             char *ep;
456
457             if (!checksw)
458                 goto next_header;
459
460             if (ct->c_digested) {
461                 advise (NULL, "message %s has multiple %s: fields",
462                         ct->c_file, MD5_FIELD);
463                 goto next_header;
464             }
465
466             ep = cp = add (hp->value, NULL);    /* get a copy */
467
468             while (isspace (*cp))
469                 cp++;
470             for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
471                 *dp++ = ' ';
472             for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
473                 if (!isspace (*dp))
474                     break;
475             *++dp = '\0';
476             if (debugsw)
477                 fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
478
479             if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
480                 free (ep);
481                 goto out;
482             }
483
484             for (dp = cp; *dp && !isspace (*dp); dp++)
485                 continue;
486             *dp = '\0';
487
488             readDigest (ct, cp);
489             free (ep);
490             ct->c_digested++;
491         }
492         else if (!mh_strcasecmp (hp->name, ID_FIELD)) {
493         /* Get Content-ID field */
494             ct->c_id = add (hp->value, ct->c_id);
495         }
496         else if (!mh_strcasecmp (hp->name, DESCR_FIELD)) {
497         /* Get Content-Description field */
498             ct->c_descr = add (hp->value, ct->c_descr);
499         }
500         else if (!mh_strcasecmp (hp->name, DISPO_FIELD)) {
501         /* Get Content-Disposition field */
502             ct->c_dispo = add (hp->value, ct->c_dispo);
503         }
504
505 next_header:
506         hp = hp->next;  /* next header field */
507     }
508
509     /*
510      * Check if we saw a Content-Type field.
511      * If not, then assign a default value for
512      * it, and the Init function.
513      */
514     if (!ct->c_ctline) {
515         /*
516          * If we are inside a multipart/digest message,
517          * so default type is message/rfc822
518          */
519         if (toplevel < 0) {
520             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
521                 goto out;
522             ct->c_type = CT_MESSAGE;
523             ct->c_ctinitfnx = InitMessage;
524         } else {
525             /*
526              * Else default type is text/plain
527              */
528             if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
529                 goto out;
530             ct->c_type = CT_TEXT;
531             ct->c_ctinitfnx = InitText;
532         }
533     }
534
535     /* Use default Transfer-Encoding, if necessary */
536     if (!ct->c_celine) {
537         ct->c_encoding = CE_7BIT;
538         Init7Bit (ct);
539     }
540
541     return ct;
542
543 out:
544     free_content (ct);
545     return NULL;
546 }
547
548
549 /*
550  * small routine to add header field to list
551  */
552
553 int
554 add_header (CT ct, char *name, char *value)
555 {
556     HF hp;
557
558     /* allocate header field structure */
559     hp = mh_xmalloc (sizeof(*hp));
560
561     /* link data into header structure */
562     hp->name = name;
563     hp->value = value;
564     hp->next = NULL;
565
566     /* link header structure into the list */
567     if (ct->c_first_hf == NULL) {
568         ct->c_first_hf = hp;            /* this is the first */
569         ct->c_last_hf = hp;
570     } else {
571         ct->c_last_hf->next = hp;       /* add it to the end */
572         ct->c_last_hf = hp;
573     }
574
575     return 0;
576 }
577
578
579 /* Make sure that buf contains at least one appearance of name,
580    followed by =.  If not, insert both name and value, just after
581    first semicolon, if any.  Note that name should not contain a
582    trailing =.  And quotes will be added around the value.  Typical
583    usage:  make sure that a Content-Disposition header contains
584    filename="foo".  If it doesn't and value does, use value from
585    that. */
586 static char *
587 incl_name_value (unsigned char *buf, char *name, char *value) {
588     char *newbuf = buf;
589
590     /* Assume that name is non-null. */
591     if (buf && value) {
592         char *name_plus_equal = concat (name, "=", NULL);
593
594         if (! strstr (buf, name_plus_equal)) {
595             char *insertion;
596             unsigned char *cp;
597             char *prefix, *suffix;
598
599             /* Trim trailing space, esp. newline. */
600             for (cp = &buf[strlen (buf) - 1];
601                  cp >= buf && isspace (*cp);
602                  --cp) {
603                 *cp = '\0';
604             }
605
606             insertion = concat ("; ", name, "=", "\"", value, "\"", NULL);
607
608             /* Insert at first semicolon, if any.  If none, append to
609                end. */
610             prefix = add (buf, NULL);
611             if ((cp = strchr (prefix, ';'))) {
612                 suffix = concat (cp, NULL);
613                 *cp = '\0';
614                 newbuf = concat (prefix, insertion, suffix, "\n", NULL);
615                 free (suffix);
616             } else {
617                 /* Append to end. */
618                 newbuf = concat (buf, insertion, "\n", NULL);
619             }
620
621             free (prefix);
622             free (insertion);
623             free (buf);
624         }
625
626         free (name_plus_equal);
627     }
628
629     return newbuf;
630 }
631
632 /* Extract just name_suffix="foo", if any, from value.  If there isn't
633    one, return the entire value.  Note that, for example, a name_suffix
634    of name will match filename="foo", and return foo. */
635 static char *
636 extract_name_value (char *name_suffix, char *value) {
637     char *extracted_name_value = value;
638     char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
639     char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
640     char *cp;
641
642     free (name_suffix_plus_quote);
643     if (name_suffix_equals) {
644         char *name_suffix_begin;
645
646         /* Find first \". */
647         for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
648         name_suffix_begin = ++cp;
649         /* Find second \". */
650         for (; *cp != '"'; ++cp) /* empty */;
651
652         extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
653         memcpy (extracted_name_value,
654                 name_suffix_begin,
655                 cp - name_suffix_begin);
656         extracted_name_value[cp - name_suffix_begin] = '\0';
657     }
658
659     return extracted_name_value;
660 }
661
662 /*
663  * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
664  * directives.  Fills in the information of the CTinfo structure.
665  */
666 int
667 get_ctinfo (unsigned char *cp, CT ct, int magic)
668 {
669     int i;
670     unsigned char *dp;
671     char **ap, **ep;
672     char c;
673     CI ci;
674
675     ci = &ct->c_ctinfo;
676     i = strlen (invo_name) + 2;
677
678     /* store copy of Content-Type line */
679     cp = ct->c_ctline = add (cp, NULL);
680
681     while (isspace (*cp))       /* trim leading spaces */
682         cp++;
683
684     /* change newlines to spaces */
685     for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
686         *dp++ = ' ';
687
688     /* trim trailing spaces */
689     for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
690         if (!isspace (*dp))
691             break;
692     *++dp = '\0';
693
694     if (debugsw)
695         fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
696
697     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
698         return NOTOK;
699
700     for (dp = cp; istoken (*dp); dp++)
701         continue;
702     c = *dp, *dp = '\0';
703     ci->ci_type = add (cp, NULL);       /* store content type */
704     *dp = c, cp = dp;
705
706     if (!*ci->ci_type) {
707         advise (NULL, "invalid %s: field in message %s (empty type)", 
708                 TYPE_FIELD, ct->c_file);
709         return NOTOK;
710     }
711
712     /* down case the content type string */
713     for (dp = ci->ci_type; *dp; dp++)
714         if (isalpha(*dp) && isupper (*dp))
715             *dp = tolower (*dp);
716
717     while (isspace (*cp))
718         cp++;
719
720     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
721         return NOTOK;
722
723     if (*cp != '/') {
724         if (!magic)
725             ci->ci_subtype = add ("", NULL);
726         goto magic_skip;
727     }
728
729     cp++;
730     while (isspace (*cp))
731         cp++;
732
733     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
734         return NOTOK;
735
736     for (dp = cp; istoken (*dp); dp++)
737         continue;
738     c = *dp, *dp = '\0';
739     ci->ci_subtype = add (cp, NULL);    /* store the content subtype */
740     *dp = c, cp = dp;
741
742     if (!*ci->ci_subtype) {
743         advise (NULL,
744                 "invalid %s: field in message %s (empty subtype for \"%s\")",
745                 TYPE_FIELD, ct->c_file, ci->ci_type);
746         return NOTOK;
747     }
748
749     /* down case the content subtype string */
750     for (dp = ci->ci_subtype; *dp; dp++)
751         if (isalpha(*dp) && isupper (*dp))
752             *dp = tolower (*dp);
753
754 magic_skip:
755     while (isspace (*cp))
756         cp++;
757
758     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
759         return NOTOK;
760
761     /*
762      * Parse attribute/value pairs given with Content-Type
763      */
764     ep = (ap = ci->ci_attrs) + NPARMS;
765     while (*cp == ';') {
766         char *vp;
767         unsigned char *up;
768
769         if (ap >= ep) {
770             advise (NULL,
771                     "too many parameters in message %s's %s: field (%d max)",
772                     ct->c_file, TYPE_FIELD, NPARMS);
773             return NOTOK;
774         }
775
776         cp++;
777         while (isspace (*cp))
778             cp++;
779
780         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
781             return NOTOK;
782
783         if (*cp == 0) {
784             advise (NULL,
785                     "extraneous trailing ';' in message %s's %s: parameter list",
786                     ct->c_file, TYPE_FIELD);
787             return OK;
788         }
789
790         /* down case the attribute name */
791         for (dp = cp; istoken (*dp); dp++)
792             if (isalpha(*dp) && isupper (*dp))
793                 *dp = tolower (*dp);
794
795         for (up = dp; isspace (*dp);)
796             dp++;
797         if (dp == cp || *dp != '=') {
798             advise (NULL,
799                     "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
800                     ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
801             return NOTOK;
802         }
803
804         vp = (*ap = add (cp, NULL)) + (up - cp);
805         *vp = '\0';
806         for (dp++; isspace (*dp);)
807             dp++;
808
809         /* now add the attribute value */
810         ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
811
812         if (*dp == '"') {
813             for (cp = ++dp, dp = vp;;) {
814                 switch (c = *cp++) {
815                     case '\0':
816 bad_quote:
817                         advise (NULL,
818                                 "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
819                                 ct->c_file, TYPE_FIELD, i, i, "", *ap);
820                         return NOTOK;
821
822                     case '\\':
823                         *dp++ = c;
824                         if ((c = *cp++) == '\0')
825                             goto bad_quote;
826                         /* else fall... */
827
828                     default:
829                         *dp++ = c;
830                         continue;
831
832                     case '"':
833                         *dp = '\0';
834                         break;
835                 }
836                 break;
837             }
838         } else {
839             for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
840                 continue;
841             *dp = '\0';
842         }
843         if (!*vp) {
844             advise (NULL,
845                     "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
846                     ct->c_file, TYPE_FIELD, i, i, "", *ap);
847             return NOTOK;
848         }
849         ap++;
850
851         while (isspace (*cp))
852             cp++;
853
854         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
855             return NOTOK;
856     }
857
858     /*
859      * Get any <Content-Id> given in buffer
860      */
861     if (magic && *cp == '<') {
862         if (ct->c_id) {
863             free (ct->c_id);
864             ct->c_id = NULL;
865         }
866         if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
867             advise (NULL, "invalid ID in message %s", ct->c_file);
868             return NOTOK;
869         }
870         c = *dp;
871         *dp = '\0';
872         if (*ct->c_id)
873             ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
874         else
875             ct->c_id = NULL;
876         *dp++ = c;
877         cp = dp;
878
879         while (isspace (*cp))
880             cp++;
881     }
882
883     /*
884      * Get any [Content-Description] given in buffer.
885      */
886     if (magic && *cp == '[') {
887         ct->c_descr = ++cp;
888         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
889             if (*dp == ']')
890                 break;
891         if (dp < cp) {
892             advise (NULL, "invalid description in message %s", ct->c_file);
893             ct->c_descr = NULL;
894             return NOTOK;
895         }
896         
897         c = *dp;
898         *dp = '\0';
899         if (*ct->c_descr)
900             ct->c_descr = concat (ct->c_descr, "\n", NULL);
901         else
902             ct->c_descr = NULL;
903         *dp++ = c;
904         cp = dp;
905
906         while (isspace (*cp))
907             cp++;
908     }
909
910     /*
911      * Get any {Content-Disposition} given in buffer.
912      */
913     if (magic && *cp == '{') {
914         ct->c_dispo = ++cp;
915         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
916             if (*dp == '}')
917                 break;
918         if (dp < cp) {
919             advise (NULL, "invalid disposition in message %s", ct->c_file);
920             ct->c_dispo = NULL;
921             return NOTOK;
922         }
923         
924         c = *dp;
925         *dp = '\0';
926         if (*ct->c_dispo)
927             ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
928         else
929             ct->c_dispo = NULL;
930         *dp++ = c;
931         cp = dp;
932
933         while (isspace (*cp))
934             cp++;
935     }
936
937     /*
938      * Check if anything is left over
939      */
940     if (*cp) {
941         if (magic) {
942             ci->ci_magic = add (cp, NULL);
943
944             /* If there is a Content-Disposition header and it doesn't
945                have a *filename=, extract it from the magic contents.
946                The r1bindex call skips any leading directory
947                components. */
948             if (ct->c_dispo)
949                 ct->c_dispo =
950                     incl_name_value (ct->c_dispo,
951                                      "filename",
952                                      r1bindex (extract_name_value ("name",
953                                                                    ci->
954                                                                    ci_magic),
955                                                '/'));
956         }
957         else
958             advise (NULL,
959                     "extraneous information in message %s's %s: field\n%*.*s(%s)",
960                     ct->c_file, TYPE_FIELD, i, i, "", cp);
961     }
962
963     return OK;
964 }
965
966
967 static int
968 get_comment (CT ct, unsigned char **ap, int istype)
969 {
970     int i;
971     char *bp;
972     unsigned char *cp;
973     char c, buffer[BUFSIZ], *dp;
974     CI ci;
975
976     ci = &ct->c_ctinfo;
977     cp = *ap;
978     bp = buffer;
979     cp++;
980
981     for (i = 0;;) {
982         switch (c = *cp++) {
983         case '\0':
984 invalid:
985         advise (NULL, "invalid comment in message %s's %s: field",
986                 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
987         return NOTOK;
988
989         case '\\':
990             *bp++ = c;
991             if ((c = *cp++) == '\0')
992                 goto invalid;
993             *bp++ = c;
994             continue;
995
996         case '(':
997             i++;
998             /* and fall... */
999         default:
1000             *bp++ = c;
1001             continue;
1002
1003         case ')':
1004             if (--i < 0)
1005                 break;
1006             *bp++ = c;
1007             continue;
1008         }
1009         break;
1010     }
1011     *bp = '\0';
1012
1013     if (istype) {
1014         if ((dp = ci->ci_comment)) {
1015             ci->ci_comment = concat (dp, " ", buffer, NULL);
1016             free (dp);
1017         } else {
1018             ci->ci_comment = add (buffer, NULL);
1019         }
1020     }
1021
1022     while (isspace (*cp))
1023         cp++;
1024
1025     *ap = cp;
1026     return OK;
1027 }
1028
1029
1030 /*
1031  * CONTENTS
1032  *
1033  * Handles content types audio, image, and video.
1034  * There's not much to do right here.
1035  */
1036
1037 static int
1038 InitGeneric (CT ct)
1039 {
1040     NMH_UNUSED (ct);
1041
1042     return OK;          /* not much to do here */
1043 }
1044
1045
1046 /*
1047  * TEXT
1048  */
1049
1050 static int
1051 InitText (CT ct)
1052 {
1053     char buffer[BUFSIZ];
1054     char *chset = NULL;
1055     char **ap, **ep, *cp;
1056     struct k2v *kv;
1057     struct text *t;
1058     CI ci = &ct->c_ctinfo;
1059
1060     /* check for missing subtype */
1061     if (!*ci->ci_subtype)
1062         ci->ci_subtype = add ("plain", ci->ci_subtype);
1063
1064     /* match subtype */
1065     for (kv = SubText; kv->kv_key; kv++)
1066         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1067             break;
1068     ct->c_subtype = kv->kv_value;
1069
1070     /* allocate text character set structure */
1071     if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
1072         adios (NULL, "out of memory");
1073     ct->c_ctparams = (void *) t;
1074
1075     /* scan for charset parameter */
1076     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1077         if (!mh_strcasecmp (*ap, "charset"))
1078             break;
1079
1080     /* check if content specified a character set */
1081     if (*ap) {
1082         /* match character set or set to CHARSET_UNKNOWN */
1083         for (kv = Charset; kv->kv_key; kv++) {
1084             if (!mh_strcasecmp (*ep, kv->kv_key)) {
1085                 chset = *ep;
1086                 break;
1087             }
1088         }
1089         t->tx_charset = kv->kv_value;
1090     } else {
1091         t->tx_charset = CHARSET_UNSPECIFIED;
1092     }
1093
1094     /*
1095      * If we can not handle character set natively,
1096      * then check profile for string to modify the
1097      * terminal or display method.
1098      *
1099      * termproc is for mhshow, though mhlist -debug prints it, too.
1100      */
1101     if (chset != NULL && !check_charset (chset, strlen (chset))) {
1102         snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
1103         if ((cp = context_find (buffer)))
1104             ct->c_termproc = getcpy (cp);
1105     }
1106
1107     return OK;
1108 }
1109
1110
1111 /*
1112  * MULTIPART
1113  */
1114
1115 static int
1116 InitMultiPart (CT ct)
1117 {
1118     int inout;
1119     long last, pos;
1120     unsigned char *cp, *dp;
1121     char **ap, **ep;
1122     char *bp, buffer[BUFSIZ];
1123     struct multipart *m;
1124     struct k2v *kv;
1125     struct part *part, **next;
1126     CI ci = &ct->c_ctinfo;
1127     CT p;
1128     FILE *fp;
1129
1130     /*
1131      * The encoding for multipart messages must be either
1132      * 7bit, 8bit, or binary (per RFC2045).
1133      */
1134     if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1135         && ct->c_encoding != CE_BINARY) {
1136         admonish (NULL,
1137                   "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1138                   ci->ci_type, ci->ci_subtype, ct->c_file);
1139         return NOTOK;
1140     }
1141
1142     /* match subtype */
1143     for (kv = SubMultiPart; kv->kv_key; kv++)
1144         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1145             break;
1146     ct->c_subtype = kv->kv_value;
1147
1148     /*
1149      * Check for "boundary" parameter, which is
1150      * required for multipart messages.
1151      */
1152     bp = 0;
1153     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1154         if (!mh_strcasecmp (*ap, "boundary")) {
1155             bp = *ep;
1156             break;
1157         }
1158     }
1159
1160     /* complain if boundary parameter is missing */
1161     if (!*ap) {
1162         advise (NULL,
1163                 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1164                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1165         return NOTOK;
1166     }
1167
1168     /* allocate primary structure for multipart info */
1169     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1170         adios (NULL, "out of memory");
1171     ct->c_ctparams = (void *) m;
1172
1173     /* check if boundary parameter contains only whitespace characters */
1174     for (cp = bp; isspace (*cp); cp++)
1175         continue;
1176     if (!*cp) {
1177         advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1178                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1179         return NOTOK;
1180     }
1181
1182     /* remove trailing whitespace from boundary parameter */
1183     for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1184         if (!isspace (*dp))
1185             break;
1186     *++dp = '\0';
1187
1188     /* record boundary separators */
1189     m->mp_start = concat (bp, "\n", NULL);
1190     m->mp_stop = concat (bp, "--\n", NULL);
1191
1192     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1193         advise (ct->c_file, "unable to open for reading");
1194         return NOTOK;
1195     }
1196
1197     fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1198     last = ct->c_end;
1199     next = &m->mp_parts;
1200     part = NULL;
1201     inout = 1;
1202
1203     while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1204         if (pos > last)
1205             break;
1206
1207         pos += strlen (buffer);
1208         if (buffer[0] != '-' || buffer[1] != '-')
1209             continue;
1210         if (inout) {
1211             if (strcmp (buffer + 2, m->mp_start))
1212                 continue;
1213 next_part:
1214             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1215                 adios (NULL, "out of memory");
1216             *next = part;
1217             next = &part->mp_next;
1218
1219             if (!(p = get_content (fp, ct->c_file,
1220                         ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1221                 ct->c_fp = NULL;
1222                 return NOTOK;
1223             }
1224             p->c_fp = NULL;
1225             part->mp_part = p;
1226             pos = p->c_begin;
1227             fseek (fp, pos, SEEK_SET);
1228             inout = 0;
1229         } else {
1230             if (strcmp (buffer + 2, m->mp_start) == 0) {
1231                 inout = 1;
1232 end_part:
1233                 p = part->mp_part;
1234                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1235                 if (p->c_end < p->c_begin)
1236                     p->c_begin = p->c_end;
1237                 if (inout)
1238                     goto next_part;
1239                 goto last_part;
1240             } else {
1241                 if (strcmp (buffer + 2, m->mp_stop) == 0)
1242                     goto end_part;
1243             }
1244         }
1245     }
1246
1247     advise (NULL, "bogus multipart content in message %s", ct->c_file);
1248     if (!inout && part) {
1249         p = part->mp_part;
1250         p->c_end = ct->c_end;
1251
1252         if (p->c_begin >= p->c_end) {
1253             for (next = &m->mp_parts; *next != part;
1254                      next = &((*next)->mp_next))
1255                 continue;
1256             *next = NULL;
1257             free_content (p);
1258             free ((char *) part);
1259         }
1260     }
1261
1262 last_part:
1263     /* reverse the order of the parts for multipart/alternative */
1264     if (ct->c_subtype == MULTI_ALTERNATE)
1265         reverse_parts (ct);
1266
1267     /*
1268      * label all subparts with part number, and
1269      * then initialize the content of the subpart.
1270      */
1271     {
1272         int partnum;
1273         char *pp;
1274         char partnam[BUFSIZ];
1275
1276         if (ct->c_partno) {
1277             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1278             pp = partnam + strlen (partnam);
1279         } else {
1280             pp = partnam;
1281         }
1282
1283         for (part = m->mp_parts, partnum = 1; part;
1284                  part = part->mp_next, partnum++) {
1285             p = part->mp_part;
1286
1287             sprintf (pp, "%d", partnum);
1288             p->c_partno = add (partnam, NULL);
1289
1290             /* initialize the content of the subparts */
1291             if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1292                 fclose (ct->c_fp);
1293                 ct->c_fp = NULL;
1294                 return NOTOK;
1295             }
1296         }
1297     }
1298
1299     fclose (ct->c_fp);
1300     ct->c_fp = NULL;
1301     return OK;
1302 }
1303
1304
1305 /*
1306  * reverse the order of the parts of a multipart
1307  */
1308
1309 static void
1310 reverse_parts (CT ct)
1311 {
1312     int i;
1313     struct multipart *m;
1314     struct part **base, **bmp, **next, *part;
1315
1316     m = (struct multipart *) ct->c_ctparams;
1317
1318     /* if only one part, just return */
1319     if (!m->mp_parts || !m->mp_parts->mp_next)
1320         return;
1321
1322     /* count number of parts */
1323     i = 0;
1324     for (part = m->mp_parts; part; part = part->mp_next)
1325         i++;
1326
1327     /* allocate array of pointers to the parts */
1328     if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1329         adios (NULL, "out of memory");
1330     bmp = base;
1331
1332     /* point at all the parts */
1333     for (part = m->mp_parts; part; part = part->mp_next)
1334         *bmp++ = part;
1335     *bmp = NULL;
1336
1337     /* reverse the order of the parts */
1338     next = &m->mp_parts;
1339     for (bmp--; bmp >= base; bmp--) {
1340         part = *bmp;
1341         *next = part;
1342         next = &part->mp_next;
1343     }
1344     *next = NULL;
1345
1346     /* free array of pointers */
1347     free ((char *) base);
1348 }
1349
1350
1351 /*
1352  * MESSAGE
1353  */
1354
1355 static int
1356 InitMessage (CT ct)
1357 {
1358     struct k2v *kv;
1359     CI ci = &ct->c_ctinfo;
1360
1361     if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1362         admonish (NULL,
1363                   "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1364                   ci->ci_type, ci->ci_subtype, ct->c_file);
1365         return NOTOK;
1366     }
1367
1368     /* check for missing subtype */
1369     if (!*ci->ci_subtype)
1370         ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1371
1372     /* match subtype */
1373     for (kv = SubMessage; kv->kv_key; kv++)
1374         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1375             break;
1376     ct->c_subtype = kv->kv_value;
1377
1378     switch (ct->c_subtype) {
1379         case MESSAGE_RFC822:
1380             break;
1381
1382         case MESSAGE_PARTIAL:
1383             {
1384                 char **ap, **ep;
1385                 struct partial *p;
1386
1387                 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1388                     adios (NULL, "out of memory");
1389                 ct->c_ctparams = (void *) p;
1390
1391                 /* scan for parameters "id", "number", and "total" */
1392                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1393                     if (!mh_strcasecmp (*ap, "id")) {
1394                         p->pm_partid = add (*ep, NULL);
1395                         continue;
1396                     }
1397                     if (!mh_strcasecmp (*ap, "number")) {
1398                         if (sscanf (*ep, "%d", &p->pm_partno) != 1
1399                                 || p->pm_partno < 1) {
1400 invalid_param:
1401                             advise (NULL,
1402                                     "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1403                                     *ap, ci->ci_type, ci->ci_subtype,
1404                                     ct->c_file, TYPE_FIELD);
1405                             return NOTOK;
1406                         }
1407                         continue;
1408                     }
1409                     if (!mh_strcasecmp (*ap, "total")) {
1410                         if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1411                                 || p->pm_maxno < 1)
1412                             goto invalid_param;
1413                         continue;
1414                     }
1415                 }
1416
1417                 if (!p->pm_partid
1418                         || !p->pm_partno
1419                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1420                     advise (NULL,
1421                             "invalid parameters for \"%s/%s\" type in message %s's %s field",
1422                             ci->ci_type, ci->ci_subtype,
1423                             ct->c_file, TYPE_FIELD);
1424                     return NOTOK;
1425                 }
1426             }
1427             break;
1428
1429         case MESSAGE_EXTERNAL:
1430             {
1431                 int exresult;
1432                 struct exbody *e;
1433                 CT p;
1434                 FILE *fp;
1435
1436                 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1437                     adios (NULL, "out of memory");
1438                 ct->c_ctparams = (void *) e;
1439
1440                 if (!ct->c_fp
1441                         && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1442                     advise (ct->c_file, "unable to open for reading");
1443                     return NOTOK;
1444                 }
1445
1446                 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1447
1448                 if (!(p = get_content (fp, ct->c_file, 0))) {
1449                     ct->c_fp = NULL;
1450                     return NOTOK;
1451                 }
1452
1453                 e->eb_parent = ct;
1454                 e->eb_content = p;
1455                 p->c_ctexbody = e;
1456                 if ((exresult = params_external (ct, 0)) != NOTOK
1457                         && p->c_ceopenfnx == openMail) {
1458                     int cc, size;
1459                     char *bp;
1460                     
1461                     if ((size = ct->c_end - p->c_begin) <= 0) {
1462                         if (!e->eb_subject)
1463                             content_error (NULL, ct,
1464                                            "empty body for access-type=mail-server");
1465                         goto no_body;
1466                     }
1467                     
1468                     e->eb_body = bp = mh_xmalloc ((unsigned) size);
1469                     fseek (p->c_fp, p->c_begin, SEEK_SET);
1470                     while (size > 0)
1471                         switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1472                             case NOTOK:
1473                                 adios ("failed", "fread");
1474
1475                             case OK:
1476                                 adios (NULL, "unexpected EOF from fread");
1477
1478                             default:
1479                                 bp += cc, size -= cc;
1480                                 break;
1481                         }
1482                     *bp = 0;
1483                 }
1484 no_body:
1485                 p->c_fp = NULL;
1486                 p->c_end = p->c_begin;
1487
1488                 fclose (ct->c_fp);
1489                 ct->c_fp = NULL;
1490
1491                 if (exresult == NOTOK)
1492                     return NOTOK;
1493                 if (e->eb_flags == NOTOK)
1494                     return OK;
1495
1496                 switch (p->c_type) {
1497                     case CT_MULTIPART:
1498                         break;
1499
1500                     case CT_MESSAGE:
1501                         if (p->c_subtype != MESSAGE_RFC822)
1502                             break;
1503                         /* else fall... */
1504                     default:
1505                         e->eb_partno = ct->c_partno;
1506                         if (p->c_ctinitfnx)
1507                             (*p->c_ctinitfnx) (p);
1508                         break;
1509                 }
1510             }
1511             break;
1512
1513         default:
1514             break;
1515     }
1516
1517     return OK;
1518 }
1519
1520
1521 int
1522 params_external (CT ct, int composing)
1523 {
1524     char **ap, **ep;
1525     struct exbody *e = (struct exbody *) ct->c_ctparams;
1526     CI ci = &ct->c_ctinfo;
1527
1528     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1529         if (!mh_strcasecmp (*ap, "access-type")) {
1530             struct str2init *s2i;
1531             CT p = e->eb_content;
1532
1533             for (s2i = str2methods; s2i->si_key; s2i++)
1534                 if (!mh_strcasecmp (*ep, s2i->si_key))
1535                     break;
1536             if (!s2i->si_key) {
1537                 e->eb_access = *ep;
1538                 e->eb_flags = NOTOK;
1539                 p->c_encoding = CE_EXTERNAL;
1540                 continue;
1541             }
1542             e->eb_access = s2i->si_key;
1543             e->eb_flags = s2i->si_val;
1544             p->c_encoding = CE_EXTERNAL;
1545
1546             /* Call the Init function for this external type */
1547             if ((*s2i->si_init)(p) == NOTOK)
1548                 return NOTOK;
1549             continue;
1550         }
1551         if (!mh_strcasecmp (*ap, "name")) {
1552             e->eb_name = *ep;
1553             continue;
1554         }
1555         if (!mh_strcasecmp (*ap, "permission")) {
1556             e->eb_permission = *ep;
1557             continue;
1558         }
1559         if (!mh_strcasecmp (*ap, "site")) {
1560             e->eb_site = *ep;
1561             continue;
1562         }
1563         if (!mh_strcasecmp (*ap, "directory")) {
1564             e->eb_dir = *ep;
1565             continue;
1566         }
1567         if (!mh_strcasecmp (*ap, "mode")) {
1568             e->eb_mode = *ep;
1569             continue;
1570         }
1571         if (!mh_strcasecmp (*ap, "size")) {
1572             sscanf (*ep, "%lu", &e->eb_size);
1573             continue;
1574         }
1575         if (!mh_strcasecmp (*ap, "server")) {
1576             e->eb_server = *ep;
1577             continue;
1578         }
1579         if (!mh_strcasecmp (*ap, "subject")) {
1580             e->eb_subject = *ep;
1581             continue;
1582         }
1583         if (composing && !mh_strcasecmp (*ap, "body")) {
1584             e->eb_body = getcpy (*ep);
1585             continue;
1586         }
1587     }
1588
1589     if (!e->eb_access) {
1590         advise (NULL,
1591                 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1592                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1593         return NOTOK;
1594     }
1595
1596     return OK;
1597 }
1598
1599
1600 /*
1601  * APPLICATION
1602  */
1603
1604 static int
1605 InitApplication (CT ct)
1606 {
1607     struct k2v *kv;
1608     CI ci = &ct->c_ctinfo;
1609
1610     /* match subtype */
1611     for (kv = SubApplication; kv->kv_key; kv++)
1612         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1613             break;
1614     ct->c_subtype = kv->kv_value;
1615
1616     return OK;
1617 }
1618
1619
1620 /*
1621  * TRANSFER ENCODINGS
1622  */
1623
1624 static int
1625 init_encoding (CT ct, OpenCEFunc openfnx)
1626 {
1627     CE ce;
1628
1629     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1630         adios (NULL, "out of memory");
1631
1632     ct->c_cefile     = ce;
1633     ct->c_ceopenfnx  = openfnx;
1634     ct->c_ceclosefnx = close_encoding;
1635     ct->c_cesizefnx  = size_encoding;
1636
1637     return OK;
1638 }
1639
1640
1641 void
1642 close_encoding (CT ct)
1643 {
1644     CE ce;
1645
1646     if (!(ce = ct->c_cefile))
1647         return;
1648
1649     if (ce->ce_fp) {
1650         fclose (ce->ce_fp);
1651         ce->ce_fp = NULL;
1652     }
1653 }
1654
1655
1656 static unsigned long
1657 size_encoding (CT ct)
1658 {
1659     int fd;
1660     unsigned long size;
1661     char *file;
1662     CE ce;
1663     struct stat st;
1664
1665     if (!(ce = ct->c_cefile))
1666         return (ct->c_end - ct->c_begin);
1667
1668     if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1669         return (long) st.st_size;
1670
1671     if (ce->ce_file) {
1672         if (stat (ce->ce_file, &st) != NOTOK)
1673             return (long) st.st_size;
1674         else
1675             return 0L;
1676     }
1677
1678     if (ct->c_encoding == CE_EXTERNAL)
1679         return (ct->c_end - ct->c_begin);       
1680
1681     file = NULL;
1682     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1683         return (ct->c_end - ct->c_begin);
1684
1685     if (fstat (fd, &st) != NOTOK)
1686         size = (long) st.st_size;
1687     else
1688         size = 0L;
1689
1690     (*ct->c_ceclosefnx) (ct);
1691     return size;
1692 }
1693
1694
1695 /*
1696  * BASE64
1697  */
1698
1699 static unsigned char b642nib[0x80] = {
1700     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1701     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1702     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1703     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1704     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1705     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1706     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1707     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1708     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
1709     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1710     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1711     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1712     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
1713     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1714     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1715     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1716 };
1717
1718
1719 static int
1720 InitBase64 (CT ct)
1721 {
1722     return init_encoding (ct, openBase64);
1723 }
1724
1725
1726 static int
1727 openBase64 (CT ct, char **file)
1728 {
1729     int bitno, cc, digested;
1730     int fd, len, skip;
1731     unsigned long bits;
1732     unsigned char value, *b, *b1, *b2, *b3;
1733     unsigned char *cp, *ep;
1734     char buffer[BUFSIZ];
1735     /* sbeck -- handle suffixes */
1736     CI ci;
1737     CE ce;
1738     MD5_CTX mdContext;
1739
1740     b  = (unsigned char *) &bits;
1741     b1 = &b[endian > 0 ? 1 : 2];
1742     b2 = &b[endian > 0 ? 2 : 1];
1743     b3 = &b[endian > 0 ? 3 : 0];
1744
1745     ce = ct->c_cefile;
1746     if (ce->ce_fp) {
1747         fseek (ce->ce_fp, 0L, SEEK_SET);
1748         goto ready_to_go;
1749     }
1750
1751     if (ce->ce_file) {
1752         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1753             content_error (ce->ce_file, ct, "unable to fopen for reading");
1754             return NOTOK;
1755         }
1756         goto ready_to_go;
1757     }
1758
1759     if (*file == NULL) {
1760         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1761         ce->ce_unlink = 1;
1762     } else {
1763         ce->ce_file = add (*file, NULL);
1764         ce->ce_unlink = 0;
1765     }
1766
1767     /* sbeck@cise.ufl.edu -- handle suffixes */
1768     ci = &ct->c_ctinfo;
1769     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1770               invo_name, ci->ci_type, ci->ci_subtype);
1771     cp = context_find (buffer);
1772     if (cp == NULL || *cp == '\0') {
1773         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1774                   ci->ci_type);
1775         cp = context_find (buffer);
1776     }
1777     if (cp != NULL && *cp != '\0') {
1778         if (ce->ce_unlink) {
1779             // Temporary file already exists, so we rename to
1780             // version with extension.
1781             char *file_org = strdup(ce->ce_file);
1782             ce->ce_file = add (cp, ce->ce_file);
1783             if (rename(file_org, ce->ce_file)) {
1784                 adios (ce->ce_file, "unable to rename %s to ", file_org);
1785             }
1786             free(file_org);
1787
1788         } else {
1789             ce->ce_file = add (cp, ce->ce_file);
1790         }
1791     }
1792
1793     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1794         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1795         return NOTOK;
1796     }
1797
1798     if ((len = ct->c_end - ct->c_begin) < 0)
1799         adios (NULL, "internal error(1)");
1800
1801     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1802         content_error (ct->c_file, ct, "unable to open for reading");
1803         return NOTOK;
1804     }
1805     
1806     if ((digested = ct->c_digested))
1807         MD5Init (&mdContext);
1808
1809     bitno = 18;
1810     bits = 0L;
1811     skip = 0;
1812
1813     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1814     while (len > 0) {
1815         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1816         case NOTOK:
1817             content_error (ct->c_file, ct, "error reading from");
1818             goto clean_up;
1819
1820         case OK:
1821             content_error (NULL, ct, "premature eof");
1822             goto clean_up;
1823
1824         default:
1825             if (cc > len)
1826                 cc = len;
1827             len -= cc;
1828
1829             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1830                 switch (*cp) {
1831                 default:
1832                     if (isspace (*cp))
1833                         break;
1834                     if (skip || (*cp & 0x80)
1835                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1836                         if (debugsw) {
1837                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1838                                 *cp,
1839                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1840                                 skip);
1841                         }
1842                         content_error (NULL, ct,
1843                                        "invalid BASE64 encoding -- continuing");
1844                         continue;
1845                     }
1846
1847                     bits |= value << bitno;
1848 test_end:
1849                     if ((bitno -= 6) < 0) {
1850                         putc ((char) *b1, ce->ce_fp);
1851                         if (digested)
1852                             MD5Update (&mdContext, b1, 1);
1853                         if (skip < 2) {
1854                             putc ((char) *b2, ce->ce_fp);
1855                             if (digested)
1856                                 MD5Update (&mdContext, b2, 1);
1857                             if (skip < 1) {
1858                                 putc ((char) *b3, ce->ce_fp);
1859                                 if (digested)
1860                                     MD5Update (&mdContext, b3, 1);
1861                             }
1862                         }
1863
1864                         if (ferror (ce->ce_fp)) {
1865                             content_error (ce->ce_file, ct,
1866                                            "error writing to");
1867                             goto clean_up;
1868                         }
1869                         bitno = 18, bits = 0L, skip = 0;
1870                     }
1871                     break;
1872
1873                 case '=':
1874                     if (++skip > 3)
1875                         goto self_delimiting;
1876                     goto test_end;
1877                 }
1878             }
1879         }
1880     }
1881
1882     if (bitno != 18) {
1883         if (debugsw)
1884             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1885
1886         content_error (NULL, ct, "invalid BASE64 encoding");
1887         goto clean_up;
1888     }
1889
1890 self_delimiting:
1891     fseek (ct->c_fp, 0L, SEEK_SET);
1892
1893     if (fflush (ce->ce_fp)) {
1894         content_error (ce->ce_file, ct, "error writing to");
1895         goto clean_up;
1896     }
1897
1898     if (digested) {
1899         unsigned char digest[16];
1900
1901         MD5Final (digest, &mdContext);
1902         if (memcmp((char *) digest, (char *) ct->c_digest,
1903                    sizeof(digest) / sizeof(digest[0])))
1904             content_error (NULL, ct,
1905                            "content integrity suspect (digest mismatch) -- continuing");
1906         else
1907             if (debugsw)
1908                 fprintf (stderr, "content integrity confirmed\n");
1909     }
1910
1911     fseek (ce->ce_fp, 0L, SEEK_SET);
1912
1913 ready_to_go:
1914     *file = ce->ce_file;
1915     return fileno (ce->ce_fp);
1916
1917 clean_up:
1918     free_encoding (ct, 0);
1919     return NOTOK;
1920 }
1921
1922
1923 /*
1924  * QUOTED PRINTABLE
1925  */
1926
1927 static char hex2nib[0x80] = {
1928     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1929     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1930     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1931     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1932     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1933     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1934     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1935     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1936     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
1937     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1938     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1939     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1940     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
1941     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1942     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1943     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1944 };
1945
1946
1947 static int 
1948 InitQuoted (CT ct)
1949 {
1950     return init_encoding (ct, openQuoted);
1951 }
1952
1953
1954 static int
1955 openQuoted (CT ct, char **file)
1956 {
1957     int cc, digested, len, quoted;
1958     unsigned char *cp, *ep;
1959     char buffer[BUFSIZ];
1960     unsigned char mask;
1961     CE ce;
1962     /* sbeck -- handle suffixes */
1963     CI ci;
1964     MD5_CTX mdContext;
1965
1966     ce = ct->c_cefile;
1967     if (ce->ce_fp) {
1968         fseek (ce->ce_fp, 0L, SEEK_SET);
1969         goto ready_to_go;
1970     }
1971
1972     if (ce->ce_file) {
1973         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1974             content_error (ce->ce_file, ct, "unable to fopen for reading");
1975             return NOTOK;
1976         }
1977         goto ready_to_go;
1978     }
1979
1980     if (*file == NULL) {
1981         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1982         ce->ce_unlink = 1;
1983     } else {
1984         ce->ce_file = add (*file, NULL);
1985         ce->ce_unlink = 0;
1986     }
1987
1988     /* sbeck@cise.ufl.edu -- handle suffixes */
1989     ci = &ct->c_ctinfo;
1990     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1991               invo_name, ci->ci_type, ci->ci_subtype);
1992     cp = context_find (buffer);
1993     if (cp == NULL || *cp == '\0') {
1994         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1995                   ci->ci_type);
1996         cp = context_find (buffer);
1997     }
1998     if (cp != NULL && *cp != '\0') {
1999         if (ce->ce_unlink) {
2000             // Temporary file already exists, so we rename to
2001             // version with extension.
2002             char *file_org = strdup(ce->ce_file);
2003             ce->ce_file = add (cp, ce->ce_file);
2004             if (rename(file_org, ce->ce_file)) {
2005                 adios (ce->ce_file, "unable to rename %s to ", file_org);
2006             }
2007             free(file_org);
2008
2009         } else {
2010             ce->ce_file = add (cp, ce->ce_file);
2011         }
2012     }
2013
2014     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2015         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2016         return NOTOK;
2017     }
2018
2019     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2020         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2021         return NOTOK;
2022     }
2023
2024     if ((len = ct->c_end - ct->c_begin) < 0)
2025         adios (NULL, "internal error(2)");
2026
2027     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2028         content_error (ct->c_file, ct, "unable to open for reading");
2029         return NOTOK;
2030     }
2031
2032     if ((digested = ct->c_digested))
2033         MD5Init (&mdContext);
2034
2035     quoted = 0;
2036 #ifdef lint
2037     mask = 0;
2038 #endif
2039
2040     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2041     while (len > 0) {
2042         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2043             content_error (NULL, ct, "premature eof");
2044             goto clean_up;
2045         }
2046
2047         if ((cc = strlen (buffer)) > len)
2048             cc = len;
2049         len -= cc;
2050
2051         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2052             if (!isspace (*ep))
2053                 break;
2054         *++ep = '\n', ep++;
2055
2056         for (; cp < ep; cp++) {
2057             if (quoted > 0) {
2058                 /* in an escape sequence */
2059                 if (quoted == 1) {
2060                     /* at byte 1 of an escape sequence */
2061                     mask = hex2nib[*cp & 0x7f];
2062                     /* next is byte 2 */
2063                     quoted = 2;
2064                 } else {
2065                     /* at byte 2 of an escape sequence */
2066                     mask <<= 4;
2067                     mask |= hex2nib[*cp & 0x7f];
2068                     putc (mask, ce->ce_fp);
2069                     if (digested)
2070                         MD5Update (&mdContext, &mask, 1);
2071                     if (ferror (ce->ce_fp)) {
2072                         content_error (ce->ce_file, ct, "error writing to");
2073                         goto clean_up;
2074                     }
2075                     /* finished escape sequence; next may be literal or a new
2076                      * escape sequence */
2077                     quoted = 0;
2078                 }
2079                 /* on to next byte */
2080                 continue;
2081             }
2082
2083             /* not in an escape sequence */
2084             if (*cp == '=') {
2085                 /* starting an escape sequence, or invalid '='? */
2086                 if (cp + 1 < ep && cp[1] == '\n') {
2087                     /* "=\n" soft line break, eat the \n */
2088                     cp++;
2089                     continue;
2090                 }
2091                 if (cp + 1 >= ep || cp + 2 >= ep) {
2092                     /* We don't have 2 bytes left, so this is an invalid
2093                      * escape sequence; just show the raw bytes (below). */
2094                 } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
2095                     /* Next 2 bytes are hex digits, making this a valid escape
2096                      * sequence; let's decode it (above). */
2097                     quoted = 1;
2098                     continue;
2099                 } else {
2100                     /* One or both of the next 2 is out of range, making this
2101                      * an invalid escape sequence; just show the raw bytes
2102                      * (below). */
2103                 }
2104             }
2105
2106             /* Just show the raw byte. */
2107             putc (*cp, ce->ce_fp);
2108             if (digested) {
2109                 if (*cp == '\n') {
2110                     MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2111                 } else {
2112                     MD5Update (&mdContext, (unsigned char *) cp, 1);
2113                 }
2114             }
2115             if (ferror (ce->ce_fp)) {
2116                 content_error (ce->ce_file, ct, "error writing to");
2117                 goto clean_up;
2118             }
2119         }
2120     }
2121     if (quoted) {
2122         content_error (NULL, ct,
2123                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2124         goto clean_up;
2125     }
2126
2127     fseek (ct->c_fp, 0L, SEEK_SET);
2128
2129     if (fflush (ce->ce_fp)) {
2130         content_error (ce->ce_file, ct, "error writing to");
2131         goto clean_up;
2132     }
2133
2134     if (digested) {
2135         unsigned char digest[16];
2136
2137         MD5Final (digest, &mdContext);
2138         if (memcmp((char *) digest, (char *) ct->c_digest,
2139                    sizeof(digest) / sizeof(digest[0])))
2140             content_error (NULL, ct,
2141                            "content integrity suspect (digest mismatch) -- continuing");
2142         else
2143             if (debugsw)
2144                 fprintf (stderr, "content integrity confirmed\n");
2145     }
2146
2147     fseek (ce->ce_fp, 0L, SEEK_SET);
2148
2149 ready_to_go:
2150     *file = ce->ce_file;
2151     return fileno (ce->ce_fp);
2152
2153 clean_up:
2154     free_encoding (ct, 0);
2155     return NOTOK;
2156 }
2157
2158
2159 /*
2160  * 7BIT
2161  */
2162
2163 static int
2164 Init7Bit (CT ct)
2165 {
2166     if (init_encoding (ct, open7Bit) == NOTOK)
2167         return NOTOK;
2168
2169     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
2170     return OK;
2171 }
2172
2173
2174 int
2175 open7Bit (CT ct, char **file)
2176 {
2177     int cc, fd, len;
2178     char buffer[BUFSIZ];
2179     /* sbeck -- handle suffixes */
2180     char *cp;
2181     CI ci;
2182     CE ce;
2183
2184     ce = ct->c_cefile;
2185     if (ce->ce_fp) {
2186         fseek (ce->ce_fp, 0L, SEEK_SET);
2187         goto ready_to_go;
2188     }
2189
2190     if (ce->ce_file) {
2191         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2192             content_error (ce->ce_file, ct, "unable to fopen for reading");
2193             return NOTOK;
2194         }
2195         goto ready_to_go;
2196     }
2197
2198     if (*file == NULL) {
2199         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2200         ce->ce_unlink = 1;
2201     } else {
2202         ce->ce_file = add (*file, NULL);
2203         ce->ce_unlink = 0;
2204     }
2205
2206     /* sbeck@cise.ufl.edu -- handle suffixes */
2207     ci = &ct->c_ctinfo;
2208     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2209               invo_name, ci->ci_type, ci->ci_subtype);
2210     cp = context_find (buffer);
2211     if (cp == NULL || *cp == '\0') {
2212         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2213                   ci->ci_type);
2214         cp = context_find (buffer);
2215     }
2216     if (cp != NULL && *cp != '\0') {
2217         if (ce->ce_unlink) {
2218             // Temporary file already exists, so we rename to
2219             // version with extension.
2220             char *file_org = strdup(ce->ce_file);
2221             ce->ce_file = add (cp, ce->ce_file);
2222             if (rename(file_org, ce->ce_file)) {
2223                 adios (ce->ce_file, "unable to rename %s to ", file_org);
2224             }
2225             free(file_org);
2226
2227         } else {
2228             ce->ce_file = add (cp, ce->ce_file);
2229         }
2230     }
2231
2232     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2233         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2234         return NOTOK;
2235     }
2236
2237     if (ct->c_type == CT_MULTIPART) {
2238         char **ap, **ep;
2239         CI ci = &ct->c_ctinfo;
2240
2241         len = 0;
2242         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2243         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2244             + 1 + strlen (ci->ci_subtype);
2245         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2246             putc (';', ce->ce_fp);
2247             len++;
2248
2249             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2250
2251             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2252                 fputs ("\n\t", ce->ce_fp);
2253                 len = 8;
2254             } else {
2255                 putc (' ', ce->ce_fp);
2256                 len++;
2257             }
2258             fprintf (ce->ce_fp, "%s", buffer);
2259             len += cc;
2260         }
2261
2262         if (ci->ci_comment) {
2263             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2264                 fputs ("\n\t", ce->ce_fp);
2265                 len = 8;
2266             }
2267             else {
2268                 putc (' ', ce->ce_fp);
2269                 len++;
2270             }
2271             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2272             len += cc;
2273         }
2274         fprintf (ce->ce_fp, "\n");
2275         if (ct->c_id)
2276             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2277         if (ct->c_descr)
2278             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2279         if (ct->c_dispo)
2280             fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2281         fprintf (ce->ce_fp, "\n");
2282     }
2283
2284     if ((len = ct->c_end - ct->c_begin) < 0)
2285         adios (NULL, "internal error(3)");
2286
2287     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2288         content_error (ct->c_file, ct, "unable to open for reading");
2289         return NOTOK;
2290     }
2291
2292     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2293     while (len > 0)
2294         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2295         case NOTOK:
2296             content_error (ct->c_file, ct, "error reading from");
2297             goto clean_up;
2298
2299         case OK:
2300             content_error (NULL, ct, "premature eof");
2301             goto clean_up;
2302
2303         default:
2304             if (cc > len)
2305                 cc = len;
2306             len -= cc;
2307
2308             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2309             if (ferror (ce->ce_fp)) {
2310                 content_error (ce->ce_file, ct, "error writing to");
2311                 goto clean_up;
2312             }
2313         }
2314
2315     fseek (ct->c_fp, 0L, SEEK_SET);
2316
2317     if (fflush (ce->ce_fp)) {
2318         content_error (ce->ce_file, ct, "error writing to");
2319         goto clean_up;
2320     }
2321
2322     fseek (ce->ce_fp, 0L, SEEK_SET);
2323
2324 ready_to_go:
2325     *file = ce->ce_file;
2326     return fileno (ce->ce_fp);
2327
2328 clean_up:
2329     free_encoding (ct, 0);
2330     return NOTOK;
2331 }
2332
2333
2334 /*
2335  * External
2336  */
2337
2338 static int
2339 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2340 {
2341     char cachefile[BUFSIZ];
2342
2343     if (ce->ce_fp) {
2344         fseek (ce->ce_fp, 0L, SEEK_SET);
2345         goto ready_already;
2346     }
2347
2348     if (ce->ce_file) {
2349         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2350             content_error (ce->ce_file, ct, "unable to fopen for reading");
2351             return NOTOK;
2352         }
2353         goto ready_already;
2354     }
2355
2356     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2357                 cachefile, sizeof(cachefile)) != NOTOK) {
2358         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2359             ce->ce_file = getcpy (cachefile);
2360             ce->ce_unlink = 0;
2361             goto ready_already;
2362         } else {
2363             admonish (cachefile, "unable to fopen for reading");
2364         }
2365     }
2366
2367     return OK;
2368
2369 ready_already:
2370     *file = ce->ce_file;
2371     *fd = fileno (ce->ce_fp);
2372     return DONE;
2373 }
2374
2375 /*
2376  * File
2377  */
2378
2379 static int
2380 InitFile (CT ct)
2381 {
2382     return init_encoding (ct, openFile);
2383 }
2384
2385
2386 static int
2387 openFile (CT ct, char **file)
2388 {
2389     int fd, cachetype;
2390     char cachefile[BUFSIZ];
2391     struct exbody *e = ct->c_ctexbody;
2392     CE ce = ct->c_cefile;
2393
2394     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2395         case NOTOK:
2396             return NOTOK;
2397
2398         case OK:
2399             break;
2400
2401         case DONE:
2402             return fd;
2403     }
2404
2405     if (!e->eb_name) {
2406         content_error (NULL, ct, "missing name parameter");
2407         return NOTOK;
2408     }
2409
2410     ce->ce_file = getcpy (e->eb_name);
2411     ce->ce_unlink = 0;
2412
2413     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2414         content_error (ce->ce_file, ct, "unable to fopen for reading");
2415         return NOTOK;
2416     }
2417
2418     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2419             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2420                 cachefile, sizeof(cachefile)) != NOTOK) {
2421         int mask;
2422         FILE *fp;
2423
2424         mask = umask (cachetype ? ~m_gmprot () : 0222);
2425         if ((fp = fopen (cachefile, "w"))) {
2426             int cc;
2427             char buffer[BUFSIZ];
2428             FILE *gp = ce->ce_fp;
2429
2430             fseek (gp, 0L, SEEK_SET);
2431
2432             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2433                        > 0)
2434                 fwrite (buffer, sizeof(*buffer), cc, fp);
2435             fflush (fp);
2436
2437             if (ferror (gp)) {
2438                 admonish (ce->ce_file, "error reading");
2439                 unlink (cachefile);
2440             }
2441             else
2442                 if (ferror (fp)) {
2443                     admonish (cachefile, "error writing");
2444                     unlink (cachefile);
2445                 }
2446             fclose (fp);
2447         }
2448         umask (mask);
2449     }
2450
2451     fseek (ce->ce_fp, 0L, SEEK_SET);
2452     *file = ce->ce_file;
2453     return fileno (ce->ce_fp);
2454 }
2455
2456 /*
2457  * FTP
2458  */
2459
2460 static int
2461 InitFTP (CT ct)
2462 {
2463     return init_encoding (ct, openFTP);
2464 }
2465
2466
2467 static int
2468 openFTP (CT ct, char **file)
2469 {
2470     int cachetype, caching, fd;
2471     int len, buflen;
2472     char *bp, *ftp, *user, *pass;
2473     char buffer[BUFSIZ], cachefile[BUFSIZ];
2474     struct exbody *e;
2475     CE ce;
2476     static char *username = NULL;
2477     static char *password = NULL;
2478
2479     e  = ct->c_ctexbody;
2480     ce = ct->c_cefile;
2481
2482     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2483         ftp = NULL;
2484
2485     if (!ftp)
2486         return NOTOK;
2487
2488     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2489         case NOTOK:
2490             return NOTOK;
2491
2492         case OK:
2493             break;
2494
2495         case DONE:
2496             return fd;
2497     }
2498
2499     if (!e->eb_name || !e->eb_site) {
2500         content_error (NULL, ct, "missing %s parameter",
2501                        e->eb_name ? "site": "name");
2502         return NOTOK;
2503     }
2504
2505     if (xpid) {
2506         if (xpid < 0)
2507             xpid = -xpid;
2508         pidcheck (pidwait (xpid, NOTOK));
2509         xpid = 0;
2510     }
2511
2512     /* Get the buffer ready to go */
2513     bp = buffer;
2514     buflen = sizeof(buffer);
2515
2516     /*
2517      * Construct the query message for user
2518      */
2519     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2520     len = strlen (bp);
2521     bp += len;
2522     buflen -= len;
2523
2524     if (e->eb_partno) {
2525         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2526         len = strlen (bp);
2527         bp += len;
2528         buflen -= len;
2529     }
2530
2531     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2532                     e->eb_flags ? "anonymous " : "", e->eb_site);
2533     len = strlen (bp);
2534     bp += len;
2535     buflen -= len;
2536
2537     if (e->eb_size > 0) {
2538         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2539         len = strlen (bp);
2540         bp += len;
2541         buflen -= len;
2542     }
2543     snprintf (bp, buflen, "? ");
2544
2545     /*
2546      * Now, check the answer
2547      */
2548     if (!getanswer (buffer))
2549         return NOTOK;
2550
2551     if (e->eb_flags) {
2552         user = "anonymous";
2553         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
2554                   LocalName (1));
2555         pass = buffer;
2556     } else {
2557         ruserpass (e->eb_site, &username, &password);
2558         user = username;
2559         pass = password;
2560     }
2561
2562     ce->ce_unlink = (*file == NULL);
2563     caching = 0;
2564     cachefile[0] = '\0';
2565     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2566             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2567                 cachefile, sizeof(cachefile)) != NOTOK) {
2568         if (*file == NULL) {
2569             ce->ce_unlink = 0;
2570             caching = 1;
2571         }
2572     }
2573
2574     if (*file)
2575         ce->ce_file = add (*file, NULL);
2576     else if (caching)
2577         ce->ce_file = add (cachefile, NULL);
2578     else
2579         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2580
2581     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2582         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2583         return NOTOK;
2584     }
2585
2586     {
2587         int child_id, i, vecp;
2588         char *vec[9];
2589
2590         vecp = 0;
2591         vec[vecp++] = r1bindex (ftp, '/');
2592         vec[vecp++] = e->eb_site;
2593         vec[vecp++] = user;
2594         vec[vecp++] = pass;
2595         vec[vecp++] = e->eb_dir;
2596         vec[vecp++] = e->eb_name;
2597         vec[vecp++] = ce->ce_file,
2598         vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2599                         ? "ascii" : "binary";
2600         vec[vecp] = NULL;
2601
2602         fflush (stdout);
2603
2604         for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
2605             sleep (5);
2606         switch (child_id) {
2607             case NOTOK:
2608                 adios ("fork", "unable to");
2609                 /* NOTREACHED */
2610
2611             case OK:
2612                 close (fileno (ce->ce_fp));
2613                 execvp (ftp, vec);
2614                 fprintf (stderr, "unable to exec ");
2615                 perror (ftp);
2616                 _exit (-1);
2617                 /* NOTREACHED */
2618
2619             default:
2620                 if (pidXwait (child_id, NULL)) {
2621                     username = password = NULL;
2622                     ce->ce_unlink = 1;
2623                     return NOTOK;
2624                 }
2625                 break;
2626         }
2627     }
2628
2629     if (cachefile[0]) {
2630         if (caching)
2631             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2632         else {
2633             int mask;
2634             FILE *fp;
2635
2636             mask = umask (cachetype ? ~m_gmprot () : 0222);
2637             if ((fp = fopen (cachefile, "w"))) {
2638                 int cc;
2639                 FILE *gp = ce->ce_fp;
2640
2641                 fseek (gp, 0L, SEEK_SET);
2642
2643                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2644                            > 0)
2645                     fwrite (buffer, sizeof(*buffer), cc, fp);
2646                 fflush (fp);
2647
2648                 if (ferror (gp)) {
2649                     admonish (ce->ce_file, "error reading");
2650                     unlink (cachefile);
2651                 }
2652                 else
2653                     if (ferror (fp)) {
2654                         admonish (cachefile, "error writing");
2655                         unlink (cachefile);
2656                     }
2657                 fclose (fp);
2658             }
2659             umask (mask);
2660         }
2661     }
2662
2663     fseek (ce->ce_fp, 0L, SEEK_SET);
2664     *file = ce->ce_file;
2665     return fileno (ce->ce_fp);
2666 }
2667
2668
2669 /*
2670  * Mail
2671  */
2672
2673 static int
2674 InitMail (CT ct)
2675 {
2676     return init_encoding (ct, openMail);
2677 }
2678
2679
2680 static int
2681 openMail (CT ct, char **file)
2682 {
2683     int child_id, fd, i, vecp;
2684     int len, buflen;
2685     char *bp, buffer[BUFSIZ], *vec[7];
2686     struct exbody *e = ct->c_ctexbody;
2687     CE ce = ct->c_cefile;
2688
2689     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2690         case NOTOK:
2691             return NOTOK;
2692
2693         case OK:
2694             break;
2695
2696         case DONE:
2697             return fd;
2698     }
2699
2700     if (!e->eb_server) {
2701         content_error (NULL, ct, "missing server parameter");
2702         return NOTOK;
2703     }
2704
2705     if (xpid) {
2706         if (xpid < 0)
2707             xpid = -xpid;
2708         pidcheck (pidwait (xpid, NOTOK));
2709         xpid = 0;
2710     }
2711
2712     /* Get buffer ready to go */
2713     bp = buffer;
2714     buflen = sizeof(buffer);
2715
2716     /* Now, construct query message */
2717     snprintf (bp, buflen, "Retrieve content");
2718     len = strlen (bp);
2719     bp += len;
2720     buflen -= len;
2721
2722     if (e->eb_partno) {
2723         snprintf (bp, buflen, " %s", e->eb_partno);
2724         len = strlen (bp);
2725         bp += len;
2726         buflen -= len;
2727     }
2728
2729     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2730                     e->eb_server,
2731                     e->eb_subject ? e->eb_subject : e->eb_body);
2732
2733     /* Now, check answer */
2734     if (!getanswer (buffer))
2735         return NOTOK;
2736
2737     vecp = 0;
2738     vec[vecp++] = r1bindex (mailproc, '/');
2739     vec[vecp++] = e->eb_server;
2740     vec[vecp++] = "-subject";
2741     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2742     vec[vecp++] = "-body";
2743     vec[vecp++] = e->eb_body;
2744     vec[vecp] = NULL;
2745
2746     for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
2747         sleep (5);
2748     switch (child_id) {
2749         case NOTOK:
2750             advise ("fork", "unable to");
2751             return NOTOK;
2752
2753         case OK:
2754             execvp (mailproc, vec);
2755             fprintf (stderr, "unable to exec ");
2756             perror (mailproc);
2757             _exit (-1);
2758             /* NOTREACHED */
2759
2760         default:
2761             if (pidXwait (child_id, NULL) == OK)
2762                 advise (NULL, "request sent");
2763             break;
2764     }
2765
2766     if (*file == NULL) {
2767         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2768         ce->ce_unlink = 1;
2769     } else {
2770         ce->ce_file = add (*file, NULL);
2771         ce->ce_unlink = 0;
2772     }
2773
2774     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2775         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2776         return NOTOK;
2777     }
2778
2779     /* showproc is for mhshow and mhstore, though mhlist -debug
2780      * prints it, too. */
2781     if (ct->c_showproc)
2782         free (ct->c_showproc);
2783     ct->c_showproc = add ("true", NULL);
2784
2785     fseek (ce->ce_fp, 0L, SEEK_SET);
2786     *file = ce->ce_file;
2787     return fileno (ce->ce_fp);
2788 }
2789
2790
2791 static int
2792 readDigest (CT ct, char *cp)
2793 {
2794     int bitno, skip;
2795     unsigned long bits;
2796     char *bp = cp;
2797     unsigned char *dp, value, *ep;
2798     unsigned char *b, *b1, *b2, *b3;
2799
2800     b  = (unsigned char *) &bits,
2801     b1 = &b[endian > 0 ? 1 : 2],
2802     b2 = &b[endian > 0 ? 2 : 1],
2803     b3 = &b[endian > 0 ? 3 : 0];
2804     bitno = 18;
2805     bits = 0L;
2806     skip = 0;
2807
2808     for (ep = (dp = ct->c_digest)
2809                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2810         switch (*cp) {
2811             default:
2812                 if (skip
2813                         || (*cp & 0x80)
2814                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2815                     if (debugsw)
2816                         fprintf (stderr, "invalid BASE64 encoding\n");
2817                     return NOTOK;
2818                 }
2819
2820                 bits |= value << bitno;
2821 test_end:
2822                 if ((bitno -= 6) < 0) {
2823                     if (dp + (3 - skip) > ep)
2824                         goto invalid_digest;
2825                     *dp++ = *b1;
2826                     if (skip < 2) {
2827                         *dp++ = *b2;
2828                         if (skip < 1)
2829                             *dp++ = *b3;
2830                     }
2831                     bitno = 18;
2832                     bits = 0L;
2833                     skip = 0;
2834                 }
2835                 break;
2836
2837             case '=':
2838                 if (++skip > 3)
2839                     goto self_delimiting;
2840                 goto test_end;
2841         }
2842     if (bitno != 18) {
2843         if (debugsw)
2844             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2845
2846         return NOTOK;
2847     }
2848 self_delimiting:
2849     if (dp != ep) {
2850 invalid_digest:
2851         if (debugsw) {
2852             while (*cp)
2853                 cp++;
2854             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2855                      (int)(cp - bp));
2856         }
2857
2858         return NOTOK;
2859     }
2860
2861     if (debugsw) {
2862         fprintf (stderr, "MD5 digest=");
2863         for (dp = ct->c_digest; dp < ep; dp++)
2864             fprintf (stderr, "%02x", *dp & 0xff);
2865         fprintf (stderr, "\n");
2866     }
2867
2868     return OK;
2869 }