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