]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Remove any enclosing quotes from a timezone identifier in an
[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/md5.h>
13 #include <h/mts.h>
14 #include <h/tws.h>
15 #include <h/mime.h>
16 #include <h/mhparse.h>
17 #include <h/utils.h>
18 #ifdef HAVE_ICONV
19 # include <iconv.h>
20 #endif /* HAVE_ICONV */
21
22
23 extern int debugsw;
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 * 3) Suppress the warning about extraneous trailing ';' in header parameter
37 * lists, and report it.
38 */
39 int skip_mp_cte_check;
40 int suppress_bogus_mp_content_warning;
41 int bogus_mp_content;
42 int suppress_extraneous_trailing_semicolon_warning;
43 int extraneous_trailing_semicolon;
44 int suppress_multiple_mime_version_warning = 1;
45
46 /* list of preferred type/subtype pairs, for -prefer */
47 char *preferred_types[NPREFS],
48 *preferred_subtypes[NPREFS];
49 int npreferred;
50
51
52 /*
53 * Structures for TEXT messages
54 */
55 struct k2v SubText[] = {
56 { "plain", TEXT_PLAIN },
57 { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */
58 { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */
59 { NULL, TEXT_UNKNOWN } /* this one must be last! */
60 };
61
62 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
63
64 /*
65 * Structures for MULTIPART messages
66 */
67 struct k2v SubMultiPart[] = {
68 { "mixed", MULTI_MIXED },
69 { "alternative", MULTI_ALTERNATE },
70 { "digest", MULTI_DIGEST },
71 { "parallel", MULTI_PARALLEL },
72 { "related", MULTI_RELATED },
73 { NULL, MULTI_UNKNOWN } /* this one must be last! */
74 };
75
76 /*
77 * Structures for MESSAGE messages
78 */
79 struct k2v SubMessage[] = {
80 { "rfc822", MESSAGE_RFC822 },
81 { "partial", MESSAGE_PARTIAL },
82 { "external-body", MESSAGE_EXTERNAL },
83 { NULL, MESSAGE_UNKNOWN } /* this one must be last! */
84 };
85
86 /*
87 * Structure for APPLICATION messages
88 */
89 struct k2v SubApplication[] = {
90 { "octet-stream", APPLICATION_OCTETS },
91 { "postscript", APPLICATION_POSTSCRIPT },
92 { NULL, APPLICATION_UNKNOWN } /* this one must be last! */
93 };
94
95 /*
96 * Mapping of names of CTE types in mhbuild directives
97 */
98 static struct k2v EncodingType[] = {
99 { "8bit", CE_8BIT },
100 { "qp", CE_QUOTED },
101 { "q-p", CE_QUOTED },
102 { "quoted-printable", CE_QUOTED },
103 { "b64", CE_BASE64 },
104 { "base64", CE_BASE64 },
105 { NULL, 0 },
106 };
107
108
109 /* mhcachesbr.c */
110 int find_cache (CT, int, int *, char *, char *, int);
111
112 /* mhmisc.c */
113 int part_ok (CT);
114 int type_ok (CT, int);
115 void content_error (char *, CT, char *, ...);
116
117 /* mhfree.c */
118 void free_encoding (CT, int);
119
120 /*
121 * static prototypes
122 */
123 static CT get_content (FILE *, char *, int);
124 static int get_comment (const char *, const char *, char **, char **);
125
126 static int InitGeneric (CT);
127 static int InitText (CT);
128 static int InitMultiPart (CT);
129 static void reverse_parts (CT);
130 static void prefer_parts(CT ct);
131 static int InitMessage (CT);
132 static int InitApplication (CT);
133 static int init_encoding (CT, OpenCEFunc);
134 static unsigned long size_encoding (CT);
135 static int InitBase64 (CT);
136 static int openBase64 (CT, char **);
137 static int InitQuoted (CT);
138 static int openQuoted (CT, char **);
139 static int Init7Bit (CT);
140 static int openExternal (CT, CT, CE, char **, int *);
141 static int InitFile (CT);
142 static int openFile (CT, char **);
143 static int InitFTP (CT);
144 static int openFTP (CT, char **);
145 static int InitMail (CT);
146 static int openMail (CT, char **);
147 static int readDigest (CT, char *);
148 static int get_leftover_mp_content (CT, int);
149 static int InitURL (CT);
150 static int openURL (CT, char **);
151 static int parse_header_attrs (const char *, const char *, char **, PM *,
152 PM *, char **);
153 static size_t param_len(PM, int, size_t, int *, int *, size_t *);
154 static size_t encode_param(PM, char *, size_t, size_t, size_t, int);
155 static size_t normal_param(PM, char *, size_t, size_t, size_t);
156 static int get_dispo (char *, CT, int);
157
158 struct str2init str2cts[] = {
159 { "application", CT_APPLICATION, InitApplication },
160 { "audio", CT_AUDIO, InitGeneric },
161 { "image", CT_IMAGE, InitGeneric },
162 { "message", CT_MESSAGE, InitMessage },
163 { "multipart", CT_MULTIPART, InitMultiPart },
164 { "text", CT_TEXT, InitText },
165 { "video", CT_VIDEO, InitGeneric },
166 { NULL, CT_EXTENSION, NULL }, /* these two must be last! */
167 { NULL, CT_UNKNOWN, NULL },
168 };
169
170 struct str2init str2ces[] = {
171 { "base64", CE_BASE64, InitBase64 },
172 { "quoted-printable", CE_QUOTED, InitQuoted },
173 { "8bit", CE_8BIT, Init7Bit },
174 { "7bit", CE_7BIT, Init7Bit },
175 { "binary", CE_BINARY, Init7Bit },
176 { NULL, CE_EXTENSION, NULL }, /* these two must be last! */
177 { NULL, CE_UNKNOWN, NULL },
178 };
179
180 /*
181 * NOTE WELL: si_key MUST NOT have value of NOTOK
182 *
183 * si_key is 1 if access method is anonymous.
184 */
185 struct str2init str2methods[] = {
186 { "afs", 1, InitFile },
187 { "anon-ftp", 1, InitFTP },
188 { "ftp", 0, InitFTP },
189 { "local-file", 0, InitFile },
190 { "mail-server", 0, InitMail },
191 { "url", 0, InitURL },
192 { NULL, 0, NULL }
193 };
194
195
196 /*
197 * Main entry point for parsing a MIME message or file.
198 * It returns the Content structure for the top level
199 * entity in the file.
200 */
201
202 CT
203 parse_mime (char *file)
204 {
205 int is_stdin;
206 char buffer[BUFSIZ];
207 FILE *fp;
208 CT ct;
209 size_t n;
210
211 /*
212 * Check if file is actually standard input
213 */
214 if ((is_stdin = !(strcmp (file, "-")))) {
215 char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
216 if (tfile == NULL) {
217 advise("mhparse", "unable to create temporary file in %s",
218 get_temp_dir());
219 return NULL;
220 }
221 file = add (tfile, NULL);
222
223 while ((n = fread(buffer, 1, sizeof(buffer), stdin)) > 0) {
224 if (fwrite(buffer, 1, n, fp) != n) {
225 (void) m_unlink (file);
226 advise (file, "error copying to temporary file");
227 return NULL;
228 }
229 }
230 fflush (fp);
231
232 if (ferror (stdin)) {
233 (void) m_unlink (file);
234 advise ("stdin", "error reading");
235 return NULL;
236 }
237 if (ferror (fp)) {
238 (void) m_unlink (file);
239 advise (file, "error writing");
240 return NULL;
241 }
242 fseek (fp, 0L, SEEK_SET);
243 } else if ((fp = fopen (file, "r")) == NULL) {
244 advise (file, "unable to read");
245 return NULL;
246 }
247
248 if (!(ct = get_content (fp, file, 1))) {
249 if (is_stdin)
250 (void) m_unlink (file);
251 advise (NULL, "unable to decode %s", file);
252 return NULL;
253 }
254
255 if (is_stdin)
256 ct->c_unlink = 1; /* temp file to remove */
257
258 ct->c_fp = NULL;
259
260 if (ct->c_end == 0L) {
261 fseek (fp, 0L, SEEK_END);
262 ct->c_end = ftell (fp);
263 }
264
265 if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
266 fclose (fp);
267 free_content (ct);
268 return NULL;
269 }
270
271 fclose (fp);
272 return ct;
273 }
274
275
276 /*
277 * Main routine for reading/parsing the headers
278 * of a message content.
279 *
280 * toplevel = 1 # we are at the top level of the message
281 * toplevel = 0 # we are inside message type or multipart type
282 * # other than multipart/digest
283 * toplevel = -1 # we are inside multipart/digest
284 * NB: on failure we will fclose(in)!
285 */
286
287 static CT
288 get_content (FILE *in, char *file, int toplevel)
289 {
290 int compnum, state;
291 char buf[BUFSIZ], name[NAMESZ];
292 char *np, *vp;
293 CT ct;
294 HF hp;
295 m_getfld_state_t gstate = 0;
296
297 /* allocate the content structure */
298 if (!(ct = (CT) mh_xcalloc (1, sizeof(*ct))))
299 adios (NULL, "out of memory");
300
301 ct->c_fp = in;
302 ct->c_file = add (file, NULL);
303 ct->c_begin = ftell (ct->c_fp) + 1;
304
305 /*
306 * Parse the header fields for this
307 * content into a linked list.
308 */
309 m_getfld_track_filepos (&gstate, in);
310 for (compnum = 1;;) {
311 int bufsz = sizeof buf;
312 switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
313 case FLD:
314 case FLDPLUS:
315 compnum++;
316
317 /* get copies of the buffers */
318 np = add (name, NULL);
319 vp = add (buf, NULL);
320
321 /* if necessary, get rest of field */
322 while (state == FLDPLUS) {
323 bufsz = sizeof buf;
324 state = m_getfld (&gstate, name, buf, &bufsz, in);
325 vp = add (buf, vp); /* add to previous value */
326 }
327
328 /* Now add the header data to the list */
329 add_header (ct, np, vp);
330
331 /* continue, to see if this isn't the last header field */
332 ct->c_begin = ftell (in) + 1;
333 continue;
334
335 case BODY:
336 ct->c_begin = ftell (in) - strlen (buf);
337 break;
338
339 case FILEEOF:
340 ct->c_begin = ftell (in);
341 break;
342
343 case LENERR:
344 case FMTERR:
345 adios (NULL, "message format error in component #%d", compnum);
346
347 default:
348 adios (NULL, "getfld() returned %d", state);
349 }
350
351 /* break out of the loop */
352 break;
353 }
354 m_getfld_state_destroy (&gstate);
355
356 /*
357 * Read the content headers. We will parse the
358 * MIME related header fields into their various
359 * structures and set internal flags related to
360 * content type/subtype, etc.
361 */
362
363 hp = ct->c_first_hf; /* start at first header field */
364 while (hp) {
365 /* Get MIME-Version field */
366 if (!strcasecmp (hp->name, VRSN_FIELD)) {
367 int ucmp;
368 char c, *cp, *dp;
369 char *vrsn;
370
371 vrsn = add (hp->value, NULL);
372
373 /* Now, cleanup this field */
374 cp = vrsn;
375
376 while (isspace ((unsigned char) *cp))
377 cp++;
378 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
379 *dp++ = ' ';
380 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
381 if (!isspace ((unsigned char) *dp))
382 break;
383 *++dp = '\0';
384 if (debugsw)
385 fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
386
387 if (*cp == '(' &&
388 get_comment (ct->c_file, VRSN_FIELD, &cp, NULL) == NOTOK)
389 goto out;
390
391 for (dp = cp; istoken (*dp); dp++)
392 continue;
393 c = *dp;
394 *dp = '\0';
395 ucmp = !strcasecmp (cp, VRSN_VALUE);
396 *dp = c;
397 if (!ucmp) {
398 admonish (NULL, "message %s has unknown value for %s: field (%s)",
399 ct->c_file, VRSN_FIELD, cp);
400 }
401 if (!ct->c_vrsn) {
402 ct->c_vrsn = vrsn;
403 } else {
404 if (! suppress_multiple_mime_version_warning)
405 advise (NULL, "message %s has multiple %s: fields",
406 ct->c_file, VRSN_FIELD);
407 free(vrsn);
408 }
409 }
410 else if (!strcasecmp (hp->name, TYPE_FIELD)) {
411 /* Get Content-Type field */
412 struct str2init *s2i;
413 CI ci = &ct->c_ctinfo;
414
415 /* Check if we've already seen a Content-Type header */
416 if (ct->c_ctline) {
417 advise (NULL, "message %s has multiple %s: fields",
418 ct->c_file, TYPE_FIELD);
419 goto next_header;
420 }
421
422 /* Parse the Content-Type field */
423 if (get_ctinfo (hp->value, ct, 0) == NOTOK)
424 goto out;
425
426 /*
427 * Set the Init function and the internal
428 * flag for this content type.
429 */
430 for (s2i = str2cts; s2i->si_key; s2i++)
431 if (!strcasecmp (ci->ci_type, s2i->si_key))
432 break;
433 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
434 s2i++;
435 ct->c_type = s2i->si_val;
436 ct->c_ctinitfnx = s2i->si_init;
437 }
438 else if (!strcasecmp (hp->name, ENCODING_FIELD)) {
439 /* Get Content-Transfer-Encoding field */
440 char c, *cp, *dp;
441 struct str2init *s2i;
442
443 /*
444 * Check if we've already seen the
445 * Content-Transfer-Encoding field
446 */
447 if (ct->c_celine) {
448 advise (NULL, "message %s has multiple %s: fields",
449 ct->c_file, ENCODING_FIELD);
450 goto next_header;
451 }
452
453 /* get copy of this field */
454 ct->c_celine = cp = add (hp->value, NULL);
455
456 while (isspace ((unsigned char) *cp))
457 cp++;
458 for (dp = cp; istoken (*dp); dp++)
459 continue;
460 c = *dp;
461 *dp = '\0';
462
463 /*
464 * Find the internal flag and Init function
465 * for this transfer encoding.
466 */
467 for (s2i = str2ces; s2i->si_key; s2i++)
468 if (!strcasecmp (cp, s2i->si_key))
469 break;
470 if (!s2i->si_key && !uprf (cp, "X-"))
471 s2i++;
472 *dp = c;
473 ct->c_encoding = s2i->si_val;
474
475 /* Call the Init function for this encoding */
476 if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
477 goto out;
478 }
479 else if (!strcasecmp (hp->name, MD5_FIELD)) {
480 /* Get Content-MD5 field */
481 char *cp, *dp, *ep;
482
483 if (!checksw)
484 goto next_header;
485
486 if (ct->c_digested) {
487 advise (NULL, "message %s has multiple %s: fields",
488 ct->c_file, MD5_FIELD);
489 goto next_header;
490 }
491
492 ep = cp = add (hp->value, NULL); /* get a copy */
493
494 while (isspace ((unsigned char) *cp))
495 cp++;
496 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
497 *dp++ = ' ';
498 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
499 if (!isspace ((unsigned char) *dp))
500 break;
501 *++dp = '\0';
502 if (debugsw)
503 fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
504
505 if (*cp == '(' &&
506 get_comment (ct->c_file, MD5_FIELD, &cp, NULL) == NOTOK) {
507 free (ep);
508 goto out;
509 }
510
511 for (dp = cp; *dp && !isspace ((unsigned char) *dp); dp++)
512 continue;
513 *dp = '\0';
514
515 readDigest (ct, cp);
516 free (ep);
517 ct->c_digested++;
518 }
519 else if (!strcasecmp (hp->name, ID_FIELD)) {
520 /* Get Content-ID field */
521 ct->c_id = add (hp->value, ct->c_id);
522 }
523 else if (!strcasecmp (hp->name, DESCR_FIELD)) {
524 /* Get Content-Description field */
525 ct->c_descr = add (hp->value, ct->c_descr);
526 }
527 else if (!strcasecmp (hp->name, DISPO_FIELD)) {
528 /* Get Content-Disposition field */
529 if (get_dispo(hp->value, ct, 0) == NOTOK)
530 goto out;
531 }
532
533 next_header:
534 hp = hp->next; /* next header field */
535 }
536
537 /*
538 * Check if we saw a Content-Type field.
539 * If not, then assign a default value for
540 * it, and the Init function.
541 */
542 if (!ct->c_ctline) {
543 /*
544 * If we are inside a multipart/digest message,
545 * so default type is message/rfc822
546 */
547 if (toplevel < 0) {
548 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
549 goto out;
550 ct->c_type = CT_MESSAGE;
551 ct->c_ctinitfnx = InitMessage;
552 } else {
553 /*
554 * Else default type is text/plain
555 */
556 if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
557 goto out;
558 ct->c_type = CT_TEXT;
559 ct->c_ctinitfnx = InitText;
560 }
561 }
562
563 /* Use default Transfer-Encoding, if necessary */
564 if (!ct->c_celine) {
565 ct->c_encoding = CE_7BIT;
566 Init7Bit (ct);
567 }
568
569 return ct;
570
571 out:
572 free_content (ct);
573 return NULL;
574 }
575
576
577 /*
578 * small routine to add header field to list
579 */
580
581 int
582 add_header (CT ct, char *name, char *value)
583 {
584 HF hp;
585
586 /* allocate header field structure */
587 hp = mh_xmalloc (sizeof(*hp));
588
589 /* link data into header structure */
590 hp->name = name;
591 hp->value = value;
592 hp->next = NULL;
593
594 /* link header structure into the list */
595 if (ct->c_first_hf == NULL) {
596 ct->c_first_hf = hp; /* this is the first */
597 ct->c_last_hf = hp;
598 } else {
599 ct->c_last_hf->next = hp; /* add it to the end */
600 ct->c_last_hf = hp;
601 }
602
603 return 0;
604 }
605
606
607 /*
608 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
609 * directives. Fills in the information of the CTinfo structure.
610 */
611 int
612 get_ctinfo (char *cp, CT ct, int magic)
613 {
614 char *dp;
615 char c;
616 CI ci;
617 int status;
618
619 ci = &ct->c_ctinfo;
620
621 /* store copy of Content-Type line */
622 cp = ct->c_ctline = add (cp, NULL);
623
624 while (isspace ((unsigned char) *cp)) /* trim leading spaces */
625 cp++;
626
627 /* change newlines to spaces */
628 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
629 *dp++ = ' ';
630
631 /* trim trailing spaces */
632 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
633 if (!isspace ((unsigned char) *dp))
634 break;
635 *++dp = '\0';
636
637 if (debugsw)
638 fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
639
640 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
641 &ci->ci_comment) == NOTOK)
642 return NOTOK;
643
644 for (dp = cp; istoken (*dp); dp++)
645 continue;
646 c = *dp, *dp = '\0';
647 ci->ci_type = add (cp, NULL); /* store content type */
648 *dp = c, cp = dp;
649
650 if (!*ci->ci_type) {
651 advise (NULL, "invalid %s: field in message %s (empty type)",
652 TYPE_FIELD, ct->c_file);
653 return NOTOK;
654 }
655
656 /* down case the content type string */
657 for (dp = ci->ci_type; *dp; dp++)
658 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
659 *dp = tolower ((unsigned char) *dp);
660
661 while (isspace ((unsigned char) *cp))
662 cp++;
663
664 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
665 &ci->ci_comment) == NOTOK)
666 return NOTOK;
667
668 if (*cp != '/') {
669 if (!magic)
670 ci->ci_subtype = add ("", NULL);
671 goto magic_skip;
672 }
673
674 cp++;
675 while (isspace ((unsigned char) *cp))
676 cp++;
677
678 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
679 &ci->ci_comment) == NOTOK)
680 return NOTOK;
681
682 for (dp = cp; istoken (*dp); dp++)
683 continue;
684 c = *dp, *dp = '\0';
685 ci->ci_subtype = add (cp, NULL); /* store the content subtype */
686 *dp = c, cp = dp;
687
688 if (!*ci->ci_subtype) {
689 advise (NULL,
690 "invalid %s: field in message %s (empty subtype for \"%s\")",
691 TYPE_FIELD, ct->c_file, ci->ci_type);
692 return NOTOK;
693 }
694
695 /* down case the content subtype string */
696 for (dp = ci->ci_subtype; *dp; dp++)
697 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
698 *dp = tolower ((unsigned char) *dp);
699
700 magic_skip:
701 while (isspace ((unsigned char) *cp))
702 cp++;
703
704 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
705 &ci->ci_comment) == NOTOK)
706 return NOTOK;
707
708 if ((status = parse_header_attrs (ct->c_file, TYPE_FIELD, &cp,
709 &ci->ci_first_pm, &ci->ci_last_pm,
710 &ci->ci_comment)) != OK) {
711 return status == NOTOK ? NOTOK : OK;
712 }
713
714 /*
715 * Get any <Content-Id> given in buffer
716 */
717 if (magic && *cp == '<') {
718 if (ct->c_id) {
719 free (ct->c_id);
720 ct->c_id = NULL;
721 }
722 if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
723 advise (NULL, "invalid ID in message %s", ct->c_file);
724 return NOTOK;
725 }
726 c = *dp;
727 *dp = '\0';
728 if (*ct->c_id)
729 ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
730 else
731 ct->c_id = NULL;
732 *dp++ = c;
733 cp = dp;
734
735 while (isspace ((unsigned char) *cp))
736 cp++;
737 }
738
739 /*
740 * Get any [Content-Description] given in buffer.
741 */
742 if (magic && *cp == '[') {
743 ct->c_descr = ++cp;
744 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
745 if (*dp == ']')
746 break;
747 if (dp < cp) {
748 advise (NULL, "invalid description in message %s", ct->c_file);
749 ct->c_descr = NULL;
750 return NOTOK;
751 }
752
753 c = *dp;
754 *dp = '\0';
755 if (*ct->c_descr)
756 ct->c_descr = concat (ct->c_descr, "\n", NULL);
757 else
758 ct->c_descr = NULL;
759 *dp++ = c;
760 cp = dp;
761
762 while (isspace ((unsigned char) *cp))
763 cp++;
764 }
765
766 /*
767 * Get any {Content-Disposition} given in buffer.
768 */
769 if (magic && *cp == '{') {
770 ++cp;
771 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
772 if (*dp == '}')
773 break;
774 if (dp < cp) {
775 advise (NULL, "invalid disposition in message %s", ct->c_file);
776 ct->c_dispo = NULL;
777 return NOTOK;
778 }
779
780 c = *dp;
781 *dp = '\0';
782
783 if (get_dispo(cp, ct, 1) != OK)
784 return NOTOK;
785
786 *dp++ = c;
787 cp = dp;
788
789 while (isspace ((unsigned char) *cp))
790 cp++;
791 }
792
793 /*
794 * Get any extension directives (right now just the content transfer
795 * encoding, but maybe others) that we care about.
796 */
797
798 if (magic && *cp == '*') {
799 /*
800 * See if it's a CTE we match on
801 */
802 struct k2v *kv;
803
804 dp = ++cp;
805 while (*cp != '\0' && ! isspace((unsigned char) *cp))
806 cp++;
807
808 if (dp == cp) {
809 advise (NULL, "invalid null transfer encoding specification");
810 return NOTOK;
811 }
812
813 if (*cp != '\0')
814 *cp++ = '\0';
815
816 ct->c_reqencoding = CE_UNKNOWN;
817
818 for (kv = EncodingType; kv->kv_key; kv++) {
819 if (strcasecmp(kv->kv_key, dp) == 0) {
820 ct->c_reqencoding = kv->kv_value;
821 break;
822 }
823 }
824
825 if (ct->c_reqencoding == CE_UNKNOWN) {
826 advise (NULL, "invalid CTE specification: \"%s\"", dp);
827 return NOTOK;
828 }
829
830 while (isspace ((unsigned char) *cp))
831 cp++;
832 }
833
834 /*
835 * Check if anything is left over
836 */
837 if (*cp) {
838 if (magic) {
839 ci->ci_magic = add (cp, NULL);
840
841 /* If there is a Content-Disposition header and it doesn't
842 have a *filename=, extract it from the magic contents.
843 The r1bindex call skips any leading directory
844 components. */
845 if (ct->c_dispo_type &&
846 !get_param(ct->c_dispo_first, "filename", '_', 1)) {
847 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename",
848 r1bindex(ci->ci_magic, '/'), 0);
849 }
850 }
851 else
852 advise (NULL,
853 "extraneous information in message %s's %s: field\n%*s(%s)",
854 ct->c_file, TYPE_FIELD, strlen(invo_name) + 2, "", cp);
855 }
856
857 return OK;
858 }
859
860
861 /*
862 * Parse out a Content-Disposition header. A lot of this is cribbed from
863 * get_ctinfo().
864 */
865 static int
866 get_dispo (char *cp, CT ct, int buildflag)
867 {
868 char *dp, *dispoheader;
869 char c;
870 int status;
871
872 /*
873 * Save the whole copy of the Content-Disposition header, unless we're
874 * processing a mhbuild directive. A NULL c_dispo will be a flag to
875 * mhbuild that the disposition header needs to be generated at that
876 * time.
877 */
878
879 dispoheader = cp = add(cp, NULL);
880
881 while (isspace ((unsigned char) *cp)) /* trim leading spaces */
882 cp++;
883
884 /* change newlines to spaces */
885 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
886 *dp++ = ' ';
887
888 /* trim trailing spaces */
889 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
890 if (!isspace ((unsigned char) *dp))
891 break;
892 *++dp = '\0';
893
894 if (debugsw)
895 fprintf (stderr, "%s: %s\n", DISPO_FIELD, cp);
896
897 if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) ==
898 NOTOK) {
899 free(dispoheader);
900 return NOTOK;
901 }
902
903 for (dp = cp; istoken (*dp); dp++)
904 continue;
905 c = *dp, *dp = '\0';
906 ct->c_dispo_type = add (cp, NULL); /* store disposition type */
907 *dp = c, cp = dp;
908
909 if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) == NOTOK)
910 return NOTOK;
911
912 if ((status = parse_header_attrs (ct->c_file, DISPO_FIELD, &cp,
913 &ct->c_dispo_first, &ct->c_dispo_last,
914 NULL)) != OK) {
915 if (status == NOTOK) {
916 free(dispoheader);
917 return NOTOK;
918 }
919 } else if (*cp) {
920 advise (NULL,
921 "extraneous information in message %s's %s: field\n%*s(%s)",
922 ct->c_file, DISPO_FIELD, strlen(invo_name) + 2, "", cp);
923 }
924
925 if (buildflag)
926 free(dispoheader);
927 else
928 ct->c_dispo = dispoheader;
929
930 return OK;
931 }
932
933
934 static int
935 get_comment (const char *filename, const char *fieldname, char **ap,
936 char **commentp)
937 {
938 int i;
939 char *bp, *cp;
940 char c, buffer[BUFSIZ], *dp;
941
942 cp = *ap;
943 bp = buffer;
944 cp++;
945
946 for (i = 0;;) {
947 switch (c = *cp++) {
948 case '\0':
949 invalid:
950 advise (NULL, "invalid comment in message %s's %s: field",
951 filename, fieldname);
952 return NOTOK;
953
954 case '\\':
955 *bp++ = c;
956 if ((c = *cp++) == '\0')
957 goto invalid;
958 *bp++ = c;
959 continue;
960
961 case '(':
962 i++;
963 /* and fall... */
964 default:
965 *bp++ = c;
966 continue;
967
968 case ')':
969 if (--i < 0)
970 break;
971 *bp++ = c;
972 continue;
973 }
974 break;
975 }
976 *bp = '\0';
977
978 if (commentp) {
979 if ((dp = *commentp)) {
980 *commentp = concat (dp, " ", buffer, NULL);
981 free (dp);
982 } else {
983 *commentp = add (buffer, NULL);
984 }
985 }
986
987 while (isspace ((unsigned char) *cp))
988 cp++;
989
990 *ap = cp;
991 return OK;
992 }
993
994
995 /*
996 * CONTENTS
997 *
998 * Handles content types audio, image, and video.
999 * There's not much to do right here.
1000 */
1001
1002 static int
1003 InitGeneric (CT ct)
1004 {
1005 NMH_UNUSED (ct);
1006
1007 return OK; /* not much to do here */
1008 }
1009
1010
1011 /*
1012 * TEXT
1013 */
1014
1015 static int
1016 InitText (CT ct)
1017 {
1018 char buffer[BUFSIZ];
1019 char *chset = NULL;
1020 char *cp;
1021 PM pm;
1022 struct text *t;
1023 CI ci = &ct->c_ctinfo;
1024
1025 /* check for missing subtype */
1026 if (!*ci->ci_subtype)
1027 ci->ci_subtype = add ("plain", ci->ci_subtype);
1028
1029 /* match subtype */
1030 ct->c_subtype = ct_str_subtype (CT_TEXT, ci->ci_subtype);
1031
1032 /* allocate text character set structure */
1033 if ((t = (struct text *) mh_xcalloc (1, sizeof(*t))) == NULL)
1034 adios (NULL, "out of memory");
1035 ct->c_ctparams = (void *) t;
1036
1037 /* scan for charset parameter */
1038 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next)
1039 if (!strcasecmp (pm->pm_name, "charset"))
1040 break;
1041
1042 /* check if content specified a character set */
1043 if (pm) {
1044 chset = pm->pm_value;
1045 t->tx_charset = CHARSET_SPECIFIED;
1046 } else {
1047 t->tx_charset = CHARSET_UNSPECIFIED;
1048 }
1049
1050 /*
1051 * If we can not handle character set natively,
1052 * then check profile for string to modify the
1053 * terminal or display method.
1054 *
1055 * termproc is for mhshow, though mhlist -debug prints it, too.
1056 */
1057 if (chset != NULL && !check_charset (chset, strlen (chset))) {
1058 snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
1059 if ((cp = context_find (buffer)))
1060 ct->c_termproc = getcpy (cp);
1061 }
1062
1063 return OK;
1064 }
1065
1066
1067 /*
1068 * MULTIPART
1069 */
1070
1071 static int
1072 InitMultiPart (CT ct)
1073 {
1074 int inout;
1075 long last, pos;
1076 char *cp, *dp;
1077 PM pm;
1078 char *bp;
1079 char *bufp = NULL;
1080 size_t buflen;
1081 ssize_t gotlen;
1082 struct multipart *m;
1083 struct part *part, **next;
1084 CI ci = &ct->c_ctinfo;
1085 CT p;
1086 FILE *fp;
1087
1088 /*
1089 * The encoding for multipart messages must be either
1090 * 7bit, 8bit, or binary (per RFC2045).
1091 */
1092 if (! skip_mp_cte_check && ct->c_encoding != CE_7BIT &&
1093 ct->c_encoding != CE_8BIT && ct->c_encoding != CE_BINARY) {
1094 /* Copy the Content-Transfer-Encoding header field body so we can
1095 remove any trailing whitespace and leading blanks from it. */
1096 char *cte = add (ct->c_celine ? ct->c_celine : "(null)", NULL);
1097
1098 bp = cte + strlen (cte) - 1;
1099 while (bp >= cte && isspace ((unsigned char) *bp)) *bp-- = '\0';
1100 for (bp = cte; *bp && isblank ((unsigned char) *bp); ++bp) continue;
1101
1102 admonish (NULL,
1103 "\"%s/%s\" type in message %s must be encoded in\n"
1104 "7bit, 8bit, or binary, per RFC 2045 (6.4). One workaround "
1105 "is to\nmanually edit the file and change the \"%s\"\n"
1106 "Content-Transfer-Encoding to one of those. For now",
1107 ci->ci_type, ci->ci_subtype, ct->c_file, bp);
1108 free (cte);
1109
1110 return NOTOK;
1111 }
1112
1113 /* match subtype */
1114 ct->c_subtype = ct_str_subtype (CT_MULTIPART, ci->ci_subtype);
1115
1116 /*
1117 * Check for "boundary" parameter, which is
1118 * required for multipart messages.
1119 */
1120 bp = 0;
1121 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1122 if (!strcasecmp (pm->pm_name, "boundary")) {
1123 bp = pm->pm_value;
1124 break;
1125 }
1126 }
1127
1128 /* complain if boundary parameter is missing */
1129 if (!pm) {
1130 advise (NULL,
1131 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1132 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1133 return NOTOK;
1134 }
1135
1136 /* allocate primary structure for multipart info */
1137 if ((m = (struct multipart *) mh_xcalloc (1, sizeof(*m))) == NULL)
1138 adios (NULL, "out of memory");
1139 ct->c_ctparams = (void *) m;
1140
1141 /* check if boundary parameter contains only whitespace characters */
1142 for (cp = bp; isspace ((unsigned char) *cp); cp++)
1143 continue;
1144 if (!*cp) {
1145 advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1146 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1147 return NOTOK;
1148 }
1149
1150 /* remove trailing whitespace from boundary parameter */
1151 for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1152 if (!isspace ((unsigned char) *dp))
1153 break;
1154 *++dp = '\0';
1155
1156 /* record boundary separators */
1157 m->mp_start = concat (bp, "\n", NULL);
1158 m->mp_stop = concat (bp, "--\n", NULL);
1159
1160 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1161 advise (ct->c_file, "unable to open for reading");
1162 return NOTOK;
1163 }
1164
1165 fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1166 last = ct->c_end;
1167 next = &m->mp_parts;
1168 part = NULL;
1169 inout = 1;
1170
1171 while ((gotlen = getline(&bufp, &buflen, fp)) != -1) {
1172 if (pos > last)
1173 break;
1174
1175 pos += gotlen;
1176 if (bufp[0] != '-' || bufp[1] != '-')
1177 continue;
1178 if (inout) {
1179 if (strcmp (bufp + 2, m->mp_start))
1180 continue;
1181 next_part:
1182 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
1183 adios (NULL, "out of memory");
1184 *next = part;
1185 next = &part->mp_next;
1186
1187 if (!(p = get_content (fp, ct->c_file,
1188 ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1189 free(bufp);
1190 ct->c_fp = NULL;
1191 return NOTOK;
1192 }
1193 p->c_fp = NULL;
1194 part->mp_part = p;
1195 pos = p->c_begin;
1196 fseek (fp, pos, SEEK_SET);
1197 inout = 0;
1198 } else {
1199 if (strcmp (bufp + 2, m->mp_start) == 0) {
1200 inout = 1;
1201 end_part:
1202 p = part->mp_part;
1203 p->c_end = ftell(fp) - (gotlen + 1);
1204 if (p->c_end < p->c_begin)
1205 p->c_begin = p->c_end;
1206 if (inout)
1207 goto next_part;
1208 goto last_part;
1209 } else {
1210 if (strcmp (bufp + 2, m->mp_stop) == 0)
1211 goto end_part;
1212 }
1213 }
1214 }
1215
1216 if (! suppress_bogus_mp_content_warning) {
1217 advise (NULL, "bogus multipart content in message %s", ct->c_file);
1218 }
1219 bogus_mp_content = 1;
1220
1221 if (!inout && part) {
1222 p = part->mp_part;
1223 p->c_end = ct->c_end;
1224
1225 if (p->c_begin >= p->c_end) {
1226 for (next = &m->mp_parts; *next != part;
1227 next = &((*next)->mp_next))
1228 continue;
1229 *next = NULL;
1230 free_content (p);
1231 free ((char *) part);
1232 }
1233 }
1234
1235 last_part:
1236 /* reverse the order of the parts for multipart/alternative */
1237 if (ct->c_subtype == MULTI_ALTERNATE) {
1238 reverse_parts (ct);
1239 prefer_parts (ct);
1240 }
1241
1242 /*
1243 * label all subparts with part number, and
1244 * then initialize the content of the subpart.
1245 */
1246 {
1247 int partnum;
1248 char *pp;
1249 char partnam[BUFSIZ];
1250
1251 if (ct->c_partno) {
1252 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1253 pp = partnam + strlen (partnam);
1254 } else {
1255 pp = partnam;
1256 }
1257
1258 for (part = m->mp_parts, partnum = 1; part;
1259 part = part->mp_next, partnum++) {
1260 p = part->mp_part;
1261
1262 sprintf (pp, "%d", partnum);
1263 p->c_partno = add (partnam, NULL);
1264
1265 /* initialize the content of the subparts */
1266 if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1267 free(bufp);
1268 fclose (ct->c_fp);
1269 ct->c_fp = NULL;
1270 return NOTOK;
1271 }
1272 }
1273 }
1274
1275 get_leftover_mp_content (ct, 1);
1276 get_leftover_mp_content (ct, 0);
1277
1278 free(bufp);
1279 fclose (ct->c_fp);
1280 ct->c_fp = NULL;
1281 return OK;
1282 }
1283
1284
1285 /*
1286 * reverse the order of the parts of a multipart/alternative,
1287 * presumably to put the "most favored" alternative first, for
1288 * ease of choosing/displaying it later on. from a mail message on
1289 * nmh-workers, from kenh:
1290 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1291 * see code in mhn that did the same thing... Acccording to the RCS
1292 * logs, that code was around from the initial checkin of mhn.c by
1293 * John Romine in 1992, which is as far back as we have."
1294 */
1295 static void
1296 reverse_parts (CT ct)
1297 {
1298 struct multipart *m = (struct multipart *) ct->c_ctparams;
1299 struct part *part;
1300 struct part *next;
1301
1302 /* Reverse the order of its parts by walking the mp_parts list
1303 and pushing each node to the front. */
1304 for (part = m->mp_parts, m->mp_parts = NULL; part; part = next) {
1305 next = part->mp_next;
1306 part->mp_next = m->mp_parts;
1307 m->mp_parts = part;
1308 }
1309 }
1310
1311 static void
1312 move_preferred_part (CT ct, char *type, char *subtype)
1313 {
1314 struct multipart *m = (struct multipart *) ct->c_ctparams;
1315 struct part *part, *prev, *head, *nhead, *ntail;
1316 struct part h, n;
1317 CI ci;
1318
1319 /* move the matching part(s) to the head of the list: walk the
1320 * list of parts, move matching parts to a new list (maintaining
1321 * their order), and finally, concatenate the old list onto the
1322 * new.
1323 */
1324
1325 head = &h;
1326 nhead = &n;
1327
1328 head->mp_next = m->mp_parts;
1329 nhead->mp_next = NULL;
1330 ntail = nhead;
1331
1332 prev = head;
1333 part = head->mp_next;
1334 while (part != NULL) {
1335 ci = &part->mp_part->c_ctinfo;
1336 if (!strcasecmp(ci->ci_type, type) &&
1337 (!subtype || !strcasecmp(ci->ci_subtype, subtype))) {
1338 prev->mp_next = part->mp_next;
1339 part->mp_next = NULL;
1340 ntail->mp_next = part;
1341 ntail = part;
1342 part = prev->mp_next;
1343 } else {
1344 prev = part;
1345 part = prev->mp_next;
1346 }
1347 }
1348 ntail->mp_next = head->mp_next;
1349 m->mp_parts = nhead->mp_next;
1350
1351 }
1352
1353 /*
1354 * move parts that match the user's preferences (-prefer) to the head
1355 * of the line. process preferences in reverse so first one given
1356 * ends up first in line
1357 */
1358 static void
1359 prefer_parts(CT ct)
1360 {
1361 int i;
1362 for (i = npreferred-1; i >= 0; i--)
1363 move_preferred_part(ct, preferred_types[i], preferred_subtypes[i]);
1364 }
1365
1366
1367
1368 /* parse_mime() arranges alternates in reverse (priority) order. This
1369 function can be used to reverse them back. This will put, for
1370 example, a text/plain part before a text/html part in a
1371 multipart/alternative part, for example, where it belongs. */
1372 void
1373 reverse_alternative_parts (CT ct) {
1374 if (ct->c_type == CT_MULTIPART) {
1375 struct multipart *m = (struct multipart *) ct->c_ctparams;
1376 struct part *part;
1377
1378 if (ct->c_subtype == MULTI_ALTERNATE) {
1379 reverse_parts (ct);
1380 }
1381
1382 /* And call recursively on each part of a multipart. */
1383 for (part = m->mp_parts; part; part = part->mp_next) {
1384 reverse_alternative_parts (part->mp_part);
1385 }
1386 }
1387 }
1388
1389
1390 /*
1391 * MESSAGE
1392 */
1393
1394 static int
1395 InitMessage (CT ct)
1396 {
1397 CI ci = &ct->c_ctinfo;
1398
1399 if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1400 admonish (NULL,
1401 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1402 ci->ci_type, ci->ci_subtype, ct->c_file);
1403 return NOTOK;
1404 }
1405
1406 /* check for missing subtype */
1407 if (!*ci->ci_subtype)
1408 ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1409
1410 /* match subtype */
1411 ct->c_subtype = ct_str_subtype (CT_MESSAGE, ci->ci_subtype);
1412
1413 switch (ct->c_subtype) {
1414 case MESSAGE_RFC822:
1415 break;
1416
1417 case MESSAGE_PARTIAL:
1418 {
1419 PM pm;
1420 struct partial *p;
1421
1422 if ((p = (struct partial *) mh_xcalloc (1, sizeof(*p))) == NULL)
1423 adios (NULL, "out of memory");
1424 ct->c_ctparams = (void *) p;
1425
1426 /* scan for parameters "id", "number", and "total" */
1427 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1428 if (!strcasecmp (pm->pm_name, "id")) {
1429 p->pm_partid = add (pm->pm_value, NULL);
1430 continue;
1431 }
1432 if (!strcasecmp (pm->pm_name, "number")) {
1433 if (sscanf (pm->pm_value, "%d", &p->pm_partno) != 1
1434 || p->pm_partno < 1) {
1435 invalid_param:
1436 advise (NULL,
1437 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1438 pm->pm_name, ci->ci_type, ci->ci_subtype,
1439 ct->c_file, TYPE_FIELD);
1440 return NOTOK;
1441 }
1442 continue;
1443 }
1444 if (!strcasecmp (pm->pm_name, "total")) {
1445 if (sscanf (pm->pm_value, "%d", &p->pm_maxno) != 1
1446 || p->pm_maxno < 1)
1447 goto invalid_param;
1448 continue;
1449 }
1450 }
1451
1452 if (!p->pm_partid
1453 || !p->pm_partno
1454 || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1455 advise (NULL,
1456 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1457 ci->ci_type, ci->ci_subtype,
1458 ct->c_file, TYPE_FIELD);
1459 return NOTOK;
1460 }
1461 }
1462 break;
1463
1464 case MESSAGE_EXTERNAL:
1465 {
1466 int exresult;
1467 struct exbody *e;
1468 CT p;
1469 FILE *fp;
1470
1471 if ((e = (struct exbody *) mh_xcalloc (1, sizeof(*e))) == NULL)
1472 adios (NULL, "out of memory");
1473 ct->c_ctparams = (void *) e;
1474
1475 if (!ct->c_fp
1476 && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1477 advise (ct->c_file, "unable to open for reading");
1478 return NOTOK;
1479 }
1480
1481 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1482
1483 if (!(p = get_content (fp, ct->c_file, 0))) {
1484 ct->c_fp = NULL;
1485 return NOTOK;
1486 }
1487
1488 e->eb_parent = ct;
1489 e->eb_content = p;
1490 p->c_ctexbody = e;
1491 p->c_ceopenfnx = NULL;
1492 if ((exresult = params_external (ct, 0)) != NOTOK
1493 && p->c_ceopenfnx == openMail) {
1494 int cc, size;
1495 char *bp;
1496
1497 if ((size = ct->c_end - p->c_begin) <= 0) {
1498 if (!e->eb_subject)
1499 content_error (NULL, ct,
1500 "empty body for access-type=mail-server");
1501 goto no_body;
1502 }
1503
1504 e->eb_body = bp = mh_xmalloc ((unsigned) size);
1505 fseek (p->c_fp, p->c_begin, SEEK_SET);
1506 while (size > 0)
1507 switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1508 case NOTOK:
1509 adios ("failed", "fread");
1510
1511 case OK:
1512 adios (NULL, "unexpected EOF from fread");
1513
1514 default:
1515 bp += cc, size -= cc;
1516 break;
1517 }
1518 *bp = 0;
1519 }
1520 no_body:
1521 p->c_fp = NULL;
1522 p->c_end = p->c_begin;
1523
1524 fclose (ct->c_fp);
1525 ct->c_fp = NULL;
1526
1527 if (exresult == NOTOK)
1528 return NOTOK;
1529 if (e->eb_flags == NOTOK)
1530 return OK;
1531
1532 switch (p->c_type) {
1533 case CT_MULTIPART:
1534 break;
1535
1536 case CT_MESSAGE:
1537 if (p->c_subtype != MESSAGE_RFC822)
1538 break;
1539 /* else fall... */
1540 default:
1541 e->eb_partno = ct->c_partno;
1542 if (p->c_ctinitfnx)
1543 (*p->c_ctinitfnx) (p);
1544 break;
1545 }
1546 }
1547 break;
1548
1549 default:
1550 break;
1551 }
1552
1553 return OK;
1554 }
1555
1556
1557 int
1558 params_external (CT ct, int composing)
1559 {
1560 PM pm;
1561 struct exbody *e = (struct exbody *) ct->c_ctparams;
1562 CI ci = &ct->c_ctinfo;
1563
1564 ct->c_ceopenfnx = NULL;
1565 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1566 if (!strcasecmp (pm->pm_name, "access-type")) {
1567 struct str2init *s2i;
1568 CT p = e->eb_content;
1569
1570 for (s2i = str2methods; s2i->si_key; s2i++)
1571 if (!strcasecmp (pm->pm_value, s2i->si_key))
1572 break;
1573 if (!s2i->si_key) {
1574 e->eb_access = pm->pm_value;
1575 e->eb_flags = NOTOK;
1576 p->c_encoding = CE_EXTERNAL;
1577 continue;
1578 }
1579 e->eb_access = s2i->si_key;
1580 e->eb_flags = s2i->si_val;
1581 p->c_encoding = CE_EXTERNAL;
1582
1583 /* Call the Init function for this external type */
1584 if ((*s2i->si_init)(p) == NOTOK)
1585 return NOTOK;
1586 continue;
1587 }
1588 if (!strcasecmp (pm->pm_name, "name")) {
1589 e->eb_name = pm->pm_value;
1590 continue;
1591 }
1592 if (!strcasecmp (pm->pm_name, "permission")) {
1593 e->eb_permission = pm->pm_value;
1594 continue;
1595 }
1596 if (!strcasecmp (pm->pm_name, "site")) {
1597 e->eb_site = pm->pm_value;
1598 continue;
1599 }
1600 if (!strcasecmp (pm->pm_name, "directory")) {
1601 e->eb_dir = pm->pm_value;
1602 continue;
1603 }
1604 if (!strcasecmp (pm->pm_name, "mode")) {
1605 e->eb_mode = pm->pm_value;
1606 continue;
1607 }
1608 if (!strcasecmp (pm->pm_name, "size")) {
1609 sscanf (pm->pm_value, "%lu", &e->eb_size);
1610 continue;
1611 }
1612 if (!strcasecmp (pm->pm_name, "server")) {
1613 e->eb_server = pm->pm_value;
1614 continue;
1615 }
1616 if (!strcasecmp (pm->pm_name, "subject")) {
1617 e->eb_subject = pm->pm_value;
1618 continue;
1619 }
1620 if (!strcasecmp (pm->pm_name, "url")) {
1621 /*
1622 * According to RFC 2017, we have to remove all whitespace from
1623 * the URL
1624 */
1625
1626 char *u, *p = pm->pm_value;
1627 e->eb_url = u = mh_xmalloc(strlen(pm->pm_value) + 1);
1628
1629 for (; *p != '\0'; p++) {
1630 if (! isspace((unsigned char) *p))
1631 *u++ = *p;
1632 }
1633
1634 *u = '\0';
1635 continue;
1636 }
1637 if (composing && !strcasecmp (pm->pm_name, "body")) {
1638 e->eb_body = getcpy (pm->pm_value);
1639 continue;
1640 }
1641 }
1642
1643 if (!e->eb_access) {
1644 advise (NULL,
1645 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1646 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1647 return NOTOK;
1648 }
1649
1650 return OK;
1651 }
1652
1653
1654 /*
1655 * APPLICATION
1656 */
1657
1658 static int
1659 InitApplication (CT ct)
1660 {
1661 CI ci = &ct->c_ctinfo;
1662
1663 /* match subtype */
1664 ct->c_subtype = ct_str_subtype (CT_APPLICATION, ci->ci_subtype);
1665
1666 return OK;
1667 }
1668
1669
1670 /*
1671 * TRANSFER ENCODINGS
1672 */
1673
1674 static int
1675 init_encoding (CT ct, OpenCEFunc openfnx)
1676 {
1677 ct->c_ceopenfnx = openfnx;
1678 ct->c_ceclosefnx = close_encoding;
1679 ct->c_cesizefnx = size_encoding;
1680
1681 return OK;
1682 }
1683
1684
1685 void
1686 close_encoding (CT ct)
1687 {
1688 CE ce = &ct->c_cefile;
1689
1690 if (ce->ce_fp) {
1691 fclose (ce->ce_fp);
1692 ce->ce_fp = NULL;
1693 }
1694 }
1695
1696
1697 static unsigned long
1698 size_encoding (CT ct)
1699 {
1700 int fd;
1701 unsigned long size;
1702 char *file;
1703 CE ce = &ct->c_cefile;
1704 struct stat st;
1705
1706 if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1707 return (long) st.st_size;
1708
1709 if (ce->ce_file) {
1710 if (stat (ce->ce_file, &st) != NOTOK)
1711 return (long) st.st_size;
1712 else
1713 return 0L;
1714 }
1715
1716 if (ct->c_encoding == CE_EXTERNAL)
1717 return (ct->c_end - ct->c_begin);
1718
1719 file = NULL;
1720 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1721 return (ct->c_end - ct->c_begin);
1722
1723 if (fstat (fd, &st) != NOTOK)
1724 size = (long) st.st_size;
1725 else
1726 size = 0L;
1727
1728 (*ct->c_ceclosefnx) (ct);
1729 return size;
1730 }
1731
1732
1733 /*
1734 * BASE64
1735 */
1736
1737 static unsigned char b642nib[0x80] = {
1738 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1739 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1740 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1741 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1742 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1743 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1744 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1745 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1746 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1747 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1748 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1749 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1750 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1751 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1752 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1753 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1754 };
1755
1756
1757 static int
1758 InitBase64 (CT ct)
1759 {
1760 return init_encoding (ct, openBase64);
1761 }
1762
1763
1764 static int
1765 openBase64 (CT ct, char **file)
1766 {
1767 int bitno, cc, digested;
1768 int fd, len, skip, own_ct_fp = 0, text = ct->c_type == CT_TEXT;
1769 uint32_t bits;
1770 unsigned char value, b;
1771 char *cp, *ep, buffer[BUFSIZ];
1772 /* sbeck -- handle suffixes */
1773 CI ci;
1774 CE ce = &ct->c_cefile;
1775 MD5_CTX mdContext;
1776
1777 if (ce->ce_fp) {
1778 fseek (ce->ce_fp, 0L, SEEK_SET);
1779 goto ready_to_go;
1780 }
1781
1782 if (ce->ce_file) {
1783 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1784 content_error (ce->ce_file, ct, "unable to fopen for reading");
1785 return NOTOK;
1786 }
1787 goto ready_to_go;
1788 }
1789
1790 if (*file == NULL) {
1791 ce->ce_unlink = 1;
1792 } else {
1793 ce->ce_file = add (*file, NULL);
1794 ce->ce_unlink = 0;
1795 }
1796
1797 /* sbeck@cise.ufl.edu -- handle suffixes */
1798 ci = &ct->c_ctinfo;
1799 if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) {
1800 if (ce->ce_unlink) {
1801 /* Create temporary file with filename extension. */
1802 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
1803 adios(NULL, "unable to create temporary file in %s",
1804 get_temp_dir());
1805 }
1806 } else {
1807 ce->ce_file = add (cp, ce->ce_file);
1808 }
1809 } else if (*file == NULL) {
1810 char *tempfile;
1811 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1812 adios(NULL, "unable to create temporary file in %s",
1813 get_temp_dir());
1814 }
1815 ce->ce_file = add (tempfile, NULL);
1816 }
1817
1818 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1819 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1820 return NOTOK;
1821 }
1822
1823 if ((len = ct->c_end - ct->c_begin) < 0)
1824 adios (NULL, "internal error(1)");
1825
1826 if (! ct->c_fp) {
1827 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1828 content_error (ct->c_file, ct, "unable to open for reading");
1829 return NOTOK;
1830 }
1831 own_ct_fp = 1;
1832 }
1833
1834 if ((digested = ct->c_digested))
1835 MD5Init (&mdContext);
1836
1837 bitno = 18;
1838 bits = 0L;
1839 skip = 0;
1840
1841 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1842 while (len > 0) {
1843 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1844 case NOTOK:
1845 content_error (ct->c_file, ct, "error reading from");
1846 goto clean_up;
1847
1848 case OK:
1849 content_error (NULL, ct, "premature eof");
1850 goto clean_up;
1851
1852 default:
1853 if (cc > len)
1854 cc = len;
1855 len -= cc;
1856
1857 for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1858 switch (*cp) {
1859 default:
1860 if (isspace ((unsigned char) *cp))
1861 break;
1862 if (skip || (((unsigned char) *cp) & 0x80)
1863 || (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) {
1864 if (debugsw) {
1865 fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1866 (unsigned char) *cp,
1867 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1868 skip);
1869 }
1870 content_error (NULL, ct,
1871 "invalid BASE64 encoding -- continuing");
1872 continue;
1873 }
1874
1875 bits |= value << bitno;
1876 test_end:
1877 if ((bitno -= 6) < 0) {
1878 b = (bits >> 16) & 0xff;
1879 if (!text || b != '\r')
1880 putc ((char) b, ce->ce_fp);
1881 if (digested)
1882 MD5Update (&mdContext, &b, 1);
1883 if (skip < 2) {
1884 b = (bits >> 8) & 0xff;
1885 if (! text || b != '\r')
1886 putc ((char) b, ce->ce_fp);
1887 if (digested)
1888 MD5Update (&mdContext, &b, 1);
1889 if (skip < 1) {
1890 b = bits & 0xff;
1891 if (! text || b != '\r')
1892 putc ((char) b, ce->ce_fp);
1893 if (digested)
1894 MD5Update (&mdContext, &b, 1);
1895 }
1896 }
1897
1898 if (ferror (ce->ce_fp)) {
1899 content_error (ce->ce_file, ct,
1900 "error writing to");
1901 goto clean_up;
1902 }
1903 bitno = 18, bits = 0L, skip = 0;
1904 }
1905 break;
1906
1907 case '=':
1908 if (++skip > 3)
1909 goto self_delimiting;
1910 goto test_end;
1911 }
1912 }
1913 }
1914 }
1915
1916 if (bitno != 18) {
1917 if (debugsw)
1918 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1919
1920 content_error (NULL, ct, "invalid BASE64 encoding");
1921 goto clean_up;
1922 }
1923
1924 self_delimiting:
1925 fseek (ct->c_fp, 0L, SEEK_SET);
1926
1927 if (fflush (ce->ce_fp)) {
1928 content_error (ce->ce_file, ct, "error writing to");
1929 goto clean_up;
1930 }
1931
1932 if (digested) {
1933 unsigned char digest[16];
1934
1935 MD5Final (digest, &mdContext);
1936 if (memcmp((char *) digest, (char *) ct->c_digest,
1937 sizeof(digest) / sizeof(digest[0])))
1938 content_error (NULL, ct,
1939 "content integrity suspect (digest mismatch) -- continuing");
1940 else
1941 if (debugsw)
1942 fprintf (stderr, "content integrity confirmed\n");
1943 }
1944
1945 fseek (ce->ce_fp, 0L, SEEK_SET);
1946
1947 ready_to_go:
1948 *file = ce->ce_file;
1949 if (own_ct_fp) {
1950 fclose (ct->c_fp);
1951 ct->c_fp = NULL;
1952 }
1953 return fileno (ce->ce_fp);
1954
1955 clean_up:
1956 if (own_ct_fp) {
1957 fclose (ct->c_fp);
1958 ct->c_fp = NULL;
1959 }
1960 free_encoding (ct, 0);
1961 return NOTOK;
1962 }
1963
1964
1965 /*
1966 * QUOTED PRINTABLE
1967 */
1968
1969 static char hex2nib[0x80] = {
1970 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1971 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1972 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1973 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1974 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1975 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1976 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1977 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1978 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1979 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1980 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1981 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1982 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1983 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1984 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1985 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1986 };
1987
1988
1989 static int
1990 InitQuoted (CT ct)
1991 {
1992 return init_encoding (ct, openQuoted);
1993 }
1994
1995
1996 static int
1997 openQuoted (CT ct, char **file)
1998 {
1999 int cc, digested, len, quoted, own_ct_fp = 0;
2000 char *cp, *ep;
2001 char *bufp = NULL;
2002 size_t buflen;
2003 ssize_t gotlen;
2004 unsigned char mask;
2005 CE ce = &ct->c_cefile;
2006 /* sbeck -- handle suffixes */
2007 CI ci;
2008 MD5_CTX mdContext;
2009
2010 if (ce->ce_fp) {
2011 fseek (ce->ce_fp, 0L, SEEK_SET);
2012 goto ready_to_go;
2013 }
2014
2015 if (ce->ce_file) {
2016 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2017 content_error (ce->ce_file, ct, "unable to fopen for reading");
2018 return NOTOK;
2019 }
2020 goto ready_to_go;
2021 }
2022
2023 if (*file == NULL) {
2024 ce->ce_unlink = 1;
2025 } else {
2026 ce->ce_file = add (*file, NULL);
2027 ce->ce_unlink = 0;
2028 }
2029
2030 /* sbeck@cise.ufl.edu -- handle suffixes */
2031 ci = &ct->c_ctinfo;
2032 if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) {
2033 if (ce->ce_unlink) {
2034 /* Create temporary file with filename extension. */
2035 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
2036 adios(NULL, "unable to create temporary file in %s",
2037 get_temp_dir());
2038 }
2039 } else {
2040 ce->ce_file = add (cp, ce->ce_file);
2041 }
2042 } else if (*file == NULL) {
2043 char *tempfile;
2044 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2045 adios(NULL, "unable to create temporary file in %s",
2046 get_temp_dir());
2047 }
2048 ce->ce_file = add (tempfile, NULL);
2049 }
2050
2051 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2052 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2053 return NOTOK;
2054 }
2055
2056 if ((len = ct->c_end - ct->c_begin) < 0)
2057 adios (NULL, "internal error(2)");
2058
2059 if (! ct->c_fp) {
2060 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2061 content_error (ct->c_file, ct, "unable to open for reading");
2062 return NOTOK;
2063 }
2064 own_ct_fp = 1;
2065 }
2066
2067 if ((digested = ct->c_digested))
2068 MD5Init (&mdContext);
2069
2070 quoted = 0;
2071 #ifdef lint
2072 mask = 0;
2073 #endif
2074
2075 fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2076 while (len > 0) {
2077 if ((gotlen = getline(&bufp, &buflen, ct->c_fp)) == -1) {
2078 content_error (NULL, ct, "premature eof");
2079 goto clean_up;
2080 }
2081
2082 if ((cc = gotlen) > len)
2083 cc = len;
2084 len -= cc;
2085
2086 for (ep = (cp = bufp) + cc - 1; cp <= ep; ep--)
2087 if (!isspace ((unsigned char) *ep))
2088 break;
2089 *++ep = '\n', ep++;
2090
2091 for (; cp < ep; cp++) {
2092 if (quoted > 0) {
2093 /* in an escape sequence */
2094 if (quoted == 1) {
2095 /* at byte 1 of an escape sequence */
2096 mask = hex2nib[((unsigned char) *cp) & 0x7f];
2097 /* next is byte 2 */
2098 quoted = 2;
2099 } else {
2100 /* at byte 2 of an escape sequence */
2101 mask <<= 4;
2102 mask |= hex2nib[((unsigned char) *cp) & 0x7f];
2103 putc (mask, ce->ce_fp);
2104 if (digested)
2105 MD5Update (&mdContext, &mask, 1);
2106 if (ferror (ce->ce_fp)) {
2107 content_error (ce->ce_file, ct, "error writing to");
2108 goto clean_up;
2109 }
2110 /* finished escape sequence; next may be literal or a new
2111 * escape sequence */
2112 quoted = 0;
2113 }
2114 /* on to next byte */
2115 continue;
2116 }
2117
2118 /* not in an escape sequence */
2119 if (*cp == '=') {
2120 /* starting an escape sequence, or invalid '='? */
2121 if (cp + 1 < ep && cp[1] == '\n') {
2122 /* "=\n" soft line break, eat the \n */
2123 cp++;
2124 continue;
2125 }
2126 if (cp + 1 >= ep || cp + 2 >= ep) {
2127 /* We don't have 2 bytes left, so this is an invalid
2128 * escape sequence; just show the raw bytes (below). */
2129 } else if (isxdigit ((unsigned char) cp[1]) &&
2130 isxdigit ((unsigned char) cp[2])) {
2131 /* Next 2 bytes are hex digits, making this a valid escape
2132 * sequence; let's decode it (above). */
2133 quoted = 1;
2134 continue;
2135 } else {
2136 /* One or both of the next 2 is out of range, making this
2137 * an invalid escape sequence; just show the raw bytes
2138 * (below). */
2139 }
2140 }
2141
2142 /* Just show the raw byte. */
2143 putc (*cp, ce->ce_fp);
2144 if (digested) {
2145 if (*cp == '\n') {
2146 MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2147 } else {
2148 MD5Update (&mdContext, (unsigned char *) cp, 1);
2149 }
2150 }
2151 if (ferror (ce->ce_fp)) {
2152 content_error (ce->ce_file, ct, "error writing to");
2153 goto clean_up;
2154 }
2155 }
2156 }
2157 if (quoted) {
2158 content_error (NULL, ct,
2159 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2160 goto clean_up;
2161 }
2162
2163 fseek (ct->c_fp, 0L, SEEK_SET);
2164
2165 if (fflush (ce->ce_fp)) {
2166 content_error (ce->ce_file, ct, "error writing to");
2167 goto clean_up;
2168 }
2169
2170 if (digested) {
2171 unsigned char digest[16];
2172
2173 MD5Final (digest, &mdContext);
2174 if (memcmp((char *) digest, (char *) ct->c_digest,
2175 sizeof(digest) / sizeof(digest[0])))
2176 content_error (NULL, ct,
2177 "content integrity suspect (digest mismatch) -- continuing");
2178 else
2179 if (debugsw)
2180 fprintf (stderr, "content integrity confirmed\n");
2181 }
2182
2183 fseek (ce->ce_fp, 0L, SEEK_SET);
2184
2185 ready_to_go:
2186 *file = ce->ce_file;
2187 if (own_ct_fp) {
2188 fclose (ct->c_fp);
2189 ct->c_fp = NULL;
2190 }
2191 free (bufp);
2192 return fileno (ce->ce_fp);
2193
2194 clean_up:
2195 free_encoding (ct, 0);
2196 if (own_ct_fp) {
2197 fclose (ct->c_fp);
2198 ct->c_fp = NULL;
2199 }
2200 free (bufp);
2201 return NOTOK;
2202 }
2203
2204
2205 /*
2206 * 7BIT
2207 */
2208
2209 static int
2210 Init7Bit (CT ct)
2211 {
2212 if (init_encoding (ct, open7Bit) == NOTOK)
2213 return NOTOK;
2214
2215 ct->c_cesizefnx = NULL; /* no need to decode for real size */
2216 return OK;
2217 }
2218
2219
2220 int
2221 open7Bit (CT ct, char **file)
2222 {
2223 int cc, fd, len, own_ct_fp = 0;
2224 char buffer[BUFSIZ];
2225 /* sbeck -- handle suffixes */
2226 char *cp;
2227 CI ci;
2228 CE ce = &ct->c_cefile;
2229
2230 if (ce->ce_fp) {
2231 fseek (ce->ce_fp, 0L, SEEK_SET);
2232 goto ready_to_go;
2233 }
2234
2235 if (ce->ce_file) {
2236 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2237 content_error (ce->ce_file, ct, "unable to fopen for reading");
2238 return NOTOK;
2239 }
2240 goto ready_to_go;
2241 }
2242
2243 if (*file == NULL) {
2244 ce->ce_unlink = 1;
2245 } else {
2246 ce->ce_file = add (*file, NULL);
2247 ce->ce_unlink = 0;
2248 }
2249
2250 /* sbeck@cise.ufl.edu -- handle suffixes */
2251 ci = &ct->c_ctinfo;
2252 if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) {
2253 if (ce->ce_unlink) {
2254 /* Create temporary file with filename extension. */
2255 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
2256 adios(NULL, "unable to create temporary file in %s",
2257 get_temp_dir());
2258 }
2259 } else {
2260 ce->ce_file = add (cp, ce->ce_file);
2261 }
2262 } else if (*file == NULL) {
2263 char *tempfile;
2264 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2265 adios(NULL, "unable to create temporary file in %s",
2266 get_temp_dir());
2267 }
2268 ce->ce_file = add (tempfile, NULL);
2269 }
2270
2271 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2272 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2273 return NOTOK;
2274 }
2275
2276 if (ct->c_type == CT_MULTIPART) {
2277 CI ci = &ct->c_ctinfo;
2278 char *buffer;
2279
2280 len = 0;
2281 fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2282 len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2283 + 1 + strlen (ci->ci_subtype);
2284 buffer = output_params(len, ci->ci_first_pm, &len, 0);
2285
2286 if (buffer) {
2287 fputs (buffer, ce->ce_fp);
2288 free(buffer);
2289 }
2290
2291 if (ci->ci_comment) {
2292 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2293 fputs ("\n\t", ce->ce_fp);
2294 len = 8;
2295 }
2296 else {
2297 putc (' ', ce->ce_fp);
2298 len++;
2299 }
2300 fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2301 len += cc;
2302 }
2303 fprintf (ce->ce_fp, "\n");
2304 if (ct->c_id)
2305 fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2306 if (ct->c_descr)
2307 fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2308 if (ct->c_dispo)
2309 fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2310 fprintf (ce->ce_fp, "\n");
2311 }
2312
2313 if ((len = ct->c_end - ct->c_begin) < 0)
2314 adios (NULL, "internal error(3)");
2315
2316 if (! ct->c_fp) {
2317 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2318 content_error (ct->c_file, ct, "unable to open for reading");
2319 return NOTOK;
2320 }
2321 own_ct_fp = 1;
2322 }
2323
2324 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2325 while (len > 0)
2326 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2327 case NOTOK:
2328 content_error (ct->c_file, ct, "error reading from");
2329 goto clean_up;
2330
2331 case OK:
2332 content_error (NULL, ct, "premature eof");
2333 goto clean_up;
2334
2335 default:
2336 if (cc > len)
2337 cc = len;
2338 len -= cc;
2339
2340 if ((int) fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp) < cc) {
2341 advise ("open7Bit", "fwrite");
2342 }
2343 if (ferror (ce->ce_fp)) {
2344 content_error (ce->ce_file, ct, "error writing to");
2345 goto clean_up;
2346 }
2347 }
2348
2349 fseek (ct->c_fp, 0L, SEEK_SET);
2350
2351 if (fflush (ce->ce_fp)) {
2352 content_error (ce->ce_file, ct, "error writing to");
2353 goto clean_up;
2354 }
2355
2356 fseek (ce->ce_fp, 0L, SEEK_SET);
2357
2358 ready_to_go:
2359 *file = ce->ce_file;
2360 if (own_ct_fp) {
2361 fclose (ct->c_fp);
2362 ct->c_fp = NULL;
2363 }
2364 return fileno (ce->ce_fp);
2365
2366 clean_up:
2367 free_encoding (ct, 0);
2368 if (own_ct_fp) {
2369 fclose (ct->c_fp);
2370 ct->c_fp = NULL;
2371 }
2372 return NOTOK;
2373 }
2374
2375
2376 /*
2377 * External
2378 */
2379
2380 static int
2381 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2382 {
2383 char cachefile[BUFSIZ];
2384
2385 if (ce->ce_fp) {
2386 fseek (ce->ce_fp, 0L, SEEK_SET);
2387 goto ready_already;
2388 }
2389
2390 if (ce->ce_file) {
2391 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2392 content_error (ce->ce_file, ct, "unable to fopen for reading");
2393 return NOTOK;
2394 }
2395 goto ready_already;
2396 }
2397
2398 if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2399 cachefile, sizeof(cachefile)) != NOTOK) {
2400 if ((ce->ce_fp = fopen (cachefile, "r"))) {
2401 ce->ce_file = getcpy (cachefile);
2402 ce->ce_unlink = 0;
2403 goto ready_already;
2404 } else {
2405 admonish (cachefile, "unable to fopen for reading");
2406 }
2407 }
2408
2409 *fd = fileno (ce->ce_fp);
2410 return OK;
2411
2412 ready_already:
2413 *file = ce->ce_file;
2414 *fd = fileno (ce->ce_fp);
2415 return DONE;
2416 }
2417
2418 /*
2419 * File
2420 */
2421
2422 static int
2423 InitFile (CT ct)
2424 {
2425 return init_encoding (ct, openFile);
2426 }
2427
2428
2429 static int
2430 openFile (CT ct, char **file)
2431 {
2432 int fd, cachetype;
2433 char cachefile[BUFSIZ];
2434 struct exbody *e = ct->c_ctexbody;
2435 CE ce = &ct->c_cefile;
2436
2437 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2438 case NOTOK:
2439 return NOTOK;
2440
2441 case OK:
2442 break;
2443
2444 case DONE:
2445 return fd;
2446 }
2447
2448 if (!e->eb_name) {
2449 content_error (NULL, ct, "missing name parameter");
2450 return NOTOK;
2451 }
2452
2453 ce->ce_file = getcpy (e->eb_name);
2454 ce->ce_unlink = 0;
2455
2456 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2457 content_error (ce->ce_file, ct, "unable to fopen for reading");
2458 return NOTOK;
2459 }
2460
2461 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2462 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2463 cachefile, sizeof(cachefile)) != NOTOK) {
2464 int mask;
2465 FILE *fp;
2466
2467 mask = umask (cachetype ? ~m_gmprot () : 0222);
2468 if ((fp = fopen (cachefile, "w"))) {
2469 int cc;
2470 char buffer[BUFSIZ];
2471 FILE *gp = ce->ce_fp;
2472
2473 fseek (gp, 0L, SEEK_SET);
2474
2475 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2476 > 0)
2477 if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) {
2478 advise ("openFile", "fwrite");
2479 }
2480 fflush (fp);
2481
2482 if (ferror (gp)) {
2483 admonish (ce->ce_file, "error reading");
2484 (void) m_unlink (cachefile);
2485 }
2486 else
2487 if (ferror (fp)) {
2488 admonish (cachefile, "error writing");
2489 (void) m_unlink (cachefile);
2490 }
2491 fclose (fp);
2492 }
2493 umask (mask);
2494 }
2495
2496 fseek (ce->ce_fp, 0L, SEEK_SET);
2497 *file = ce->ce_file;
2498 return fileno (ce->ce_fp);
2499 }
2500
2501 /*
2502 * FTP
2503 */
2504
2505 static int
2506 InitFTP (CT ct)
2507 {
2508 return init_encoding (ct, openFTP);
2509 }
2510
2511
2512 static int
2513 openFTP (CT ct, char **file)
2514 {
2515 int cachetype, caching, fd;
2516 int len, buflen;
2517 char *bp, *ftp, *user, *pass;
2518 char buffer[BUFSIZ], cachefile[BUFSIZ];
2519 struct exbody *e;
2520 CE ce = &ct->c_cefile;
2521 static char *username = NULL;
2522 static char *password = NULL;
2523
2524 e = ct->c_ctexbody;
2525
2526 if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2527 ftp = NULL;
2528
2529 if (!ftp)
2530 return NOTOK;
2531
2532 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2533 case NOTOK:
2534 return NOTOK;
2535
2536 case OK:
2537 break;
2538
2539 case DONE:
2540 return fd;
2541 }
2542
2543 if (!e->eb_name || !e->eb_site) {
2544 content_error (NULL, ct, "missing %s parameter",
2545 e->eb_name ? "site": "name");
2546 return NOTOK;
2547 }
2548
2549 /* Get the buffer ready to go */
2550 bp = buffer;
2551 buflen = sizeof(buffer);
2552
2553 /*
2554 * Construct the query message for user
2555 */
2556 snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2557 len = strlen (bp);
2558 bp += len;
2559 buflen -= len;
2560
2561 if (e->eb_partno) {
2562 snprintf (bp, buflen, " (content %s)", e->eb_partno);
2563 len = strlen (bp);
2564 bp += len;
2565 buflen -= len;
2566 }
2567
2568 snprintf (bp, buflen, "\n using %sFTP from site %s",
2569 e->eb_flags ? "anonymous " : "", e->eb_site);
2570 len = strlen (bp);
2571 bp += len;
2572 buflen -= len;
2573
2574 if (e->eb_size > 0) {
2575 snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2576 len = strlen (bp);
2577 bp += len;
2578 buflen -= len;
2579 }
2580 snprintf (bp, buflen, "? ");
2581
2582 /*
2583 * Now, check the answer
2584 */
2585 if (!getanswer (buffer))
2586 return NOTOK;
2587
2588 if (e->eb_flags) {
2589 user = "anonymous";
2590 snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
2591 LocalName (1));
2592 pass = buffer;
2593 } else {
2594 ruserpass (e->eb_site, &username, &password);
2595 user = username;
2596 pass = password;
2597 }
2598
2599 ce->ce_unlink = (*file == NULL);
2600 caching = 0;
2601 cachefile[0] = '\0';
2602 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2603 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2604 cachefile, sizeof(cachefile)) != NOTOK) {
2605 if (*file == NULL) {
2606 ce->ce_unlink = 0;
2607 caching = 1;
2608 }
2609 }
2610
2611 if (*file)
2612 ce->ce_file = add (*file, NULL);
2613 else if (caching)
2614 ce->ce_file = add (cachefile, NULL);
2615 else {
2616 char *tempfile;
2617 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2618 adios(NULL, "unable to create temporary file in %s",
2619 get_temp_dir());
2620 }
2621 ce->ce_file = add (tempfile, NULL);
2622 }
2623
2624 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2625 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2626 return NOTOK;
2627 }
2628
2629 {
2630 int child_id, i, vecp;
2631 char *vec[9];
2632
2633 vecp = 0;
2634 vec[vecp++] = r1bindex (ftp, '/');
2635 vec[vecp++] = e->eb_site;
2636 vec[vecp++] = user;
2637 vec[vecp++] = pass;
2638 vec[vecp++] = e->eb_dir;
2639 vec[vecp++] = e->eb_name;
2640 vec[vecp++] = ce->ce_file,
2641 vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2642 ? "ascii" : "binary";
2643 vec[vecp] = NULL;
2644
2645 fflush (stdout);
2646
2647 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2648 sleep (5);
2649 switch (child_id) {
2650 case NOTOK:
2651 adios ("fork", "unable to");
2652 /* NOTREACHED */
2653
2654 case OK:
2655 close (fileno (ce->ce_fp));
2656 execvp (ftp, vec);
2657 fprintf (stderr, "unable to exec ");
2658 perror (ftp);
2659 _exit (-1);
2660 /* NOTREACHED */
2661
2662 default:
2663 if (pidXwait (child_id, NULL)) {
2664 username = password = NULL;
2665 ce->ce_unlink = 1;
2666 return NOTOK;
2667 }
2668 break;
2669 }
2670 }
2671
2672 if (cachefile[0]) {
2673 if (caching)
2674 chmod (cachefile, cachetype ? m_gmprot () : 0444);
2675 else {
2676 int mask;
2677 FILE *fp;
2678
2679 mask = umask (cachetype ? ~m_gmprot () : 0222);
2680 if ((fp = fopen (cachefile, "w"))) {
2681 int cc;
2682 FILE *gp = ce->ce_fp;
2683
2684 fseek (gp, 0L, SEEK_SET);
2685
2686 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2687 > 0)
2688 if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) {
2689 advise ("openFTP", "fwrite");
2690 }
2691 fflush (fp);
2692
2693 if (ferror (gp)) {
2694 admonish (ce->ce_file, "error reading");
2695 (void) m_unlink (cachefile);
2696 }
2697 else
2698 if (ferror (fp)) {
2699 admonish (cachefile, "error writing");
2700 (void) m_unlink (cachefile);
2701 }
2702 fclose (fp);
2703 }
2704 umask (mask);
2705 }
2706 }
2707
2708 fseek (ce->ce_fp, 0L, SEEK_SET);
2709 *file = ce->ce_file;
2710 return fileno (ce->ce_fp);
2711 }
2712
2713
2714 /*
2715 * Mail
2716 */
2717
2718 static int
2719 InitMail (CT ct)
2720 {
2721 return init_encoding (ct, openMail);
2722 }
2723
2724
2725 static int
2726 openMail (CT ct, char **file)
2727 {
2728 int child_id, fd, i, vecp;
2729 int len, buflen;
2730 char *bp, buffer[BUFSIZ], *vec[7];
2731 struct exbody *e = ct->c_ctexbody;
2732 CE ce = &ct->c_cefile;
2733
2734 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2735 case NOTOK:
2736 return NOTOK;
2737
2738 case OK:
2739 break;
2740
2741 case DONE:
2742 return fd;
2743 }
2744
2745 if (!e->eb_server) {
2746 content_error (NULL, ct, "missing server parameter");
2747 return NOTOK;
2748 }
2749
2750 /* Get buffer ready to go */
2751 bp = buffer;
2752 buflen = sizeof(buffer);
2753
2754 /* Now, construct query message */
2755 snprintf (bp, buflen, "Retrieve content");
2756 len = strlen (bp);
2757 bp += len;
2758 buflen -= len;
2759
2760 if (e->eb_partno) {
2761 snprintf (bp, buflen, " %s", e->eb_partno);
2762 len = strlen (bp);
2763 bp += len;
2764 buflen -= len;
2765 }
2766
2767 snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2768 e->eb_server,
2769 e->eb_subject ? e->eb_subject : e->eb_body);
2770
2771 /* Now, check answer */
2772 if (!getanswer (buffer))
2773 return NOTOK;
2774
2775 vecp = 0;
2776 vec[vecp++] = r1bindex (mailproc, '/');
2777 vec[vecp++] = e->eb_server;
2778 vec[vecp++] = "-subject";
2779 vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2780 vec[vecp++] = "-body";
2781 vec[vecp++] = e->eb_body;
2782 vec[vecp] = NULL;
2783
2784 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2785 sleep (5);
2786 switch (child_id) {
2787 case NOTOK:
2788 advise ("fork", "unable to");
2789 return NOTOK;
2790
2791 case OK:
2792 execvp (mailproc, vec);
2793 fprintf (stderr, "unable to exec ");
2794 perror (mailproc);
2795 _exit (-1);
2796 /* NOTREACHED */
2797
2798 default:
2799 if (pidXwait (child_id, NULL) == OK)
2800 advise (NULL, "request sent");
2801 break;
2802 }
2803
2804 if (*file == NULL) {
2805 char *tempfile;
2806 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2807 adios(NULL, "unable to create temporary file in %s",
2808 get_temp_dir());
2809 }
2810 ce->ce_file = add (tempfile, NULL);
2811 ce->ce_unlink = 1;
2812 } else {
2813 ce->ce_file = add (*file, NULL);
2814 ce->ce_unlink = 0;
2815 }
2816
2817 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2818 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2819 return NOTOK;
2820 }
2821
2822 /* showproc is for mhshow and mhstore, though mhlist -debug
2823 * prints it, too. */
2824 if (ct->c_showproc)
2825 free (ct->c_showproc);
2826 ct->c_showproc = add ("true", NULL);
2827
2828 fseek (ce->ce_fp, 0L, SEEK_SET);
2829 *file = ce->ce_file;
2830 return fileno (ce->ce_fp);
2831 }
2832
2833
2834 /*
2835 * URL
2836 */
2837
2838 static int
2839 InitURL (CT ct)
2840 {
2841 return init_encoding (ct, openURL);
2842 }
2843
2844
2845 static int
2846 openURL (CT ct, char **file)
2847 {
2848 struct exbody *e = ct->c_ctexbody;
2849 CE ce = &ct->c_cefile;
2850 char *urlprog, *program;
2851 char buffer[BUFSIZ], cachefile[BUFSIZ];
2852 int fd, caching, cachetype;
2853 struct msgs_array args = { 0, 0, NULL};
2854 pid_t child_id;
2855
2856 if ((urlprog = context_find(nmhaccessurl)) && *urlprog == '\0')
2857 urlprog = NULL;
2858
2859 if (! urlprog) {
2860 content_error(NULL, ct, "No entry for nmh-access-url in profile");
2861 return NOTOK;
2862 }
2863
2864 switch (openExternal(e->eb_parent, e->eb_content, ce, file, &fd)) {
2865 case NOTOK:
2866 return NOTOK;
2867
2868 case OK:
2869 break;
2870
2871 case DONE:
2872 return fd;
2873 }
2874
2875 if (!e->eb_url) {
2876 content_error(NULL, ct, "missing url parameter");
2877 return NOTOK;
2878 }
2879
2880 ce->ce_unlink = (*file == NULL);
2881 caching = 0;
2882 cachefile[0] = '\0';
2883
2884 if (find_cache(NULL, wcachesw, &cachetype, e->eb_content->c_id,
2885 cachefile, sizeof(cachefile)) != NOTOK) {
2886 if (*file == NULL) {
2887 ce->ce_unlink = 0;
2888 caching = 1;
2889 }
2890 }
2891
2892 if (*file)
2893 ce->ce_file = add(*file, NULL);
2894 else if (caching)
2895 ce->ce_file = add(cachefile, NULL);
2896 else {
2897 char *tempfile;
2898 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2899 adios(NULL, "unable to create temporary file in %s",
2900 get_temp_dir());
2901 }
2902 ce->ce_file = add (tempfile, NULL);
2903 }
2904
2905 if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
2906 content_error(ce->ce_file, ct, "unable to fopen for read/writing");
2907 return NOTOK;
2908 }
2909
2910 switch (child_id = fork()) {
2911 case NOTOK:
2912 adios ("fork", "unable to");
2913 /* NOTREACHED */
2914
2915 case OK:
2916 argsplit_msgarg(&args, urlprog, &program);
2917 app_msgarg(&args, e->eb_url);
2918 app_msgarg(&args, NULL);
2919 dup2(fileno(ce->ce_fp), 1);
2920 close(fileno(ce->ce_fp));
2921 execvp(program, args.msgs);
2922 fprintf(stderr, "Unable to exec ");
2923 perror(program);
2924 _exit(-1);
2925 /* NOTREACHED */
2926
2927 default:
2928 if (pidXwait(child_id, NULL)) {
2929 ce->ce_unlink = 1;
2930 return NOTOK;
2931 }
2932 }
2933
2934 if (cachefile[0]) {
2935 if (caching)
2936 chmod(cachefile, cachetype ? m_gmprot() : 0444);
2937 else {
2938 int mask;
2939 FILE *fp;
2940
2941 mask = umask (cachetype ? ~m_gmprot() : 0222);
2942 if ((fp = fopen(cachefile, "w"))) {
2943 int cc;
2944 FILE *gp = ce->ce_fp;
2945
2946 fseeko(gp, 0, SEEK_SET);
2947
2948 while ((cc = fread(buffer, sizeof(*buffer),
2949 sizeof(buffer), gp)) > 0)
2950 if ((int) fwrite(buffer, sizeof(*buffer), cc, fp) < cc) {
2951 advise ("openURL", "fwrite");
2952 }
2953
2954 fflush(fp);
2955
2956 if (ferror(gp)) {
2957 admonish(ce->ce_file, "error reading");
2958 (void) m_unlink (cachefile);
2959 }
2960 }
2961 umask(mask);
2962 }
2963 }
2964
2965 fseeko(ce->ce_fp, 0, SEEK_SET);
2966 *file = ce->ce_file;
2967 return fd;
2968 }
2969
2970 static int
2971 readDigest (CT ct, char *cp)
2972 {
2973 int bitno, skip;
2974 uint32_t bits;
2975 char *bp = cp;
2976 unsigned char *dp, value, *ep;
2977
2978 bitno = 18;
2979 bits = 0L;
2980 skip = 0;
2981
2982 for (ep = (dp = ct->c_digest)
2983 + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2984 switch (*cp) {
2985 default:
2986 if (skip
2987 || (*cp & 0x80)
2988 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2989 if (debugsw)
2990 fprintf (stderr, "invalid BASE64 encoding\n");
2991 return NOTOK;
2992 }
2993
2994 bits |= value << bitno;
2995 test_end:
2996 if ((bitno -= 6) < 0) {
2997 if (dp + (3 - skip) > ep)
2998 goto invalid_digest;
2999 *dp++ = (bits >> 16) & 0xff;
3000 if (skip < 2) {
3001 *dp++ = (bits >> 8) & 0xff;
3002 if (skip < 1)
3003 *dp++ = bits & 0xff;
3004 }
3005 bitno = 18;
3006 bits = 0L;
3007 skip = 0;
3008 }
3009 break;
3010
3011 case '=':
3012 if (++skip > 3)
3013 goto self_delimiting;
3014 goto test_end;
3015 }
3016 if (bitno != 18) {
3017 if (debugsw)
3018 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
3019
3020 return NOTOK;
3021 }
3022 self_delimiting:
3023 if (dp != ep) {
3024 invalid_digest:
3025 if (debugsw) {
3026 while (*cp)
3027 cp++;
3028 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
3029 (int)(cp - bp));
3030 }
3031
3032 return NOTOK;
3033 }
3034
3035 if (debugsw) {
3036 fprintf (stderr, "MD5 digest=");
3037 for (dp = ct->c_digest; dp < ep; dp++)
3038 fprintf (stderr, "%02x", *dp & 0xff);
3039 fprintf (stderr, "\n");
3040 }
3041
3042 return OK;
3043 }
3044
3045
3046 /* Multipart parts might have content before the first subpart and/or
3047 after the last subpart that hasn't been stored anywhere else, so do
3048 that. */
3049 int
3050 get_leftover_mp_content (CT ct, int before /* or after */)
3051 {
3052 struct multipart *m = (struct multipart *) ct->c_ctparams;
3053 char *boundary;
3054 int found_boundary = 0;
3055 int max = BUFSIZ;
3056 char *bufp = NULL;
3057 size_t buflen;
3058 ssize_t gotlen;
3059 int read = 0;
3060 char *content = NULL;
3061
3062 if (! m) return NOTOK;
3063
3064 if (before) {
3065 if (! m->mp_parts || ! m->mp_parts->mp_part) return NOTOK;
3066
3067 /* Isolate the beginning of this part to the beginning of the
3068 first subpart and save any content between them. */
3069 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
3070 max = m->mp_parts->mp_part->c_begin - ct->c_begin;
3071 boundary = concat ("--", m->mp_start, NULL);
3072 } else {
3073 struct part *last_subpart = NULL;
3074 struct part *subpart;
3075
3076 /* Go to the last subpart to get its end position. */
3077 for (subpart = m->mp_parts; subpart; subpart = subpart->mp_next) {
3078 last_subpart = subpart;
3079 }
3080
3081 if (last_subpart == NULL) return NOTOK;
3082
3083 /* Isolate the end of the last subpart to the end of this part
3084 and save any content between them. */
3085 fseeko (ct->c_fp, last_subpart->mp_part->c_end, SEEK_SET);
3086 max = ct->c_end - last_subpart->mp_part->c_end;
3087 boundary = concat ("--", m->mp_stop, NULL);
3088 }
3089
3090 /* Back up by 1 to pick up the newline. */
3091 while ((gotlen = getline(&bufp, &buflen, ct->c_fp)) != -1) {
3092 read += gotlen;
3093 /* Don't look beyond beginning of first subpart (before) or
3094 next part (after). */
3095 if (read > max) bufp[read-max] = '\0';
3096
3097 if (before) {
3098 if (! strcmp (bufp, boundary)) {
3099 found_boundary = 1;
3100 }
3101 } else {
3102 if (! found_boundary && ! strcmp (bufp, boundary)) {
3103 found_boundary = 1;
3104 continue;
3105 }
3106 }
3107
3108 if ((before && ! found_boundary) || (! before && found_boundary)) {
3109 if (content) {
3110 char *old_content = content;
3111 content = concat (content, bufp, NULL);
3112 free (old_content);
3113 } else {
3114 content = before
3115 ? concat ("\n", bufp, NULL)
3116 : concat (bufp, NULL);
3117 }
3118 }
3119
3120 if (before) {
3121 if (found_boundary || read > max) break;
3122 } else {
3123 if (read > max) break;
3124 }
3125 }
3126
3127 /* Skip the newline if that's all there is. */
3128 if (content) {
3129 char *cp;
3130
3131 /* Remove trailing newline, except at EOF. */
3132 if ((before || ! feof (ct->c_fp)) &&
3133 (cp = content + strlen (content)) > content &&
3134 *--cp == '\n') {
3135 *cp = '\0';
3136 }
3137
3138 if (strlen (content) > 1) {
3139 if (before) {
3140 m->mp_content_before = content;
3141 } else {
3142 m->mp_content_after = content;
3143 }
3144 } else {
3145 free (content);
3146 }
3147 }
3148
3149 free (boundary);
3150 free (bufp);
3151
3152 return OK;
3153 }
3154
3155
3156 char *
3157 ct_type_str (int type) {
3158 switch (type) {
3159 case CT_APPLICATION:
3160 return "application";
3161 case CT_AUDIO:
3162 return "audio";
3163 case CT_IMAGE:
3164 return "image";
3165 case CT_MESSAGE:
3166 return "message";
3167 case CT_MULTIPART:
3168 return "multipart";
3169 case CT_TEXT:
3170 return "text";
3171 case CT_VIDEO:
3172 return "video";
3173 case CT_EXTENSION:
3174 return "extension";
3175 default:
3176 return "unknown_type";
3177 }
3178 }
3179
3180
3181 char *
3182 ct_subtype_str (int type, int subtype) {
3183 switch (type) {
3184 case CT_APPLICATION:
3185 switch (subtype) {
3186 case APPLICATION_OCTETS:
3187 return "octets";
3188 case APPLICATION_POSTSCRIPT:
3189 return "postscript";
3190 default:
3191 return "unknown_app_subtype";
3192 }
3193 case CT_MESSAGE:
3194 switch (subtype) {
3195 case MESSAGE_RFC822:
3196 return "rfc822";
3197 case MESSAGE_PARTIAL:
3198 return "partial";
3199 case MESSAGE_EXTERNAL:
3200 return "external";
3201 default:
3202 return "unknown_msg_subtype";
3203 }
3204 case CT_MULTIPART:
3205 switch (subtype) {
3206 case MULTI_MIXED:
3207 return "mixed";
3208 case MULTI_ALTERNATE:
3209 return "alternative";
3210 case MULTI_DIGEST:
3211 return "digest";
3212 case MULTI_PARALLEL:
3213 return "parallel";
3214 case MULTI_RELATED:
3215 return "related";
3216 default:
3217 return "unknown_multipart_subtype";
3218 }
3219 case CT_TEXT:
3220 switch (subtype) {
3221 case TEXT_PLAIN:
3222 return "plain";
3223 case TEXT_RICHTEXT:
3224 return "richtext";
3225 case TEXT_ENRICHED:
3226 return "enriched";
3227 default:
3228 return "unknown_text_subtype";
3229 }
3230 default:
3231 return "unknown_type";
3232 }
3233 }
3234
3235
3236 int
3237 ct_str_type (const char *type) {
3238 struct str2init *s2i;
3239
3240 for (s2i = str2cts; s2i->si_key; ++s2i) {
3241 if (! strcasecmp (type, s2i->si_key)) {
3242 break;
3243 }
3244 }
3245 if (! s2i->si_key && ! uprf (type, "X-")) {
3246 ++s2i;
3247 }
3248
3249 return s2i->si_val;
3250 }
3251
3252
3253 int
3254 ct_str_subtype (int type, const char *subtype) {
3255 struct k2v *kv;
3256
3257 switch (type) {
3258 case CT_APPLICATION:
3259 for (kv = SubApplication; kv->kv_key; ++kv) {
3260 if (! strcasecmp (subtype, kv->kv_key)) {
3261 break;
3262 }
3263 }
3264 return kv->kv_value;
3265 case CT_MESSAGE:
3266 for (kv = SubMessage; kv->kv_key; ++kv) {
3267 if (! strcasecmp (subtype, kv->kv_key)) {
3268 break;
3269 }
3270 }
3271 return kv->kv_value;
3272 case CT_MULTIPART:
3273 for (kv = SubMultiPart; kv->kv_key; ++kv) {
3274 if (! strcasecmp (subtype, kv->kv_key)) {
3275 break;
3276 }
3277 }
3278 return kv->kv_value;
3279 case CT_TEXT:
3280 for (kv = SubText; kv->kv_key; ++kv) {
3281 if (! strcasecmp (subtype, kv->kv_key)) {
3282 break;
3283 }
3284 }
3285 return kv->kv_value;
3286 default:
3287 return 0;
3288 }
3289 }
3290
3291
3292 /* Find the content type and InitFunc for the CT. */
3293 const struct str2init *
3294 get_ct_init (int type) {
3295 const struct str2init *sp;
3296
3297 for (sp = str2cts; sp->si_key; ++sp) {
3298 if (type == sp->si_val) {
3299 return sp;
3300 }
3301 }
3302
3303 return NULL;
3304 }
3305
3306 const char *
3307 ce_str (int encoding) {
3308 switch (encoding) {
3309 case CE_BASE64:
3310 return "base64";
3311 case CE_QUOTED:
3312 return "quoted-printable";
3313 case CE_8BIT:
3314 return "8bit";
3315 case CE_7BIT:
3316 return "7bit";
3317 case CE_BINARY:
3318 return "binary";
3319 case CE_EXTENSION:
3320 return "extension";
3321 case CE_EXTERNAL:
3322 return "external";
3323 default:
3324 return "unknown";
3325 }
3326 }
3327
3328 /* Find the content type and InitFunc for the content encoding method. */
3329 const struct str2init *
3330 get_ce_method (const char *method) {
3331 struct str2init *sp;
3332
3333 for (sp = str2ces; sp->si_key; ++sp) {
3334 if (! strcasecmp (method, sp->si_key)) {
3335 return sp;
3336 }
3337 }
3338
3339 return NULL;
3340 }
3341
3342 /*
3343 * Parse a series of MIME attributes (or parameters) given a header as
3344 * input.
3345 *
3346 * Arguments include:
3347 *
3348 * filename - Name of input file (for error messages)
3349 * fieldname - Name of field being processed
3350 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3351 * Updated to point to end of attributes when finished.
3352 * param_head - Pointer to head of parameter list
3353 * param_tail - Pointer to tail of parameter list
3354 * commentp - Pointer to header comment pointer (may be NULL)
3355 *
3356 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3357 * DONE to indicate a benign error (minor parsing error, but the program
3358 * should continue).
3359 */
3360
3361 static int
3362 parse_header_attrs (const char *filename, const char *fieldname,
3363 char **header_attrp, PM *param_head, PM *param_tail,
3364 char **commentp)
3365 {
3366 char *cp = *header_attrp;
3367 PM pm;
3368 struct sectlist {
3369 char *value;
3370 int index;
3371 int len;
3372 struct sectlist *next;
3373 } *sp, *sp2;
3374 struct parmlist {
3375 char *name;
3376 char *charset;
3377 char *lang;
3378 struct sectlist *sechead;
3379 struct parmlist *next;
3380 } *pp, *pp2, *phead = NULL;
3381
3382 while (*cp == ';') {
3383 char *dp, *vp, *up, *nameptr, *valptr, *charset = NULL, *lang = NULL;
3384 int encoded = 0, partial = 0, len = 0, index = 0;
3385
3386 cp++;
3387 while (isspace ((unsigned char) *cp))
3388 cp++;
3389
3390 if (*cp == '(' &&
3391 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3392 return NOTOK;
3393 }
3394
3395 if (*cp == 0) {
3396 if (! suppress_extraneous_trailing_semicolon_warning) {
3397 advise (NULL,
3398 "extraneous trailing ';' in message %s's %s: "
3399 "parameter list",
3400 filename, fieldname);
3401 }
3402 extraneous_trailing_semicolon = 1;
3403 return DONE;
3404 }
3405
3406 /* down case the attribute name */
3407 for (dp = cp; istoken ((unsigned char) *dp); dp++)
3408 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
3409 *dp = tolower ((unsigned char) *dp);
3410
3411 for (up = dp; isspace ((unsigned char) *dp);)
3412 dp++;
3413 if (dp == cp || *dp != '=') {
3414 advise (NULL,
3415 "invalid parameter in message %s's %s: "
3416 "field\n%*sparameter %s (error detected at offset %d)",
3417 filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp);
3418 return NOTOK;
3419 }
3420
3421 /*
3422 * To handle RFC 2231, we have to deal with the following extensions:
3423 *
3424 * name*=encoded-value
3425 * name*<N>=part-N-of-a-parameter-value
3426 * name*<N>*=encoded-part-N-of-a-parameter-value
3427 *
3428 * So the rule is:
3429 * If there's a * right before the equal sign, it's encoded.
3430 * If there's a * and one or more digits, then it's section N.
3431 *
3432 * Remember we can have one or the other, or both. cp points to
3433 * beginning of name, up points past the last character in the
3434 * parameter name.
3435 */
3436
3437 for (vp = cp; vp < up; vp++) {
3438 if (*vp == '*' && vp < up - 1) {
3439 partial = 1;
3440 continue;
3441 } else if (*vp == '*' && vp == up - 1) {
3442 encoded = 1;
3443 } else if (partial) {
3444 if (isdigit((unsigned char) *vp))
3445 index = *vp - '0' + index * 10;
3446 else {
3447 advise (NULL, "invalid parameter index in message %s's "
3448 "%s: field\n%*s(parameter %s)", filename,
3449 fieldname, strlen(invo_name) + 2, "", cp);
3450 return NOTOK;
3451 }
3452 } else {
3453 len++;
3454 }
3455 }
3456
3457 /*
3458 * Break out the parameter name and value sections and allocate
3459 * memory for each.
3460 */
3461
3462 nameptr = mh_xmalloc(len + 1);
3463 strncpy(nameptr, cp, len);
3464 nameptr[len] = '\0';
3465
3466 for (dp++; isspace ((unsigned char) *dp);)
3467 dp++;
3468
3469 if (encoded) {
3470 /*
3471 * Single quotes delimit the character set and language tag.
3472 * They are required on the first section (or a complete
3473 * parameter).
3474 */
3475 if (index == 0) {
3476 vp = dp;
3477 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3478 *vp != '\0')
3479 vp++;
3480 if (*vp == '\'') {
3481 if (vp != dp) {
3482 len = vp - dp;
3483 charset = mh_xmalloc(len + 1);
3484 strncpy(charset, dp, len);
3485 charset[len] = '\0';
3486 } else {
3487 charset = NULL;
3488 }
3489 vp++;
3490 } else {
3491 advise(NULL, "missing charset in message %s's %s: "
3492 "field\n%*s(parameter %s)", filename, fieldname,
3493 strlen(invo_name) + 2, "", nameptr);
3494 free(nameptr);
3495 return NOTOK;
3496 }
3497 dp = vp;
3498
3499 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3500 *vp != '\0')
3501 vp++;
3502
3503 if (*vp == '\'') {
3504 if (vp != dp) {
3505 len = vp - dp;
3506 lang = mh_xmalloc(len + 1);
3507 strncpy(lang, dp, len);
3508 lang[len] = '\0';
3509 } else {
3510 lang = NULL;
3511 }
3512 vp++;
3513 } else {
3514 advise(NULL, "missing language tag in message %s's %s: "
3515 "field\n%*s(parameter %s)", filename, fieldname,
3516 strlen(invo_name) + 2, "", nameptr);
3517 free(nameptr);
3518 if (charset)
3519 free(charset);
3520 return NOTOK;
3521 }
3522
3523 dp = vp;
3524 }
3525
3526 /*
3527 * At this point vp should be pointing at the beginning
3528 * of the encoded value/section. Continue until we reach
3529 * the end or get whitespace. But first, calculate the
3530 * length so we can allocate the correct buffer size.
3531 */
3532
3533 for (vp = dp, len = 0; istoken(*vp); vp++) {
3534 if (*vp == '%') {
3535 if (*(vp + 1) == '\0' ||
3536 !isxdigit((unsigned char) *(vp + 1)) ||
3537 *(vp + 2) == '\0' ||
3538 !isxdigit((unsigned char) *(vp + 2))) {
3539 advise(NULL, "invalid encoded sequence in message "
3540 "%s's %s: field\n%*s(parameter %s)",
3541 filename, fieldname, strlen(invo_name) + 2,
3542 "", nameptr);
3543 free(nameptr);
3544 if (charset)
3545 free(charset);
3546 if (lang)
3547 free(lang);
3548 return NOTOK;
3549 }
3550 vp += 2;
3551 }
3552 len++;
3553 }
3554
3555 up = valptr = mh_xmalloc(len + 1);
3556
3557 for (vp = dp; istoken(*vp); vp++) {
3558 if (*vp == '%') {
3559 *up++ = decode_qp(*(vp + 1), *(vp + 2));
3560 vp += 2;
3561 } else {
3562 *up++ = *vp;
3563 }
3564 }
3565
3566 *up = '\0';
3567 cp = vp;
3568 } else {
3569 /*
3570 * A "normal" string. If it's got a leading quote, then we
3571 * strip the quotes out. Otherwise go until we reach the end
3572 * or get whitespace. Note we scan it twice; once to get the
3573 * length, then the second time copies it into the destination
3574 * buffer.
3575 */
3576
3577 len = 0;
3578
3579 if (*dp == '"') {
3580 for (cp = dp + 1;;) {
3581 switch (*cp++) {
3582 case '\0':
3583 bad_quote:
3584 advise (NULL,
3585 "invalid quoted-string in message %s's %s: "
3586 "field\n%*s(parameter %s)",
3587 filename, fieldname, strlen(invo_name) + 2, "",
3588 nameptr);
3589 free(nameptr);
3590 if (charset)
3591 free(charset);
3592 if (lang)
3593 free(lang);
3594 return NOTOK;
3595 case '"':
3596 break;
3597
3598 case '\\':
3599 if (*++cp == '\0')
3600 goto bad_quote;
3601 /* FALL THROUGH */
3602 default:
3603 len++;
3604 continue;
3605 }
3606 break;
3607 }
3608
3609 } else {
3610 for (cp = dp; istoken (*cp); cp++) {
3611 len++;
3612 }
3613 }
3614
3615 valptr = mh_xmalloc(len + 1);
3616
3617 if (*dp == '"') {
3618 int i;
3619 for (cp = dp + 1, vp = valptr, i = 0; i < len; i++) {
3620 if (*cp == '\\') {
3621 cp++;
3622 }
3623 *vp++ = *cp++;
3624 }
3625 cp++;
3626 } else {
3627 strncpy(valptr, cp = dp, len);
3628 cp += len;
3629 }
3630
3631 valptr[len] = '\0';
3632 }
3633
3634 /*
3635 * If 'partial' is set, we don't allocate a parameter now. We
3636 * put it on the parameter linked list to be reassembled later.
3637 *
3638 * "phead" points to a list of all parameters we need to reassemble.
3639 * Each parameter has a list of sections. We insert the sections in
3640 * order.
3641 */
3642
3643 if (partial) {
3644 for (pp = phead; pp != NULL; pp = pp->next) {
3645 if (strcasecmp(nameptr, pp->name) == 0)
3646 break;
3647 }
3648
3649 if (pp == NULL) {
3650 pp = mh_xmalloc(sizeof(*pp));
3651 memset(pp, 0, sizeof(*pp));
3652 pp->name = nameptr;
3653 pp->next = phead;
3654 phead = pp;
3655 }
3656
3657 /*
3658 * Insert this into the section linked list
3659 */
3660
3661 sp = mh_xmalloc(sizeof(*sp));
3662 memset(sp, 0, sizeof(*sp));
3663 sp->value = valptr;
3664 sp->index = index;
3665 sp->len = len;
3666
3667 if (pp->sechead == NULL || pp->sechead->index > index) {
3668 sp->next = pp->sechead;
3669 pp->sechead = sp;
3670 } else {
3671 for (sp2 = pp->sechead; sp2 != NULL; sp2 = sp2->next) {
3672 if (sp2->index == sp->index) {
3673 advise (NULL, "duplicate index (%d) in message "
3674 "%s's %s: field\n%*s(parameter %s)", sp->index,
3675 filename, fieldname, strlen(invo_name) + 2, "",
3676 nameptr);
3677 free (nameptr);
3678 return NOTOK;
3679 }
3680 if (sp2->index < sp->index &&
3681 (sp2->next == NULL || sp2->next->index > sp->index)) {
3682 sp->next = sp2->next;
3683 sp2->next = sp;
3684 break;
3685 }
3686 }
3687
3688 if (sp2 == NULL) {
3689 advise(NULL, "Internal error: cannot insert partial "
3690 "param in message %s's %s: field\n%*s(parameter %s)",
3691 filename, fieldname, strlen(invo_name) + 2, "",
3692 nameptr);
3693 free (nameptr);
3694 return NOTOK;
3695 }
3696 }
3697
3698 /*
3699 * Save our charset and lang tags.
3700 */
3701
3702 if (index == 0 && encoded) {
3703 if (pp->charset)
3704 free(pp->charset);
3705 pp->charset = charset;
3706 if (pp->lang)
3707 free(pp->lang);
3708 pp->lang = lang;
3709 }
3710 } else {
3711 pm = add_param(param_head, param_tail, nameptr, valptr, 1);
3712 pm->pm_charset = charset;
3713 pm->pm_lang = lang;
3714 }
3715
3716 while (isspace ((unsigned char) *cp))
3717 cp++;
3718
3719 if (*cp == '(' &&
3720 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3721 return NOTOK;
3722 }
3723 }
3724
3725 /*
3726 * Now that we're done, reassemble all of the partial parameters.
3727 */
3728
3729 for (pp = phead; pp != NULL; ) {
3730 char *p, *q;
3731 size_t tlen = 0;
3732 int pindex = 0;
3733 for (sp = pp->sechead; sp != NULL; sp = sp->next) {
3734 if (sp->index != pindex++) {
3735 advise(NULL, "missing section %d for parameter in "
3736 "message %s's %s: field\n%*s(parameter %s)", pindex - 1,
3737 filename, fieldname, strlen(invo_name) + 2, "",
3738 pp->name);
3739 return NOTOK;
3740 }
3741 tlen += sp->len;
3742 }
3743
3744 p = q = mh_xmalloc(tlen + 1);
3745 for (sp = pp->sechead; sp != NULL; ) {
3746 memcpy(q, sp->value, sp->len);
3747 q += sp->len;
3748 free(sp->value);
3749 sp2 = sp->next;
3750 free(sp);
3751 sp = sp2;
3752 }
3753
3754 p[tlen] = '\0';
3755
3756 pm = add_param(param_head, param_tail, pp->name, p, 1);
3757 pm->pm_charset = pp->charset;
3758 pm->pm_lang = pp->lang;
3759 pp2 = pp->next;
3760 free(pp);
3761 pp = pp2;
3762 }
3763
3764 *header_attrp = cp;
3765 return OK;
3766 }
3767
3768 /*
3769 * Return the charset for a particular content type.
3770 */
3771
3772 char *
3773 content_charset (CT ct) {
3774 char *ret_charset = NULL;
3775
3776 ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
3777
3778 return ret_charset ? ret_charset : getcpy ("US-ASCII");
3779 }
3780
3781
3782 /*
3783 * Create a string based on a list of output parameters. Assume that this
3784 * parameter string will be appended to an existing header, so start out
3785 * with the separator (;). Perform RFC 2231 encoding when necessary.
3786 */
3787
3788 char *
3789 output_params(size_t initialwidth, PM params, int *offsetout, int external)
3790 {
3791 char *paramout = NULL;
3792 char line[CPERLIN * 2], *q;
3793 int curlen, index, cont, encode, i;
3794 size_t valoff, numchars;
3795
3796 while (params != NULL) {
3797 encode = 0;
3798 index = 0;
3799 valoff = 0;
3800 q = line;
3801
3802 if (external && strcasecmp(params->pm_name, "body") == 0)
3803 continue;
3804
3805 if (strlen(params->pm_name) > CPERLIN) {
3806 advise(NULL, "Parameter name \"%s\" is too long", params->pm_name);
3807 if (paramout)
3808 free(paramout);
3809 return NULL;
3810 }
3811
3812 curlen = param_len(params, index, valoff, &encode, &cont, &numchars);
3813
3814 /*
3815 * Loop until we get a parameter that fits within a line. We
3816 * assume new lines start with a tab, so check our overflow based
3817 * on that.
3818 */
3819
3820 while (cont) {
3821 *q++ = ';';
3822 *q++ = '\n';
3823 *q++ = '\t';
3824
3825 /*
3826 * At this point we're definitely continuing the line, so
3827 * be sure to include the parameter name and section index.
3828 */
3829
3830 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3831 params->pm_name, index);
3832
3833 /*
3834 * Both of these functions do a NUL termination
3835 */
3836
3837 if (encode)
3838 i = encode_param(params, q, sizeof(line) - (q - line),
3839 numchars, valoff, index);
3840 else
3841 i = normal_param(params, q, sizeof(line) - (q - line),
3842 numchars, valoff);
3843
3844 if (i == 0) {
3845 if (paramout)
3846 free(paramout);
3847 return NULL;
3848 }
3849
3850 valoff += numchars;
3851 index++;
3852 curlen = param_len(params, index, valoff, &encode, &cont,
3853 &numchars);
3854 q = line;
3855
3856 /*
3857 * "line" starts with a ;\n\t, so that doesn't count against
3858 * the length. But add 8 since it starts with a tab; that's
3859 * how we end up with 5.
3860 */
3861
3862 initialwidth = strlen(line) + 5;
3863
3864 /*
3865 * At this point the line should be built, so add it to our
3866 * current output buffer.
3867 */
3868
3869 paramout = add(line, paramout);
3870 }
3871
3872 /*
3873 * If this won't fit on the line, start a new one. Save room in
3874 * case we need a semicolon on the end
3875 */
3876
3877 if (initialwidth + curlen > CPERLIN - 1) {
3878 *q++ = ';';
3879 *q++ = '\n';
3880 *q++ = '\t';
3881 initialwidth = 8;
3882 } else {
3883 *q++ = ';';
3884 *q++ = ' ';
3885 initialwidth += 2;
3886 }
3887
3888 /*
3889 * At this point, we're either finishing a contined parameter, or
3890 * we're working on a new one.
3891 */
3892
3893 if (index > 0) {
3894 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3895 params->pm_name, index);
3896 } else {
3897 strncpy(q, params->pm_name, sizeof(line) - (q - line));
3898 q += strlen(q);
3899 }
3900
3901 if (encode)
3902 i = encode_param(params, q, sizeof(line) - (q - line),
3903 strlen(params->pm_value + valoff), valoff, index);
3904 else
3905 i = normal_param(params, q, sizeof(line) - (q - line),
3906 strlen(params->pm_value + valoff), valoff);
3907
3908 if (i == 0) {
3909 if (paramout)
3910 free(paramout);
3911 return NULL;
3912 }
3913
3914 paramout = add(line, paramout);
3915 initialwidth += strlen(line);
3916
3917 params = params->pm_next;
3918 }
3919
3920 if (offsetout)
3921 *offsetout = initialwidth;
3922
3923 return paramout;
3924 }
3925
3926 /*
3927 * Calculate the size of a parameter.
3928 *
3929 * Arguments include
3930 *
3931 * pm - The parameter being output
3932 * index - If continuing the parameter, the index of the section
3933 * we're on.
3934 * valueoff - The current offset into the parameter value that we're
3935 * working on (previous sections have consumed valueoff bytes).
3936 * encode - Set if we should perform encoding on this parameter section
3937 * (given that we're consuming bytesfit bytes).
3938 * cont - Set if the remaining data in value will not fit on a single
3939 * line and will need to be continued.
3940 * bytesfit - The number of bytes that we can consume from the parameter
3941 * value and still fit on a completely new line. The
3942 * calculation assumes the new line starts with a tab,
3943 * includes the parameter name and any encoding, and fits
3944 * within CPERLIN bytes. Will always be at least 1.
3945 */
3946
3947 static size_t
3948 param_len(PM pm, int index, size_t valueoff, int *encode, int *cont,
3949 size_t *bytesfit)
3950 {
3951 char *start = pm->pm_value + valueoff, *p, indexchar[32];
3952 size_t len = 0, fit = 0;
3953 int fitlimit = 0, eightbit, maxfit;
3954
3955 *encode = 0;
3956
3957 /*
3958 * Add up the length. First, start with the parameter name.
3959 */
3960
3961 len = strlen(pm->pm_name);
3962
3963 /*
3964 * Scan the parameter value and see if we need to do encoding for this
3965 * section.
3966 */
3967
3968 eightbit = contains8bit(start, NULL);
3969
3970 /*
3971 * Determine if we need to encode this section. Encoding is necessary if:
3972 *
3973 * - There are any 8-bit characters at all and we're on the first
3974 * section.
3975 * - There are 8-bit characters within N bytes of our section start.
3976 * N is calculated based on the number of bytes it would take to
3977 * reach CPERLIN. Specifically:
3978 * 8 (starting tab) +
3979 * strlen(param name) +
3980 * 4 ('* for section marker, '=', opening/closing '"')
3981 * strlen (index)
3982 * is the number of bytes used by everything that isn't part of the
3983 * value. So that gets subtracted from CPERLIN.
3984 */
3985
3986 snprintf(indexchar, sizeof(indexchar), "%d", index);
3987 maxfit = CPERLIN - (12 + len + strlen(indexchar));
3988 if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) {
3989 *encode = 1;
3990 }
3991
3992 len++; /* Add in equal sign */
3993
3994 if (*encode) {
3995 /*
3996 * We're using maxfit as a marker for how many characters we can
3997 * fit into the line. Bump it by two because we're not using quotes
3998 * when encoding.
3999 */
4000
4001 maxfit += 2;
4002
4003 /*
4004 * If we don't have a charset or language tag in this parameter,
4005 * add them now.
4006 */
4007
4008 if (! pm->pm_charset) {
4009 pm->pm_charset = getcpy(write_charset_8bit());
4010 if (strcasecmp(pm->pm_charset, "US-ASCII") == 0)
4011 adios(NULL, "8-bit characters in parameter \"%s\", but "
4012 "local character set is US-ASCII", pm->pm_name);
4013 }
4014 if (! pm->pm_lang)
4015 pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */
4016
4017 len++; /* For the encoding marker */
4018 maxfit--;
4019 if (index == 0) {
4020 int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
4021 len += enclen;
4022 maxfit-= enclen;
4023 } else {
4024 /*
4025 * We know we definitely need to include an index. maxfit already
4026 * includes the section marker.
4027 */
4028 len += strlen(indexchar);
4029 }
4030 for (p = start; *p != '\0'; p++) {
4031 if (isparamencode(*p)) {
4032 len += 3;
4033 maxfit -= 3;
4034 } else {
4035 len++;
4036 maxfit--;
4037 }
4038 /*
4039 * Just so there's no confusion: maxfit is counting OUTPUT
4040 * characters (post-encoding). fit is counting INPUT characters.
4041 */
4042 if (! fitlimit && maxfit >= 0)
4043 fit++;
4044 else if (! fitlimit)
4045 fitlimit++;
4046 }
4047 } else {
4048 /*
4049 * Calculate the string length, but add room for quoting \
4050 * and " if necessary. Also account for quotes at beginning
4051 * and end.
4052 */
4053 for (p = start; *p != '\0'; p++) {
4054 switch (*p) {
4055 case '"':
4056 case '\\':
4057 len++;
4058 maxfit--;
4059 /* FALL THROUGH */
4060 default:
4061 len++;
4062 maxfit--;
4063 }
4064 if (! fitlimit && maxfit >= 0)
4065 fit++;
4066 else if (! fitlimit)
4067 fitlimit++;
4068 }
4069
4070 len += 2;
4071 }
4072
4073 if (fit < 1)
4074 fit = 1;
4075
4076 *cont = fitlimit;
4077 *bytesfit = fit;
4078
4079 return len;
4080 }
4081
4082 /*
4083 * Output an encoded parameter string.
4084 */
4085
4086 static size_t
4087 encode_param(PM pm, char *output, size_t len, size_t valuelen,
4088 size_t valueoff, int index)
4089 {
4090 size_t outlen = 0, n;
4091 char *endptr = output + len, *p;
4092
4093 /*
4094 * First, output the marker for an encoded string.
4095 */
4096
4097 *output++ = '*';
4098 *output++ = '=';
4099 outlen += 2;
4100
4101 /*
4102 * If the index is 0, output the character set and language tag.
4103 * If theses were NULL, they should have already been filled in
4104 * by param_len().
4105 */
4106
4107 if (index == 0) {
4108 n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset,
4109 pm->pm_lang);
4110 output += n;
4111 outlen += n;
4112 if (output > endptr) {
4113 advise(NULL, "Internal error: parameter buffer overflow");
4114 return 0;
4115 }
4116 }
4117
4118 /*
4119 * Copy over the value, encoding if necessary
4120 */
4121
4122 p = pm->pm_value + valueoff;
4123 while (valuelen-- > 0) {
4124 if (isparamencode(*p)) {
4125 n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++);
4126 output += n;
4127 outlen += n;
4128 } else {
4129 *output++ = *p++;
4130 outlen++;
4131 }
4132 if (output > endptr) {
4133 advise(NULL, "Internal error: parameter buffer overflow");
4134 return 0;
4135 }
4136 }
4137
4138 *output = '\0';
4139
4140 return outlen;
4141 }
4142
4143 /*
4144 * Output a "normal" parameter, without encoding. Be sure to escape
4145 * quotes and backslashes if necessary.
4146 */
4147
4148 static size_t
4149 normal_param(PM pm, char *output, size_t len, size_t valuelen,
4150 size_t valueoff)
4151 {
4152 size_t outlen = 0;
4153 char *endptr = output + len, *p;
4154
4155 *output++ = '=';
4156 *output++ = '"';
4157 outlen += 2;
4158
4159 p = pm->pm_value + valueoff;
4160
4161 while (valuelen-- > 0) {
4162 switch (*p) {
4163 case '\\':
4164 case '"':
4165 *output++ = '\\';
4166 outlen++;
4167 default:
4168 *output++ = *p++;
4169 outlen++;
4170 }
4171 if (output > endptr) {
4172 advise(NULL, "Internal error: parameter buffer overflow");
4173 return 0;
4174 }
4175 }
4176
4177 if (output - 2 > endptr) {
4178 advise(NULL, "Internal error: parameter buffer overflow");
4179 return 0;
4180 }
4181
4182 *output++ = '"';
4183 *output++ = '\0';
4184
4185 return outlen + 1;
4186 }
4187
4188 /*
4189 * Add a parameter to the parameter linked list
4190 */
4191
4192 PM
4193 add_param(PM *first, PM *last, char *name, char *value, int nocopy)
4194 {
4195 PM pm = mh_xmalloc(sizeof(*pm));
4196
4197 memset(pm, 0, sizeof(*pm));
4198
4199 pm->pm_name = nocopy ? name : getcpy(name);
4200 pm->pm_value = nocopy ? value : getcpy(value);
4201
4202 if (*first) {
4203 (*last)->pm_next = pm;
4204 *last = pm;
4205 } else {
4206 *first = pm;
4207 *last = pm;
4208 }
4209
4210 return pm;
4211 }
4212
4213 /*
4214 * Either replace a current parameter with a new value, or add the parameter
4215 * to the parameter linked list.
4216 */
4217
4218 PM
4219 replace_param(PM *first, PM *last, char *name, char *value, int nocopy)
4220 {
4221 PM pm;
4222
4223 for (pm = *first; pm != NULL; pm = pm->pm_next) {
4224 if (strcasecmp(name, pm->pm_name) == 0) {
4225 /*
4226 * If nocopy is set, it's assumed that we own both name
4227 * and value. We don't need name, so we discard it now.
4228 */
4229 if (nocopy)
4230 free(name);
4231 free(pm->pm_value);
4232 pm->pm_value = nocopy ? value : getcpy(value);
4233 return pm;
4234 }
4235 }
4236
4237 return add_param(first, last, name, value, nocopy);
4238 }
4239
4240 /*
4241 * Retrieve a parameter value from a parameter linked list. If the parameter
4242 * value needs converted to the local character set, do that now.
4243 */
4244
4245 char *
4246 get_param(PM first, const char *name, char replace, int fetchonly)
4247 {
4248 while (first != NULL) {
4249 if (strcasecmp(name, first->pm_name) == 0) {
4250 if (fetchonly)
4251 return first->pm_value;
4252 else
4253 return getcpy(get_param_value(first, replace));
4254 }
4255 first = first->pm_next;
4256 }
4257
4258 return NULL;
4259 }
4260
4261 /*
4262 * Return a parameter value, converting to the local character set if
4263 * necessary
4264 */
4265
4266 char *get_param_value(PM pm, char replace)
4267 {
4268 static char buffer[4096]; /* I hope no parameters are larger */
4269 size_t bufsize = sizeof(buffer);
4270 #ifdef HAVE_ICONV
4271 size_t inbytes;
4272 int utf8;
4273 iconv_t cd;
4274 ICONV_CONST char *p;
4275 #else /* HAVE_ICONV */
4276 char *p;
4277 #endif /* HAVE_ICONV */
4278
4279 char *q;
4280
4281 /*
4282 * If we don't have a character set indicated, it's assumed to be
4283 * US-ASCII. If it matches our character set, we don't need to convert
4284 * anything.
4285 */
4286
4287 if (!pm->pm_charset || check_charset(pm->pm_charset,
4288 strlen(pm->pm_charset))) {
4289 return pm->pm_value;
4290 }
4291
4292 /*
4293 * In this case, we need to convert. If we have iconv support, use
4294 * that. Otherwise, go through and simply replace every non-ASCII
4295 * character with the substitution character.
4296 */
4297
4298 #ifdef HAVE_ICONV
4299 q = buffer;
4300 bufsize = sizeof(buffer);
4301 utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0;
4302
4303 cd = iconv_open(get_charset(), pm->pm_charset);
4304 if (cd == (iconv_t) -1) {
4305 goto noiconv;
4306 }
4307
4308 inbytes = strlen(pm->pm_value);
4309 p = pm->pm_value;
4310
4311 while (inbytes) {
4312 if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) {
4313 if (errno != EILSEQ) {
4314 iconv_close(cd);
4315 goto noiconv;
4316 }
4317 /*
4318 * Reset shift state, substitute our character,
4319 * try to restart conversion.
4320 */
4321
4322 iconv(cd, NULL, NULL, &q, &bufsize);
4323
4324 if (bufsize == 0) {
4325 iconv_close(cd);
4326 goto noiconv;
4327 }
4328 *q++ = replace;
4329 bufsize--;
4330 if (bufsize == 0) {
4331 iconv_close(cd);
4332 goto noiconv;
4333 }
4334 if (utf8) {
4335 for (++p, --inbytes;
4336 inbytes > 0 && (((unsigned char) *q) & 0xc0) == 0x80;
4337 ++p, --inbytes)
4338 continue;
4339 } else {
4340 p++;
4341 inbytes--;
4342 }
4343 }
4344 }
4345
4346 iconv_close(cd);
4347
4348 if (bufsize == 0)
4349 q--;
4350 *q = '\0';
4351
4352 return buffer;
4353
4354 noiconv:
4355 #endif /* HAVE_ICONV */
4356
4357 /*
4358 * Take everything non-ASCII and substituite the replacement character
4359 */
4360
4361 q = buffer;
4362 bufsize = sizeof(buffer);
4363 for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) {
4364 if (isascii((unsigned char) *p) && !iscntrl((unsigned char) *p))
4365 *q = *p;
4366 else
4367 *q = replace;
4368 }
4369
4370 *q = '\0';
4371
4372 return buffer;
4373 }