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