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