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