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