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