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