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