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