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