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