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