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