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