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