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