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