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