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