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