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