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