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