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