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