]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Remove mhbuild backup files at end of a couple of tests, if successful.
[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, text = ct->c_type == CT_TEXT;
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 ce->ce_unlink = 1;
1704 } else {
1705 ce->ce_file = add (*file, NULL);
1706 ce->ce_unlink = 0;
1707 }
1708
1709 /* sbeck@cise.ufl.edu -- handle suffixes */
1710 ci = &ct->c_ctinfo;
1711 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1712 invo_name, ci->ci_type, ci->ci_subtype);
1713 cp = context_find (buffer);
1714 if (cp == NULL || *cp == '\0') {
1715 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1716 ci->ci_type);
1717 cp = context_find (buffer);
1718 }
1719 if (cp != NULL && *cp != '\0') {
1720 if (ce->ce_unlink) {
1721 /* Create temporary file with filename extension. */
1722 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
1723 adios(NULL, "unable to create temporary file in %s",
1724 get_temp_dir());
1725 }
1726 } else {
1727 ce->ce_file = add (cp, ce->ce_file);
1728 }
1729 } else if (*file == NULL) {
1730 char *tempfile;
1731 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1732 adios(NULL, "unable to create temporary file in %s",
1733 get_temp_dir());
1734 }
1735 ce->ce_file = add (tempfile, NULL);
1736 }
1737
1738 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1739 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1740 return NOTOK;
1741 }
1742
1743 if ((len = ct->c_end - ct->c_begin) < 0)
1744 adios (NULL, "internal error(1)");
1745
1746 if (! ct->c_fp) {
1747 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1748 content_error (ct->c_file, ct, "unable to open for reading");
1749 return NOTOK;
1750 }
1751 own_ct_fp = 1;
1752 }
1753
1754 if ((digested = ct->c_digested))
1755 MD5Init (&mdContext);
1756
1757 bitno = 18;
1758 bits = 0L;
1759 skip = 0;
1760
1761 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1762 while (len > 0) {
1763 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1764 case NOTOK:
1765 content_error (ct->c_file, ct, "error reading from");
1766 goto clean_up;
1767
1768 case OK:
1769 content_error (NULL, ct, "premature eof");
1770 goto clean_up;
1771
1772 default:
1773 if (cc > len)
1774 cc = len;
1775 len -= cc;
1776
1777 for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1778 switch (*cp) {
1779 default:
1780 if (isspace ((unsigned char) *cp))
1781 break;
1782 if (skip || (((unsigned char) *cp) & 0x80)
1783 || (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) {
1784 if (debugsw) {
1785 fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1786 (unsigned char) *cp,
1787 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1788 skip);
1789 }
1790 content_error (NULL, ct,
1791 "invalid BASE64 encoding -- continuing");
1792 continue;
1793 }
1794
1795 bits |= value << bitno;
1796 test_end:
1797 if ((bitno -= 6) < 0) {
1798 b = (bits >> 16) & 0xff;
1799 if (!text || b != '\r')
1800 putc ((char) b, ce->ce_fp);
1801 if (digested)
1802 MD5Update (&mdContext, &b, 1);
1803 if (skip < 2) {
1804 b = (bits >> 8) & 0xff;
1805 if (! text || b != '\r')
1806 putc ((char) b, ce->ce_fp);
1807 if (digested)
1808 MD5Update (&mdContext, &b, 1);
1809 if (skip < 1) {
1810 b = bits & 0xff;
1811 if (! text || b != '\r')
1812 putc ((char) b, ce->ce_fp);
1813 if (digested)
1814 MD5Update (&mdContext, &b, 1);
1815 }
1816 }
1817
1818 if (ferror (ce->ce_fp)) {
1819 content_error (ce->ce_file, ct,
1820 "error writing to");
1821 goto clean_up;
1822 }
1823 bitno = 18, bits = 0L, skip = 0;
1824 }
1825 break;
1826
1827 case '=':
1828 if (++skip > 3)
1829 goto self_delimiting;
1830 goto test_end;
1831 }
1832 }
1833 }
1834 }
1835
1836 if (bitno != 18) {
1837 if (debugsw)
1838 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1839
1840 content_error (NULL, ct, "invalid BASE64 encoding");
1841 goto clean_up;
1842 }
1843
1844 self_delimiting:
1845 fseek (ct->c_fp, 0L, SEEK_SET);
1846
1847 if (fflush (ce->ce_fp)) {
1848 content_error (ce->ce_file, ct, "error writing to");
1849 goto clean_up;
1850 }
1851
1852 if (digested) {
1853 unsigned char digest[16];
1854
1855 MD5Final (digest, &mdContext);
1856 if (memcmp((char *) digest, (char *) ct->c_digest,
1857 sizeof(digest) / sizeof(digest[0])))
1858 content_error (NULL, ct,
1859 "content integrity suspect (digest mismatch) -- continuing");
1860 else
1861 if (debugsw)
1862 fprintf (stderr, "content integrity confirmed\n");
1863 }
1864
1865 fseek (ce->ce_fp, 0L, SEEK_SET);
1866
1867 ready_to_go:
1868 *file = ce->ce_file;
1869 if (own_ct_fp) {
1870 fclose (ct->c_fp);
1871 ct->c_fp = NULL;
1872 }
1873 return fileno (ce->ce_fp);
1874
1875 clean_up:
1876 if (own_ct_fp) {
1877 fclose (ct->c_fp);
1878 ct->c_fp = NULL;
1879 }
1880 free_encoding (ct, 0);
1881 return NOTOK;
1882 }
1883
1884
1885 /*
1886 * QUOTED PRINTABLE
1887 */
1888
1889 static char hex2nib[0x80] = {
1890 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1897 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1906 };
1907
1908
1909 static int
1910 InitQuoted (CT ct)
1911 {
1912 return init_encoding (ct, openQuoted);
1913 }
1914
1915
1916 static int
1917 openQuoted (CT ct, char **file)
1918 {
1919 int cc, digested, len, quoted, own_ct_fp = 0;
1920 char *cp, *ep;
1921 char buffer[BUFSIZ];
1922 unsigned char mask;
1923 CE ce = &ct->c_cefile;
1924 /* sbeck -- handle suffixes */
1925 CI ci;
1926 MD5_CTX mdContext;
1927
1928 if (ce->ce_fp) {
1929 fseek (ce->ce_fp, 0L, SEEK_SET);
1930 goto ready_to_go;
1931 }
1932
1933 if (ce->ce_file) {
1934 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1935 content_error (ce->ce_file, ct, "unable to fopen for reading");
1936 return NOTOK;
1937 }
1938 goto ready_to_go;
1939 }
1940
1941 if (*file == NULL) {
1942 ce->ce_unlink = 1;
1943 } else {
1944 ce->ce_file = add (*file, NULL);
1945 ce->ce_unlink = 0;
1946 }
1947
1948 /* sbeck@cise.ufl.edu -- handle suffixes */
1949 ci = &ct->c_ctinfo;
1950 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1951 invo_name, ci->ci_type, ci->ci_subtype);
1952 cp = context_find (buffer);
1953 if (cp == NULL || *cp == '\0') {
1954 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1955 ci->ci_type);
1956 cp = context_find (buffer);
1957 }
1958 if (cp != NULL && *cp != '\0') {
1959 if (ce->ce_unlink) {
1960 /* Create temporary file with filename extension. */
1961 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
1962 adios(NULL, "unable to create temporary file in %s",
1963 get_temp_dir());
1964 }
1965 } else {
1966 ce->ce_file = add (cp, ce->ce_file);
1967 }
1968 } else if (*file == NULL) {
1969 char *tempfile;
1970 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1971 adios(NULL, "unable to create temporary file in %s",
1972 get_temp_dir());
1973 }
1974 ce->ce_file = add (tempfile, NULL);
1975 }
1976
1977 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1978 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1979 return NOTOK;
1980 }
1981
1982 if ((len = ct->c_end - ct->c_begin) < 0)
1983 adios (NULL, "internal error(2)");
1984
1985 if (! ct->c_fp) {
1986 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1987 content_error (ct->c_file, ct, "unable to open for reading");
1988 return NOTOK;
1989 }
1990 own_ct_fp = 1;
1991 }
1992
1993 if ((digested = ct->c_digested))
1994 MD5Init (&mdContext);
1995
1996 quoted = 0;
1997 #ifdef lint
1998 mask = 0;
1999 #endif
2000
2001 fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2002 while (len > 0) {
2003 if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2004 content_error (NULL, ct, "premature eof");
2005 goto clean_up;
2006 }
2007
2008 if ((cc = strlen (buffer)) > len)
2009 cc = len;
2010 len -= cc;
2011
2012 for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2013 if (!isspace ((unsigned char) *ep))
2014 break;
2015 *++ep = '\n', ep++;
2016
2017 for (; cp < ep; cp++) {
2018 if (quoted > 0) {
2019 /* in an escape sequence */
2020 if (quoted == 1) {
2021 /* at byte 1 of an escape sequence */
2022 mask = hex2nib[((unsigned char) *cp) & 0x7f];
2023 /* next is byte 2 */
2024 quoted = 2;
2025 } else {
2026 /* at byte 2 of an escape sequence */
2027 mask <<= 4;
2028 mask |= hex2nib[((unsigned char) *cp) & 0x7f];
2029 putc (mask, ce->ce_fp);
2030 if (digested)
2031 MD5Update (&mdContext, &mask, 1);
2032 if (ferror (ce->ce_fp)) {
2033 content_error (ce->ce_file, ct, "error writing to");
2034 goto clean_up;
2035 }
2036 /* finished escape sequence; next may be literal or a new
2037 * escape sequence */
2038 quoted = 0;
2039 }
2040 /* on to next byte */
2041 continue;
2042 }
2043
2044 /* not in an escape sequence */
2045 if (*cp == '=') {
2046 /* starting an escape sequence, or invalid '='? */
2047 if (cp + 1 < ep && cp[1] == '\n') {
2048 /* "=\n" soft line break, eat the \n */
2049 cp++;
2050 continue;
2051 }
2052 if (cp + 1 >= ep || cp + 2 >= ep) {
2053 /* We don't have 2 bytes left, so this is an invalid
2054 * escape sequence; just show the raw bytes (below). */
2055 } else if (isxdigit ((unsigned char) cp[1]) &&
2056 isxdigit ((unsigned char) cp[2])) {
2057 /* Next 2 bytes are hex digits, making this a valid escape
2058 * sequence; let's decode it (above). */
2059 quoted = 1;
2060 continue;
2061 } else {
2062 /* One or both of the next 2 is out of range, making this
2063 * an invalid escape sequence; just show the raw bytes
2064 * (below). */
2065 }
2066 }
2067
2068 /* Just show the raw byte. */
2069 putc (*cp, ce->ce_fp);
2070 if (digested) {
2071 if (*cp == '\n') {
2072 MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2073 } else {
2074 MD5Update (&mdContext, (unsigned char *) cp, 1);
2075 }
2076 }
2077 if (ferror (ce->ce_fp)) {
2078 content_error (ce->ce_file, ct, "error writing to");
2079 goto clean_up;
2080 }
2081 }
2082 }
2083 if (quoted) {
2084 content_error (NULL, ct,
2085 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2086 goto clean_up;
2087 }
2088
2089 fseek (ct->c_fp, 0L, SEEK_SET);
2090
2091 if (fflush (ce->ce_fp)) {
2092 content_error (ce->ce_file, ct, "error writing to");
2093 goto clean_up;
2094 }
2095
2096 if (digested) {
2097 unsigned char digest[16];
2098
2099 MD5Final (digest, &mdContext);
2100 if (memcmp((char *) digest, (char *) ct->c_digest,
2101 sizeof(digest) / sizeof(digest[0])))
2102 content_error (NULL, ct,
2103 "content integrity suspect (digest mismatch) -- continuing");
2104 else
2105 if (debugsw)
2106 fprintf (stderr, "content integrity confirmed\n");
2107 }
2108
2109 fseek (ce->ce_fp, 0L, SEEK_SET);
2110
2111 ready_to_go:
2112 *file = ce->ce_file;
2113 if (own_ct_fp) {
2114 fclose (ct->c_fp);
2115 ct->c_fp = NULL;
2116 }
2117 return fileno (ce->ce_fp);
2118
2119 clean_up:
2120 free_encoding (ct, 0);
2121 if (own_ct_fp) {
2122 fclose (ct->c_fp);
2123 ct->c_fp = NULL;
2124 }
2125 return NOTOK;
2126 }
2127
2128
2129 /*
2130 * 7BIT
2131 */
2132
2133 static int
2134 Init7Bit (CT ct)
2135 {
2136 if (init_encoding (ct, open7Bit) == NOTOK)
2137 return NOTOK;
2138
2139 ct->c_cesizefnx = NULL; /* no need to decode for real size */
2140 return OK;
2141 }
2142
2143
2144 int
2145 open7Bit (CT ct, char **file)
2146 {
2147 int cc, fd, len, own_ct_fp = 0;
2148 char buffer[BUFSIZ];
2149 /* sbeck -- handle suffixes */
2150 char *cp;
2151 CI ci;
2152 CE ce = &ct->c_cefile;
2153
2154 if (ce->ce_fp) {
2155 fseek (ce->ce_fp, 0L, SEEK_SET);
2156 goto ready_to_go;
2157 }
2158
2159 if (ce->ce_file) {
2160 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2161 content_error (ce->ce_file, ct, "unable to fopen for reading");
2162 return NOTOK;
2163 }
2164 goto ready_to_go;
2165 }
2166
2167 if (*file == NULL) {
2168 ce->ce_unlink = 1;
2169 } else {
2170 ce->ce_file = add (*file, NULL);
2171 ce->ce_unlink = 0;
2172 }
2173
2174 /* sbeck@cise.ufl.edu -- handle suffixes */
2175 ci = &ct->c_ctinfo;
2176 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2177 invo_name, ci->ci_type, ci->ci_subtype);
2178 cp = context_find (buffer);
2179 if (cp == NULL || *cp == '\0') {
2180 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2181 ci->ci_type);
2182 cp = context_find (buffer);
2183 }
2184 if (cp != NULL && *cp != '\0') {
2185 if (ce->ce_unlink) {
2186 /* Create temporary file with filename extension. */
2187 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
2188 adios(NULL, "unable to create temporary file in %s",
2189 get_temp_dir());
2190 }
2191 } else {
2192 ce->ce_file = add (cp, ce->ce_file);
2193 }
2194 } else if (*file == NULL) {
2195 char *tempfile;
2196 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2197 adios(NULL, "unable to create temporary file in %s",
2198 get_temp_dir());
2199 }
2200 ce->ce_file = add (tempfile, NULL);
2201 }
2202
2203 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2204 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2205 return NOTOK;
2206 }
2207
2208 if (ct->c_type == CT_MULTIPART) {
2209 char **ap, **ep;
2210 CI ci = &ct->c_ctinfo;
2211
2212 len = 0;
2213 fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2214 len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2215 + 1 + strlen (ci->ci_subtype);
2216 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2217 putc (';', ce->ce_fp);
2218 len++;
2219
2220 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2221
2222 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2223 fputs ("\n\t", ce->ce_fp);
2224 len = 8;
2225 } else {
2226 putc (' ', ce->ce_fp);
2227 len++;
2228 }
2229 fprintf (ce->ce_fp, "%s", buffer);
2230 len += cc;
2231 }
2232
2233 if (ci->ci_comment) {
2234 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2235 fputs ("\n\t", ce->ce_fp);
2236 len = 8;
2237 }
2238 else {
2239 putc (' ', ce->ce_fp);
2240 len++;
2241 }
2242 fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2243 len += cc;
2244 }
2245 fprintf (ce->ce_fp, "\n");
2246 if (ct->c_id)
2247 fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2248 if (ct->c_descr)
2249 fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2250 if (ct->c_dispo)
2251 fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2252 fprintf (ce->ce_fp, "\n");
2253 }
2254
2255 if ((len = ct->c_end - ct->c_begin) < 0)
2256 adios (NULL, "internal error(3)");
2257
2258 if (! ct->c_fp) {
2259 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2260 content_error (ct->c_file, ct, "unable to open for reading");
2261 return NOTOK;
2262 }
2263 own_ct_fp = 1;
2264 }
2265
2266 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2267 while (len > 0)
2268 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2269 case NOTOK:
2270 content_error (ct->c_file, ct, "error reading from");
2271 goto clean_up;
2272
2273 case OK:
2274 content_error (NULL, ct, "premature eof");
2275 goto clean_up;
2276
2277 default:
2278 if (cc > len)
2279 cc = len;
2280 len -= cc;
2281
2282 fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2283 if (ferror (ce->ce_fp)) {
2284 content_error (ce->ce_file, ct, "error writing to");
2285 goto clean_up;
2286 }
2287 }
2288
2289 fseek (ct->c_fp, 0L, SEEK_SET);
2290
2291 if (fflush (ce->ce_fp)) {
2292 content_error (ce->ce_file, ct, "error writing to");
2293 goto clean_up;
2294 }
2295
2296 fseek (ce->ce_fp, 0L, SEEK_SET);
2297
2298 ready_to_go:
2299 *file = ce->ce_file;
2300 if (own_ct_fp) {
2301 fclose (ct->c_fp);
2302 ct->c_fp = NULL;
2303 }
2304 return fileno (ce->ce_fp);
2305
2306 clean_up:
2307 free_encoding (ct, 0);
2308 if (own_ct_fp) {
2309 fclose (ct->c_fp);
2310 ct->c_fp = NULL;
2311 }
2312 return NOTOK;
2313 }
2314
2315
2316 /*
2317 * External
2318 */
2319
2320 static int
2321 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2322 {
2323 char cachefile[BUFSIZ];
2324
2325 if (ce->ce_fp) {
2326 fseek (ce->ce_fp, 0L, SEEK_SET);
2327 goto ready_already;
2328 }
2329
2330 if (ce->ce_file) {
2331 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2332 content_error (ce->ce_file, ct, "unable to fopen for reading");
2333 return NOTOK;
2334 }
2335 goto ready_already;
2336 }
2337
2338 if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2339 cachefile, sizeof(cachefile)) != NOTOK) {
2340 if ((ce->ce_fp = fopen (cachefile, "r"))) {
2341 ce->ce_file = getcpy (cachefile);
2342 ce->ce_unlink = 0;
2343 goto ready_already;
2344 } else {
2345 admonish (cachefile, "unable to fopen for reading");
2346 }
2347 }
2348
2349 return OK;
2350
2351 ready_already:
2352 *file = ce->ce_file;
2353 *fd = fileno (ce->ce_fp);
2354 return DONE;
2355 }
2356
2357 /*
2358 * File
2359 */
2360
2361 static int
2362 InitFile (CT ct)
2363 {
2364 return init_encoding (ct, openFile);
2365 }
2366
2367
2368 static int
2369 openFile (CT ct, char **file)
2370 {
2371 int fd, cachetype;
2372 char cachefile[BUFSIZ];
2373 struct exbody *e = ct->c_ctexbody;
2374 CE ce = &ct->c_cefile;
2375
2376 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2377 case NOTOK:
2378 return NOTOK;
2379
2380 case OK:
2381 break;
2382
2383 case DONE:
2384 return fd;
2385 }
2386
2387 if (!e->eb_name) {
2388 content_error (NULL, ct, "missing name parameter");
2389 return NOTOK;
2390 }
2391
2392 ce->ce_file = getcpy (e->eb_name);
2393 ce->ce_unlink = 0;
2394
2395 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2396 content_error (ce->ce_file, ct, "unable to fopen for reading");
2397 return NOTOK;
2398 }
2399
2400 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2401 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2402 cachefile, sizeof(cachefile)) != NOTOK) {
2403 int mask;
2404 FILE *fp;
2405
2406 mask = umask (cachetype ? ~m_gmprot () : 0222);
2407 if ((fp = fopen (cachefile, "w"))) {
2408 int cc;
2409 char buffer[BUFSIZ];
2410 FILE *gp = ce->ce_fp;
2411
2412 fseek (gp, 0L, SEEK_SET);
2413
2414 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2415 > 0)
2416 fwrite (buffer, sizeof(*buffer), cc, fp);
2417 fflush (fp);
2418
2419 if (ferror (gp)) {
2420 admonish (ce->ce_file, "error reading");
2421 (void) m_unlink (cachefile);
2422 }
2423 else
2424 if (ferror (fp)) {
2425 admonish (cachefile, "error writing");
2426 (void) m_unlink (cachefile);
2427 }
2428 fclose (fp);
2429 }
2430 umask (mask);
2431 }
2432
2433 fseek (ce->ce_fp, 0L, SEEK_SET);
2434 *file = ce->ce_file;
2435 return fileno (ce->ce_fp);
2436 }
2437
2438 /*
2439 * FTP
2440 */
2441
2442 static int
2443 InitFTP (CT ct)
2444 {
2445 return init_encoding (ct, openFTP);
2446 }
2447
2448
2449 static int
2450 openFTP (CT ct, char **file)
2451 {
2452 int cachetype, caching, fd;
2453 int len, buflen;
2454 char *bp, *ftp, *user, *pass;
2455 char buffer[BUFSIZ], cachefile[BUFSIZ];
2456 struct exbody *e;
2457 CE ce = &ct->c_cefile;
2458 static char *username = NULL;
2459 static char *password = NULL;
2460
2461 e = ct->c_ctexbody;
2462
2463 if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2464 ftp = NULL;
2465
2466 if (!ftp)
2467 return NOTOK;
2468
2469 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2470 case NOTOK:
2471 return NOTOK;
2472
2473 case OK:
2474 break;
2475
2476 case DONE:
2477 return fd;
2478 }
2479
2480 if (!e->eb_name || !e->eb_site) {
2481 content_error (NULL, ct, "missing %s parameter",
2482 e->eb_name ? "site": "name");
2483 return NOTOK;
2484 }
2485
2486 if (xpid) {
2487 if (xpid < 0)
2488 xpid = -xpid;
2489 pidcheck (pidwait (xpid, NOTOK));
2490 xpid = 0;
2491 }
2492
2493 /* Get the buffer ready to go */
2494 bp = buffer;
2495 buflen = sizeof(buffer);
2496
2497 /*
2498 * Construct the query message for user
2499 */
2500 snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2501 len = strlen (bp);
2502 bp += len;
2503 buflen -= len;
2504
2505 if (e->eb_partno) {
2506 snprintf (bp, buflen, " (content %s)", e->eb_partno);
2507 len = strlen (bp);
2508 bp += len;
2509 buflen -= len;
2510 }
2511
2512 snprintf (bp, buflen, "\n using %sFTP from site %s",
2513 e->eb_flags ? "anonymous " : "", e->eb_site);
2514 len = strlen (bp);
2515 bp += len;
2516 buflen -= len;
2517
2518 if (e->eb_size > 0) {
2519 snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2520 len = strlen (bp);
2521 bp += len;
2522 buflen -= len;
2523 }
2524 snprintf (bp, buflen, "? ");
2525
2526 /*
2527 * Now, check the answer
2528 */
2529 if (!getanswer (buffer))
2530 return NOTOK;
2531
2532 if (e->eb_flags) {
2533 user = "anonymous";
2534 snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
2535 LocalName (1));
2536 pass = buffer;
2537 } else {
2538 ruserpass (e->eb_site, &username, &password);
2539 user = username;
2540 pass = password;
2541 }
2542
2543 ce->ce_unlink = (*file == NULL);
2544 caching = 0;
2545 cachefile[0] = '\0';
2546 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2547 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2548 cachefile, sizeof(cachefile)) != NOTOK) {
2549 if (*file == NULL) {
2550 ce->ce_unlink = 0;
2551 caching = 1;
2552 }
2553 }
2554
2555 if (*file)
2556 ce->ce_file = add (*file, NULL);
2557 else if (caching)
2558 ce->ce_file = add (cachefile, NULL);
2559 else {
2560 char *tempfile;
2561 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2562 adios(NULL, "unable to create temporary file in %s",
2563 get_temp_dir());
2564 }
2565 ce->ce_file = add (tempfile, NULL);
2566 }
2567
2568 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2569 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2570 return NOTOK;
2571 }
2572
2573 {
2574 int child_id, i, vecp;
2575 char *vec[9];
2576
2577 vecp = 0;
2578 vec[vecp++] = r1bindex (ftp, '/');
2579 vec[vecp++] = e->eb_site;
2580 vec[vecp++] = user;
2581 vec[vecp++] = pass;
2582 vec[vecp++] = e->eb_dir;
2583 vec[vecp++] = e->eb_name;
2584 vec[vecp++] = ce->ce_file,
2585 vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2586 ? "ascii" : "binary";
2587 vec[vecp] = NULL;
2588
2589 fflush (stdout);
2590
2591 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2592 sleep (5);
2593 switch (child_id) {
2594 case NOTOK:
2595 adios ("fork", "unable to");
2596 /* NOTREACHED */
2597
2598 case OK:
2599 close (fileno (ce->ce_fp));
2600 execvp (ftp, vec);
2601 fprintf (stderr, "unable to exec ");
2602 perror (ftp);
2603 _exit (-1);
2604 /* NOTREACHED */
2605
2606 default:
2607 if (pidXwait (child_id, NULL)) {
2608 username = password = NULL;
2609 ce->ce_unlink = 1;
2610 return NOTOK;
2611 }
2612 break;
2613 }
2614 }
2615
2616 if (cachefile[0]) {
2617 if (caching)
2618 chmod (cachefile, cachetype ? m_gmprot () : 0444);
2619 else {
2620 int mask;
2621 FILE *fp;
2622
2623 mask = umask (cachetype ? ~m_gmprot () : 0222);
2624 if ((fp = fopen (cachefile, "w"))) {
2625 int cc;
2626 FILE *gp = ce->ce_fp;
2627
2628 fseek (gp, 0L, SEEK_SET);
2629
2630 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2631 > 0)
2632 fwrite (buffer, sizeof(*buffer), cc, fp);
2633 fflush (fp);
2634
2635 if (ferror (gp)) {
2636 admonish (ce->ce_file, "error reading");
2637 (void) m_unlink (cachefile);
2638 }
2639 else
2640 if (ferror (fp)) {
2641 admonish (cachefile, "error writing");
2642 (void) m_unlink (cachefile);
2643 }
2644 fclose (fp);
2645 }
2646 umask (mask);
2647 }
2648 }
2649
2650 fseek (ce->ce_fp, 0L, SEEK_SET);
2651 *file = ce->ce_file;
2652 return fileno (ce->ce_fp);
2653 }
2654
2655
2656 /*
2657 * Mail
2658 */
2659
2660 static int
2661 InitMail (CT ct)
2662 {
2663 return init_encoding (ct, openMail);
2664 }
2665
2666
2667 static int
2668 openMail (CT ct, char **file)
2669 {
2670 int child_id, fd, i, vecp;
2671 int len, buflen;
2672 char *bp, buffer[BUFSIZ], *vec[7];
2673 struct exbody *e = ct->c_ctexbody;
2674 CE ce = &ct->c_cefile;
2675
2676 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2677 case NOTOK:
2678 return NOTOK;
2679
2680 case OK:
2681 break;
2682
2683 case DONE:
2684 return fd;
2685 }
2686
2687 if (!e->eb_server) {
2688 content_error (NULL, ct, "missing server parameter");
2689 return NOTOK;
2690 }
2691
2692 if (xpid) {
2693 if (xpid < 0)
2694 xpid = -xpid;
2695 pidcheck (pidwait (xpid, NOTOK));
2696 xpid = 0;
2697 }
2698
2699 /* Get buffer ready to go */
2700 bp = buffer;
2701 buflen = sizeof(buffer);
2702
2703 /* Now, construct query message */
2704 snprintf (bp, buflen, "Retrieve content");
2705 len = strlen (bp);
2706 bp += len;
2707 buflen -= len;
2708
2709 if (e->eb_partno) {
2710 snprintf (bp, buflen, " %s", e->eb_partno);
2711 len = strlen (bp);
2712 bp += len;
2713 buflen -= len;
2714 }
2715
2716 snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2717 e->eb_server,
2718 e->eb_subject ? e->eb_subject : e->eb_body);
2719
2720 /* Now, check answer */
2721 if (!getanswer (buffer))
2722 return NOTOK;
2723
2724 vecp = 0;
2725 vec[vecp++] = r1bindex (mailproc, '/');
2726 vec[vecp++] = e->eb_server;
2727 vec[vecp++] = "-subject";
2728 vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2729 vec[vecp++] = "-body";
2730 vec[vecp++] = e->eb_body;
2731 vec[vecp] = NULL;
2732
2733 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2734 sleep (5);
2735 switch (child_id) {
2736 case NOTOK:
2737 advise ("fork", "unable to");
2738 return NOTOK;
2739
2740 case OK:
2741 execvp (mailproc, vec);
2742 fprintf (stderr, "unable to exec ");
2743 perror (mailproc);
2744 _exit (-1);
2745 /* NOTREACHED */
2746
2747 default:
2748 if (pidXwait (child_id, NULL) == OK)
2749 advise (NULL, "request sent");
2750 break;
2751 }
2752
2753 if (*file == NULL) {
2754 char *tempfile;
2755 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2756 adios(NULL, "unable to create temporary file in %s",
2757 get_temp_dir());
2758 }
2759 ce->ce_file = add (tempfile, NULL);
2760 ce->ce_unlink = 1;
2761 } else {
2762 ce->ce_file = add (*file, NULL);
2763 ce->ce_unlink = 0;
2764 }
2765
2766 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2767 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2768 return NOTOK;
2769 }
2770
2771 /* showproc is for mhshow and mhstore, though mhlist -debug
2772 * prints it, too. */
2773 if (ct->c_showproc)
2774 free (ct->c_showproc);
2775 ct->c_showproc = add ("true", NULL);
2776
2777 fseek (ce->ce_fp, 0L, SEEK_SET);
2778 *file = ce->ce_file;
2779 return fileno (ce->ce_fp);
2780 }
2781
2782
2783 /*
2784 * URL
2785 */
2786
2787 static int
2788 InitURL (CT ct)
2789 {
2790 return init_encoding (ct, openURL);
2791 }
2792
2793
2794 static int
2795 openURL (CT ct, char **file)
2796 {
2797 struct exbody *e = ct->c_ctexbody;
2798 CE ce = &ct->c_cefile;
2799 char *urlprog, *program;
2800 char buffer[BUFSIZ], cachefile[BUFSIZ];
2801 int fd, caching, cachetype;
2802 struct msgs_array args = { 0, 0, NULL};
2803 pid_t child_id;
2804
2805 if ((urlprog = context_find(nmhaccessurl)) && *urlprog == '\0')
2806 urlprog = NULL;
2807
2808 if (! urlprog) {
2809 content_error(NULL, ct, "No entry for nmh-access-url in profile");
2810 return NOTOK;
2811 }
2812
2813 switch (openExternal(e->eb_parent, e->eb_content, ce, file, &fd)) {
2814 case NOTOK:
2815 return NOTOK;
2816
2817 case OK:
2818 break;
2819
2820 case DONE:
2821 return fd;
2822 }
2823
2824 if (!e->eb_url) {
2825 content_error(NULL, ct, "missing url parameter");
2826 return NOTOK;
2827 }
2828
2829 if (xpid) {
2830 if (xpid < 0)
2831 xpid = -xpid;
2832 pidcheck (pidwait (xpid, NOTOK));
2833 xpid = 0;
2834 }
2835
2836 ce->ce_unlink = (*file == NULL);
2837 caching = 0;
2838 cachefile[0] = '\0';
2839
2840 if (find_cache(NULL, wcachesw, &cachetype, e->eb_content->c_id,
2841 cachefile, sizeof(cachefile)) != NOTOK) {
2842 if (*file == NULL) {
2843 ce->ce_unlink = 0;
2844 caching = 1;
2845 }
2846 }
2847
2848 if (*file)
2849 ce->ce_file = add(*file, NULL);
2850 else if (caching)
2851 ce->ce_file = add(cachefile, NULL);
2852 else {
2853 char *tempfile;
2854 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2855 adios(NULL, "unable to create temporary file in %s",
2856 get_temp_dir());
2857 }
2858 ce->ce_file = add (tempfile, NULL);
2859 }
2860
2861 if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
2862 content_error(ce->ce_file, ct, "unable to fopen for read/writing");
2863 return NOTOK;
2864 }
2865
2866 switch (child_id = fork()) {
2867 case NOTOK:
2868 adios ("fork", "unable to");
2869 /* NOTREACHED */
2870
2871 case OK:
2872 argsplit_msgarg(&args, urlprog, &program);
2873 app_msgarg(&args, e->eb_url);
2874 app_msgarg(&args, NULL);
2875 dup2(fileno(ce->ce_fp), 1);
2876 close(fileno(ce->ce_fp));
2877 execvp(program, args.msgs);
2878 fprintf(stderr, "Unable to exec ");
2879 perror(program);
2880 _exit(-1);
2881 /* NOTREACHED */
2882
2883 default:
2884 if (pidXwait(child_id, NULL)) {
2885 ce->ce_unlink = 1;
2886 return NOTOK;
2887 }
2888 }
2889
2890 if (cachefile[0]) {
2891 if (caching)
2892 chmod(cachefile, cachetype ? m_gmprot() : 0444);
2893 else {
2894 int mask;
2895 FILE *fp;
2896
2897 mask = umask (cachetype ? ~m_gmprot() : 0222);
2898 if ((fp = fopen(cachefile, "w"))) {
2899 int cc;
2900 FILE *gp = ce->ce_fp;
2901
2902 fseeko(gp, 0, SEEK_SET);
2903
2904 while ((cc = fread(buffer, sizeof(*buffer),
2905 sizeof(buffer), gp)) > 0)
2906 fwrite(buffer, sizeof(*buffer), cc, fp);
2907
2908 fflush(fp);
2909
2910 if (ferror(gp)) {
2911 admonish(ce->ce_file, "error reading");
2912 (void) m_unlink (cachefile);
2913 }
2914 }
2915 umask(mask);
2916 }
2917 }
2918
2919 fseeko(ce->ce_fp, 0, SEEK_SET);
2920 *file = ce->ce_file;
2921 return fd;
2922 }
2923
2924 static int
2925 readDigest (CT ct, char *cp)
2926 {
2927 int bitno, skip;
2928 uint32_t bits;
2929 char *bp = cp;
2930 unsigned char *dp, value, *ep;
2931
2932 bitno = 18;
2933 bits = 0L;
2934 skip = 0;
2935
2936 for (ep = (dp = ct->c_digest)
2937 + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2938 switch (*cp) {
2939 default:
2940 if (skip
2941 || (*cp & 0x80)
2942 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2943 if (debugsw)
2944 fprintf (stderr, "invalid BASE64 encoding\n");
2945 return NOTOK;
2946 }
2947
2948 bits |= value << bitno;
2949 test_end:
2950 if ((bitno -= 6) < 0) {
2951 if (dp + (3 - skip) > ep)
2952 goto invalid_digest;
2953 *dp++ = (bits >> 16) & 0xff;
2954 if (skip < 2) {
2955 *dp++ = (bits >> 8) & 0xff;
2956 if (skip < 1)
2957 *dp++ = bits & 0xff;
2958 }
2959 bitno = 18;
2960 bits = 0L;
2961 skip = 0;
2962 }
2963 break;
2964
2965 case '=':
2966 if (++skip > 3)
2967 goto self_delimiting;
2968 goto test_end;
2969 }
2970 if (bitno != 18) {
2971 if (debugsw)
2972 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2973
2974 return NOTOK;
2975 }
2976 self_delimiting:
2977 if (dp != ep) {
2978 invalid_digest:
2979 if (debugsw) {
2980 while (*cp)
2981 cp++;
2982 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2983 (int)(cp - bp));
2984 }
2985
2986 return NOTOK;
2987 }
2988
2989 if (debugsw) {
2990 fprintf (stderr, "MD5 digest=");
2991 for (dp = ct->c_digest; dp < ep; dp++)
2992 fprintf (stderr, "%02x", *dp & 0xff);
2993 fprintf (stderr, "\n");
2994 }
2995
2996 return OK;
2997 }
2998
2999
3000 /* Multipart parts might have content before the first subpart and/or
3001 after the last subpart that hasn't been stored anywhere else, so do
3002 that. */
3003 int
3004 get_leftover_mp_content (CT ct, int before /* or after */) {
3005 struct multipart *m = (struct multipart *) ct->c_ctparams;
3006 char *boundary;
3007 int found_boundary = 0;
3008 char buffer[BUFSIZ];
3009 int max = BUFSIZ;
3010 int read = 0;
3011 char *content = NULL;
3012
3013 if (! m) return NOTOK;
3014
3015 if (before) {
3016 if (! m->mp_parts || ! m->mp_parts->mp_part) return NOTOK;
3017
3018 /* Isolate the beginning of this part to the beginning of the
3019 first subpart and save any content between them. */
3020 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
3021 max = m->mp_parts->mp_part->c_begin - ct->c_begin;
3022 boundary = concat ("--", m->mp_start, NULL);
3023 } else {
3024 struct part *last_subpart = NULL;
3025 struct part *subpart;
3026
3027 /* Go to the last subpart to get its end position. */
3028 for (subpart = m->mp_parts; subpart; subpart = subpart->mp_next) {
3029 last_subpart = subpart;
3030 }
3031
3032 if (last_subpart == NULL) return NOTOK;
3033
3034 /* Isolate the end of the last subpart to the end of this part
3035 and save any content between them. */
3036 fseeko (ct->c_fp, last_subpart->mp_part->c_end, SEEK_SET);
3037 max = ct->c_end - last_subpart->mp_part->c_end;
3038 boundary = concat ("--", m->mp_stop, NULL);
3039 }
3040
3041 /* Back up by 1 to pick up the newline. */
3042 while (fgets (buffer, sizeof(buffer) - 1, ct->c_fp)) {
3043 read += strlen (buffer);
3044 /* Don't look beyond beginning of first subpart (before) or
3045 next part (after). */
3046 if (read > max) buffer[read-max] = '\0';
3047
3048 if (before) {
3049 if (! strcmp (buffer, boundary)) {
3050 found_boundary = 1;
3051 }
3052 } else {
3053 if (! found_boundary && ! strcmp (buffer, boundary)) {
3054 found_boundary = 1;
3055 continue;
3056 }
3057 }
3058
3059 if ((before && ! found_boundary) || (! before && found_boundary)) {
3060 if (content) {
3061 char *old_content = content;
3062 content = concat (content, buffer, NULL);
3063 free (old_content);
3064 } else {
3065 content = before
3066 ? concat ("\n", buffer, NULL)
3067 : concat (buffer, NULL);
3068 }
3069 }
3070
3071 if (before) {
3072 if (found_boundary || read > max) break;
3073 } else {
3074 if (read > max) break;
3075 }
3076 }
3077
3078 /* Skip the newline if that's all there is. */
3079 if (content) {
3080 char *cp;
3081
3082 /* Remove trailing newline, except at EOF. */
3083 if ((before || ! feof (ct->c_fp)) &&
3084 (cp = content + strlen (content)) > content &&
3085 *--cp == '\n') {
3086 *cp = '\0';
3087 }
3088
3089 if (strlen (content) > 1) {
3090 if (before) {
3091 m->mp_content_before = content;
3092 } else {
3093 m->mp_content_after = content;
3094 }
3095 } else {
3096 free (content);
3097 }
3098 }
3099
3100 free (boundary);
3101
3102 return OK;
3103 }
3104
3105
3106 char *
3107 ct_type_str (int type) {
3108 switch (type) {
3109 case CT_APPLICATION:
3110 return "application";
3111 case CT_AUDIO:
3112 return "audio";
3113 case CT_IMAGE:
3114 return "image";
3115 case CT_MESSAGE:
3116 return "message";
3117 case CT_MULTIPART:
3118 return "multipart";
3119 case CT_TEXT:
3120 return "text";
3121 case CT_VIDEO:
3122 return "video";
3123 case CT_EXTENSION:
3124 return "extension";
3125 default:
3126 return "unknown_type";
3127 }
3128 }
3129
3130
3131 char *
3132 ct_subtype_str (int type, int subtype) {
3133 switch (type) {
3134 case CT_APPLICATION:
3135 switch (subtype) {
3136 case APPLICATION_OCTETS:
3137 return "octets";
3138 case APPLICATION_POSTSCRIPT:
3139 return "postscript";
3140 default:
3141 return "unknown_app_subtype";
3142 }
3143 case CT_MESSAGE:
3144 switch (subtype) {
3145 case MESSAGE_RFC822:
3146 return "rfc822";
3147 case MESSAGE_PARTIAL:
3148 return "partial";
3149 case MESSAGE_EXTERNAL:
3150 return "external";
3151 default:
3152 return "unknown_msg_subtype";
3153 }
3154 case CT_MULTIPART:
3155 switch (subtype) {
3156 case MULTI_MIXED:
3157 return "mixed";
3158 case MULTI_ALTERNATE:
3159 return "alternative";
3160 case MULTI_DIGEST:
3161 return "digest";
3162 case MULTI_PARALLEL:
3163 return "parallel";
3164 default:
3165 return "unknown_multipart_subtype";
3166 }
3167 case CT_TEXT:
3168 switch (subtype) {
3169 case TEXT_PLAIN:
3170 return "plain";
3171 case TEXT_RICHTEXT:
3172 return "richtext";
3173 case TEXT_ENRICHED:
3174 return "enriched";
3175 default:
3176 return "unknown_text_subtype";
3177 }
3178 default:
3179 return "unknown_type";
3180 }
3181 }
3182
3183
3184 /* Find the content type and InitFunc for the CT. */
3185 const struct str2init *
3186 get_ct_init (int type) {
3187 const struct str2init *sp;
3188
3189 for (sp = str2cts; sp->si_key; ++sp) {
3190 if (type == sp->si_val) {
3191 return sp;
3192 }
3193 }
3194
3195 return NULL;
3196 }
3197
3198 const char *
3199 ce_str (int encoding) {
3200 switch (encoding) {
3201 case CE_BASE64:
3202 return "base64";
3203 case CE_QUOTED:
3204 return "quoted-printable";
3205 case CE_8BIT:
3206 return "8bit";
3207 case CE_7BIT:
3208 return "7bit";
3209 case CE_BINARY:
3210 return "binary";
3211 case CE_EXTENSION:
3212 return "extension";
3213 case CE_EXTERNAL:
3214 return "external";
3215 default:
3216 return "unknown";
3217 }
3218 }
3219
3220 /* Find the content type and InitFunc for the content encoding method. */
3221 const struct str2init *
3222 get_ce_method (const char *method) {
3223 struct str2init *sp;
3224
3225 for (sp = str2ces; sp->si_key; ++sp) {
3226 if (! strcasecmp (method, sp->si_key)) {
3227 return sp;
3228 }
3229 }
3230
3231 return NULL;
3232 }
3233
3234 int
3235 parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci,
3236 int *status) {
3237 char **attr = ci->ci_attrs;
3238 char *cp = *header_attrp;
3239
3240 while (*cp == ';') {
3241 char *dp, *vp, *up, c;
3242
3243 /* Relies on knowledge of this declaration:
3244 * char *ci_attrs[NPARMS + 2];
3245 */
3246 if (attr >= ci->ci_attrs + sizeof ci->ci_attrs/sizeof (char *) - 2) {
3247 advise (NULL,
3248 "too many parameters in message %s's %s: field (%d max)",
3249 filename, TYPE_FIELD, NPARMS);
3250 *status = NOTOK;
3251 return NOTOK;
3252 }
3253
3254 cp++;
3255 while (isspace ((unsigned char) *cp))
3256 cp++;
3257
3258 if (*cp == '(' &&
3259 get_comment (filename, ci, &cp, 1) == NOTOK) {
3260 *status = NOTOK;
3261 return NOTOK;
3262 }
3263
3264 if (*cp == 0) {
3265 advise (NULL,
3266 "extraneous trailing ';' in message %s's %s: "
3267 "parameter list",
3268 filename, TYPE_FIELD);
3269 *status = OK;
3270 return NOTOK;
3271 }
3272
3273 /* down case the attribute name */
3274 for (dp = cp; istoken ((unsigned char) *dp); dp++)
3275 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
3276 *dp = tolower ((unsigned char) *dp);
3277
3278 for (up = dp; isspace ((unsigned char) *dp);)
3279 dp++;
3280 if (dp == cp || *dp != '=') {
3281 advise (NULL,
3282 "invalid parameter in message %s's %s: "
3283 "field\n%*.*sparameter %s (error detected at offset %d)",
3284 filename, TYPE_FIELD, len, len, "", cp, dp - cp);
3285 *status = NOTOK;
3286 return NOTOK;
3287 }
3288
3289 vp = (*attr = add (cp, NULL)) + (up - cp);
3290 *vp = '\0';
3291 for (dp++; isspace ((unsigned char) *dp);)
3292 dp++;
3293
3294 /* Now store the attribute value. */
3295 ci->ci_values[attr - ci->ci_attrs] = vp = *attr + (dp - cp);
3296
3297 if (*dp == '"') {
3298 for (cp = ++dp, dp = vp;;) {
3299 switch (c = *cp++) {
3300 case '\0':
3301 bad_quote:
3302 advise (NULL,
3303 "invalid quoted-string in message %s's %s: "
3304 "field\n%*.*s(parameter %s)",
3305 filename, TYPE_FIELD, len, len, "", *attr);
3306 *status = NOTOK;
3307 return NOTOK;
3308
3309 case '\\':
3310 *dp++ = c;
3311 if ((c = *cp++) == '\0')
3312 goto bad_quote;
3313 /* else fall... */
3314
3315 default:
3316 *dp++ = c;
3317 continue;
3318
3319 case '"':
3320 *dp = '\0';
3321 break;
3322 }
3323 break;
3324 }
3325 } else {
3326 for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
3327 continue;
3328 *dp = '\0';
3329 }
3330 if (!*vp) {
3331 advise (NULL,
3332 "invalid parameter in message %s's %s: "
3333 "field\n%*.*s(parameter %s)",
3334 filename, TYPE_FIELD, len, len, "", *attr);
3335 *status = NOTOK;
3336 return NOTOK;
3337 }
3338
3339 while (isspace ((unsigned char) *cp))
3340 cp++;
3341
3342 if (*cp == '(' &&
3343 get_comment (filename, ci, &cp, 1) == NOTOK) {
3344 *status = NOTOK;
3345 return NOTOK;
3346 }
3347
3348 ++attr;
3349 }
3350
3351 *header_attrp = cp;
3352 return OK;
3353 }
3354
3355
3356 char *
3357 content_charset (CT ct) {
3358 const char *const charset = "charset";
3359 char *default_charset = NULL;
3360 CI ctinfo = &ct->c_ctinfo;
3361 char **ap, **vp;
3362 char **src_charset = NULL;
3363
3364 for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) {
3365 if (! strcasecmp (*ap, charset)) {
3366 src_charset = vp;
3367 break;
3368 }
3369 }
3370
3371 /* RFC 2045, Sec. 5.2: default to us-ascii. */
3372 if (src_charset == NULL) src_charset = &default_charset;
3373 if (*src_charset == NULL) *src_charset = "US-ASCII";
3374
3375 return *src_charset;
3376 }
3377
3378
3379 /* Change the value of a name=value pair in a header field body.
3380 If the name isn't there, append them. In any case, a new
3381 string will be allocated and must be free'd by the caller.
3382 Trims any trailing newlines. */
3383 char *
3384 update_attr (char *body, const char *name, const char *value) {
3385 char *bp = nmh_strcasestr (body, name);
3386 char *new_body;
3387
3388 if (bp) {
3389 char *other_attrs = strchr (bp, ';');
3390
3391 *(bp + strlen (name)) = '\0';
3392 new_body = concat (body, "\"", value, "\"", NULL);
3393
3394 if (other_attrs) {
3395 char *cp;
3396
3397 /* Trim any trailing newlines. */
3398 for (cp = &other_attrs[strlen (other_attrs) - 1];
3399 cp > other_attrs && *cp == '\n';
3400 *cp-- = '\0') continue;
3401 new_body = add (other_attrs, new_body);
3402 }
3403 } else {
3404 char *cp;
3405
3406 /* Append name/value pair, after first removing a final newline
3407 and (extraneous) semicolon. */
3408 if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0';
3409 if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0';
3410 new_body = concat (body, "; ", name, "\"", value, "\"", NULL);
3411 }
3412
3413 return new_body;
3414 }