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