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