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