mhsign manpage: mention hostname bug
[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 <signal.h>
12 #include <errno.h>
13 #include <h/tws.h>
14 #include <h/mime.h>
15 #include <h/mhparse.h>
16 #include <h/utils.h>
17 #include <unistd.h>
18 #include <ctype.h>
19 #include <sys/stat.h>
20 #include <sysexits.h>
21
22 extern int debugsw;
23
24 extern int endian;  /* mhmisc.c */
25
26 extern pid_t xpid;  /* mhshowsbr.c  */
27
28 /*
29 ** Directory to place temp files.  This must
30 ** be set before these routines are called.
31 */
32 char *tmp;
33
34 /*
35 ** Structures for TEXT messages
36 */
37 struct k2v SubText[] = {
38         { "plain", TEXT_PLAIN },
39         { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341 */
40         { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896 */
41         { NULL, TEXT_UNKNOWN }  /* this one must be last! */
42 };
43
44 struct k2v Charset[] = {
45         { "us-ascii",   CHARSET_USASCII },
46         { "iso-8859-1", CHARSET_LATIN },
47         { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
48 };
49
50 /*
51 ** Structures for MULTIPART messages
52 */
53 struct k2v SubMultiPart[] = {
54         { "mixed",       MULTI_MIXED },
55         { "alternative", MULTI_ALTERNATE },
56         { "digest",      MULTI_DIGEST },
57         { "parallel",    MULTI_PARALLEL },
58         { NULL,          MULTI_UNKNOWN }  /* this one must be last! */
59 };
60
61 /*
62 ** Structures for MESSAGE messages
63 */
64 struct k2v SubMessage[] = {
65         { "rfc822",        MESSAGE_RFC822 },
66         { "partial",       MESSAGE_PARTIAL },
67         { "external-body", MESSAGE_EXTERNAL },
68         { NULL,            MESSAGE_UNKNOWN }  /* this one must be last! */
69 };
70
71 /*
72 ** Structure for APPLICATION messages
73 */
74 struct k2v SubApplication[] = {
75         { "octet-stream", APPLICATION_OCTETS },
76         { "postscript",   APPLICATION_POSTSCRIPT },
77         { NULL,           APPLICATION_UNKNOWN }  /* this one must be last! */
78 };
79
80
81 /* mhmisc.c */
82 int part_ok(CT, int);
83 int type_ok(CT, int);
84 int make_intermediates(char *);
85 void content_error(char *, CT, char *, ...);
86
87 /* mhfree.c */
88 void free_content(CT);
89 void free_encoding(CT, int);
90
91 /*
92 ** static prototypes
93 */
94 static CT get_content(FILE *, char *, int);
95 static int get_comment(CT, unsigned char **, int);
96
97 static int InitGeneric(CT);
98 static int InitText(CT);
99 static int InitMultiPart(CT);
100 static void reverse_parts(CT);
101 static int InitMessage(CT);
102 static int InitApplication(CT);
103 static int init_encoding(CT, OpenCEFunc);
104 static unsigned long size_encoding(CT);
105 static int InitBase64(CT);
106 static int openBase64(CT, char **);
107 static int InitQuoted(CT);
108 static int openQuoted(CT, char **);
109 static int Init7Bit(CT);
110
111 struct str2init str2cts[] = {
112         { "application", CT_APPLICATION, InitApplication },
113         { "audio",       CT_AUDIO,       InitGeneric },
114         { "image",       CT_IMAGE,       InitGeneric },
115         { "message",     CT_MESSAGE,     InitMessage },
116         { "multipart",   CT_MULTIPART,   InitMultiPart },
117         { "text",        CT_TEXT,        InitText },
118         { "video",       CT_VIDEO,       InitGeneric },
119         { NULL,          CT_EXTENSION,   NULL },  /* these two must be last! */
120         { NULL,          CT_UNKNOWN,     NULL },
121 };
122
123 struct str2init str2ces[] = {
124         { "base64",           CE_BASE64,    InitBase64 },
125         { "quoted-printable", CE_QUOTED,    InitQuoted },
126         { "8bit",             CE_8BIT,      Init7Bit },
127         { "7bit",             CE_7BIT,      Init7Bit },
128         { "binary",           CE_BINARY,    Init7Bit },
129         { NULL,               CE_EXTENSION, NULL }, /* these two must be last! */
130         { NULL,               CE_UNKNOWN,    NULL },
131 };
132
133
134 int
135 pidcheck(int status)
136 {
137         if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
138                 return status;
139
140         fflush(stdout);
141         fflush(stderr);
142         exit(EX_SOFTWARE);
143         return 1;
144 }
145
146
147 /*
148 ** Main entry point for parsing a MIME message or file.
149 ** It returns the Content structure for the top level
150 ** entity in the file.
151 */
152 CT
153 parse_mime(char *file)
154 {
155         int is_stdin;
156         char buffer[BUFSIZ];
157         FILE *fp;
158         CT ct;
159
160         /*
161         ** Check if file is actually standard input
162         */
163         if ((is_stdin = (strcmp(file, "-")==0))) {
164                 char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
165                 if (tfile == NULL) {
166                         advise("mhparse", "unable to create temporary file");
167                         return NULL;
168                 }
169                 file = mh_xstrdup(tfile);
170                 chmod(file, 0600);
171
172                 while (fgets(buffer, sizeof(buffer), stdin))
173                         fputs(buffer, fp);
174                 fflush(fp);
175
176                 if (ferror(stdin)) {
177                         unlink(file);
178                         advise("stdin", "error reading");
179                         return NULL;
180                 }
181                 if (ferror(fp)) {
182                         unlink(file);
183                         advise(file, "error writing");
184                         return NULL;
185                 }
186                 fseek(fp, 0L, SEEK_SET);
187         } else if ((fp = fopen(file, "r")) == NULL) {
188                 advise(file, "unable to read");
189                 return NULL;
190         }
191
192         if (!(ct = get_content(fp, file, 1))) {
193                 if (is_stdin)
194                         unlink(file);
195                 advise(NULL, "unable to decode %s", file);
196                 return NULL;
197         }
198
199         if (is_stdin)
200                 ct->c_unlink = 1;  /* temp file to remove */
201
202         ct->c_fp = NULL;
203
204         if (ct->c_end == 0L) {
205                 fseek(fp, 0L, SEEK_END);
206                 ct->c_end = ftell(fp);
207         }
208
209         if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
210                 fclose(fp);
211                 free_content(ct);
212                 return NULL;
213         }
214
215         fclose(fp);
216         return ct;
217 }
218
219
220 /*
221 ** Main routine for reading/parsing the headers
222 ** of a message content.
223 **
224 ** toplevel =  1   # we are at the top level of the message
225 ** toplevel =  0   # we are inside message type or multipart type
226 **                 # other than multipart/digest
227 ** toplevel = -1   # we are inside multipart/digest
228 ** NB: on failure we will fclose(in)!
229 */
230
231 static CT
232 get_content(FILE *in, char *file, int toplevel)
233 {
234         enum state state;
235         struct field f = {{0}};
236         int compnum;
237         CT ct;
238         HF hp;
239
240         /* allocate the content structure */
241         ct = mh_xcalloc(1, sizeof(*ct));
242
243         ct->c_fp = in;
244         ct->c_file = mh_xstrdup(file);
245         ct->c_begin = ftell(ct->c_fp) + 1;
246
247         /*
248         ** Parse the header fields for this
249         ** content into a linked list.
250         */
251         for (compnum = 1, state = FLD2;;) {
252                 switch (state = m_getfld2(state, &f, in)) {
253                 case LENERR2:
254                         state = FLD2;
255                         /* FALL */
256                 case FLD2:
257                         compnum++;
258
259                         /* add the header data to the list */
260                         add_header(ct, mh_xstrdup(f.name), mh_xstrdup(f.value));
261
262                         ct->c_begin = ftell(in) + 1;
263                         continue;
264
265                 case BODY2:
266                         ct->c_begin = ftell(in) - strlen(f.value);
267                         break;
268
269                 case FILEEOF2:
270                         ct->c_begin = ftell(in);
271                         break;
272
273                 case FMTERR2:
274                         advise(NULL, "message format error in component #%d", compnum);
275                         state = FLD2;
276                         continue;
277
278                 case IOERR2:
279                         adios(EX_IOERR, "m_getfld2", "io error");
280
281                 default:
282                         adios(EX_SOFTWARE, NULL, "getfld() returned %d", state);
283                 }
284                 break;
285         }
286
287         /*
288         ** Read the content headers.  We will parse the
289         ** MIME related header fields into their various
290         ** structures and set internal flags related to
291         ** content type/subtype, etc.
292         */
293
294         hp = ct->c_first_hf;  /* start at first header field */
295         while (hp) {
296                 /* Get MIME-Version field */
297                 if (!mh_strcasecmp(hp->name, VRSN_FIELD)) {
298                         int ucmp;
299                         char c;
300                         unsigned char *cp, *dp;
301
302                         if (ct->c_vrsn) {
303                                 advise(NULL, "message %s has multiple %s: fields", ct->c_file, VRSN_FIELD);
304                                 goto next_header;
305                         }
306                         ct->c_vrsn = mh_xstrdup(hp->value);
307
308                         /* Now, cleanup this field */
309                         cp = ct->c_vrsn;
310
311                         while (isspace(*cp))
312                                 cp++;
313                         for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
314                                 *dp++ = ' ';
315                         for (dp = cp + strlen(cp) - 1; dp >= cp; dp--)
316                                 if (!isspace(*dp))
317                                         break;
318                         *++dp = '\0';
319                         if (debugsw)
320                                 fprintf(stderr, "%s: %s\n", VRSN_FIELD, cp);
321
322                         if (*cp == '(' && get_comment(ct, &cp, 0) == NOTOK)
323                                 goto out;
324
325                         for (dp = cp; istoken(*dp); dp++)
326                                 continue;
327                         c = *dp;
328                         *dp = '\0';
329                         ucmp = !mh_strcasecmp(cp, VRSN_VALUE);
330                         *dp = c;
331                         if (!ucmp) {
332                                 admonish(NULL, "message %s has unknown value for %s: field (%s)", ct->c_file, VRSN_FIELD, cp);
333                         }
334
335                 } else if (!mh_strcasecmp(hp->name, TYPE_FIELD)) {
336                         /* Get Content-Type field */
337                         struct str2init *s2i;
338                         CI ci = &ct->c_ctinfo;
339
340                         /* Check if we've already seen a Content-Type header */
341                         if (ct->c_ctline) {
342                                 advise(NULL, "message %s has multiple %s: fields", ct->c_file, TYPE_FIELD);
343                                 goto next_header;
344                         }
345
346                         /* Parse the Content-Type field */
347                         if (get_ctinfo(hp->value, ct, 0) == NOTOK)
348                                 goto out;
349
350                         /*
351                         ** Set the Init function and the internal
352                         ** flag for this content type.
353                         */
354                         for (s2i = str2cts; s2i->si_key; s2i++)
355                                 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
356                                         break;
357                         if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
358                                 s2i++;
359                         ct->c_type = s2i->si_val;
360                         ct->c_ctinitfnx = s2i->si_init;
361
362                 } else if (!mh_strcasecmp(hp->name, ENCODING_FIELD)) {
363                         /* Get Content-Transfer-Encoding field */
364                         char c;
365                         unsigned char *cp, *dp;
366                         struct str2init *s2i;
367
368                         /*
369                         ** Check if we've already seen the
370                         ** Content-Transfer-Encoding field
371                         */
372                         if (ct->c_celine) {
373                                 advise(NULL, "message %s has multiple %s: fields", ct->c_file, ENCODING_FIELD);
374                                 goto next_header;
375                         }
376
377                         /* get copy of this field */
378                         ct->c_celine = cp = mh_xstrdup(hp->value);
379
380                         while (isspace(*cp))
381                                 cp++;
382                         for (dp = cp; istoken(*dp); dp++)
383                                 continue;
384                         c = *dp;
385                         *dp = '\0';
386
387                         /*
388                         ** Find the internal flag and Init function
389                         ** for this transfer encoding.
390                         */
391                         for (s2i = str2ces; s2i->si_key; s2i++)
392                                 if (!mh_strcasecmp(cp, s2i->si_key))
393                                         break;
394                         if (!s2i->si_key && !uprf(cp, "X-"))
395                                 s2i++;
396                         *dp = c;
397                         ct->c_encoding = s2i->si_val;
398
399                         /* Call the Init function for this encoding */
400                         if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
401                                 goto out;
402
403                 } else if (!mh_strcasecmp(hp->name, ID_FIELD)) {
404                         /* Get Content-ID field */
405                         ct->c_id = add(hp->value, ct->c_id);
406
407                 } else if (!mh_strcasecmp(hp->name, DESCR_FIELD)) {
408                         /* Get Content-Description field */
409                         ct->c_descr = add(hp->value, ct->c_descr);
410
411                 } else if (!mh_strcasecmp(hp->name, DISPO_FIELD)) {
412                         /* Get Content-Disposition field */
413                         ct->c_dispo = add(hp->value, ct->c_dispo);
414                 }
415
416 next_header:
417                 hp = hp->next;  /* next header field */
418         }
419
420         /*
421         ** Check if we saw a Content-Type field.
422         ** If not, then assign a default value for
423         ** it, and the Init function.
424         */
425         if (!ct->c_ctline) {
426                 /*
427                 ** If we are inside a multipart/digest message,
428                 ** so default type is message/rfc822
429                 */
430                 if (toplevel < 0) {
431                         if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
432                                 goto out;
433                         ct->c_type = CT_MESSAGE;
434                         ct->c_ctinitfnx = InitMessage;
435                 } else {
436                         /*
437                         ** Else default type is text/plain
438                         */
439                         if (get_ctinfo("text/plain", ct, 0) == NOTOK)
440                                 goto out;
441                         ct->c_type = CT_TEXT;
442                         ct->c_ctinitfnx = InitText;
443                 }
444         }
445
446         /* Use default Transfer-Encoding, if necessary */
447         if (!ct->c_celine) {
448                 ct->c_encoding = CE_7BIT;
449                 Init7Bit(ct);
450         }
451
452         return ct;
453
454 out:
455         free_content(ct);
456         return NULL;
457 }
458
459
460 /*
461 ** small routine to add header field to list
462 */
463
464 int
465 add_header(CT ct, char *name, char *value)
466 {
467         HF hp;
468
469         /* allocate header field structure */
470         hp = mh_xcalloc(1, sizeof(*hp));
471
472         /* link data into header structure */
473         hp->name = name;
474         hp->value = value;
475         hp->next = NULL;
476
477         /* link header structure into the list */
478         if (ct->c_first_hf == NULL) {
479                 ct->c_first_hf = hp;  /* this is the first */
480                 ct->c_last_hf = hp;
481         } else {
482                 ct->c_last_hf->next = hp;  /* add it to the end */
483                 ct->c_last_hf = hp;
484         }
485
486         return 0;
487 }
488
489
490 /*
491 ** Make sure that buf contains at least one appearance of name,
492 ** followed by =.  If not, insert both name and value, just after
493 ** first semicolon, if any.  Note that name should not contain a
494 ** trailing =.  And quotes will be added around the value.  Typical
495 ** usage:  make sure that a Content-Disposition header contains
496 ** filename="foo".  If it doesn't and value does, use value from
497 ** that.
498 */
499 static char *
500 incl_name_value(unsigned char *buf, char *name, char *value) {
501         char *newbuf = buf;
502
503         /* Assume that name is non-null. */
504         if (buf && value) {
505                 char *name_plus_equal = concat(name, "=", NULL);
506
507                 if (!strstr(buf, name_plus_equal)) {
508                         char *insertion;
509                         unsigned char *cp;
510                         char *prefix, *suffix;
511
512                         /* Trim trailing space, esp. newline. */
513                         for (cp = &buf[strlen(buf) - 1];
514                                          cp >= buf && isspace(*cp); --cp) {
515                                 *cp = '\0';
516                         }
517
518                         insertion = concat("; ", name, "=", "\"", value, "\"",
519                                         NULL);
520
521                         /*
522                         ** Insert at first semicolon, if any.
523                         ** If none, append to end.
524                         */
525                         prefix = mh_xstrdup(buf);
526                         if ((cp = strchr(prefix, ';'))) {
527                                 suffix = concat(cp, NULL);
528                                 *cp = '\0';
529                                 newbuf = concat(prefix, insertion, suffix,
530                                                 "\n", NULL);
531                                 mh_free0(&suffix);
532                         } else {
533                                 /* Append to end. */
534                                 newbuf = concat(buf, insertion, "\n", NULL);
535                         }
536
537                         mh_free0(&prefix);
538                         mh_free0(&insertion);
539                         mh_free0(&buf);
540                 }
541
542                 mh_free0(&name_plus_equal);
543         }
544
545         return newbuf;
546 }
547
548 /*
549 ** Extract just name_suffix="foo", if any, from value.  If there isn't
550 ** one, return the entire value.  Note that, for example, a name_suffix
551 ** of name will match filename="foo", and return foo.
552 */
553 char *
554 extract_name_value(char *name_suffix, char *value) {
555         char *extracted_name_value;
556         char *name_suffix_plus_quote;
557         char *name_suffix_equals;
558         char *cp;
559
560         if (!value) {
561                 return value;
562         }
563         extracted_name_value = value;
564         name_suffix_plus_quote = concat(name_suffix, "=\"", NULL);
565         name_suffix_equals = strstr(value, name_suffix_plus_quote);
566         mh_free0(&name_suffix_plus_quote);
567         if (name_suffix_equals) {
568                 char *name_suffix_begin;
569
570                 /* Find first \". */
571                 for (cp = name_suffix_equals; *cp != '"'; ++cp)
572                         ;
573                 name_suffix_begin = ++cp;
574                 /* Find second \". */
575                 for (; *cp != '"'; ++cp)
576                         ;
577
578                 extracted_name_value = mh_xcalloc(cp - name_suffix_begin + 1, sizeof(char));
579                 memcpy(extracted_name_value, name_suffix_begin,
580                                 cp - name_suffix_begin);
581                 extracted_name_value[cp - name_suffix_begin] = '\0';
582         }
583
584         return extracted_name_value;
585 }
586
587 /*
588 ** Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
589 ** directives.  Fills in the information of the CTinfo structure.
590 */
591 int
592 get_ctinfo(unsigned char *cp, CT ct, int magic)
593 {
594         int i;
595         unsigned char *dp;
596         char **ap, **ep;
597         char c;
598         CI ci;
599
600         ci = &ct->c_ctinfo;
601         i = strlen(invo_name) + 2;
602
603         /* store copy of Content-Type line */
604         cp = ct->c_ctline = mh_xstrdup(cp);
605
606         while (isspace(*cp))  /* trim leading spaces */
607                 cp++;
608
609         /* change newlines to spaces */
610         for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
611                 *dp++ = ' ';
612
613         /* trim trailing spaces */
614         for (dp = cp + strlen(cp) - 1; dp >= cp; dp--)
615                 if (!isspace(*dp))
616                         break;
617         *++dp = '\0';
618
619         if (debugsw)
620                 fprintf(stderr, "%s: %s\n", TYPE_FIELD, cp);
621
622         if (*cp == '(' && get_comment(ct, &cp, 1) == NOTOK)
623                 return NOTOK;
624
625         for (dp = cp; istoken(*dp); dp++)
626                 continue;
627         c = *dp, *dp = '\0';
628         ci->ci_type = mh_xstrdup(cp);  /* store content type */
629         *dp = c, cp = dp;
630
631         if (!*ci->ci_type) {
632                 advise(NULL, "invalid %s: field in message %s (empty type)",
633                                 TYPE_FIELD, ct->c_file);
634                 return NOTOK;
635         }
636
637         /* down case the content type string */
638         for (dp = ci->ci_type; *dp; dp++)
639                 if (isalpha(*dp) && isupper(*dp))
640                         *dp = tolower(*dp);
641
642         while (isspace(*cp))
643                 cp++;
644
645         if (*cp == '(' && get_comment(ct, &cp, 1) == NOTOK)
646                 return NOTOK;
647
648         if (*cp != '/') {
649                 if (!magic)
650                         ci->ci_subtype = mh_xstrdup("");
651                 goto magic_skip;
652         }
653
654         cp++;
655         while (isspace(*cp))
656                 cp++;
657
658         if (*cp == '(' && get_comment(ct, &cp, 1) == NOTOK)
659                 return NOTOK;
660
661         for (dp = cp; istoken(*dp); dp++)
662                 continue;
663         c = *dp, *dp = '\0';
664         ci->ci_subtype = mh_xstrdup(cp);  /* store the content subtype */
665         *dp = c, cp = dp;
666
667         if (!*ci->ci_subtype) {
668                 advise(NULL, "invalid %s: field in message %s (empty subtype for \"%s\")", TYPE_FIELD, ct->c_file, ci->ci_type);
669                 return NOTOK;
670         }
671
672         /* down case the content subtype string */
673         for (dp = ci->ci_subtype; *dp; dp++)
674                 if (isalpha(*dp) && isupper(*dp))
675                         *dp = tolower(*dp);
676
677 magic_skip:
678         while (isspace(*cp))
679                 cp++;
680
681         if (*cp == '(' && get_comment(ct, &cp, 1) == NOTOK)
682                 return NOTOK;
683
684         /*
685         ** Parse attribute/value pairs given with Content-Type
686         */
687         ep = (ap = ci->ci_attrs) + NPARMS;
688         while (*cp == ';') {
689                 char *vp;
690                 unsigned char *up;
691
692                 if (ap >= ep) {
693                         advise(NULL, "too many parameters in message %s's %s: field (%d max)", ct->c_file, TYPE_FIELD, NPARMS);
694                         return NOTOK;
695                 }
696
697                 cp++;
698                 while (isspace(*cp))
699                         cp++;
700
701                 if (*cp == '(' && get_comment(ct, &cp, 1) == NOTOK)
702                         return NOTOK;
703
704                 if (*cp == 0) {
705                         advise (NULL, "extraneous trailing ';' in message %s's %s: parameter list", ct->c_file, TYPE_FIELD);
706                         return OK;
707                 }
708
709                 /* down case the attribute name */
710                 for (dp = cp; istoken(*dp); dp++)
711                         if (isalpha(*dp) && isupper(*dp))
712                                 *dp = tolower(*dp);
713
714                 for (up = dp; isspace(*dp);)
715                         dp++;
716                 if (dp == cp || *dp != '=') {
717                         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);
718                         return NOTOK;
719                 }
720
721                 vp = (*ap = mh_xstrdup(cp)) + (up - cp);
722                 *vp = '\0';
723                 for (dp++; isspace(*dp);)
724                         dp++;
725
726                 /* now add the attribute value */
727                 ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
728
729                 if (*dp == '"') {
730                         for (cp = ++dp, dp = vp;;) {
731                                 switch (c = *cp++) {
732                                 case '\0':
733 bad_quote:
734                                         advise(NULL, "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)", ct->c_file, TYPE_FIELD, i, i, "", *ap);
735                                         return NOTOK;
736
737                                 case '\\':
738                                         *dp++ = c;
739                                         if ((c = *cp++) == '\0')
740                                                 goto bad_quote;
741                                         /* else fall... */
742
743                                 default:
744                                         *dp++ = c;
745                                         continue;
746
747                                 case '"':
748                                         *dp = '\0';
749                                         break;
750                                 }
751                                 break;
752                         }
753                 } else {
754                         for (cp = dp, dp = vp; istoken(*cp); cp++, dp++)
755                                 continue;
756                         *dp = '\0';
757                 }
758                 if (!*vp) {
759                         advise(NULL, "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)", ct->c_file, TYPE_FIELD, i, i, "", *ap);
760                         *ci->ci_values[ap - ci->ci_attrs] = '\0';
761                         *ci->ci_attrs[ap - ci->ci_attrs] = '\0';
762                         continue;
763                 }
764                 ap++;
765
766                 while (isspace(*cp))
767                         cp++;
768
769                 if (*cp == '(' && get_comment(ct, &cp, 1) == NOTOK)
770                         return NOTOK;
771         }
772
773         /*
774         ** Get any <Content-Id> given in buffer
775         */
776         if (magic && *cp == '<') {
777                 if (ct->c_id) {
778                         mh_free0(&(ct->c_id));
779                 }
780                 if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
781                         advise(NULL, "invalid ID in message %s", ct->c_file);
782                         return NOTOK;
783                 }
784                 c = *dp;
785                 *dp = '\0';
786                 if (*ct->c_id)
787                         ct->c_id = concat("<", ct->c_id, ">\n", NULL);
788                 else
789                         ct->c_id = NULL;
790                 *dp++ = c;
791                 cp = dp;
792
793                 while (isspace(*cp))
794                         cp++;
795         }
796
797         /*
798         ** Get any [Content-Description] given in buffer.
799         */
800         if (magic && *cp == '[') {
801                 ct->c_descr = ++cp;
802                 for (dp = cp + strlen(cp) - 1; dp >= cp; dp--)
803                         if (*dp == ']')
804                                 break;
805                 if (dp < cp) {
806                         advise(NULL, "invalid description in message %s",
807                                         ct->c_file);
808                         ct->c_descr = NULL;
809                         return NOTOK;
810                 }
811
812                 c = *dp;
813                 *dp = '\0';
814                 if (*ct->c_descr)
815                         ct->c_descr = concat(ct->c_descr, "\n", NULL);
816                 else
817                         ct->c_descr = NULL;
818                 *dp++ = c;
819                 cp = dp;
820
821                 while (isspace(*cp))
822                         cp++;
823         }
824
825         /*
826         ** Get any {Content-Disposition} given in buffer.
827         */
828         if (magic && *cp == '{') {
829                 ct->c_dispo = ++cp;
830                 for (dp = cp + strlen(cp) - 1; dp >= cp; dp--)
831                         if (*dp == '}')
832                                 break;
833                 if (dp < cp) {
834                         advise(NULL, "invalid disposition in message %s",
835                                         ct->c_file);
836                         ct->c_dispo = NULL;
837                         return NOTOK;
838                 }
839
840                 c = *dp;
841                 *dp = '\0';
842                 if (*ct->c_dispo)
843                         ct->c_dispo = concat(ct->c_dispo, "\n", NULL);
844                 else
845                         ct->c_dispo = NULL;
846                 *dp++ = c;
847                 cp = dp;
848
849                 while (isspace(*cp))
850                         cp++;
851         }
852
853         /*
854         ** Check if anything is left over
855         */
856         if (*cp) {
857                 if (magic) {
858                         ci->ci_magic = mh_xstrdup(cp);
859
860                         /*
861                         ** If there is a Content-Disposition header and
862                         ** it doesn't have a *filename=, extract it from
863                         ** the magic contents.  The mhbasename call skips
864                         ** any leading directory components.
865                         */
866                         if (ct->c_dispo)
867                                 ct->c_dispo = incl_name_value(ct->c_dispo, "filename", mhbasename(extract_name_value("name", ci->ci_magic)));
868                         } else
869                                 advise(NULL, "extraneous information in message %s's %s: field\n%*.*s(%s)", ct->c_file, TYPE_FIELD, i, i, "", cp);
870         }
871
872         return OK;
873 }
874
875
876 static int
877 get_comment(CT ct, unsigned char **ap, int istype)
878 {
879         int i;
880         char *bp;
881         unsigned char *cp;
882         char c, buffer[BUFSIZ], *dp;
883         CI ci;
884
885         ci = &ct->c_ctinfo;
886         cp = *ap;
887         bp = buffer;
888         cp++;
889
890         for (i = 0;;) {
891                 switch (c = *cp++) {
892                 case '\0':
893 invalid:
894                 advise(NULL, "invalid comment in message %s's %s: field",
895                                 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
896                 return NOTOK;
897
898                 case '\\':
899                         *bp++ = c;
900                         if ((c = *cp++) == '\0')
901                                 goto invalid;
902                         *bp++ = c;
903                         continue;
904
905                 case '(':
906                         i++;
907                         /* and fall... */
908                 default:
909                         *bp++ = c;
910                         continue;
911
912                 case ')':
913                         if (--i < 0)
914                                 break;
915                         *bp++ = c;
916                         continue;
917                 }
918                 break;
919         }
920         *bp = '\0';
921
922         if (istype) {
923                 if ((dp = ci->ci_comment)) {
924                         ci->ci_comment = concat(dp, " ", buffer, NULL);
925                         mh_free0(&dp);
926                 } else {
927                         ci->ci_comment = mh_xstrdup(buffer);
928                 }
929         }
930
931         while (isspace(*cp))
932                 cp++;
933
934         *ap = cp;
935         return OK;
936 }
937
938
939 /*
940 ** CONTENTS
941 **
942 ** Handles content types audio, image, and video.
943 ** There's not much to do right here.
944 */
945
946 static int
947 InitGeneric(CT ct)
948 {
949         return OK;  /* not much to do here */
950 }
951
952
953 /*
954 ** TEXT
955 */
956
957 static int
958 InitText(CT ct)
959 {
960         char **ap, **ep;
961         struct k2v *kv;
962         struct text *t;
963         CI ci = &ct->c_ctinfo;
964
965         /* check for missing subtype */
966         if (!*ci->ci_subtype)
967                 ci->ci_subtype = add("plain", ci->ci_subtype);
968
969         /* match subtype */
970         for (kv = SubText; kv->kv_key; kv++)
971                 if (!mh_strcasecmp(ci->ci_subtype, kv->kv_key))
972                         break;
973         ct->c_subtype = kv->kv_value;
974
975         /* allocate text character set structure */
976         t = mh_xcalloc(1, sizeof(*t));
977         ct->c_ctparams = (void *) t;
978
979         /* scan for charset parameter */
980         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
981                 if (!mh_strcasecmp(*ap, "charset"))
982                         break;
983
984         /* check if content specified a character set */
985         if (*ap) {
986                 /* store its name */
987                 ct->c_charset = mh_xstrdup(norm_charmap(*ep));
988                 /* match character set or set to CHARSET_UNKNOWN */
989                 for (kv = Charset; kv->kv_key; kv++) {
990                         if (!mh_strcasecmp(*ep, kv->kv_key)) {
991                                 break;
992                         }
993                 }
994                 t->tx_charset = kv->kv_value;
995         } else {
996                 t->tx_charset = CHARSET_UNSPECIFIED;
997         }
998
999         return OK;
1000 }
1001
1002
1003 /*
1004 ** MULTIPART
1005 */
1006
1007 static int
1008 InitMultiPart(CT ct)
1009 {
1010         int inout;
1011         long last, pos;
1012         unsigned char *cp, *dp;
1013         char **ap, **ep;
1014         char *bp, buffer[BUFSIZ];
1015         struct multipart *m;
1016         struct k2v *kv;
1017         struct part *part, **next;
1018         CI ci = &ct->c_ctinfo;
1019         CT p;
1020         FILE *fp;
1021
1022         /*
1023         ** The encoding for multipart messages must be either
1024         ** 7bit, 8bit, or binary (per RFC2045).
1025         */
1026         if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1027                 && ct->c_encoding != CE_BINARY) {
1028                 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);
1029                 ct->c_encoding = CE_7BIT;
1030         }
1031
1032         /* match subtype */
1033         for (kv = SubMultiPart; kv->kv_key; kv++)
1034                 if (!mh_strcasecmp(ci->ci_subtype, kv->kv_key))
1035                         break;
1036         ct->c_subtype = kv->kv_value;
1037
1038         /*
1039         ** Check for "boundary" parameter, which is
1040         ** required for multipart messages.
1041         */
1042         bp = 0;
1043         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1044                 if (!mh_strcasecmp(*ap, "boundary")) {
1045                         bp = *ep;
1046                         break;
1047                 }
1048         }
1049
1050         /* complain if boundary parameter is missing */
1051         if (!*ap) {
1052                 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);
1053                 return NOTOK;
1054         }
1055
1056         /* allocate primary structure for multipart info */
1057         m = mh_xcalloc(1, sizeof(*m));
1058         ct->c_ctparams = (void *) m;
1059
1060         /* check if boundary parameter contains only whitespace characters */
1061         for (cp = bp; isspace(*cp); cp++)
1062                 continue;
1063         if (!*cp) {
1064                 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);
1065                 return NOTOK;
1066         }
1067
1068         /* remove trailing whitespace from boundary parameter */
1069         for (cp = bp, dp = cp + strlen(cp) - 1; dp > cp; dp--)
1070                 if (!isspace(*dp))
1071                         break;
1072         *++dp = '\0';
1073
1074         /* record boundary separators */
1075         m->mp_start = concat(bp, "\n", NULL);
1076         m->mp_stop = concat(bp, "--\n", NULL);
1077
1078         if (!ct->c_fp && (ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
1079                 advise(ct->c_file, "unable to open for reading");
1080                 return NOTOK;
1081         }
1082
1083         fseek(fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1084         last = ct->c_end;
1085         next = &m->mp_parts;
1086         part = NULL;
1087         inout = 1;
1088
1089         while (fgets(buffer, sizeof(buffer) - 1, fp)) {
1090                 if (pos > last)
1091                         break;
1092
1093                 pos += strlen(buffer);
1094                 if (buffer[0] != '-' || buffer[1] != '-')
1095                         continue;
1096                 if (inout) {
1097                         if (strcmp(buffer + 2, m->mp_start)!=0)
1098                                 continue;
1099 next_part:
1100                         part = mh_xcalloc(1, sizeof(*part));
1101                         *next = part;
1102                         next = &part->mp_next;
1103
1104                         if (!(p = get_content(fp, ct->c_file,
1105                                 ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1106                                 ct->c_fp = NULL;
1107                                 return NOTOK;
1108                         }
1109                         p->c_fp = NULL;
1110                         part->mp_part = p;
1111                         pos = p->c_begin;
1112                         fseek(fp, pos, SEEK_SET);
1113                         inout = 0;
1114                 } else {
1115                         if (strcmp(buffer + 2, m->mp_start) == 0) {
1116                                 inout = 1;
1117 end_part:
1118                                 p = part->mp_part;
1119                                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1120                                 if (p->c_end < p->c_begin)
1121                                         p->c_begin = p->c_end;
1122                                 if (inout)
1123                                         goto next_part;
1124                                 goto last_part;
1125                         } else {
1126                                 if (strcmp(buffer + 2, m->mp_stop) == 0)
1127                                         goto end_part;
1128                         }
1129                 }
1130         }
1131
1132         advise(NULL, "bogus multipart content in message %s", ct->c_file);
1133         if (!inout && part) {
1134                 p = part->mp_part;
1135                 p->c_end = ct->c_end;
1136
1137                 if (p->c_begin >= p->c_end) {
1138                         for (next = &m->mp_parts; *next != part;
1139                                 next = &((*next)->mp_next))
1140                                 continue;
1141                         *next = NULL;
1142                         free_content(p);
1143                         mh_free0(&part);
1144                 }
1145         }
1146
1147 last_part:
1148         /* reverse the order of the parts for multipart/alternative */
1149         if (ct->c_subtype == MULTI_ALTERNATE)
1150                 reverse_parts(ct);
1151
1152         /*
1153         ** label all subparts with part number, and
1154         ** then initialize the content of the subpart.
1155         */
1156         {
1157                 int partnum;
1158                 char *pp;
1159                 char partnam[BUFSIZ];
1160
1161                 if (ct->c_partno) {
1162                         snprintf(partnam, sizeof(partnam), "%s.",
1163                                         ct->c_partno);
1164                         pp = partnam + strlen(partnam);
1165                 } else {
1166                         pp = partnam;
1167                 }
1168
1169                 for (part = m->mp_parts, partnum = 1; part;
1170                         part = part->mp_next, partnum++) {
1171                         p = part->mp_part;
1172
1173                         sprintf(pp, "%d", partnum);
1174                         p->c_partno = mh_xstrdup(partnam);
1175
1176                         /* initialize the content of the subparts */
1177                         if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1178                                 fclose(ct->c_fp);
1179                                 ct->c_fp = NULL;
1180                                 return NOTOK;
1181                         }
1182                 }
1183         }
1184
1185         fclose(ct->c_fp);
1186         ct->c_fp = NULL;
1187         return OK;
1188 }
1189
1190
1191 /*
1192 ** reverse the order of the parts of a multipart
1193 */
1194
1195 static void
1196 reverse_parts(CT ct)
1197 {
1198         int i;
1199         struct multipart *m;
1200         struct part **base, **bmp, **next, *part;
1201
1202         m = (struct multipart *) ct->c_ctparams;
1203
1204         /* if only one part, just return */
1205         if (!m->mp_parts || !m->mp_parts->mp_next)
1206                 return;
1207
1208         /* count number of parts */
1209         i = 0;
1210         for (part = m->mp_parts; part; part = part->mp_next)
1211                 i++;
1212
1213         /* allocate array of pointers to the parts */
1214         base = mh_xcalloc(i + 1, sizeof(*base));
1215         bmp = base;
1216
1217         /* point at all the parts */
1218         for (part = m->mp_parts; part; part = part->mp_next)
1219                 *bmp++ = part;
1220         *bmp = NULL;
1221
1222         /* reverse the order of the parts */
1223         next = &m->mp_parts;
1224         for (bmp--; bmp >= base; bmp--) {
1225                 part = *bmp;
1226                 *next = part;
1227                 next = &part->mp_next;
1228         }
1229         *next = NULL;
1230
1231         /* free array of pointers */
1232         mh_free0(&base);
1233 }
1234
1235
1236 /*
1237 ** MESSAGE
1238 */
1239
1240 static int
1241 InitMessage(CT ct)
1242 {
1243         struct k2v *kv;
1244         CI ci = &ct->c_ctinfo;
1245
1246         if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1247                 admonish(NULL, "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit", ci->ci_type, ci->ci_subtype, ct->c_file);
1248                 return NOTOK;
1249         }
1250
1251         /* check for missing subtype */
1252         if (!*ci->ci_subtype)
1253                 ci->ci_subtype = add("rfc822", ci->ci_subtype);
1254
1255         /* match subtype */
1256         for (kv = SubMessage; kv->kv_key; kv++)
1257                 if (!mh_strcasecmp(ci->ci_subtype, kv->kv_key))
1258                         break;
1259         ct->c_subtype = kv->kv_value;
1260
1261         switch (ct->c_subtype) {
1262         case MESSAGE_RFC822:
1263                 break;
1264
1265         case MESSAGE_PARTIAL:
1266                 {
1267                 char **ap, **ep;
1268                 struct partial *p;
1269
1270                 p = mh_xcalloc(1, sizeof(*p));
1271                 ct->c_ctparams = (void *) p;
1272
1273                 /*
1274                 ** scan for parameters "id", "number",
1275                 ** and "total"
1276                 */
1277                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1278                         if (!mh_strcasecmp(*ap, "id")) {
1279                                 p->pm_partid = mh_xstrdup(*ep);
1280                                 continue;
1281                         }
1282                         if (!mh_strcasecmp(*ap, "number")) {
1283                                 if (sscanf(*ep, "%d", &p->pm_partno) != 1 || p->pm_partno < 1) {
1284 invalid_param:
1285                                         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);
1286                                         return NOTOK;
1287                                 }
1288                                 continue;
1289                         }
1290                         if (!mh_strcasecmp(*ap, "total")) {
1291                                 if (sscanf(*ep, "%d", &p->pm_maxno) != 1 ||
1292                                                 p->pm_maxno < 1)
1293                                         goto invalid_param;
1294                                 continue;
1295                         }
1296                 }
1297
1298                 if (!p->pm_partid || !p->pm_partno
1299                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1300                         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);
1301                         return NOTOK;
1302                 }
1303                 }
1304                 break;
1305
1306         case MESSAGE_EXTERNAL:
1307                 {
1308                 CT p;
1309                 FILE *fp;
1310
1311                 if (!ct->c_fp && (ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
1312                         advise(ct->c_file, "unable to open for reading");
1313                         return NOTOK;
1314                 }
1315
1316                 fseek(fp = ct->c_fp, ct->c_begin, SEEK_SET);
1317
1318                 if (!(p = get_content(fp, ct->c_file, 0))) {
1319                         ct->c_fp = NULL;
1320                         return NOTOK;
1321                 }
1322
1323                 p->c_fp = NULL;
1324                 p->c_end = p->c_begin;
1325
1326                 fclose(ct->c_fp);
1327                 ct->c_fp = NULL;
1328
1329                 switch (p->c_type) {
1330                 case CT_MULTIPART:
1331                         break;
1332
1333                 case CT_MESSAGE:
1334                         if (p->c_subtype != MESSAGE_RFC822)
1335                                 break;
1336                         /* else fall... */
1337                 default:
1338                         if (p->c_ctinitfnx)
1339                                 (*p->c_ctinitfnx) (p);
1340                         break;
1341                 }
1342                 }
1343                 break;
1344
1345         default:
1346                 break;
1347         }
1348
1349         return OK;
1350 }
1351
1352
1353 /*
1354 ** APPLICATION
1355 */
1356
1357 static int
1358 InitApplication(CT ct)
1359 {
1360         struct k2v *kv;
1361         CI ci = &ct->c_ctinfo;
1362
1363         /* match subtype */
1364         for (kv = SubApplication; kv->kv_key; kv++)
1365                 if (!mh_strcasecmp(ci->ci_subtype, kv->kv_key))
1366                         break;
1367         ct->c_subtype = kv->kv_value;
1368
1369         return OK;
1370 }
1371
1372
1373 /*
1374 ** TRANSFER ENCODINGS
1375 */
1376
1377 static int
1378 init_encoding(CT ct, OpenCEFunc openfnx)
1379 {
1380         CE ce;
1381
1382         ce = mh_xcalloc(1, sizeof(*ce));
1383
1384         ct->c_cefile     = ce;
1385         ct->c_ceopenfnx  = openfnx;
1386         ct->c_ceclosefnx = close_encoding;
1387         ct->c_cesizefnx  = size_encoding;
1388
1389         return OK;
1390 }
1391
1392
1393 void
1394 close_encoding(CT ct)
1395 {
1396         CE ce;
1397
1398         if (!(ce = ct->c_cefile))
1399                 return;
1400
1401         if (ce->ce_fp) {
1402                 fclose(ce->ce_fp);
1403                 ce->ce_fp = NULL;
1404         }
1405 }
1406
1407
1408 static unsigned long
1409 size_encoding(CT ct)
1410 {
1411         int fd;
1412         unsigned long size;
1413         char *file;
1414         CE ce;
1415         struct stat st;
1416
1417         if (!(ce = ct->c_cefile))
1418                 return (ct->c_end - ct->c_begin);
1419
1420         if (ce->ce_fp && fstat(fileno(ce->ce_fp), &st) != NOTOK)
1421                 return (long) st.st_size;
1422
1423         if (ce->ce_file) {
1424                 if (stat(ce->ce_file, &st) != NOTOK)
1425                         return (long) st.st_size;
1426                 else
1427                         return 0L;
1428         }
1429
1430         if (ct->c_encoding == CE_EXTERNAL)
1431                 return (ct->c_end - ct->c_begin);
1432
1433         file = NULL;
1434         if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1435                 return (ct->c_end - ct->c_begin);
1436
1437         if (fstat(fd, &st) != NOTOK)
1438                 size = (long) st.st_size;
1439         else
1440                 size = 0L;
1441
1442         (*ct->c_ceclosefnx) (ct);
1443         return size;
1444 }
1445
1446
1447 /*
1448 ** BASE64
1449 */
1450
1451 static unsigned char b642nib[0x80] = {
1452         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1453         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1454         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1455         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1456         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1457         0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1458         0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1459         0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1460         0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1461         0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1462         0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1463         0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1464         0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1465         0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1466         0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1467         0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1468 };
1469
1470
1471 static int
1472 InitBase64(CT ct)
1473 {
1474         return init_encoding(ct, openBase64);
1475 }
1476
1477
1478 static int
1479 openBase64(CT ct, char **file)
1480 {
1481         int bitno, cc;
1482         int fd, len, skip, own_ct_fp = 0;
1483         unsigned long bits;
1484         unsigned char value, *b, *b1, *b2, *b3;
1485         unsigned char *cp, *ep;
1486         char buffer[BUFSIZ];
1487         /* sbeck -- handle suffixes */
1488         CI ci;
1489         CE ce;
1490
1491         b  = (unsigned char *) &bits;
1492         b1 = &b[endian > 0 ? 1 : 2];
1493         b2 = &b[endian > 0 ? 2 : 1];
1494         b3 = &b[endian > 0 ? 3 : 0];
1495
1496         ce = ct->c_cefile;
1497         if (ce->ce_fp) {
1498                 fseek(ce->ce_fp, 0L, SEEK_SET);
1499                 goto ready_to_go;
1500         }
1501
1502         if (ce->ce_file) {
1503                 if ((ce->ce_fp = fopen(ce->ce_file, "r")) == NULL) {
1504                         content_error(ce->ce_file, ct,
1505                                         "unable to fopen for reading");
1506                         return NOTOK;
1507                 }
1508                 goto ready_to_go;
1509         }
1510
1511         if (*file == NULL) {
1512                 ce->ce_file = mh_xstrdup(m_mktemp(tmp, NULL, NULL));
1513                 ce->ce_unlink = 1;
1514         } else {
1515                 ce->ce_file = mh_xstrdup(*file);
1516                 ce->ce_unlink = 0;
1517         }
1518
1519         /* sbeck@cise.ufl.edu -- handle suffixes */
1520         ci = &ct->c_ctinfo;
1521         snprintf(buffer, sizeof(buffer), "%s-suffix-%s/%s",
1522                         invo_name, ci->ci_type, ci->ci_subtype);
1523         cp = context_find(buffer);
1524         if (cp == NULL || *cp == '\0') {
1525                 snprintf(buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1526                                 ci->ci_type);
1527                 cp = context_find(buffer);
1528         }
1529         if (cp != NULL && *cp != '\0') {
1530                 if (ce->ce_unlink) {
1531                         /*
1532                         ** Temporary file already exists, so we rename to
1533                         ** version with extension.
1534                         */
1535                         char *file_org = mh_xstrdup(ce->ce_file);
1536                         ce->ce_file = add(cp, ce->ce_file);
1537                         if (rename(file_org, ce->ce_file)) {
1538                                 adios(EX_IOERR, ce->ce_file, "unable to rename %s to ",
1539                                                 file_org);
1540                         }
1541                         mh_free0(&file_org);
1542
1543                 } else {
1544                         ce->ce_file = add(cp, ce->ce_file);
1545                 }
1546         }
1547
1548         if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
1549                 content_error(ce->ce_file, ct,
1550                                 "unable to fopen for reading/writing");
1551                 return NOTOK;
1552         }
1553
1554         if ((len = ct->c_end - ct->c_begin) < 0)
1555                 adios(EX_SOFTWARE, NULL, "internal error(1)");
1556
1557         if (!ct->c_fp) {
1558                 if ((ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
1559                         content_error(ct->c_file, ct,
1560                                         "unable to open for reading");
1561                         return NOTOK;
1562                 }
1563                 own_ct_fp = 1;
1564         }
1565
1566         bitno = 18;
1567         bits = 0L;
1568         skip = 0;
1569
1570         lseek(fd = fileno(ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1571         while (len > 0) {
1572                 switch (cc = read(fd, buffer, sizeof(buffer) - 1)) {
1573                 case NOTOK:
1574                         content_error(ct->c_file, ct, "error reading from");
1575                         goto clean_up;
1576
1577                 case OK:
1578                         content_error(NULL, ct, "premature eof");
1579                         goto clean_up;
1580
1581                 default:
1582                         if (cc > len)
1583                                 cc = len;
1584                         len -= cc;
1585
1586                         for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1587                                 switch (*cp) {
1588                                 default:
1589                                         if (isspace(*cp))
1590                                                 break;
1591                                         if (skip || (*cp & 0x80) || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1592                                                 if (debugsw) {
1593                                                         fprintf(stderr, "*cp=0x%x pos=%ld skip=%d\n", *cp, (long) (lseek(fd, (off_t) 0, SEEK_CUR) - (ep - cp)), skip);
1594                                                 }
1595                                                 content_error(NULL, ct, "invalid BASE64 encoding -- continuing");
1596                                                 continue;
1597                                         }
1598
1599                                         bits |= value << bitno;
1600 test_end:
1601                                         if ((bitno -= 6) < 0) {
1602                                                 putc((char) *b1, ce->ce_fp);
1603                                                 if (skip < 2) {
1604                                                         putc((char) *b2, ce->ce_fp);
1605                                                         if (skip < 1) {
1606                                                                 putc((char) *b3, ce->ce_fp);
1607                                                         }
1608                                                 }
1609
1610                                                 if (ferror(ce->ce_fp)) {
1611                                                         content_error(ce->ce_file, ct,
1612                                                                                    "error writing to");
1613                                                         goto clean_up;
1614                                                 }
1615                                                 bitno = 18, bits = 0L, skip = 0;
1616                                         }
1617                                         break;
1618
1619                                 case '=':
1620                                         if (++skip > 3)
1621                                                 goto self_delimiting;
1622                                         goto test_end;
1623                                 }
1624                         }
1625                 }
1626         }
1627
1628         if (bitno != 18) {
1629                 if (debugsw)
1630                         fprintf(stderr, "premature ending (bitno %d)\n",
1631                                         bitno);
1632
1633                 content_error(NULL, ct, "invalid BASE64 encoding");
1634                 goto clean_up;
1635         }
1636
1637 self_delimiting:
1638         fseek(ct->c_fp, 0L, SEEK_SET);
1639
1640         if (fflush(ce->ce_fp)) {
1641                 content_error(ce->ce_file, ct, "error writing to");
1642                 goto clean_up;
1643         }
1644
1645         fseek(ce->ce_fp, 0L, SEEK_SET);
1646
1647 ready_to_go:
1648         *file = ce->ce_file;
1649         if (own_ct_fp) {
1650                 fclose(ct->c_fp);
1651                 ct->c_fp = NULL;
1652         }
1653         return fileno(ce->ce_fp);
1654
1655 clean_up:
1656         free_encoding(ct, 0);
1657         if (own_ct_fp) {
1658                 fclose(ct->c_fp);
1659                 ct->c_fp = NULL;
1660         }
1661         return NOTOK;
1662 }
1663
1664
1665 /*
1666 ** QUOTED PRINTABLE
1667 */
1668
1669 static char hex2nib[0x80] = {
1670         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1671         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1672         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1673         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1674         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1675         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1676         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1677         0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1678         0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1679         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1680         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1681         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1682         0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1683         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1684         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1685         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1686 };
1687
1688
1689 static int
1690 InitQuoted(CT ct)
1691 {
1692         return init_encoding(ct, openQuoted);
1693 }
1694
1695
1696 static int
1697 openQuoted(CT ct, char **file)
1698 {
1699         int cc, len, quoted, own_ct_fp = 0;
1700         unsigned char *cp, *ep;
1701         char buffer[BUFSIZ];
1702         unsigned char mask = 0;
1703         CE ce;
1704         /* sbeck -- handle suffixes */
1705         CI ci;
1706
1707         ce = ct->c_cefile;
1708         if (ce->ce_fp) {
1709                 fseek(ce->ce_fp, 0L, SEEK_SET);
1710                 goto ready_to_go;
1711         }
1712
1713         if (ce->ce_file) {
1714                 if ((ce->ce_fp = fopen(ce->ce_file, "r")) == NULL) {
1715                         content_error(ce->ce_file, ct,
1716                                         "unable to fopen for reading");
1717                         return NOTOK;
1718                 }
1719                 goto ready_to_go;
1720         }
1721
1722         if (*file == NULL) {
1723                 ce->ce_file = mh_xstrdup(m_mktemp(tmp, NULL, NULL));
1724                 ce->ce_unlink = 1;
1725         } else {
1726                 ce->ce_file = mh_xstrdup(*file);
1727                 ce->ce_unlink = 0;
1728         }
1729
1730         /* sbeck@cise.ufl.edu -- handle suffixes */
1731         ci = &ct->c_ctinfo;
1732         snprintf(buffer, sizeof(buffer), "%s-suffix-%s/%s",
1733                         invo_name, ci->ci_type, ci->ci_subtype);
1734         cp = context_find(buffer);
1735         if (cp == NULL || *cp == '\0') {
1736                 snprintf(buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1737                                 ci->ci_type);
1738                 cp = context_find(buffer);
1739         }
1740         if (cp != NULL && *cp != '\0') {
1741                 if (ce->ce_unlink) {
1742                         /*
1743                         ** Temporary file already exists, so we rename to
1744                         ** version with extension.
1745                         */
1746                         char *file_org = mh_xstrdup(ce->ce_file);
1747                         ce->ce_file = add(cp, ce->ce_file);
1748                         if (rename(file_org, ce->ce_file)) {
1749                                 adios(EX_IOERR, ce->ce_file, "unable to rename %s to ",
1750                                                 file_org);
1751                         }
1752                         mh_free0(&file_org);
1753
1754                 } else {
1755                         ce->ce_file = add(cp, ce->ce_file);
1756                 }
1757         }
1758
1759         if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
1760                 content_error(ce->ce_file, ct,
1761                                 "unable to fopen for reading/writing");
1762                 return NOTOK;
1763         }
1764
1765         if ((len = ct->c_end - ct->c_begin) < 0)
1766                 adios(EX_SOFTWARE, NULL, "internal error(2)");
1767
1768         if (!ct->c_fp) {
1769                 if ((ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
1770                         content_error(ct->c_file, ct,
1771                                         "unable to open for reading");
1772                         return NOTOK;
1773                 }
1774                 own_ct_fp = 1;
1775         }
1776
1777         quoted = 0;
1778
1779         fseek(ct->c_fp, ct->c_begin, SEEK_SET);
1780         while (len > 0) {
1781                 if (fgets(buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
1782                         content_error(NULL, ct, "premature eof");
1783                         goto clean_up;
1784                 }
1785
1786                 if ((cc = strlen(buffer)) > len)
1787                         cc = len;
1788                 len -= cc;
1789
1790                 for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
1791                         if (!isspace(*ep))
1792                                 break;
1793                 *++ep = '\n', ep++;
1794
1795                 for (; cp < ep; cp++) {
1796                         if (quoted > 0) {
1797                                 /* in an escape sequence */
1798                                 if (quoted == 1) {
1799                                         /* at byte 1 of an escape sequence */
1800                                         mask = hex2nib[*cp & 0x7f];
1801                                         /* next is byte 2 */
1802                                         quoted = 2;
1803                                 } else {
1804                                         /* at byte 2 of an escape sequence */
1805                                         mask <<= 4;
1806                                         mask |= hex2nib[*cp & 0x7f];
1807                                         putc(mask, ce->ce_fp);
1808                                         if (ferror(ce->ce_fp)) {
1809                                                 content_error(ce->ce_file, ct, "error writing to");
1810                                                 goto clean_up;
1811                                         }
1812                                         /*
1813                                         ** finished escape sequence; next may
1814                                         ** be literal or a new escape sequence
1815                                         */
1816                                         quoted = 0;
1817                                 }
1818                                 /* on to next byte */
1819                                 continue;
1820                         }
1821
1822                         /* not in an escape sequence */
1823                         if (*cp == '=') {
1824                                 /*
1825                                 ** starting an escape sequence,
1826                                 ** or invalid '='?
1827                                 */
1828                                 if (cp + 1 < ep && cp[1] == '\n') {
1829                                         /* "=\n" soft line break, eat the \n */
1830                                         cp++;
1831                                         continue;
1832                                 }
1833                                 if (cp + 1 >= ep || cp + 2 >= ep) {
1834                                         /*
1835                                         ** We don't have 2 bytes left,
1836                                         ** so this is an invalid escape
1837                                         ** sequence; just show the raw bytes
1838                                         ** (below).
1839                                         */
1840                                 } else if (isxdigit(cp[1]) && isxdigit(cp[2])) {
1841                                         /*
1842                                         ** Next 2 bytes are hex digits,
1843                                         ** making this a valid escape
1844                                         ** sequence; let's decode it (above).
1845                                         */
1846                                         quoted = 1;
1847                                         continue;
1848                                 } else {
1849                                         /*
1850                                         ** One or both of the next 2 is
1851                                         ** out of range, making this an
1852                                         ** invalid escape sequence; just
1853                                         ** show the raw bytes (below).
1854                                         */
1855                                 }
1856                         }
1857
1858                         /* Just show the raw byte. */
1859                         putc(*cp, ce->ce_fp);
1860                         if (ferror(ce->ce_fp)) {
1861                                 content_error(ce->ce_file, ct,
1862                                                 "error writing to");
1863                                 goto clean_up;
1864                         }
1865                 }
1866         }
1867         if (quoted) {
1868                 content_error(NULL, ct, "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
1869                 goto clean_up;
1870         }
1871
1872         fseek(ct->c_fp, 0L, SEEK_SET);
1873
1874         if (fflush(ce->ce_fp)) {
1875                 content_error(ce->ce_file, ct, "error writing to");
1876                 goto clean_up;
1877         }
1878
1879         fseek(ce->ce_fp, 0L, SEEK_SET);
1880
1881 ready_to_go:
1882         *file = ce->ce_file;
1883         if (own_ct_fp) {
1884                 fclose(ct->c_fp);
1885                 ct->c_fp = NULL;
1886         }
1887         return fileno(ce->ce_fp);
1888
1889 clean_up:
1890         free_encoding(ct, 0);
1891         if (own_ct_fp) {
1892                 fclose(ct->c_fp);
1893                 ct->c_fp = NULL;
1894         }
1895         return NOTOK;
1896 }
1897
1898
1899 /*
1900 ** 7BIT
1901 */
1902
1903 static int
1904 Init7Bit(CT ct)
1905 {
1906         if (init_encoding(ct, open7Bit) == NOTOK)
1907                 return NOTOK;
1908
1909         ct->c_cesizefnx = NULL;  /* no need to decode for real size */
1910         return OK;
1911 }
1912
1913
1914 int
1915 open7Bit(CT ct, char **file)
1916 {
1917         int cc, fd, len, own_ct_fp = 0;
1918         char buffer[BUFSIZ];
1919         /* sbeck -- handle suffixes */
1920         char *cp;
1921         CI ci;
1922         CE ce;
1923
1924         ce = ct->c_cefile;
1925         if (ce->ce_fp) {
1926                 fseek(ce->ce_fp, 0L, SEEK_SET);
1927                 goto ready_to_go;
1928         }
1929
1930         if (ce->ce_file) {
1931                 if ((ce->ce_fp = fopen(ce->ce_file, "r")) == NULL) {
1932                         content_error(ce->ce_file, ct,
1933                                         "unable to fopen for reading");
1934                         return NOTOK;
1935                 }
1936                 goto ready_to_go;
1937         }
1938
1939         if (*file == NULL) {
1940                 ce->ce_file = mh_xstrdup(m_mktemp(tmp, NULL, NULL));
1941                 ce->ce_unlink = 1;
1942         } else {
1943                 ce->ce_file = mh_xstrdup(*file);
1944                 ce->ce_unlink = 0;
1945         }
1946
1947         /* sbeck@cise.ufl.edu -- handle suffixes */
1948         ci = &ct->c_ctinfo;
1949         snprintf(buffer, sizeof(buffer), "%s-suffix-%s/%s",
1950                         invo_name, ci->ci_type, ci->ci_subtype);
1951         cp = context_find(buffer);
1952         if (cp == NULL || *cp == '\0') {
1953                 snprintf(buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1954                                 ci->ci_type);
1955                 cp = context_find(buffer);
1956         }
1957         if (cp != NULL && *cp != '\0') {
1958                 if (ce->ce_unlink) {
1959                         /*
1960                         ** Temporary file already exists, so we rename to
1961                         ** version with extension.
1962                         */
1963                         char *file_org = mh_xstrdup(ce->ce_file);
1964                         ce->ce_file = add(cp, ce->ce_file);
1965                         if (rename(file_org, ce->ce_file)) {
1966                                 adios(EX_IOERR, ce->ce_file, "unable to rename %s to ",
1967                                                 file_org);
1968                         }
1969                         mh_free0(&file_org);
1970
1971                 } else {
1972                         ce->ce_file = add(cp, ce->ce_file);
1973                 }
1974         }
1975
1976         if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
1977                 content_error(ce->ce_file, ct,
1978                                 "unable to fopen for reading/writing");
1979                 return NOTOK;
1980         }
1981
1982         if (ct->c_type == CT_MULTIPART) {
1983                 char **ap, **ep;
1984                 CI ci = &ct->c_ctinfo;
1985
1986                 len = 0;
1987                 fprintf(ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type,
1988                                 ci->ci_subtype);
1989                 len += strlen(TYPE_FIELD) + 2 + strlen(ci->ci_type) + 1 +
1990                                 strlen(ci->ci_subtype);
1991                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1992                         putc(';', ce->ce_fp);
1993                         len++;
1994
1995                         snprintf(buffer, sizeof(buffer), "%s=\"%s\"",
1996                                         *ap, *ep);
1997
1998                         if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1999                                 fputs("\n\t", ce->ce_fp);
2000                                 len = 8;
2001                         } else {
2002                                 putc(' ', ce->ce_fp);
2003                                 len++;
2004                         }
2005                         fprintf(ce->ce_fp, "%s", buffer);
2006                         len += cc;
2007                 }
2008
2009                 if (ci->ci_comment) {
2010                         if (len + 1 + (cc = 2 + strlen(ci->ci_comment))
2011                                                 >= CPERLIN) {
2012                                 fputs("\n\t", ce->ce_fp);
2013                                 len = 8;
2014                         } else {
2015                                 putc(' ', ce->ce_fp);
2016                                 len++;
2017                         }
2018                         fprintf(ce->ce_fp, "(%s)", ci->ci_comment);
2019                         len += cc;
2020                 }
2021                 fprintf(ce->ce_fp, "\n");
2022                 if (ct->c_id)
2023                         fprintf(ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2024                 if (ct->c_descr)
2025                         fprintf(ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2026                 if (ct->c_dispo)
2027                         fprintf(ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2028                 fprintf(ce->ce_fp, "\n");
2029         }
2030
2031         if ((len = ct->c_end - ct->c_begin) < 0)
2032                 adios(EX_SOFTWARE, NULL, "internal error(3)");
2033
2034         if (!ct->c_fp) {
2035                 if ((ct->c_fp = fopen(ct->c_file, "r")) == NULL) {
2036                         content_error(ct->c_file, ct,
2037                                         "unable to open for reading");
2038                         return NOTOK;
2039                 }
2040                 own_ct_fp = 1;
2041         }
2042
2043         lseek(fd = fileno(ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2044         while (len > 0)
2045                 switch (cc = read(fd, buffer, sizeof(buffer) - 1)) {
2046                 case NOTOK:
2047                         content_error(ct->c_file, ct, "error reading from");
2048                         goto clean_up;
2049
2050                 case OK:
2051                         content_error(NULL, ct, "premature eof");
2052                         goto clean_up;
2053
2054                 default:
2055                         if (cc > len)
2056                                 cc = len;
2057                         len -= cc;
2058
2059                         fwrite(buffer, sizeof(*buffer), cc, ce->ce_fp);
2060                         if (ferror(ce->ce_fp)) {
2061                                 content_error(ce->ce_file, ct,
2062                                                 "error writing to");
2063                                 goto clean_up;
2064                         }
2065                 }
2066
2067         fseek(ct->c_fp, 0L, SEEK_SET);
2068
2069         if (fflush(ce->ce_fp)) {
2070                 content_error(ce->ce_file, ct, "error writing to");
2071                 goto clean_up;
2072         }
2073
2074         fseek(ce->ce_fp, 0L, SEEK_SET);
2075
2076 ready_to_go:
2077         *file = ce->ce_file;
2078         if (own_ct_fp) {
2079                 fclose(ct->c_fp);
2080                 ct->c_fp = NULL;
2081         }
2082         return fileno(ce->ce_fp);
2083
2084 clean_up:
2085         free_encoding(ct, 0);
2086         if (own_ct_fp) {
2087                 fclose(ct->c_fp);
2088                 ct->c_fp = NULL;
2089         }
2090         return NOTOK;
2091 }