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