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