]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Another pass at cleaning up (some of) the manpages.
[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). "
1105 "mhfixmsg -fixcte can fix it, or\n"
1106 "manually edit the file and change the \"%s\"\n"
1107 "Content-Transfer-Encoding to one of those. For now",
1108 ci->ci_type, ci->ci_subtype, ct->c_file, bp);
1109 free (cte);
1110
1111 return NOTOK;
1112 }
1113
1114 /* match subtype */
1115 ct->c_subtype = ct_str_subtype (CT_MULTIPART, ci->ci_subtype);
1116
1117 /*
1118 * Check for "boundary" parameter, which is
1119 * required for multipart messages.
1120 */
1121 bp = 0;
1122 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1123 if (!strcasecmp (pm->pm_name, "boundary")) {
1124 bp = pm->pm_value;
1125 break;
1126 }
1127 }
1128
1129 /* complain if boundary parameter is missing */
1130 if (!pm) {
1131 advise (NULL,
1132 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1133 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1134 return NOTOK;
1135 }
1136
1137 /* allocate primary structure for multipart info */
1138 if ((m = (struct multipart *) mh_xcalloc (1, sizeof(*m))) == NULL)
1139 adios (NULL, "out of memory");
1140 ct->c_ctparams = (void *) m;
1141
1142 /* check if boundary parameter contains only whitespace characters */
1143 for (cp = bp; isspace ((unsigned char) *cp); cp++)
1144 continue;
1145 if (!*cp) {
1146 advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1147 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1148 return NOTOK;
1149 }
1150
1151 /* remove trailing whitespace from boundary parameter */
1152 for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1153 if (!isspace ((unsigned char) *dp))
1154 break;
1155 *++dp = '\0';
1156
1157 /* record boundary separators */
1158 m->mp_start = concat (bp, "\n", NULL);
1159 m->mp_stop = concat (bp, "--\n", NULL);
1160
1161 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1162 advise (ct->c_file, "unable to open for reading");
1163 return NOTOK;
1164 }
1165
1166 fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1167 last = ct->c_end;
1168 next = &m->mp_parts;
1169 part = NULL;
1170 inout = 1;
1171
1172 while ((gotlen = getline(&bufp, &buflen, fp)) != -1) {
1173 if (pos > last)
1174 break;
1175
1176 pos += gotlen;
1177 if (bufp[0] != '-' || bufp[1] != '-')
1178 continue;
1179 if (inout) {
1180 if (strcmp (bufp + 2, m->mp_start))
1181 continue;
1182 next_part:
1183 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
1184 adios (NULL, "out of memory");
1185 *next = part;
1186 next = &part->mp_next;
1187
1188 if (!(p = get_content (fp, ct->c_file,
1189 ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1190 free(bufp);
1191 ct->c_fp = NULL;
1192 return NOTOK;
1193 }
1194 p->c_fp = NULL;
1195 part->mp_part = p;
1196 pos = p->c_begin;
1197 fseek (fp, pos, SEEK_SET);
1198 inout = 0;
1199 } else {
1200 if (strcmp (bufp + 2, m->mp_start) == 0) {
1201 inout = 1;
1202 end_part:
1203 p = part->mp_part;
1204 p->c_end = ftell(fp) - (gotlen + 1);
1205 if (p->c_end < p->c_begin)
1206 p->c_begin = p->c_end;
1207 if (inout)
1208 goto next_part;
1209 goto last_part;
1210 } else {
1211 if (strcmp (bufp + 2, m->mp_stop) == 0)
1212 goto end_part;
1213 }
1214 }
1215 }
1216
1217 if (! suppress_bogus_mp_content_warning) {
1218 advise (NULL, "bogus multipart content in message %s", ct->c_file);
1219 }
1220 bogus_mp_content = 1;
1221
1222 if (!inout && part) {
1223 p = part->mp_part;
1224 p->c_end = ct->c_end;
1225
1226 if (p->c_begin >= p->c_end) {
1227 for (next = &m->mp_parts; *next != part;
1228 next = &((*next)->mp_next))
1229 continue;
1230 *next = NULL;
1231 free_content (p);
1232 free ((char *) part);
1233 }
1234 }
1235
1236 last_part:
1237 /* reverse the order of the parts for multipart/alternative */
1238 if (ct->c_subtype == MULTI_ALTERNATE) {
1239 reverse_parts (ct);
1240 prefer_parts (ct);
1241 }
1242
1243 /*
1244 * label all subparts with part number, and
1245 * then initialize the content of the subpart.
1246 */
1247 {
1248 int partnum;
1249 char *pp;
1250 char partnam[BUFSIZ];
1251
1252 if (ct->c_partno) {
1253 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1254 pp = partnam + strlen (partnam);
1255 } else {
1256 pp = partnam;
1257 }
1258
1259 for (part = m->mp_parts, partnum = 1; part;
1260 part = part->mp_next, partnum++) {
1261 p = part->mp_part;
1262
1263 sprintf (pp, "%d", partnum);
1264 p->c_partno = add (partnam, NULL);
1265
1266 /* initialize the content of the subparts */
1267 if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1268 free(bufp);
1269 fclose (ct->c_fp);
1270 ct->c_fp = NULL;
1271 return NOTOK;
1272 }
1273 }
1274 }
1275
1276 get_leftover_mp_content (ct, 1);
1277 get_leftover_mp_content (ct, 0);
1278
1279 free(bufp);
1280 fclose (ct->c_fp);
1281 ct->c_fp = NULL;
1282 return OK;
1283 }
1284
1285
1286 /*
1287 * reverse the order of the parts of a multipart/alternative,
1288 * presumably to put the "most favored" alternative first, for
1289 * ease of choosing/displaying it later on. from a mail message on
1290 * nmh-workers, from kenh:
1291 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1292 * see code in mhn that did the same thing... Acccording to the RCS
1293 * logs, that code was around from the initial checkin of mhn.c by
1294 * John Romine in 1992, which is as far back as we have."
1295 */
1296 static void
1297 reverse_parts (CT ct)
1298 {
1299 struct multipart *m = (struct multipart *) ct->c_ctparams;
1300 struct part *part;
1301 struct part *next;
1302
1303 /* Reverse the order of its parts by walking the mp_parts list
1304 and pushing each node to the front. */
1305 for (part = m->mp_parts, m->mp_parts = NULL; part; part = next) {
1306 next = part->mp_next;
1307 part->mp_next = m->mp_parts;
1308 m->mp_parts = part;
1309 }
1310 }
1311
1312 static void
1313 move_preferred_part (CT ct, char *type, char *subtype)
1314 {
1315 struct multipart *m = (struct multipart *) ct->c_ctparams;
1316 struct part *part, *prev, *head, *nhead, *ntail;
1317 struct part h, n;
1318 CI ci;
1319
1320 /* move the matching part(s) to the head of the list: walk the
1321 * list of parts, move matching parts to a new list (maintaining
1322 * their order), and finally, concatenate the old list onto the
1323 * new.
1324 */
1325
1326 head = &h;
1327 nhead = &n;
1328
1329 head->mp_next = m->mp_parts;
1330 nhead->mp_next = NULL;
1331 ntail = nhead;
1332
1333 prev = head;
1334 part = head->mp_next;
1335 while (part != NULL) {
1336 ci = &part->mp_part->c_ctinfo;
1337 if (!strcasecmp(ci->ci_type, type) &&
1338 (!subtype || !strcasecmp(ci->ci_subtype, subtype))) {
1339 prev->mp_next = part->mp_next;
1340 part->mp_next = NULL;
1341 ntail->mp_next = part;
1342 ntail = part;
1343 part = prev->mp_next;
1344 } else {
1345 prev = part;
1346 part = prev->mp_next;
1347 }
1348 }
1349 ntail->mp_next = head->mp_next;
1350 m->mp_parts = nhead->mp_next;
1351
1352 }
1353
1354 /*
1355 * move parts that match the user's preferences (-prefer) to the head
1356 * of the line. process preferences in reverse so first one given
1357 * ends up first in line
1358 */
1359 static void
1360 prefer_parts(CT ct)
1361 {
1362 int i;
1363 for (i = npreferred-1; i >= 0; i--)
1364 move_preferred_part(ct, preferred_types[i], preferred_subtypes[i]);
1365 }
1366
1367
1368
1369 /* parse_mime() arranges alternates in reverse (priority) order. This
1370 function can be used to reverse them back. This will put, for
1371 example, a text/plain part before a text/html part in a
1372 multipart/alternative part, for example, where it belongs. */
1373 void
1374 reverse_alternative_parts (CT ct) {
1375 if (ct->c_type == CT_MULTIPART) {
1376 struct multipart *m = (struct multipart *) ct->c_ctparams;
1377 struct part *part;
1378
1379 if (ct->c_subtype == MULTI_ALTERNATE) {
1380 reverse_parts (ct);
1381 }
1382
1383 /* And call recursively on each part of a multipart. */
1384 for (part = m->mp_parts; part; part = part->mp_next) {
1385 reverse_alternative_parts (part->mp_part);
1386 }
1387 }
1388 }
1389
1390
1391 /*
1392 * MESSAGE
1393 */
1394
1395 static int
1396 InitMessage (CT ct)
1397 {
1398 CI ci = &ct->c_ctinfo;
1399
1400 if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1401 admonish (NULL,
1402 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1403 ci->ci_type, ci->ci_subtype, ct->c_file);
1404 return NOTOK;
1405 }
1406
1407 /* check for missing subtype */
1408 if (!*ci->ci_subtype)
1409 ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1410
1411 /* match subtype */
1412 ct->c_subtype = ct_str_subtype (CT_MESSAGE, ci->ci_subtype);
1413
1414 switch (ct->c_subtype) {
1415 case MESSAGE_RFC822:
1416 break;
1417
1418 case MESSAGE_PARTIAL:
1419 {
1420 PM pm;
1421 struct partial *p;
1422
1423 if ((p = (struct partial *) mh_xcalloc (1, sizeof(*p))) == NULL)
1424 adios (NULL, "out of memory");
1425 ct->c_ctparams = (void *) p;
1426
1427 /* scan for parameters "id", "number", and "total" */
1428 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1429 if (!strcasecmp (pm->pm_name, "id")) {
1430 p->pm_partid = add (pm->pm_value, NULL);
1431 continue;
1432 }
1433 if (!strcasecmp (pm->pm_name, "number")) {
1434 if (sscanf (pm->pm_value, "%d", &p->pm_partno) != 1
1435 || p->pm_partno < 1) {
1436 invalid_param:
1437 advise (NULL,
1438 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1439 pm->pm_name, ci->ci_type, ci->ci_subtype,
1440 ct->c_file, TYPE_FIELD);
1441 return NOTOK;
1442 }
1443 continue;
1444 }
1445 if (!strcasecmp (pm->pm_name, "total")) {
1446 if (sscanf (pm->pm_value, "%d", &p->pm_maxno) != 1
1447 || p->pm_maxno < 1)
1448 goto invalid_param;
1449 continue;
1450 }
1451 }
1452
1453 if (!p->pm_partid
1454 || !p->pm_partno
1455 || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1456 advise (NULL,
1457 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1458 ci->ci_type, ci->ci_subtype,
1459 ct->c_file, TYPE_FIELD);
1460 return NOTOK;
1461 }
1462 }
1463 break;
1464
1465 case MESSAGE_EXTERNAL:
1466 {
1467 int exresult;
1468 struct exbody *e;
1469 CT p;
1470 FILE *fp;
1471
1472 if ((e = (struct exbody *) mh_xcalloc (1, sizeof(*e))) == NULL)
1473 adios (NULL, "out of memory");
1474 ct->c_ctparams = (void *) e;
1475
1476 if (!ct->c_fp
1477 && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1478 advise (ct->c_file, "unable to open for reading");
1479 return NOTOK;
1480 }
1481
1482 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1483
1484 if (!(p = get_content (fp, ct->c_file, 0))) {
1485 ct->c_fp = NULL;
1486 return NOTOK;
1487 }
1488
1489 e->eb_parent = ct;
1490 e->eb_content = p;
1491 p->c_ctexbody = e;
1492 p->c_ceopenfnx = NULL;
1493 if ((exresult = params_external (ct, 0)) != NOTOK
1494 && p->c_ceopenfnx == openMail) {
1495 int cc, size;
1496 char *bp;
1497
1498 if ((size = ct->c_end - p->c_begin) <= 0) {
1499 if (!e->eb_subject)
1500 content_error (NULL, ct,
1501 "empty body for access-type=mail-server");
1502 goto no_body;
1503 }
1504
1505 e->eb_body = bp = mh_xmalloc ((unsigned) size);
1506 fseek (p->c_fp, p->c_begin, SEEK_SET);
1507 while (size > 0)
1508 switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1509 case NOTOK:
1510 adios ("failed", "fread");
1511
1512 case OK:
1513 adios (NULL, "unexpected EOF from fread");
1514
1515 default:
1516 bp += cc, size -= cc;
1517 break;
1518 }
1519 *bp = 0;
1520 }
1521 no_body:
1522 p->c_fp = NULL;
1523 p->c_end = p->c_begin;
1524
1525 fclose (ct->c_fp);
1526 ct->c_fp = NULL;
1527
1528 if (exresult == NOTOK)
1529 return NOTOK;
1530 if (e->eb_flags == NOTOK)
1531 return OK;
1532
1533 switch (p->c_type) {
1534 case CT_MULTIPART:
1535 break;
1536
1537 case CT_MESSAGE:
1538 if (p->c_subtype != MESSAGE_RFC822)
1539 break;
1540 /* else fall... */
1541 default:
1542 e->eb_partno = ct->c_partno;
1543 if (p->c_ctinitfnx)
1544 (*p->c_ctinitfnx) (p);
1545 break;
1546 }
1547 }
1548 break;
1549
1550 default:
1551 break;
1552 }
1553
1554 return OK;
1555 }
1556
1557
1558 int
1559 params_external (CT ct, int composing)
1560 {
1561 PM pm;
1562 struct exbody *e = (struct exbody *) ct->c_ctparams;
1563 CI ci = &ct->c_ctinfo;
1564
1565 ct->c_ceopenfnx = NULL;
1566 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1567 if (!strcasecmp (pm->pm_name, "access-type")) {
1568 struct str2init *s2i;
1569 CT p = e->eb_content;
1570
1571 for (s2i = str2methods; s2i->si_key; s2i++)
1572 if (!strcasecmp (pm->pm_value, s2i->si_key))
1573 break;
1574 if (!s2i->si_key) {
1575 e->eb_access = pm->pm_value;
1576 e->eb_flags = NOTOK;
1577 p->c_encoding = CE_EXTERNAL;
1578 continue;
1579 }
1580 e->eb_access = s2i->si_key;
1581 e->eb_flags = s2i->si_val;
1582 p->c_encoding = CE_EXTERNAL;
1583
1584 /* Call the Init function for this external type */
1585 if ((*s2i->si_init)(p) == NOTOK)
1586 return NOTOK;
1587 continue;
1588 }
1589 if (!strcasecmp (pm->pm_name, "name")) {
1590 e->eb_name = pm->pm_value;
1591 continue;
1592 }
1593 if (!strcasecmp (pm->pm_name, "permission")) {
1594 e->eb_permission = pm->pm_value;
1595 continue;
1596 }
1597 if (!strcasecmp (pm->pm_name, "site")) {
1598 e->eb_site = pm->pm_value;
1599 continue;
1600 }
1601 if (!strcasecmp (pm->pm_name, "directory")) {
1602 e->eb_dir = pm->pm_value;
1603 continue;
1604 }
1605 if (!strcasecmp (pm->pm_name, "mode")) {
1606 e->eb_mode = pm->pm_value;
1607 continue;
1608 }
1609 if (!strcasecmp (pm->pm_name, "size")) {
1610 sscanf (pm->pm_value, "%lu", &e->eb_size);
1611 continue;
1612 }
1613 if (!strcasecmp (pm->pm_name, "server")) {
1614 e->eb_server = pm->pm_value;
1615 continue;
1616 }
1617 if (!strcasecmp (pm->pm_name, "subject")) {
1618 e->eb_subject = pm->pm_value;
1619 continue;
1620 }
1621 if (!strcasecmp (pm->pm_name, "url")) {
1622 /*
1623 * According to RFC 2017, we have to remove all whitespace from
1624 * the URL
1625 */
1626
1627 char *u, *p = pm->pm_value;
1628 e->eb_url = u = mh_xmalloc(strlen(pm->pm_value) + 1);
1629
1630 for (; *p != '\0'; p++) {
1631 if (! isspace((unsigned char) *p))
1632 *u++ = *p;
1633 }
1634
1635 *u = '\0';
1636 continue;
1637 }
1638 if (composing && !strcasecmp (pm->pm_name, "body")) {
1639 e->eb_body = getcpy (pm->pm_value);
1640 continue;
1641 }
1642 }
1643
1644 if (!e->eb_access) {
1645 advise (NULL,
1646 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1647 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1648 return NOTOK;
1649 }
1650
1651 return OK;
1652 }
1653
1654
1655 /*
1656 * APPLICATION
1657 */
1658
1659 static int
1660 InitApplication (CT ct)
1661 {
1662 CI ci = &ct->c_ctinfo;
1663
1664 /* match subtype */
1665 ct->c_subtype = ct_str_subtype (CT_APPLICATION, ci->ci_subtype);
1666
1667 return OK;
1668 }
1669
1670
1671 /*
1672 * TRANSFER ENCODINGS
1673 */
1674
1675 static int
1676 init_encoding (CT ct, OpenCEFunc openfnx)
1677 {
1678 ct->c_ceopenfnx = openfnx;
1679 ct->c_ceclosefnx = close_encoding;
1680 ct->c_cesizefnx = size_encoding;
1681
1682 return OK;
1683 }
1684
1685
1686 void
1687 close_encoding (CT ct)
1688 {
1689 CE ce = &ct->c_cefile;
1690
1691 if (ce->ce_fp) {
1692 fclose (ce->ce_fp);
1693 ce->ce_fp = NULL;
1694 }
1695 }
1696
1697
1698 static unsigned long
1699 size_encoding (CT ct)
1700 {
1701 int fd;
1702 unsigned long size;
1703 char *file;
1704 CE ce = &ct->c_cefile;
1705 struct stat st;
1706
1707 if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1708 return (long) st.st_size;
1709
1710 if (ce->ce_file) {
1711 if (stat (ce->ce_file, &st) != NOTOK)
1712 return (long) st.st_size;
1713 else
1714 return 0L;
1715 }
1716
1717 if (ct->c_encoding == CE_EXTERNAL)
1718 return (ct->c_end - ct->c_begin);
1719
1720 file = NULL;
1721 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1722 return (ct->c_end - ct->c_begin);
1723
1724 if (fstat (fd, &st) != NOTOK)
1725 size = (long) st.st_size;
1726 else
1727 size = 0L;
1728
1729 (*ct->c_ceclosefnx) (ct);
1730 return size;
1731 }
1732
1733
1734 /*
1735 * BASE64
1736 */
1737
1738 static unsigned char b642nib[0x80] = {
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, 0xff, 0xff, 0xff, 0xff, 0xff,
1744 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1745 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1746 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1747 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1748 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1749 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1750 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1751 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1752 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1753 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1754 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1755 };
1756
1757
1758 static int
1759 InitBase64 (CT ct)
1760 {
1761 return init_encoding (ct, openBase64);
1762 }
1763
1764
1765 static int
1766 openBase64 (CT ct, char **file)
1767 {
1768 int bitno, cc, digested;
1769 int fd, len, skip, own_ct_fp = 0, text = ct->c_type == CT_TEXT;
1770 uint32_t bits;
1771 unsigned char value, b;
1772 char *cp, *ep, buffer[BUFSIZ];
1773 /* sbeck -- handle suffixes */
1774 CI ci;
1775 CE ce = &ct->c_cefile;
1776 MD5_CTX mdContext;
1777
1778 if (ce->ce_fp) {
1779 fseek (ce->ce_fp, 0L, SEEK_SET);
1780 goto ready_to_go;
1781 }
1782
1783 if (ce->ce_file) {
1784 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1785 content_error (ce->ce_file, ct, "unable to fopen for reading");
1786 return NOTOK;
1787 }
1788 goto ready_to_go;
1789 }
1790
1791 if (*file == NULL) {
1792 ce->ce_unlink = 1;
1793 } else {
1794 ce->ce_file = add (*file, NULL);
1795 ce->ce_unlink = 0;
1796 }
1797
1798 /* sbeck@cise.ufl.edu -- handle suffixes */
1799 ci = &ct->c_ctinfo;
1800 if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) {
1801 if (ce->ce_unlink) {
1802 /* Create temporary file with filename extension. */
1803 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
1804 adios(NULL, "unable to create temporary file in %s",
1805 get_temp_dir());
1806 }
1807 } else {
1808 ce->ce_file = add (cp, ce->ce_file);
1809 }
1810 } else if (*file == NULL) {
1811 char *tempfile;
1812 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1813 adios(NULL, "unable to create temporary file in %s",
1814 get_temp_dir());
1815 }
1816 ce->ce_file = add (tempfile, NULL);
1817 }
1818
1819 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1820 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1821 return NOTOK;
1822 }
1823
1824 if ((len = ct->c_end - ct->c_begin) < 0)
1825 adios (NULL, "internal error(1)");
1826
1827 if (! ct->c_fp) {
1828 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1829 content_error (ct->c_file, ct, "unable to open for reading");
1830 return NOTOK;
1831 }
1832 own_ct_fp = 1;
1833 }
1834
1835 if ((digested = ct->c_digested))
1836 MD5Init (&mdContext);
1837
1838 bitno = 18;
1839 bits = 0L;
1840 skip = 0;
1841
1842 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1843 while (len > 0) {
1844 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1845 case NOTOK:
1846 content_error (ct->c_file, ct, "error reading from");
1847 goto clean_up;
1848
1849 case OK:
1850 content_error (NULL, ct, "premature eof");
1851 goto clean_up;
1852
1853 default:
1854 if (cc > len)
1855 cc = len;
1856 len -= cc;
1857
1858 for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1859 switch (*cp) {
1860 default:
1861 if (isspace ((unsigned char) *cp))
1862 break;
1863 if (skip || (((unsigned char) *cp) & 0x80)
1864 || (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) {
1865 if (debugsw) {
1866 fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1867 (unsigned char) *cp,
1868 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1869 skip);
1870 }
1871 content_error (NULL, ct,
1872 "invalid BASE64 encoding -- continuing");
1873 continue;
1874 }
1875
1876 bits |= value << bitno;
1877 test_end:
1878 if ((bitno -= 6) < 0) {
1879 b = (bits >> 16) & 0xff;
1880 if (!text || b != '\r')
1881 putc ((char) b, ce->ce_fp);
1882 if (digested)
1883 MD5Update (&mdContext, &b, 1);
1884 if (skip < 2) {
1885 b = (bits >> 8) & 0xff;
1886 if (! text || b != '\r')
1887 putc ((char) b, ce->ce_fp);
1888 if (digested)
1889 MD5Update (&mdContext, &b, 1);
1890 if (skip < 1) {
1891 b = bits & 0xff;
1892 if (! text || b != '\r')
1893 putc ((char) b, ce->ce_fp);
1894 if (digested)
1895 MD5Update (&mdContext, &b, 1);
1896 }
1897 }
1898
1899 if (ferror (ce->ce_fp)) {
1900 content_error (ce->ce_file, ct,
1901 "error writing to");
1902 goto clean_up;
1903 }
1904 bitno = 18, bits = 0L, skip = 0;
1905 }
1906 break;
1907
1908 case '=':
1909 if (++skip > 3)
1910 goto self_delimiting;
1911 goto test_end;
1912 }
1913 }
1914 }
1915 }
1916
1917 if (bitno != 18) {
1918 if (debugsw)
1919 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1920
1921 content_error (NULL, ct, "invalid BASE64 encoding");
1922 goto clean_up;
1923 }
1924
1925 self_delimiting:
1926 fseek (ct->c_fp, 0L, SEEK_SET);
1927
1928 if (fflush (ce->ce_fp)) {
1929 content_error (ce->ce_file, ct, "error writing to");
1930 goto clean_up;
1931 }
1932
1933 if (digested) {
1934 unsigned char digest[16];
1935
1936 MD5Final (digest, &mdContext);
1937 if (memcmp((char *) digest, (char *) ct->c_digest,
1938 sizeof(digest) / sizeof(digest[0])))
1939 content_error (NULL, ct,
1940 "content integrity suspect (digest mismatch) -- continuing");
1941 else
1942 if (debugsw)
1943 fprintf (stderr, "content integrity confirmed\n");
1944 }
1945
1946 fseek (ce->ce_fp, 0L, SEEK_SET);
1947
1948 ready_to_go:
1949 *file = ce->ce_file;
1950 if (own_ct_fp) {
1951 fclose (ct->c_fp);
1952 ct->c_fp = NULL;
1953 }
1954 return fileno (ce->ce_fp);
1955
1956 clean_up:
1957 if (own_ct_fp) {
1958 fclose (ct->c_fp);
1959 ct->c_fp = NULL;
1960 }
1961 free_encoding (ct, 0);
1962 return NOTOK;
1963 }
1964
1965
1966 /*
1967 * QUOTED PRINTABLE
1968 */
1969
1970 static char hex2nib[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1977 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1978 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1979 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1980 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1981 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1982 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1983 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1984 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1985 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1986 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1987 };
1988
1989
1990 static int
1991 InitQuoted (CT ct)
1992 {
1993 return init_encoding (ct, openQuoted);
1994 }
1995
1996
1997 static int
1998 openQuoted (CT ct, char **file)
1999 {
2000 int cc, digested, len, quoted, own_ct_fp = 0;
2001 char *cp, *ep;
2002 char *bufp = NULL;
2003 size_t buflen;
2004 ssize_t gotlen;
2005 unsigned char mask;
2006 CE ce = &ct->c_cefile;
2007 /* sbeck -- handle suffixes */
2008 CI ci;
2009 MD5_CTX mdContext;
2010
2011 if (ce->ce_fp) {
2012 fseek (ce->ce_fp, 0L, SEEK_SET);
2013 goto ready_to_go;
2014 }
2015
2016 if (ce->ce_file) {
2017 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2018 content_error (ce->ce_file, ct, "unable to fopen for reading");
2019 return NOTOK;
2020 }
2021 goto ready_to_go;
2022 }
2023
2024 if (*file == NULL) {
2025 ce->ce_unlink = 1;
2026 } else {
2027 ce->ce_file = add (*file, NULL);
2028 ce->ce_unlink = 0;
2029 }
2030
2031 /* sbeck@cise.ufl.edu -- handle suffixes */
2032 ci = &ct->c_ctinfo;
2033 if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) {
2034 if (ce->ce_unlink) {
2035 /* Create temporary file with filename extension. */
2036 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
2037 adios(NULL, "unable to create temporary file in %s",
2038 get_temp_dir());
2039 }
2040 } else {
2041 ce->ce_file = add (cp, ce->ce_file);
2042 }
2043 } else if (*file == NULL) {
2044 char *tempfile;
2045 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2046 adios(NULL, "unable to create temporary file in %s",
2047 get_temp_dir());
2048 }
2049 ce->ce_file = add (tempfile, NULL);
2050 }
2051
2052 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2053 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2054 return NOTOK;
2055 }
2056
2057 if ((len = ct->c_end - ct->c_begin) < 0)
2058 adios (NULL, "internal error(2)");
2059
2060 if (! ct->c_fp) {
2061 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2062 content_error (ct->c_file, ct, "unable to open for reading");
2063 return NOTOK;
2064 }
2065 own_ct_fp = 1;
2066 }
2067
2068 if ((digested = ct->c_digested))
2069 MD5Init (&mdContext);
2070
2071 quoted = 0;
2072 #ifdef lint
2073 mask = 0;
2074 #endif
2075
2076 fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2077 while (len > 0) {
2078 if ((gotlen = getline(&bufp, &buflen, ct->c_fp)) == -1) {
2079 content_error (NULL, ct, "premature eof");
2080 goto clean_up;
2081 }
2082
2083 if ((cc = gotlen) > len)
2084 cc = len;
2085 len -= cc;
2086
2087 for (ep = (cp = bufp) + cc - 1; cp <= ep; ep--)
2088 if (!isspace ((unsigned char) *ep))
2089 break;
2090 *++ep = '\n', ep++;
2091
2092 for (; cp < ep; cp++) {
2093 if (quoted > 0) {
2094 /* in an escape sequence */
2095 if (quoted == 1) {
2096 /* at byte 1 of an escape sequence */
2097 mask = hex2nib[((unsigned char) *cp) & 0x7f];
2098 /* next is byte 2 */
2099 quoted = 2;
2100 } else {
2101 /* at byte 2 of an escape sequence */
2102 mask <<= 4;
2103 mask |= hex2nib[((unsigned char) *cp) & 0x7f];
2104 putc (mask, ce->ce_fp);
2105 if (digested)
2106 MD5Update (&mdContext, &mask, 1);
2107 if (ferror (ce->ce_fp)) {
2108 content_error (ce->ce_file, ct, "error writing to");
2109 goto clean_up;
2110 }
2111 /* finished escape sequence; next may be literal or a new
2112 * escape sequence */
2113 quoted = 0;
2114 }
2115 /* on to next byte */
2116 continue;
2117 }
2118
2119 /* not in an escape sequence */
2120 if (*cp == '=') {
2121 /* starting an escape sequence, or invalid '='? */
2122 if (cp + 1 < ep && cp[1] == '\n') {
2123 /* "=\n" soft line break, eat the \n */
2124 cp++;
2125 continue;
2126 }
2127 if (cp + 1 >= ep || cp + 2 >= ep) {
2128 /* We don't have 2 bytes left, so this is an invalid
2129 * escape sequence; just show the raw bytes (below). */
2130 } else if (isxdigit ((unsigned char) cp[1]) &&
2131 isxdigit ((unsigned char) cp[2])) {
2132 /* Next 2 bytes are hex digits, making this a valid escape
2133 * sequence; let's decode it (above). */
2134 quoted = 1;
2135 continue;
2136 } else {
2137 /* One or both of the next 2 is out of range, making this
2138 * an invalid escape sequence; just show the raw bytes
2139 * (below). */
2140 }
2141 }
2142
2143 /* Just show the raw byte. */
2144 putc (*cp, ce->ce_fp);
2145 if (digested) {
2146 if (*cp == '\n') {
2147 MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2148 } else {
2149 MD5Update (&mdContext, (unsigned char *) cp, 1);
2150 }
2151 }
2152 if (ferror (ce->ce_fp)) {
2153 content_error (ce->ce_file, ct, "error writing to");
2154 goto clean_up;
2155 }
2156 }
2157 }
2158 if (quoted) {
2159 content_error (NULL, ct,
2160 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2161 goto clean_up;
2162 }
2163
2164 fseek (ct->c_fp, 0L, SEEK_SET);
2165
2166 if (fflush (ce->ce_fp)) {
2167 content_error (ce->ce_file, ct, "error writing to");
2168 goto clean_up;
2169 }
2170
2171 if (digested) {
2172 unsigned char digest[16];
2173
2174 MD5Final (digest, &mdContext);
2175 if (memcmp((char *) digest, (char *) ct->c_digest,
2176 sizeof(digest) / sizeof(digest[0])))
2177 content_error (NULL, ct,
2178 "content integrity suspect (digest mismatch) -- continuing");
2179 else
2180 if (debugsw)
2181 fprintf (stderr, "content integrity confirmed\n");
2182 }
2183
2184 fseek (ce->ce_fp, 0L, SEEK_SET);
2185
2186 ready_to_go:
2187 *file = ce->ce_file;
2188 if (own_ct_fp) {
2189 fclose (ct->c_fp);
2190 ct->c_fp = NULL;
2191 }
2192 free (bufp);
2193 return fileno (ce->ce_fp);
2194
2195 clean_up:
2196 free_encoding (ct, 0);
2197 if (own_ct_fp) {
2198 fclose (ct->c_fp);
2199 ct->c_fp = NULL;
2200 }
2201 free (bufp);
2202 return NOTOK;
2203 }
2204
2205
2206 /*
2207 * 7BIT
2208 */
2209
2210 static int
2211 Init7Bit (CT ct)
2212 {
2213 if (init_encoding (ct, open7Bit) == NOTOK)
2214 return NOTOK;
2215
2216 ct->c_cesizefnx = NULL; /* no need to decode for real size */
2217 return OK;
2218 }
2219
2220
2221 int
2222 open7Bit (CT ct, char **file)
2223 {
2224 int cc, fd, len, own_ct_fp = 0;
2225 char buffer[BUFSIZ];
2226 /* sbeck -- handle suffixes */
2227 char *cp;
2228 CI ci;
2229 CE ce = &ct->c_cefile;
2230
2231 if (ce->ce_fp) {
2232 fseek (ce->ce_fp, 0L, SEEK_SET);
2233 goto ready_to_go;
2234 }
2235
2236 if (ce->ce_file) {
2237 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2238 content_error (ce->ce_file, ct, "unable to fopen for reading");
2239 return NOTOK;
2240 }
2241 goto ready_to_go;
2242 }
2243
2244 if (*file == NULL) {
2245 ce->ce_unlink = 1;
2246 } else {
2247 ce->ce_file = add (*file, NULL);
2248 ce->ce_unlink = 0;
2249 }
2250
2251 /* sbeck@cise.ufl.edu -- handle suffixes */
2252 ci = &ct->c_ctinfo;
2253 if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) {
2254 if (ce->ce_unlink) {
2255 /* Create temporary file with filename extension. */
2256 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
2257 adios(NULL, "unable to create temporary file in %s",
2258 get_temp_dir());
2259 }
2260 } else {
2261 ce->ce_file = add (cp, ce->ce_file);
2262 }
2263 } else if (*file == NULL) {
2264 char *tempfile;
2265 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2266 adios(NULL, "unable to create temporary file in %s",
2267 get_temp_dir());
2268 }
2269 ce->ce_file = add (tempfile, NULL);
2270 }
2271
2272 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2273 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2274 return NOTOK;
2275 }
2276
2277 if (ct->c_type == CT_MULTIPART) {
2278 CI ci = &ct->c_ctinfo;
2279 char *buffer;
2280
2281 len = 0;
2282 fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2283 len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2284 + 1 + strlen (ci->ci_subtype);
2285 buffer = output_params(len, ci->ci_first_pm, &len, 0);
2286
2287 if (buffer) {
2288 fputs (buffer, ce->ce_fp);
2289 free(buffer);
2290 }
2291
2292 if (ci->ci_comment) {
2293 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2294 fputs ("\n\t", ce->ce_fp);
2295 len = 8;
2296 }
2297 else {
2298 putc (' ', ce->ce_fp);
2299 len++;
2300 }
2301 fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2302 len += cc;
2303 }
2304 fprintf (ce->ce_fp, "\n");
2305 if (ct->c_id)
2306 fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2307 if (ct->c_descr)
2308 fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2309 if (ct->c_dispo)
2310 fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2311 fprintf (ce->ce_fp, "\n");
2312 }
2313
2314 if ((len = ct->c_end - ct->c_begin) < 0)
2315 adios (NULL, "internal error(3)");
2316
2317 if (! ct->c_fp) {
2318 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2319 content_error (ct->c_file, ct, "unable to open for reading");
2320 return NOTOK;
2321 }
2322 own_ct_fp = 1;
2323 }
2324
2325 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2326 while (len > 0)
2327 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2328 case NOTOK:
2329 content_error (ct->c_file, ct, "error reading from");
2330 goto clean_up;
2331
2332 case OK:
2333 content_error (NULL, ct, "premature eof");
2334 goto clean_up;
2335
2336 default:
2337 if (cc > len)
2338 cc = len;
2339 len -= cc;
2340
2341 if ((int) fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp) < cc) {
2342 advise ("open7Bit", "fwrite");
2343 }
2344 if (ferror (ce->ce_fp)) {
2345 content_error (ce->ce_file, ct, "error writing to");
2346 goto clean_up;
2347 }
2348 }
2349
2350 fseek (ct->c_fp, 0L, SEEK_SET);
2351
2352 if (fflush (ce->ce_fp)) {
2353 content_error (ce->ce_file, ct, "error writing to");
2354 goto clean_up;
2355 }
2356
2357 fseek (ce->ce_fp, 0L, SEEK_SET);
2358
2359 ready_to_go:
2360 *file = ce->ce_file;
2361 if (own_ct_fp) {
2362 fclose (ct->c_fp);
2363 ct->c_fp = NULL;
2364 }
2365 return fileno (ce->ce_fp);
2366
2367 clean_up:
2368 free_encoding (ct, 0);
2369 if (own_ct_fp) {
2370 fclose (ct->c_fp);
2371 ct->c_fp = NULL;
2372 }
2373 return NOTOK;
2374 }
2375
2376
2377 /*
2378 * External
2379 */
2380
2381 static int
2382 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2383 {
2384 char cachefile[BUFSIZ];
2385
2386 if (ce->ce_fp) {
2387 fseek (ce->ce_fp, 0L, SEEK_SET);
2388 goto ready_already;
2389 }
2390
2391 if (ce->ce_file) {
2392 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2393 content_error (ce->ce_file, ct, "unable to fopen for reading");
2394 return NOTOK;
2395 }
2396 goto ready_already;
2397 }
2398
2399 if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2400 cachefile, sizeof(cachefile)) != NOTOK) {
2401 if ((ce->ce_fp = fopen (cachefile, "r"))) {
2402 ce->ce_file = getcpy (cachefile);
2403 ce->ce_unlink = 0;
2404 goto ready_already;
2405 } else {
2406 admonish (cachefile, "unable to fopen for reading");
2407 }
2408 }
2409
2410 *fd = fileno (ce->ce_fp);
2411 return OK;
2412
2413 ready_already:
2414 *file = ce->ce_file;
2415 *fd = fileno (ce->ce_fp);
2416 return DONE;
2417 }
2418
2419 /*
2420 * File
2421 */
2422
2423 static int
2424 InitFile (CT ct)
2425 {
2426 return init_encoding (ct, openFile);
2427 }
2428
2429
2430 static int
2431 openFile (CT ct, char **file)
2432 {
2433 int fd, cachetype;
2434 char cachefile[BUFSIZ];
2435 struct exbody *e = ct->c_ctexbody;
2436 CE ce = &ct->c_cefile;
2437
2438 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2439 case NOTOK:
2440 return NOTOK;
2441
2442 case OK:
2443 break;
2444
2445 case DONE:
2446 return fd;
2447 }
2448
2449 if (!e->eb_name) {
2450 content_error (NULL, ct, "missing name parameter");
2451 return NOTOK;
2452 }
2453
2454 ce->ce_file = getcpy (e->eb_name);
2455 ce->ce_unlink = 0;
2456
2457 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2458 content_error (ce->ce_file, ct, "unable to fopen for reading");
2459 return NOTOK;
2460 }
2461
2462 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2463 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2464 cachefile, sizeof(cachefile)) != NOTOK) {
2465 int mask;
2466 FILE *fp;
2467
2468 mask = umask (cachetype ? ~m_gmprot () : 0222);
2469 if ((fp = fopen (cachefile, "w"))) {
2470 int cc;
2471 char buffer[BUFSIZ];
2472 FILE *gp = ce->ce_fp;
2473
2474 fseek (gp, 0L, SEEK_SET);
2475
2476 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2477 > 0)
2478 if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) {
2479 advise ("openFile", "fwrite");
2480 }
2481 fflush (fp);
2482
2483 if (ferror (gp)) {
2484 admonish (ce->ce_file, "error reading");
2485 (void) m_unlink (cachefile);
2486 }
2487 else
2488 if (ferror (fp)) {
2489 admonish (cachefile, "error writing");
2490 (void) m_unlink (cachefile);
2491 }
2492 fclose (fp);
2493 }
2494 umask (mask);
2495 }
2496
2497 fseek (ce->ce_fp, 0L, SEEK_SET);
2498 *file = ce->ce_file;
2499 return fileno (ce->ce_fp);
2500 }
2501
2502 /*
2503 * FTP
2504 */
2505
2506 static int
2507 InitFTP (CT ct)
2508 {
2509 return init_encoding (ct, openFTP);
2510 }
2511
2512
2513 static int
2514 openFTP (CT ct, char **file)
2515 {
2516 int cachetype, caching, fd;
2517 int len, buflen;
2518 char *bp, *ftp, *user, *pass;
2519 char buffer[BUFSIZ], cachefile[BUFSIZ];
2520 struct exbody *e;
2521 CE ce = &ct->c_cefile;
2522 static char *username = NULL;
2523 static char *password = NULL;
2524
2525 e = ct->c_ctexbody;
2526
2527 if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2528 ftp = NULL;
2529
2530 if (!ftp)
2531 return NOTOK;
2532
2533 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2534 case NOTOK:
2535 return NOTOK;
2536
2537 case OK:
2538 break;
2539
2540 case DONE:
2541 return fd;
2542 }
2543
2544 if (!e->eb_name || !e->eb_site) {
2545 content_error (NULL, ct, "missing %s parameter",
2546 e->eb_name ? "site": "name");
2547 return NOTOK;
2548 }
2549
2550 /* Get the buffer ready to go */
2551 bp = buffer;
2552 buflen = sizeof(buffer);
2553
2554 /*
2555 * Construct the query message for user
2556 */
2557 snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2558 len = strlen (bp);
2559 bp += len;
2560 buflen -= len;
2561
2562 if (e->eb_partno) {
2563 snprintf (bp, buflen, " (content %s)", e->eb_partno);
2564 len = strlen (bp);
2565 bp += len;
2566 buflen -= len;
2567 }
2568
2569 snprintf (bp, buflen, "\n using %sFTP from site %s",
2570 e->eb_flags ? "anonymous " : "", e->eb_site);
2571 len = strlen (bp);
2572 bp += len;
2573 buflen -= len;
2574
2575 if (e->eb_size > 0) {
2576 snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2577 len = strlen (bp);
2578 bp += len;
2579 buflen -= len;
2580 }
2581 snprintf (bp, buflen, "? ");
2582
2583 /*
2584 * Now, check the answer
2585 */
2586 if (!getanswer (buffer))
2587 return NOTOK;
2588
2589 if (e->eb_flags) {
2590 user = "anonymous";
2591 snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
2592 LocalName (1));
2593 pass = buffer;
2594 } else {
2595 ruserpass (e->eb_site, &username, &password);
2596 user = username;
2597 pass = password;
2598 }
2599
2600 ce->ce_unlink = (*file == NULL);
2601 caching = 0;
2602 cachefile[0] = '\0';
2603 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2604 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2605 cachefile, sizeof(cachefile)) != NOTOK) {
2606 if (*file == NULL) {
2607 ce->ce_unlink = 0;
2608 caching = 1;
2609 }
2610 }
2611
2612 if (*file)
2613 ce->ce_file = add (*file, NULL);
2614 else if (caching)
2615 ce->ce_file = add (cachefile, NULL);
2616 else {
2617 char *tempfile;
2618 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2619 adios(NULL, "unable to create temporary file in %s",
2620 get_temp_dir());
2621 }
2622 ce->ce_file = add (tempfile, NULL);
2623 }
2624
2625 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2626 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2627 return NOTOK;
2628 }
2629
2630 {
2631 int child_id, i, vecp;
2632 char *vec[9];
2633
2634 vecp = 0;
2635 vec[vecp++] = r1bindex (ftp, '/');
2636 vec[vecp++] = e->eb_site;
2637 vec[vecp++] = user;
2638 vec[vecp++] = pass;
2639 vec[vecp++] = e->eb_dir;
2640 vec[vecp++] = e->eb_name;
2641 vec[vecp++] = ce->ce_file,
2642 vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2643 ? "ascii" : "binary";
2644 vec[vecp] = NULL;
2645
2646 fflush (stdout);
2647
2648 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2649 sleep (5);
2650 switch (child_id) {
2651 case NOTOK:
2652 adios ("fork", "unable to");
2653 /* NOTREACHED */
2654
2655 case OK:
2656 close (fileno (ce->ce_fp));
2657 execvp (ftp, vec);
2658 fprintf (stderr, "unable to exec ");
2659 perror (ftp);
2660 _exit (-1);
2661 /* NOTREACHED */
2662
2663 default:
2664 if (pidXwait (child_id, NULL)) {
2665 username = password = NULL;
2666 ce->ce_unlink = 1;
2667 return NOTOK;
2668 }
2669 break;
2670 }
2671 }
2672
2673 if (cachefile[0]) {
2674 if (caching)
2675 chmod (cachefile, cachetype ? m_gmprot () : 0444);
2676 else {
2677 int mask;
2678 FILE *fp;
2679
2680 mask = umask (cachetype ? ~m_gmprot () : 0222);
2681 if ((fp = fopen (cachefile, "w"))) {
2682 int cc;
2683 FILE *gp = ce->ce_fp;
2684
2685 fseek (gp, 0L, SEEK_SET);
2686
2687 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2688 > 0)
2689 if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) {
2690 advise ("openFTP", "fwrite");
2691 }
2692 fflush (fp);
2693
2694 if (ferror (gp)) {
2695 admonish (ce->ce_file, "error reading");
2696 (void) m_unlink (cachefile);
2697 }
2698 else
2699 if (ferror (fp)) {
2700 admonish (cachefile, "error writing");
2701 (void) m_unlink (cachefile);
2702 }
2703 fclose (fp);
2704 }
2705 umask (mask);
2706 }
2707 }
2708
2709 fseek (ce->ce_fp, 0L, SEEK_SET);
2710 *file = ce->ce_file;
2711 return fileno (ce->ce_fp);
2712 }
2713
2714
2715 /*
2716 * Mail
2717 */
2718
2719 static int
2720 InitMail (CT ct)
2721 {
2722 return init_encoding (ct, openMail);
2723 }
2724
2725
2726 static int
2727 openMail (CT ct, char **file)
2728 {
2729 int child_id, fd, i, vecp;
2730 int len, buflen;
2731 char *bp, buffer[BUFSIZ], *vec[7];
2732 struct exbody *e = ct->c_ctexbody;
2733 CE ce = &ct->c_cefile;
2734
2735 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2736 case NOTOK:
2737 return NOTOK;
2738
2739 case OK:
2740 break;
2741
2742 case DONE:
2743 return fd;
2744 }
2745
2746 if (!e->eb_server) {
2747 content_error (NULL, ct, "missing server parameter");
2748 return NOTOK;
2749 }
2750
2751 /* Get buffer ready to go */
2752 bp = buffer;
2753 buflen = sizeof(buffer);
2754
2755 /* Now, construct query message */
2756 snprintf (bp, buflen, "Retrieve content");
2757 len = strlen (bp);
2758 bp += len;
2759 buflen -= len;
2760
2761 if (e->eb_partno) {
2762 snprintf (bp, buflen, " %s", e->eb_partno);
2763 len = strlen (bp);
2764 bp += len;
2765 buflen -= len;
2766 }
2767
2768 snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2769 e->eb_server,
2770 e->eb_subject ? e->eb_subject : e->eb_body);
2771
2772 /* Now, check answer */
2773 if (!getanswer (buffer))
2774 return NOTOK;
2775
2776 vecp = 0;
2777 vec[vecp++] = r1bindex (mailproc, '/');
2778 vec[vecp++] = e->eb_server;
2779 vec[vecp++] = "-subject";
2780 vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2781 vec[vecp++] = "-body";
2782 vec[vecp++] = e->eb_body;
2783 vec[vecp] = NULL;
2784
2785 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2786 sleep (5);
2787 switch (child_id) {
2788 case NOTOK:
2789 advise ("fork", "unable to");
2790 return NOTOK;
2791
2792 case OK:
2793 execvp (mailproc, vec);
2794 fprintf (stderr, "unable to exec ");
2795 perror (mailproc);
2796 _exit (-1);
2797 /* NOTREACHED */
2798
2799 default:
2800 if (pidXwait (child_id, NULL) == OK)
2801 advise (NULL, "request sent");
2802 break;
2803 }
2804
2805 if (*file == NULL) {
2806 char *tempfile;
2807 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2808 adios(NULL, "unable to create temporary file in %s",
2809 get_temp_dir());
2810 }
2811 ce->ce_file = add (tempfile, NULL);
2812 ce->ce_unlink = 1;
2813 } else {
2814 ce->ce_file = add (*file, NULL);
2815 ce->ce_unlink = 0;
2816 }
2817
2818 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2819 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2820 return NOTOK;
2821 }
2822
2823 /* showproc is for mhshow and mhstore, though mhlist -debug
2824 * prints it, too. */
2825 if (ct->c_showproc)
2826 free (ct->c_showproc);
2827 ct->c_showproc = add ("true", NULL);
2828
2829 fseek (ce->ce_fp, 0L, SEEK_SET);
2830 *file = ce->ce_file;
2831 return fileno (ce->ce_fp);
2832 }
2833
2834
2835 /*
2836 * URL
2837 */
2838
2839 static int
2840 InitURL (CT ct)
2841 {
2842 return init_encoding (ct, openURL);
2843 }
2844
2845
2846 static int
2847 openURL (CT ct, char **file)
2848 {
2849 struct exbody *e = ct->c_ctexbody;
2850 CE ce = &ct->c_cefile;
2851 char *urlprog, *program;
2852 char buffer[BUFSIZ], cachefile[BUFSIZ];
2853 int fd, caching, cachetype;
2854 struct msgs_array args = { 0, 0, NULL};
2855 pid_t child_id;
2856
2857 if ((urlprog = context_find(nmhaccessurl)) && *urlprog == '\0')
2858 urlprog = NULL;
2859
2860 if (! urlprog) {
2861 content_error(NULL, ct, "No entry for nmh-access-url in profile");
2862 return NOTOK;
2863 }
2864
2865 switch (openExternal(e->eb_parent, e->eb_content, ce, file, &fd)) {
2866 case NOTOK:
2867 return NOTOK;
2868
2869 case OK:
2870 break;
2871
2872 case DONE:
2873 return fd;
2874 }
2875
2876 if (!e->eb_url) {
2877 content_error(NULL, ct, "missing url parameter");
2878 return NOTOK;
2879 }
2880
2881 ce->ce_unlink = (*file == NULL);
2882 caching = 0;
2883 cachefile[0] = '\0';
2884
2885 if (find_cache(NULL, wcachesw, &cachetype, e->eb_content->c_id,
2886 cachefile, sizeof(cachefile)) != NOTOK) {
2887 if (*file == NULL) {
2888 ce->ce_unlink = 0;
2889 caching = 1;
2890 }
2891 }
2892
2893 if (*file)
2894 ce->ce_file = add(*file, NULL);
2895 else if (caching)
2896 ce->ce_file = add(cachefile, NULL);
2897 else {
2898 char *tempfile;
2899 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2900 adios(NULL, "unable to create temporary file in %s",
2901 get_temp_dir());
2902 }
2903 ce->ce_file = add (tempfile, NULL);
2904 }
2905
2906 if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
2907 content_error(ce->ce_file, ct, "unable to fopen for read/writing");
2908 return NOTOK;
2909 }
2910
2911 switch (child_id = fork()) {
2912 case NOTOK:
2913 adios ("fork", "unable to");
2914 /* NOTREACHED */
2915
2916 case OK:
2917 argsplit_msgarg(&args, urlprog, &program);
2918 app_msgarg(&args, e->eb_url);
2919 app_msgarg(&args, NULL);
2920 dup2(fileno(ce->ce_fp), 1);
2921 close(fileno(ce->ce_fp));
2922 execvp(program, args.msgs);
2923 fprintf(stderr, "Unable to exec ");
2924 perror(program);
2925 _exit(-1);
2926 /* NOTREACHED */
2927
2928 default:
2929 if (pidXwait(child_id, NULL)) {
2930 ce->ce_unlink = 1;
2931 return NOTOK;
2932 }
2933 }
2934
2935 if (cachefile[0]) {
2936 if (caching)
2937 chmod(cachefile, cachetype ? m_gmprot() : 0444);
2938 else {
2939 int mask;
2940 FILE *fp;
2941
2942 mask = umask (cachetype ? ~m_gmprot() : 0222);
2943 if ((fp = fopen(cachefile, "w"))) {
2944 int cc;
2945 FILE *gp = ce->ce_fp;
2946
2947 fseeko(gp, 0, SEEK_SET);
2948
2949 while ((cc = fread(buffer, sizeof(*buffer),
2950 sizeof(buffer), gp)) > 0)
2951 if ((int) fwrite(buffer, sizeof(*buffer), cc, fp) < cc) {
2952 advise ("openURL", "fwrite");
2953 }
2954
2955 fflush(fp);
2956
2957 if (ferror(gp)) {
2958 admonish(ce->ce_file, "error reading");
2959 (void) m_unlink (cachefile);
2960 }
2961 }
2962 umask(mask);
2963 }
2964 }
2965
2966 fseeko(ce->ce_fp, 0, SEEK_SET);
2967 *file = ce->ce_file;
2968 return fd;
2969 }
2970
2971 static int
2972 readDigest (CT ct, char *cp)
2973 {
2974 int bitno, skip;
2975 uint32_t bits;
2976 char *bp = cp;
2977 unsigned char *dp, value, *ep;
2978
2979 bitno = 18;
2980 bits = 0L;
2981 skip = 0;
2982
2983 for (ep = (dp = ct->c_digest)
2984 + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2985 switch (*cp) {
2986 default:
2987 if (skip
2988 || (*cp & 0x80)
2989 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2990 if (debugsw)
2991 fprintf (stderr, "invalid BASE64 encoding\n");
2992 return NOTOK;
2993 }
2994
2995 bits |= value << bitno;
2996 test_end:
2997 if ((bitno -= 6) < 0) {
2998 if (dp + (3 - skip) > ep)
2999 goto invalid_digest;
3000 *dp++ = (bits >> 16) & 0xff;
3001 if (skip < 2) {
3002 *dp++ = (bits >> 8) & 0xff;
3003 if (skip < 1)
3004 *dp++ = bits & 0xff;
3005 }
3006 bitno = 18;
3007 bits = 0L;
3008 skip = 0;
3009 }
3010 break;
3011
3012 case '=':
3013 if (++skip > 3)
3014 goto self_delimiting;
3015 goto test_end;
3016 }
3017 if (bitno != 18) {
3018 if (debugsw)
3019 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
3020
3021 return NOTOK;
3022 }
3023 self_delimiting:
3024 if (dp != ep) {
3025 invalid_digest:
3026 if (debugsw) {
3027 while (*cp)
3028 cp++;
3029 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
3030 (int)(cp - bp));
3031 }
3032
3033 return NOTOK;
3034 }
3035
3036 if (debugsw) {
3037 fprintf (stderr, "MD5 digest=");
3038 for (dp = ct->c_digest; dp < ep; dp++)
3039 fprintf (stderr, "%02x", *dp & 0xff);
3040 fprintf (stderr, "\n");
3041 }
3042
3043 return OK;
3044 }
3045
3046
3047 /* Multipart parts might have content before the first subpart and/or
3048 after the last subpart that hasn't been stored anywhere else, so do
3049 that. */
3050 int
3051 get_leftover_mp_content (CT ct, int before /* or after */)
3052 {
3053 struct multipart *m = (struct multipart *) ct->c_ctparams;
3054 char *boundary;
3055 int found_boundary = 0;
3056 int max = BUFSIZ;
3057 char *bufp = NULL;
3058 size_t buflen;
3059 ssize_t gotlen;
3060 int read = 0;
3061 char *content = NULL;
3062
3063 if (! m) return NOTOK;
3064
3065 if (before) {
3066 if (! m->mp_parts || ! m->mp_parts->mp_part) return NOTOK;
3067
3068 /* Isolate the beginning of this part to the beginning of the
3069 first subpart and save any content between them. */
3070 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
3071 max = m->mp_parts->mp_part->c_begin - ct->c_begin;
3072 boundary = concat ("--", m->mp_start, NULL);
3073 } else {
3074 struct part *last_subpart = NULL;
3075 struct part *subpart;
3076
3077 /* Go to the last subpart to get its end position. */
3078 for (subpart = m->mp_parts; subpart; subpart = subpart->mp_next) {
3079 last_subpart = subpart;
3080 }
3081
3082 if (last_subpart == NULL) return NOTOK;
3083
3084 /* Isolate the end of the last subpart to the end of this part
3085 and save any content between them. */
3086 fseeko (ct->c_fp, last_subpart->mp_part->c_end, SEEK_SET);
3087 max = ct->c_end - last_subpart->mp_part->c_end;
3088 boundary = concat ("--", m->mp_stop, NULL);
3089 }
3090
3091 /* Back up by 1 to pick up the newline. */
3092 while ((gotlen = getline(&bufp, &buflen, ct->c_fp)) != -1) {
3093 read += gotlen;
3094 /* Don't look beyond beginning of first subpart (before) or
3095 next part (after). */
3096 if (read > max) bufp[read-max] = '\0';
3097
3098 if (before) {
3099 if (! strcmp (bufp, boundary)) {
3100 found_boundary = 1;
3101 }
3102 } else {
3103 if (! found_boundary && ! strcmp (bufp, boundary)) {
3104 found_boundary = 1;
3105 continue;
3106 }
3107 }
3108
3109 if ((before && ! found_boundary) || (! before && found_boundary)) {
3110 if (content) {
3111 char *old_content = content;
3112 content = concat (content, bufp, NULL);
3113 free (old_content);
3114 } else {
3115 content = before
3116 ? concat ("\n", bufp, NULL)
3117 : concat (bufp, NULL);
3118 }
3119 }
3120
3121 if (before) {
3122 if (found_boundary || read > max) break;
3123 } else {
3124 if (read > max) break;
3125 }
3126 }
3127
3128 /* Skip the newline if that's all there is. */
3129 if (content) {
3130 char *cp;
3131
3132 /* Remove trailing newline, except at EOF. */
3133 if ((before || ! feof (ct->c_fp)) &&
3134 (cp = content + strlen (content)) > content &&
3135 *--cp == '\n') {
3136 *cp = '\0';
3137 }
3138
3139 if (strlen (content) > 1) {
3140 if (before) {
3141 m->mp_content_before = content;
3142 } else {
3143 m->mp_content_after = content;
3144 }
3145 } else {
3146 free (content);
3147 }
3148 }
3149
3150 free (boundary);
3151 free (bufp);
3152
3153 return OK;
3154 }
3155
3156
3157 char *
3158 ct_type_str (int type) {
3159 switch (type) {
3160 case CT_APPLICATION:
3161 return "application";
3162 case CT_AUDIO:
3163 return "audio";
3164 case CT_IMAGE:
3165 return "image";
3166 case CT_MESSAGE:
3167 return "message";
3168 case CT_MULTIPART:
3169 return "multipart";
3170 case CT_TEXT:
3171 return "text";
3172 case CT_VIDEO:
3173 return "video";
3174 case CT_EXTENSION:
3175 return "extension";
3176 default:
3177 return "unknown_type";
3178 }
3179 }
3180
3181
3182 char *
3183 ct_subtype_str (int type, int subtype) {
3184 switch (type) {
3185 case CT_APPLICATION:
3186 switch (subtype) {
3187 case APPLICATION_OCTETS:
3188 return "octets";
3189 case APPLICATION_POSTSCRIPT:
3190 return "postscript";
3191 default:
3192 return "unknown_app_subtype";
3193 }
3194 case CT_MESSAGE:
3195 switch (subtype) {
3196 case MESSAGE_RFC822:
3197 return "rfc822";
3198 case MESSAGE_PARTIAL:
3199 return "partial";
3200 case MESSAGE_EXTERNAL:
3201 return "external";
3202 default:
3203 return "unknown_msg_subtype";
3204 }
3205 case CT_MULTIPART:
3206 switch (subtype) {
3207 case MULTI_MIXED:
3208 return "mixed";
3209 case MULTI_ALTERNATE:
3210 return "alternative";
3211 case MULTI_DIGEST:
3212 return "digest";
3213 case MULTI_PARALLEL:
3214 return "parallel";
3215 case MULTI_RELATED:
3216 return "related";
3217 default:
3218 return "unknown_multipart_subtype";
3219 }
3220 case CT_TEXT:
3221 switch (subtype) {
3222 case TEXT_PLAIN:
3223 return "plain";
3224 case TEXT_RICHTEXT:
3225 return "richtext";
3226 case TEXT_ENRICHED:
3227 return "enriched";
3228 default:
3229 return "unknown_text_subtype";
3230 }
3231 default:
3232 return "unknown_type";
3233 }
3234 }
3235
3236
3237 int
3238 ct_str_type (const char *type) {
3239 struct str2init *s2i;
3240
3241 for (s2i = str2cts; s2i->si_key; ++s2i) {
3242 if (! strcasecmp (type, s2i->si_key)) {
3243 break;
3244 }
3245 }
3246 if (! s2i->si_key && ! uprf (type, "X-")) {
3247 ++s2i;
3248 }
3249
3250 return s2i->si_val;
3251 }
3252
3253
3254 int
3255 ct_str_subtype (int type, const char *subtype) {
3256 struct k2v *kv;
3257
3258 switch (type) {
3259 case CT_APPLICATION:
3260 for (kv = SubApplication; kv->kv_key; ++kv) {
3261 if (! strcasecmp (subtype, kv->kv_key)) {
3262 break;
3263 }
3264 }
3265 return kv->kv_value;
3266 case CT_MESSAGE:
3267 for (kv = SubMessage; kv->kv_key; ++kv) {
3268 if (! strcasecmp (subtype, kv->kv_key)) {
3269 break;
3270 }
3271 }
3272 return kv->kv_value;
3273 case CT_MULTIPART:
3274 for (kv = SubMultiPart; kv->kv_key; ++kv) {
3275 if (! strcasecmp (subtype, kv->kv_key)) {
3276 break;
3277 }
3278 }
3279 return kv->kv_value;
3280 case CT_TEXT:
3281 for (kv = SubText; kv->kv_key; ++kv) {
3282 if (! strcasecmp (subtype, kv->kv_key)) {
3283 break;
3284 }
3285 }
3286 return kv->kv_value;
3287 default:
3288 return 0;
3289 }
3290 }
3291
3292
3293 /* Find the content type and InitFunc for the CT. */
3294 const struct str2init *
3295 get_ct_init (int type) {
3296 const struct str2init *sp;
3297
3298 for (sp = str2cts; sp->si_key; ++sp) {
3299 if (type == sp->si_val) {
3300 return sp;
3301 }
3302 }
3303
3304 return NULL;
3305 }
3306
3307 const char *
3308 ce_str (int encoding) {
3309 switch (encoding) {
3310 case CE_BASE64:
3311 return "base64";
3312 case CE_QUOTED:
3313 return "quoted-printable";
3314 case CE_8BIT:
3315 return "8bit";
3316 case CE_7BIT:
3317 return "7bit";
3318 case CE_BINARY:
3319 return "binary";
3320 case CE_EXTENSION:
3321 return "extension";
3322 case CE_EXTERNAL:
3323 return "external";
3324 default:
3325 return "unknown";
3326 }
3327 }
3328
3329 /* Find the content type and InitFunc for the content encoding method. */
3330 const struct str2init *
3331 get_ce_method (const char *method) {
3332 struct str2init *sp;
3333
3334 for (sp = str2ces; sp->si_key; ++sp) {
3335 if (! strcasecmp (method, sp->si_key)) {
3336 return sp;
3337 }
3338 }
3339
3340 return NULL;
3341 }
3342
3343 /*
3344 * Parse a series of MIME attributes (or parameters) given a header as
3345 * input.
3346 *
3347 * Arguments include:
3348 *
3349 * filename - Name of input file (for error messages)
3350 * fieldname - Name of field being processed
3351 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3352 * Updated to point to end of attributes when finished.
3353 * param_head - Pointer to head of parameter list
3354 * param_tail - Pointer to tail of parameter list
3355 * commentp - Pointer to header comment pointer (may be NULL)
3356 *
3357 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3358 * DONE to indicate a benign error (minor parsing error, but the program
3359 * should continue).
3360 */
3361
3362 static int
3363 parse_header_attrs (const char *filename, const char *fieldname,
3364 char **header_attrp, PM *param_head, PM *param_tail,
3365 char **commentp)
3366 {
3367 char *cp = *header_attrp;
3368 PM pm;
3369 struct sectlist {
3370 char *value;
3371 int index;
3372 int len;
3373 struct sectlist *next;
3374 } *sp, *sp2;
3375 struct parmlist {
3376 char *name;
3377 char *charset;
3378 char *lang;
3379 struct sectlist *sechead;
3380 struct parmlist *next;
3381 } *pp, *pp2, *phead = NULL;
3382
3383 while (*cp == ';') {
3384 char *dp, *vp, *up, *nameptr, *valptr, *charset = NULL, *lang = NULL;
3385 int encoded = 0, partial = 0, len = 0, index = 0;
3386
3387 cp++;
3388 while (isspace ((unsigned char) *cp))
3389 cp++;
3390
3391 if (*cp == '(' &&
3392 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3393 return NOTOK;
3394 }
3395
3396 if (*cp == 0) {
3397 if (! suppress_extraneous_trailing_semicolon_warning) {
3398 advise (NULL,
3399 "extraneous trailing ';' in message %s's %s: "
3400 "parameter list",
3401 filename, fieldname);
3402 }
3403 extraneous_trailing_semicolon = 1;
3404 return DONE;
3405 }
3406
3407 /* down case the attribute name */
3408 for (dp = cp; istoken ((unsigned char) *dp); dp++)
3409 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
3410 *dp = tolower ((unsigned char) *dp);
3411
3412 for (up = dp; isspace ((unsigned char) *dp);)
3413 dp++;
3414 if (dp == cp || *dp != '=') {
3415 advise (NULL,
3416 "invalid parameter in message %s's %s: "
3417 "field\n%*sparameter %s (error detected at offset %d)",
3418 filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp);
3419 return NOTOK;
3420 }
3421
3422 /*
3423 * To handle RFC 2231, we have to deal with the following extensions:
3424 *
3425 * name*=encoded-value
3426 * name*<N>=part-N-of-a-parameter-value
3427 * name*<N>*=encoded-part-N-of-a-parameter-value
3428 *
3429 * So the rule is:
3430 * If there's a * right before the equal sign, it's encoded.
3431 * If there's a * and one or more digits, then it's section N.
3432 *
3433 * Remember we can have one or the other, or both. cp points to
3434 * beginning of name, up points past the last character in the
3435 * parameter name.
3436 */
3437
3438 for (vp = cp; vp < up; vp++) {
3439 if (*vp == '*' && vp < up - 1) {
3440 partial = 1;
3441 continue;
3442 } else if (*vp == '*' && vp == up - 1) {
3443 encoded = 1;
3444 } else if (partial) {
3445 if (isdigit((unsigned char) *vp))
3446 index = *vp - '0' + index * 10;
3447 else {
3448 advise (NULL, "invalid parameter index in message %s's "
3449 "%s: field\n%*s(parameter %s)", filename,
3450 fieldname, strlen(invo_name) + 2, "", cp);
3451 return NOTOK;
3452 }
3453 } else {
3454 len++;
3455 }
3456 }
3457
3458 /*
3459 * Break out the parameter name and value sections and allocate
3460 * memory for each.
3461 */
3462
3463 nameptr = mh_xmalloc(len + 1);
3464 strncpy(nameptr, cp, len);
3465 nameptr[len] = '\0';
3466
3467 for (dp++; isspace ((unsigned char) *dp);)
3468 dp++;
3469
3470 if (encoded) {
3471 /*
3472 * Single quotes delimit the character set and language tag.
3473 * They are required on the first section (or a complete
3474 * parameter).
3475 */
3476 if (index == 0) {
3477 vp = dp;
3478 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3479 *vp != '\0')
3480 vp++;
3481 if (*vp == '\'') {
3482 if (vp != dp) {
3483 len = vp - dp;
3484 charset = mh_xmalloc(len + 1);
3485 strncpy(charset, dp, len);
3486 charset[len] = '\0';
3487 } else {
3488 charset = NULL;
3489 }
3490 vp++;
3491 } else {
3492 advise(NULL, "missing charset in message %s's %s: "
3493 "field\n%*s(parameter %s)", filename, fieldname,
3494 strlen(invo_name) + 2, "", nameptr);
3495 free(nameptr);
3496 return NOTOK;
3497 }
3498 dp = vp;
3499
3500 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3501 *vp != '\0')
3502 vp++;
3503
3504 if (*vp == '\'') {
3505 if (vp != dp) {
3506 len = vp - dp;
3507 lang = mh_xmalloc(len + 1);
3508 strncpy(lang, dp, len);
3509 lang[len] = '\0';
3510 } else {
3511 lang = NULL;
3512 }
3513 vp++;
3514 } else {
3515 advise(NULL, "missing language tag in message %s's %s: "
3516 "field\n%*s(parameter %s)", filename, fieldname,
3517 strlen(invo_name) + 2, "", nameptr);
3518 free(nameptr);
3519 if (charset)
3520 free(charset);
3521 return NOTOK;
3522 }
3523
3524 dp = vp;
3525 }
3526
3527 /*
3528 * At this point vp should be pointing at the beginning
3529 * of the encoded value/section. Continue until we reach
3530 * the end or get whitespace. But first, calculate the
3531 * length so we can allocate the correct buffer size.
3532 */
3533
3534 for (vp = dp, len = 0; istoken(*vp); vp++) {
3535 if (*vp == '%') {
3536 if (*(vp + 1) == '\0' ||
3537 !isxdigit((unsigned char) *(vp + 1)) ||
3538 *(vp + 2) == '\0' ||
3539 !isxdigit((unsigned char) *(vp + 2))) {
3540 advise(NULL, "invalid encoded sequence in message "
3541 "%s's %s: field\n%*s(parameter %s)",
3542 filename, fieldname, strlen(invo_name) + 2,
3543 "", nameptr);
3544 free(nameptr);
3545 if (charset)
3546 free(charset);
3547 if (lang)
3548 free(lang);
3549 return NOTOK;
3550 }
3551 vp += 2;
3552 }
3553 len++;
3554 }
3555
3556 up = valptr = mh_xmalloc(len + 1);
3557
3558 for (vp = dp; istoken(*vp); vp++) {
3559 if (*vp == '%') {
3560 *up++ = decode_qp(*(vp + 1), *(vp + 2));
3561 vp += 2;
3562 } else {
3563 *up++ = *vp;
3564 }
3565 }
3566
3567 *up = '\0';
3568 cp = vp;
3569 } else {
3570 /*
3571 * A "normal" string. If it's got a leading quote, then we
3572 * strip the quotes out. Otherwise go until we reach the end
3573 * or get whitespace. Note we scan it twice; once to get the
3574 * length, then the second time copies it into the destination
3575 * buffer.
3576 */
3577
3578 len = 0;
3579
3580 if (*dp == '"') {
3581 for (cp = dp + 1;;) {
3582 switch (*cp++) {
3583 case '\0':
3584 bad_quote:
3585 advise (NULL,
3586 "invalid quoted-string in message %s's %s: "
3587 "field\n%*s(parameter %s)",
3588 filename, fieldname, strlen(invo_name) + 2, "",
3589 nameptr);
3590 free(nameptr);
3591 if (charset)
3592 free(charset);
3593 if (lang)
3594 free(lang);
3595 return NOTOK;
3596 case '"':
3597 break;
3598
3599 case '\\':
3600 if (*++cp == '\0')
3601 goto bad_quote;
3602 /* FALL THROUGH */
3603 default:
3604 len++;
3605 continue;
3606 }
3607 break;
3608 }
3609
3610 } else {
3611 for (cp = dp; istoken (*cp); cp++) {
3612 len++;
3613 }
3614 }
3615
3616 valptr = mh_xmalloc(len + 1);
3617
3618 if (*dp == '"') {
3619 int i;
3620 for (cp = dp + 1, vp = valptr, i = 0; i < len; i++) {
3621 if (*cp == '\\') {
3622 cp++;
3623 }
3624 *vp++ = *cp++;
3625 }
3626 cp++;
3627 } else {
3628 strncpy(valptr, cp = dp, len);
3629 cp += len;
3630 }
3631
3632 valptr[len] = '\0';
3633 }
3634
3635 /*
3636 * If 'partial' is set, we don't allocate a parameter now. We
3637 * put it on the parameter linked list to be reassembled later.
3638 *
3639 * "phead" points to a list of all parameters we need to reassemble.
3640 * Each parameter has a list of sections. We insert the sections in
3641 * order.
3642 */
3643
3644 if (partial) {
3645 for (pp = phead; pp != NULL; pp = pp->next) {
3646 if (strcasecmp(nameptr, pp->name) == 0)
3647 break;
3648 }
3649
3650 if (pp == NULL) {
3651 pp = mh_xmalloc(sizeof(*pp));
3652 memset(pp, 0, sizeof(*pp));
3653 pp->name = nameptr;
3654 pp->next = phead;
3655 phead = pp;
3656 }
3657
3658 /*
3659 * Insert this into the section linked list
3660 */
3661
3662 sp = mh_xmalloc(sizeof(*sp));
3663 memset(sp, 0, sizeof(*sp));
3664 sp->value = valptr;
3665 sp->index = index;
3666 sp->len = len;
3667
3668 if (pp->sechead == NULL || pp->sechead->index > index) {
3669 sp->next = pp->sechead;
3670 pp->sechead = sp;
3671 } else {
3672 for (sp2 = pp->sechead; sp2 != NULL; sp2 = sp2->next) {
3673 if (sp2->index == sp->index) {
3674 advise (NULL, "duplicate index (%d) in message "
3675 "%s's %s: field\n%*s(parameter %s)", sp->index,
3676 filename, fieldname, strlen(invo_name) + 2, "",
3677 nameptr);
3678 free (nameptr);
3679 return NOTOK;
3680 }
3681 if (sp2->index < sp->index &&
3682 (sp2->next == NULL || sp2->next->index > sp->index)) {
3683 sp->next = sp2->next;
3684 sp2->next = sp;
3685 break;
3686 }
3687 }
3688
3689 if (sp2 == NULL) {
3690 advise(NULL, "Internal error: cannot insert partial "
3691 "param in message %s's %s: field\n%*s(parameter %s)",
3692 filename, fieldname, strlen(invo_name) + 2, "",
3693 nameptr);
3694 free (nameptr);
3695 return NOTOK;
3696 }
3697 }
3698
3699 /*
3700 * Save our charset and lang tags.
3701 */
3702
3703 if (index == 0 && encoded) {
3704 if (pp->charset)
3705 free(pp->charset);
3706 pp->charset = charset;
3707 if (pp->lang)
3708 free(pp->lang);
3709 pp->lang = lang;
3710 }
3711 } else {
3712 pm = add_param(param_head, param_tail, nameptr, valptr, 1);
3713 pm->pm_charset = charset;
3714 pm->pm_lang = lang;
3715 }
3716
3717 while (isspace ((unsigned char) *cp))
3718 cp++;
3719
3720 if (*cp == '(' &&
3721 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3722 return NOTOK;
3723 }
3724 }
3725
3726 /*
3727 * Now that we're done, reassemble all of the partial parameters.
3728 */
3729
3730 for (pp = phead; pp != NULL; ) {
3731 char *p, *q;
3732 size_t tlen = 0;
3733 int pindex = 0;
3734 for (sp = pp->sechead; sp != NULL; sp = sp->next) {
3735 if (sp->index != pindex++) {
3736 advise(NULL, "missing section %d for parameter in "
3737 "message %s's %s: field\n%*s(parameter %s)", pindex - 1,
3738 filename, fieldname, strlen(invo_name) + 2, "",
3739 pp->name);
3740 return NOTOK;
3741 }
3742 tlen += sp->len;
3743 }
3744
3745 p = q = mh_xmalloc(tlen + 1);
3746 for (sp = pp->sechead; sp != NULL; ) {
3747 memcpy(q, sp->value, sp->len);
3748 q += sp->len;
3749 free(sp->value);
3750 sp2 = sp->next;
3751 free(sp);
3752 sp = sp2;
3753 }
3754
3755 p[tlen] = '\0';
3756
3757 pm = add_param(param_head, param_tail, pp->name, p, 1);
3758 pm->pm_charset = pp->charset;
3759 pm->pm_lang = pp->lang;
3760 pp2 = pp->next;
3761 free(pp);
3762 pp = pp2;
3763 }
3764
3765 *header_attrp = cp;
3766 return OK;
3767 }
3768
3769 /*
3770 * Return the charset for a particular content type.
3771 */
3772
3773 char *
3774 content_charset (CT ct) {
3775 char *ret_charset = NULL;
3776
3777 ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
3778
3779 return ret_charset ? ret_charset : getcpy ("US-ASCII");
3780 }
3781
3782
3783 /*
3784 * Create a string based on a list of output parameters. Assume that this
3785 * parameter string will be appended to an existing header, so start out
3786 * with the separator (;). Perform RFC 2231 encoding when necessary.
3787 */
3788
3789 char *
3790 output_params(size_t initialwidth, PM params, int *offsetout, int external)
3791 {
3792 char *paramout = NULL;
3793 char line[CPERLIN * 2], *q;
3794 int curlen, index, cont, encode, i;
3795 size_t valoff, numchars;
3796
3797 while (params != NULL) {
3798 encode = 0;
3799 index = 0;
3800 valoff = 0;
3801 q = line;
3802
3803 if (external && strcasecmp(params->pm_name, "body") == 0)
3804 continue;
3805
3806 if (strlen(params->pm_name) > CPERLIN) {
3807 advise(NULL, "Parameter name \"%s\" is too long", params->pm_name);
3808 if (paramout)
3809 free(paramout);
3810 return NULL;
3811 }
3812
3813 curlen = param_len(params, index, valoff, &encode, &cont, &numchars);
3814
3815 /*
3816 * Loop until we get a parameter that fits within a line. We
3817 * assume new lines start with a tab, so check our overflow based
3818 * on that.
3819 */
3820
3821 while (cont) {
3822 *q++ = ';';
3823 *q++ = '\n';
3824 *q++ = '\t';
3825
3826 /*
3827 * At this point we're definitely continuing the line, so
3828 * be sure to include the parameter name and section index.
3829 */
3830
3831 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3832 params->pm_name, index);
3833
3834 /*
3835 * Both of these functions do a NUL termination
3836 */
3837
3838 if (encode)
3839 i = encode_param(params, q, sizeof(line) - (q - line),
3840 numchars, valoff, index);
3841 else
3842 i = normal_param(params, q, sizeof(line) - (q - line),
3843 numchars, valoff);
3844
3845 if (i == 0) {
3846 if (paramout)
3847 free(paramout);
3848 return NULL;
3849 }
3850
3851 valoff += numchars;
3852 index++;
3853 curlen = param_len(params, index, valoff, &encode, &cont,
3854 &numchars);
3855 q = line;
3856
3857 /*
3858 * "line" starts with a ;\n\t, so that doesn't count against
3859 * the length. But add 8 since it starts with a tab; that's
3860 * how we end up with 5.
3861 */
3862
3863 initialwidth = strlen(line) + 5;
3864
3865 /*
3866 * At this point the line should be built, so add it to our
3867 * current output buffer.
3868 */
3869
3870 paramout = add(line, paramout);
3871 }
3872
3873 /*
3874 * If this won't fit on the line, start a new one. Save room in
3875 * case we need a semicolon on the end
3876 */
3877
3878 if (initialwidth + curlen > CPERLIN - 1) {
3879 *q++ = ';';
3880 *q++ = '\n';
3881 *q++ = '\t';
3882 initialwidth = 8;
3883 } else {
3884 *q++ = ';';
3885 *q++ = ' ';
3886 initialwidth += 2;
3887 }
3888
3889 /*
3890 * At this point, we're either finishing a contined parameter, or
3891 * we're working on a new one.
3892 */
3893
3894 if (index > 0) {
3895 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3896 params->pm_name, index);
3897 } else {
3898 strncpy(q, params->pm_name, sizeof(line) - (q - line));
3899 q += strlen(q);
3900 }
3901
3902 if (encode)
3903 i = encode_param(params, q, sizeof(line) - (q - line),
3904 strlen(params->pm_value + valoff), valoff, index);
3905 else
3906 i = normal_param(params, q, sizeof(line) - (q - line),
3907 strlen(params->pm_value + valoff), valoff);
3908
3909 if (i == 0) {
3910 if (paramout)
3911 free(paramout);
3912 return NULL;
3913 }
3914
3915 paramout = add(line, paramout);
3916 initialwidth += strlen(line);
3917
3918 params = params->pm_next;
3919 }
3920
3921 if (offsetout)
3922 *offsetout = initialwidth;
3923
3924 return paramout;
3925 }
3926
3927 /*
3928 * Calculate the size of a parameter.
3929 *
3930 * Arguments include
3931 *
3932 * pm - The parameter being output
3933 * index - If continuing the parameter, the index of the section
3934 * we're on.
3935 * valueoff - The current offset into the parameter value that we're
3936 * working on (previous sections have consumed valueoff bytes).
3937 * encode - Set if we should perform encoding on this parameter section
3938 * (given that we're consuming bytesfit bytes).
3939 * cont - Set if the remaining data in value will not fit on a single
3940 * line and will need to be continued.
3941 * bytesfit - The number of bytes that we can consume from the parameter
3942 * value and still fit on a completely new line. The
3943 * calculation assumes the new line starts with a tab,
3944 * includes the parameter name and any encoding, and fits
3945 * within CPERLIN bytes. Will always be at least 1.
3946 */
3947
3948 static size_t
3949 param_len(PM pm, int index, size_t valueoff, int *encode, int *cont,
3950 size_t *bytesfit)
3951 {
3952 char *start = pm->pm_value + valueoff, *p, indexchar[32];
3953 size_t len = 0, fit = 0;
3954 int fitlimit = 0, eightbit, maxfit;
3955
3956 *encode = 0;
3957
3958 /*
3959 * Add up the length. First, start with the parameter name.
3960 */
3961
3962 len = strlen(pm->pm_name);
3963
3964 /*
3965 * Scan the parameter value and see if we need to do encoding for this
3966 * section.
3967 */
3968
3969 eightbit = contains8bit(start, NULL);
3970
3971 /*
3972 * Determine if we need to encode this section. Encoding is necessary if:
3973 *
3974 * - There are any 8-bit characters at all and we're on the first
3975 * section.
3976 * - There are 8-bit characters within N bytes of our section start.
3977 * N is calculated based on the number of bytes it would take to
3978 * reach CPERLIN. Specifically:
3979 * 8 (starting tab) +
3980 * strlen(param name) +
3981 * 4 ('* for section marker, '=', opening/closing '"')
3982 * strlen (index)
3983 * is the number of bytes used by everything that isn't part of the
3984 * value. So that gets subtracted from CPERLIN.
3985 */
3986
3987 snprintf(indexchar, sizeof(indexchar), "%d", index);
3988 maxfit = CPERLIN - (12 + len + strlen(indexchar));
3989 if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) {
3990 *encode = 1;
3991 }
3992
3993 len++; /* Add in equal sign */
3994
3995 if (*encode) {
3996 /*
3997 * We're using maxfit as a marker for how many characters we can
3998 * fit into the line. Bump it by two because we're not using quotes
3999 * when encoding.
4000 */
4001
4002 maxfit += 2;
4003
4004 /*
4005 * If we don't have a charset or language tag in this parameter,
4006 * add them now.
4007 */
4008
4009 if (! pm->pm_charset) {
4010 pm->pm_charset = getcpy(write_charset_8bit());
4011 if (strcasecmp(pm->pm_charset, "US-ASCII") == 0)
4012 adios(NULL, "8-bit characters in parameter \"%s\", but "
4013 "local character set is US-ASCII", pm->pm_name);
4014 }
4015 if (! pm->pm_lang)
4016 pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */
4017
4018 len++; /* For the encoding marker */
4019 maxfit--;
4020 if (index == 0) {
4021 int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
4022 len += enclen;
4023 maxfit-= enclen;
4024 } else {
4025 /*
4026 * We know we definitely need to include an index. maxfit already
4027 * includes the section marker.
4028 */
4029 len += strlen(indexchar);
4030 }
4031 for (p = start; *p != '\0'; p++) {
4032 if (isparamencode(*p)) {
4033 len += 3;
4034 maxfit -= 3;
4035 } else {
4036 len++;
4037 maxfit--;
4038 }
4039 /*
4040 * Just so there's no confusion: maxfit is counting OUTPUT
4041 * characters (post-encoding). fit is counting INPUT characters.
4042 */
4043 if (! fitlimit && maxfit >= 0)
4044 fit++;
4045 else if (! fitlimit)
4046 fitlimit++;
4047 }
4048 } else {
4049 /*
4050 * Calculate the string length, but add room for quoting \
4051 * and " if necessary. Also account for quotes at beginning
4052 * and end.
4053 */
4054 for (p = start; *p != '\0'; p++) {
4055 switch (*p) {
4056 case '"':
4057 case '\\':
4058 len++;
4059 maxfit--;
4060 /* FALL THROUGH */
4061 default:
4062 len++;
4063 maxfit--;
4064 }
4065 if (! fitlimit && maxfit >= 0)
4066 fit++;
4067 else if (! fitlimit)
4068 fitlimit++;
4069 }
4070
4071 len += 2;
4072 }
4073
4074 if (fit < 1)
4075 fit = 1;
4076
4077 *cont = fitlimit;
4078 *bytesfit = fit;
4079
4080 return len;
4081 }
4082
4083 /*
4084 * Output an encoded parameter string.
4085 */
4086
4087 static size_t
4088 encode_param(PM pm, char *output, size_t len, size_t valuelen,
4089 size_t valueoff, int index)
4090 {
4091 size_t outlen = 0, n;
4092 char *endptr = output + len, *p;
4093
4094 /*
4095 * First, output the marker for an encoded string.
4096 */
4097
4098 *output++ = '*';
4099 *output++ = '=';
4100 outlen += 2;
4101
4102 /*
4103 * If the index is 0, output the character set and language tag.
4104 * If theses were NULL, they should have already been filled in
4105 * by param_len().
4106 */
4107
4108 if (index == 0) {
4109 n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset,
4110 pm->pm_lang);
4111 output += n;
4112 outlen += n;
4113 if (output > endptr) {
4114 advise(NULL, "Internal error: parameter buffer overflow");
4115 return 0;
4116 }
4117 }
4118
4119 /*
4120 * Copy over the value, encoding if necessary
4121 */
4122
4123 p = pm->pm_value + valueoff;
4124 while (valuelen-- > 0) {
4125 if (isparamencode(*p)) {
4126 n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++);
4127 output += n;
4128 outlen += n;
4129 } else {
4130 *output++ = *p++;
4131 outlen++;
4132 }
4133 if (output > endptr) {
4134 advise(NULL, "Internal error: parameter buffer overflow");
4135 return 0;
4136 }
4137 }
4138
4139 *output = '\0';
4140
4141 return outlen;
4142 }
4143
4144 /*
4145 * Output a "normal" parameter, without encoding. Be sure to escape
4146 * quotes and backslashes if necessary.
4147 */
4148
4149 static size_t
4150 normal_param(PM pm, char *output, size_t len, size_t valuelen,
4151 size_t valueoff)
4152 {
4153 size_t outlen = 0;
4154 char *endptr = output + len, *p;
4155
4156 *output++ = '=';
4157 *output++ = '"';
4158 outlen += 2;
4159
4160 p = pm->pm_value + valueoff;
4161
4162 while (valuelen-- > 0) {
4163 switch (*p) {
4164 case '\\':
4165 case '"':
4166 *output++ = '\\';
4167 outlen++;
4168 default:
4169 *output++ = *p++;
4170 outlen++;
4171 }
4172 if (output > endptr) {
4173 advise(NULL, "Internal error: parameter buffer overflow");
4174 return 0;
4175 }
4176 }
4177
4178 if (output - 2 > endptr) {
4179 advise(NULL, "Internal error: parameter buffer overflow");
4180 return 0;
4181 }
4182
4183 *output++ = '"';
4184 *output++ = '\0';
4185
4186 return outlen + 1;
4187 }
4188
4189 /*
4190 * Add a parameter to the parameter linked list
4191 */
4192
4193 PM
4194 add_param(PM *first, PM *last, char *name, char *value, int nocopy)
4195 {
4196 PM pm = mh_xmalloc(sizeof(*pm));
4197
4198 memset(pm, 0, sizeof(*pm));
4199
4200 pm->pm_name = nocopy ? name : getcpy(name);
4201 pm->pm_value = nocopy ? value : getcpy(value);
4202
4203 if (*first) {
4204 (*last)->pm_next = pm;
4205 *last = pm;
4206 } else {
4207 *first = pm;
4208 *last = pm;
4209 }
4210
4211 return pm;
4212 }
4213
4214 /*
4215 * Either replace a current parameter with a new value, or add the parameter
4216 * to the parameter linked list.
4217 */
4218
4219 PM
4220 replace_param(PM *first, PM *last, char *name, char *value, int nocopy)
4221 {
4222 PM pm;
4223
4224 for (pm = *first; pm != NULL; pm = pm->pm_next) {
4225 if (strcasecmp(name, pm->pm_name) == 0) {
4226 /*
4227 * If nocopy is set, it's assumed that we own both name
4228 * and value. We don't need name, so we discard it now.
4229 */
4230 if (nocopy)
4231 free(name);
4232 free(pm->pm_value);
4233 pm->pm_value = nocopy ? value : getcpy(value);
4234 return pm;
4235 }
4236 }
4237
4238 return add_param(first, last, name, value, nocopy);
4239 }
4240
4241 /*
4242 * Retrieve a parameter value from a parameter linked list. If the parameter
4243 * value needs converted to the local character set, do that now.
4244 */
4245
4246 char *
4247 get_param(PM first, const char *name, char replace, int fetchonly)
4248 {
4249 while (first != NULL) {
4250 if (strcasecmp(name, first->pm_name) == 0) {
4251 if (fetchonly)
4252 return first->pm_value;
4253 else
4254 return getcpy(get_param_value(first, replace));
4255 }
4256 first = first->pm_next;
4257 }
4258
4259 return NULL;
4260 }
4261
4262 /*
4263 * Return a parameter value, converting to the local character set if
4264 * necessary
4265 */
4266
4267 char *get_param_value(PM pm, char replace)
4268 {
4269 static char buffer[4096]; /* I hope no parameters are larger */
4270 size_t bufsize = sizeof(buffer);
4271 #ifdef HAVE_ICONV
4272 size_t inbytes;
4273 int utf8;
4274 iconv_t cd;
4275 ICONV_CONST char *p;
4276 #else /* HAVE_ICONV */
4277 char *p;
4278 #endif /* HAVE_ICONV */
4279
4280 char *q;
4281
4282 /*
4283 * If we don't have a character set indicated, it's assumed to be
4284 * US-ASCII. If it matches our character set, we don't need to convert
4285 * anything.
4286 */
4287
4288 if (!pm->pm_charset || check_charset(pm->pm_charset,
4289 strlen(pm->pm_charset))) {
4290 return pm->pm_value;
4291 }
4292
4293 /*
4294 * In this case, we need to convert. If we have iconv support, use
4295 * that. Otherwise, go through and simply replace every non-ASCII
4296 * character with the substitution character.
4297 */
4298
4299 #ifdef HAVE_ICONV
4300 q = buffer;
4301 bufsize = sizeof(buffer);
4302 utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0;
4303
4304 cd = iconv_open(get_charset(), pm->pm_charset);
4305 if (cd == (iconv_t) -1) {
4306 goto noiconv;
4307 }
4308
4309 inbytes = strlen(pm->pm_value);
4310 p = pm->pm_value;
4311
4312 while (inbytes) {
4313 if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) {
4314 if (errno != EILSEQ) {
4315 iconv_close(cd);
4316 goto noiconv;
4317 }
4318 /*
4319 * Reset shift state, substitute our character,
4320 * try to restart conversion.
4321 */
4322
4323 iconv(cd, NULL, NULL, &q, &bufsize);
4324
4325 if (bufsize == 0) {
4326 iconv_close(cd);
4327 goto noiconv;
4328 }
4329 *q++ = replace;
4330 bufsize--;
4331 if (bufsize == 0) {
4332 iconv_close(cd);
4333 goto noiconv;
4334 }
4335 if (utf8) {
4336 for (++p, --inbytes;
4337 inbytes > 0 && (((unsigned char) *p) & 0xc0) == 0x80;
4338 ++p, --inbytes)
4339 continue;
4340 } else {
4341 p++;
4342 inbytes--;
4343 }
4344 }
4345 }
4346
4347 iconv_close(cd);
4348
4349 if (bufsize == 0)
4350 q--;
4351 *q = '\0';
4352
4353 return buffer;
4354
4355 noiconv:
4356 #endif /* HAVE_ICONV */
4357
4358 /*
4359 * Take everything non-ASCII and substituite the replacement character
4360 */
4361
4362 q = buffer;
4363 bufsize = sizeof(buffer);
4364 for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) {
4365 if (isascii((unsigned char) *p) && !iscntrl((unsigned char) *p))
4366 *q = *p;
4367 else
4368 *q = replace;
4369 }
4370
4371 *q = '\0';
4372
4373 return buffer;
4374 }