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