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