]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Use existing macros min() and max() more.
[nmh] / uip / mhparse.c
1 /* mhparse.c -- routines to parse the contents of MIME messages
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include <h/mh.h>
9 #include <fcntl.h>
10 #include <h/md5.h>
11 #include <h/mts.h>
12 #include <h/tws.h>
13 #include <h/mime.h>
14 #include <h/mhparse.h>
15 #include <h/utils.h>
16 #ifdef HAVE_ICONV
17 # include <iconv.h>
18 #endif /* HAVE_ICONV */
19
20
21 extern int debugsw;
22
23 /* cache policies */
24 extern int rcachesw; /* mhcachesbr.c */
25 extern int wcachesw; /* mhcachesbr.c */
26
27 int checksw = 0; /* check Content-MD5 field */
28
29 /*
30 * These are for mhfixmsg to:
31 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
32 * in a multipart.
33 * 2) Suppress the warning about bogus multipart content, and report it.
34 * 3) Suppress the warning about extraneous trailing ';' in header parameter
35 * lists.
36 */
37 int skip_mp_cte_check;
38 int suppress_bogus_mp_content_warning;
39 int bogus_mp_content;
40 int suppress_extraneous_trailing_semicolon_warning;
41
42 /*
43 * By default, suppress warning about multiple MIME-Version header fields.
44 */
45 int suppress_multiple_mime_version_warning = 1;
46
47 /* list of preferred type/subtype pairs, for -prefer */
48 char *preferred_types[NPREFS],
49 *preferred_subtypes[NPREFS];
50 int npreferred;
51
52
53 /*
54 * Structures for TEXT messages
55 */
56 struct k2v SubText[] = {
57 { "plain", TEXT_PLAIN },
58 { "richtext", TEXT_RICHTEXT }, /* defined in RFC 1341 */
59 { "enriched", TEXT_ENRICHED }, /* defined in RFC 1896 */
60 { NULL, TEXT_UNKNOWN } /* this one must be last! */
61 };
62
63 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
64
65 /*
66 * Structures for MULTIPART messages
67 */
68 struct k2v SubMultiPart[] = {
69 { "mixed", MULTI_MIXED },
70 { "alternative", MULTI_ALTERNATE },
71 { "digest", MULTI_DIGEST },
72 { "parallel", MULTI_PARALLEL },
73 { "related", MULTI_RELATED },
74 { NULL, MULTI_UNKNOWN } /* this one must be last! */
75 };
76
77 /*
78 * Structures for MESSAGE messages
79 */
80 struct k2v SubMessage[] = {
81 { "rfc822", MESSAGE_RFC822 },
82 { "partial", MESSAGE_PARTIAL },
83 { "external-body", MESSAGE_EXTERNAL },
84 { NULL, MESSAGE_UNKNOWN } /* this one must be last! */
85 };
86
87 /*
88 * Structure for APPLICATION messages
89 */
90 struct k2v SubApplication[] = {
91 { "octet-stream", APPLICATION_OCTETS },
92 { "postscript", APPLICATION_POSTSCRIPT },
93 { NULL, APPLICATION_UNKNOWN } /* this one must be last! */
94 };
95
96 /*
97 * Mapping of names of CTE types in mhbuild directives
98 */
99 static struct k2v EncodingType[] = {
100 { "8bit", CE_8BIT },
101 { "qp", CE_QUOTED },
102 { "q-p", CE_QUOTED },
103 { "quoted-printable", CE_QUOTED },
104 { "b64", CE_BASE64 },
105 { "base64", CE_BASE64 },
106 { NULL, 0 },
107 };
108
109
110 /* mhcachesbr.c */
111 int find_cache (CT, int, int *, char *, char *, int);
112
113 /* mhmisc.c */
114 int part_ok (CT);
115 int type_ok (CT, int);
116 void content_error (char *, CT, char *, ...);
117
118 /* mhfree.c */
119 void free_encoding (CT, int);
120
121 /*
122 * static prototypes
123 */
124 static CT get_content (FILE *, char *, int);
125 static int get_comment (const char *, const char *, char **, char **);
126
127 static int InitGeneric (CT);
128 static int InitText (CT);
129 static int InitMultiPart (CT);
130 static void reverse_parts (CT);
131 static void prefer_parts(CT ct);
132 static int InitMessage (CT);
133 static int InitApplication (CT);
134 static int init_encoding (CT, OpenCEFunc);
135 static unsigned long size_encoding (CT);
136 static int InitBase64 (CT);
137 static int openBase64 (CT, char **);
138 static int InitQuoted (CT);
139 static int openQuoted (CT, char **);
140 static int Init7Bit (CT);
141 static int openExternal (CT, CT, CE, char **, int *);
142 static int InitFile (CT);
143 static int openFile (CT, char **);
144 static int InitFTP (CT);
145 static int openFTP (CT, char **);
146 static int InitMail (CT);
147 static int openMail (CT, char **);
148 static int readDigest (CT, char *);
149 static int get_leftover_mp_content (CT, int);
150 static int InitURL (CT);
151 static int openURL (CT, char **);
152 static int parse_header_attrs (const char *, const char *, char **, PM *,
153 PM *, char **);
154 static size_t param_len(PM, int, size_t, int *, int *, size_t *);
155 static size_t normal_param(PM, char *, size_t, size_t, size_t);
156 static int get_dispo (char *, CT, int);
157
158 struct str2init str2cts[] = {
159 { "application", CT_APPLICATION, InitApplication },
160 { "audio", CT_AUDIO, InitGeneric },
161 { "image", CT_IMAGE, InitGeneric },
162 { "message", CT_MESSAGE, InitMessage },
163 { "multipart", CT_MULTIPART, InitMultiPart },
164 { "text", CT_TEXT, InitText },
165 { "video", CT_VIDEO, InitGeneric },
166 { NULL, CT_EXTENSION, NULL }, /* these two must be last! */
167 { NULL, CT_UNKNOWN, NULL },
168 };
169
170 struct str2init str2ces[] = {
171 { "base64", CE_BASE64, InitBase64 },
172 { "quoted-printable", CE_QUOTED, InitQuoted },
173 { "8bit", CE_8BIT, Init7Bit },
174 { "7bit", CE_7BIT, Init7Bit },
175 { "binary", CE_BINARY, Init7Bit },
176 { NULL, CE_EXTENSION, NULL }, /* these two must be last! */
177 { NULL, CE_UNKNOWN, NULL },
178 };
179
180 /*
181 * NOTE WELL: si_key MUST NOT have value of NOTOK
182 *
183 * si_key is 1 if access method is anonymous.
184 */
185 struct str2init str2methods[] = {
186 { "afs", 1, InitFile },
187 { "anon-ftp", 1, InitFTP },
188 { "ftp", 0, InitFTP },
189 { "local-file", 0, InitFile },
190 { "mail-server", 0, InitMail },
191 { "url", 0, InitURL },
192 { NULL, 0, NULL }
193 };
194
195
196 /*
197 * Main entry point for parsing a MIME message or file.
198 * It returns the Content structure for the top level
199 * entity in the file.
200 */
201
202 CT
203 parse_mime (char *file)
204 {
205 int is_stdin;
206 char buffer[BUFSIZ];
207 FILE *fp;
208 CT ct;
209 size_t n;
210 struct stat statbuf;
211
212 bogus_mp_content = 0;
213
214 /*
215 * Check if file is actually standard input
216 */
217 if ((is_stdin = !(strcmp (file, "-")))) {
218 char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
219 if (tfile == NULL) {
220 advise("mhparse", "unable to create temporary file in %s",
221 get_temp_dir());
222 return NULL;
223 }
224 file = mh_xstrdup(tfile);
225
226 while ((n = fread(buffer, 1, sizeof(buffer), stdin)) > 0) {
227 if (fwrite(buffer, 1, n, fp) != n) {
228 (void) m_unlink (file);
229 advise (file, "error copying to temporary file");
230 return NULL;
231 }
232 }
233 fflush (fp);
234
235 if (ferror (stdin)) {
236 (void) m_unlink (file);
237 advise ("stdin", "error reading");
238 return NULL;
239 }
240 if (ferror (fp)) {
241 (void) m_unlink (file);
242 advise (file, "error writing");
243 return NULL;
244 }
245 fseek (fp, 0L, SEEK_SET);
246 } else if (stat (file, &statbuf) == NOTOK) {
247 advise (file, "unable to stat");
248 return NULL;
249 } else if (S_ISDIR(statbuf.st_mode)) {
250 /* Don't try to parse a directory. */
251 inform("%s is a directory", file);
252 return NULL;
253 } else if ((fp = fopen (file, "r")) == NULL) {
254 advise (file, "unable to read");
255 return NULL;
256 }
257
258 if (!(ct = get_content (fp, file, 1))) {
259 if (is_stdin)
260 (void) m_unlink (file);
261 inform("unable to decode %s", file);
262 return NULL;
263 }
264
265 if (is_stdin)
266 ct->c_unlink = 1; /* temp file to remove */
267
268 ct->c_fp = NULL;
269
270 if (ct->c_end == 0L) {
271 fseek (fp, 0L, SEEK_END);
272 ct->c_end = ftell (fp);
273 }
274
275 if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
276 fclose (fp);
277 free_content (ct);
278 return NULL;
279 }
280
281 fclose (fp);
282 return ct;
283 }
284
285
286 /*
287 * Main routine for reading/parsing the headers
288 * of a message content.
289 *
290 * toplevel = 1 # we are at the top level of the message
291 * toplevel = 0 # we are inside message type or multipart type
292 * # other than multipart/digest
293 * toplevel = -1 # we are inside multipart/digest
294 * NB: on failure we will fclose(in)!
295 */
296
297 static CT
298 get_content (FILE *in, char *file, int toplevel)
299 {
300 int compnum, state;
301 char buf[NMH_BUFSIZ], name[NAMESZ];
302 char *np, *vp;
303 CT ct;
304 HF hp;
305 m_getfld_state_t gstate = 0;
306
307 /* allocate the content structure */
308 NEW0(ct);
309 ct->c_fp = in;
310 ct->c_file = add (file, NULL);
311 ct->c_begin = ftell (ct->c_fp) + 1;
312
313 /*
314 * Parse the header fields for this
315 * content into a linked list.
316 */
317 m_getfld_track_filepos (&gstate, in);
318 for (compnum = 1;;) {
319 int bufsz = sizeof buf;
320 switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
321 case FLD:
322 case FLDPLUS:
323 compnum++;
324
325 /* get copies of the buffers */
326 np = mh_xstrdup(name);
327 vp = mh_xstrdup(buf);
328
329 /* if necessary, get rest of field */
330 while (state == FLDPLUS) {
331 bufsz = sizeof buf;
332 state = m_getfld (&gstate, name, buf, &bufsz, in);
333 vp = add (buf, vp); /* add to previous value */
334 }
335
336 /* Now add the header data to the list */
337 add_header (ct, np, vp);
338
339 /* continue, to see if this isn't the last header field */
340 ct->c_begin = ftell (in) + 1;
341 continue;
342
343 case BODY:
344 if (name[0] == ':') {
345 /* Special case: no blank line between header and body. The
346 file position indicator is on the newline at the end of the
347 line, but it needs to be one prior to the beginning of the
348 line. So subtract the length of the line, bufsz, plus 1. */
349 ct->c_begin = ftell (in) - (bufsz + 1);
350 } else {
351 ct->c_begin = ftell (in) - (bufsz - 1);
352 }
353 break;
354
355 case FILEEOF:
356 ct->c_begin = ftell (in);
357 break;
358
359 case LENERR:
360 case FMTERR:
361 adios (NULL, "message format error in component #%d", compnum);
362
363 default:
364 adios (NULL, "getfld() returned %d", state);
365 }
366
367 /* break out of the loop */
368 break;
369 }
370 m_getfld_state_destroy (&gstate);
371
372 /*
373 * Read the content headers. We will parse the
374 * MIME related header fields into their various
375 * structures and set internal flags related to
376 * content type/subtype, etc.
377 */
378
379 hp = ct->c_first_hf; /* start at first header field */
380 while (hp) {
381 /* Get MIME-Version field */
382 if (!strcasecmp (hp->name, VRSN_FIELD)) {
383 int ucmp;
384 char c, *cp, *dp;
385 char *vrsn;
386
387 vrsn = add (hp->value, NULL);
388
389 /* Now, cleanup this field */
390 cp = vrsn;
391
392 while (isspace ((unsigned char) *cp))
393 cp++;
394 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
395 *dp++ = ' ';
396 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
397 if (!isspace ((unsigned char) *dp))
398 break;
399 *++dp = '\0';
400 if (debugsw)
401 fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
402
403 if (*cp == '(' &&
404 get_comment (ct->c_file, VRSN_FIELD, &cp, NULL) == NOTOK)
405 goto out;
406
407 for (dp = cp; istoken (*dp); dp++)
408 continue;
409 c = *dp;
410 *dp = '\0';
411 ucmp = !strcasecmp (cp, VRSN_VALUE);
412 *dp = c;
413 if (!ucmp) {
414 inform("message %s has unknown value for %s: field (%s), continuing...",
415 ct->c_file, VRSN_FIELD, cp);
416 }
417 if (!ct->c_vrsn) {
418 ct->c_vrsn = vrsn;
419 } else {
420 if (! suppress_multiple_mime_version_warning)
421 inform("message %s has multiple %s: fields",
422 ct->c_file, VRSN_FIELD);
423 free(vrsn);
424 }
425 }
426 else if (!strcasecmp (hp->name, TYPE_FIELD)) {
427 /* Get Content-Type field */
428 struct str2init *s2i;
429 CI ci = &ct->c_ctinfo;
430
431 /* Check if we've already seen a Content-Type header */
432 if (ct->c_ctline) {
433 inform("message %s has multiple %s: fields",
434 ct->c_file, TYPE_FIELD);
435 goto next_header;
436 }
437
438 /* Parse the Content-Type field */
439 if (get_ctinfo (hp->value, ct, 0) == NOTOK)
440 goto out;
441
442 /*
443 * Set the Init function and the internal
444 * flag for this content type.
445 */
446 for (s2i = str2cts; s2i->si_key; s2i++)
447 if (!strcasecmp (ci->ci_type, s2i->si_key))
448 break;
449 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
450 s2i++;
451 ct->c_type = s2i->si_val;
452 ct->c_ctinitfnx = s2i->si_init;
453 }
454 else if (!strcasecmp (hp->name, ENCODING_FIELD)) {
455 /* Get Content-Transfer-Encoding field */
456 char c, *cp, *dp;
457 struct str2init *s2i;
458
459 /*
460 * Check if we've already seen the
461 * Content-Transfer-Encoding field
462 */
463 if (ct->c_celine) {
464 inform("message %s has multiple %s: fields",
465 ct->c_file, ENCODING_FIELD);
466 goto next_header;
467 }
468
469 /* get copy of this field */
470 ct->c_celine = cp = add (hp->value, NULL);
471
472 while (isspace ((unsigned char) *cp))
473 cp++;
474 for (dp = cp; istoken (*dp); dp++)
475 continue;
476 c = *dp;
477 *dp = '\0';
478
479 /*
480 * Find the internal flag and Init function
481 * for this transfer encoding.
482 */
483 for (s2i = str2ces; s2i->si_key; s2i++)
484 if (!strcasecmp (cp, s2i->si_key))
485 break;
486 if (!s2i->si_key && !uprf (cp, "X-"))
487 s2i++;
488 *dp = c;
489 ct->c_encoding = s2i->si_val;
490
491 /* Call the Init function for this encoding */
492 if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
493 goto out;
494 }
495 else if (!strcasecmp (hp->name, MD5_FIELD)) {
496 /* Get Content-MD5 field */
497 char *cp, *dp, *ep;
498
499 if (!checksw)
500 goto next_header;
501
502 if (ct->c_digested) {
503 inform("message %s has multiple %s: fields",
504 ct->c_file, MD5_FIELD);
505 goto next_header;
506 }
507
508 ep = cp = add (hp->value, NULL); /* get a copy */
509
510 while (isspace ((unsigned char) *cp))
511 cp++;
512 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
513 *dp++ = ' ';
514 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
515 if (!isspace ((unsigned char) *dp))
516 break;
517 *++dp = '\0';
518 if (debugsw)
519 fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
520
521 if (*cp == '(' &&
522 get_comment (ct->c_file, MD5_FIELD, &cp, NULL) == NOTOK) {
523 free (ep);
524 goto out;
525 }
526
527 for (dp = cp; *dp && !isspace ((unsigned char) *dp); dp++)
528 continue;
529 *dp = '\0';
530
531 readDigest (ct, cp);
532 free (ep);
533 ct->c_digested++;
534 }
535 else if (!strcasecmp (hp->name, ID_FIELD)) {
536 /* Get Content-ID field */
537 ct->c_id = add (hp->value, ct->c_id);
538 }
539 else if (!strcasecmp (hp->name, DESCR_FIELD)) {
540 /* Get Content-Description field */
541 ct->c_descr = add (hp->value, ct->c_descr);
542 }
543 else if (!strcasecmp (hp->name, DISPO_FIELD)) {
544 /* Get Content-Disposition field */
545 if (get_dispo(hp->value, ct, 0) == NOTOK)
546 goto out;
547 }
548
549 next_header:
550 hp = hp->next; /* next header field */
551 }
552
553 /*
554 * Check if we saw a Content-Type field.
555 * If not, then assign a default value for
556 * it, and the Init function.
557 */
558 if (!ct->c_ctline) {
559 /*
560 * If we are inside a multipart/digest message,
561 * so default type is message/rfc822
562 */
563 if (toplevel < 0) {
564 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
565 goto out;
566 ct->c_type = CT_MESSAGE;
567 ct->c_ctinitfnx = InitMessage;
568 } else {
569 /*
570 * Else default type is text/plain
571 */
572 if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
573 goto out;
574 ct->c_type = CT_TEXT;
575 ct->c_ctinitfnx = InitText;
576 }
577 }
578
579 /* Use default Transfer-Encoding, if necessary */
580 if (!ct->c_celine) {
581 ct->c_encoding = CE_7BIT;
582 Init7Bit (ct);
583 }
584
585 return ct;
586
587 out:
588 free_content (ct);
589 return NULL;
590 }
591
592
593 /*
594 * small routine to add header field to list
595 */
596
597 int
598 add_header (CT ct, char *name, char *value)
599 {
600 HF hp;
601
602 /* allocate header field structure */
603 NEW(hp);
604
605 /* link data into header structure */
606 hp->name = name;
607 hp->value = value;
608 hp->next = NULL;
609
610 /* link header structure into the list */
611 if (ct->c_first_hf == NULL) {
612 ct->c_first_hf = hp; /* this is the first */
613 ct->c_last_hf = hp;
614 } else {
615 ct->c_last_hf->next = hp; /* add it to the end */
616 ct->c_last_hf = hp;
617 }
618
619 return 0;
620 }
621
622
623 /*
624 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
625 * directives. Fills in the information of the CTinfo structure.
626 */
627 int
628 get_ctinfo (char *cp, CT ct, int magic)
629 {
630 char *dp;
631 char c;
632 CI ci;
633 int status;
634
635 ci = &ct->c_ctinfo;
636
637 /* store copy of Content-Type line */
638 cp = ct->c_ctline = add (cp, NULL);
639
640 while (isspace ((unsigned char) *cp)) /* trim leading spaces */
641 cp++;
642
643 /* change newlines to spaces */
644 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
645 *dp++ = ' ';
646
647 /* trim trailing spaces */
648 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
649 if (!isspace ((unsigned char) *dp))
650 break;
651 *++dp = '\0';
652
653 if (debugsw)
654 fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
655
656 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
657 &ci->ci_comment) == NOTOK)
658 return NOTOK;
659
660 for (dp = cp; istoken (*dp); dp++)
661 continue;
662 c = *dp;
663 *dp = '\0';
664 ci->ci_type = mh_xstrdup(cp); /* store content type */
665 *dp = c;
666 cp = dp;
667
668 if (!*ci->ci_type) {
669 inform("invalid %s: field in message %s (empty type)",
670 TYPE_FIELD, ct->c_file);
671 return NOTOK;
672 }
673 to_lower(ci->ci_type);
674
675 while (isspace ((unsigned char) *cp))
676 cp++;
677
678 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
679 &ci->ci_comment) == NOTOK)
680 return NOTOK;
681
682 if (*cp != '/') {
683 if (!magic)
684 ci->ci_subtype = mh_xstrdup("");
685 goto magic_skip;
686 }
687
688 cp++;
689 while (isspace ((unsigned char) *cp))
690 cp++;
691
692 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
693 &ci->ci_comment) == NOTOK)
694 return NOTOK;
695
696 for (dp = cp; istoken (*dp); dp++)
697 continue;
698 c = *dp;
699 *dp = '\0';
700 ci->ci_subtype = mh_xstrdup(cp); /* store the content subtype */
701 *dp = c;
702 cp = dp;
703
704 if (!*ci->ci_subtype) {
705 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
706 TYPE_FIELD, ct->c_file, ci->ci_type);
707 return NOTOK;
708 }
709 to_lower(ci->ci_subtype);
710
711 magic_skip:
712 while (isspace ((unsigned char) *cp))
713 cp++;
714
715 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
716 &ci->ci_comment) == NOTOK)
717 return NOTOK;
718
719 if ((status = parse_header_attrs (ct->c_file, TYPE_FIELD, &cp,
720 &ci->ci_first_pm, &ci->ci_last_pm,
721 &ci->ci_comment)) != OK) {
722 return status == NOTOK ? NOTOK : OK;
723 }
724
725 /*
726 * Get any <Content-Id> given in buffer
727 */
728 if (magic && *cp == '<') {
729 mh_xfree(ct->c_id);
730 ct->c_id = NULL;
731 if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
732 inform("invalid ID in message %s", ct->c_file);
733 return NOTOK;
734 }
735 c = *dp;
736 *dp = '\0';
737 if (*ct->c_id)
738 ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
739 else
740 ct->c_id = NULL;
741 *dp++ = c;
742 cp = dp;
743
744 while (isspace ((unsigned char) *cp))
745 cp++;
746 }
747
748 /*
749 * Get any [Content-Description] given in buffer.
750 */
751 if (magic && *cp == '[') {
752 ct->c_descr = ++cp;
753 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
754 if (*dp == ']')
755 break;
756 if (dp < cp) {
757 inform("invalid description in message %s", ct->c_file);
758 ct->c_descr = NULL;
759 return NOTOK;
760 }
761
762 c = *dp;
763 *dp = '\0';
764 if (*ct->c_descr)
765 ct->c_descr = concat (ct->c_descr, "\n", NULL);
766 else
767 ct->c_descr = NULL;
768 *dp++ = c;
769 cp = dp;
770
771 while (isspace ((unsigned char) *cp))
772 cp++;
773 }
774
775 /*
776 * Get any {Content-Disposition} given in buffer.
777 */
778 if (magic && *cp == '{') {
779 ++cp;
780 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
781 if (*dp == '}')
782 break;
783 if (dp < cp) {
784 inform("invalid disposition in message %s", ct->c_file);
785 ct->c_dispo = NULL;
786 return NOTOK;
787 }
788
789 c = *dp;
790 *dp = '\0';
791
792 if (get_dispo(cp, ct, 1) != OK)
793 return NOTOK;
794
795 *dp++ = c;
796 cp = dp;
797
798 while (isspace ((unsigned char) *cp))
799 cp++;
800 }
801
802 /*
803 * Get any extension directives (right now just the content transfer
804 * encoding, but maybe others) that we care about.
805 */
806
807 if (magic && *cp == '*') {
808 /*
809 * See if it's a CTE we match on
810 */
811 struct k2v *kv;
812
813 dp = ++cp;
814 while (*cp != '\0' && ! isspace((unsigned char) *cp))
815 cp++;
816
817 if (dp == cp) {
818 inform("invalid null transfer encoding specification");
819 return NOTOK;
820 }
821
822 if (*cp != '\0')
823 *cp++ = '\0';
824
825 ct->c_reqencoding = CE_UNKNOWN;
826
827 for (kv = EncodingType; kv->kv_key; kv++) {
828 if (strcasecmp(kv->kv_key, dp) == 0) {
829 ct->c_reqencoding = kv->kv_value;
830 break;
831 }
832 }
833
834 if (ct->c_reqencoding == CE_UNKNOWN) {
835 inform("invalid CTE specification: \"%s\"", dp);
836 return NOTOK;
837 }
838
839 while (isspace ((unsigned char) *cp))
840 cp++;
841 }
842
843 /*
844 * Check if anything is left over
845 */
846 if (*cp) {
847 if (magic) {
848 ci->ci_magic = mh_xstrdup(cp);
849
850 /* If there is a Content-Disposition header and it doesn't
851 have a *filename=, extract it from the magic contents.
852 The r1bindex call skips any leading directory
853 components. */
854 if (ct->c_dispo_type &&
855 !get_param(ct->c_dispo_first, "filename", '_', 1)) {
856 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename",
857 r1bindex(ci->ci_magic, '/'), 0);
858 }
859 }
860 else
861 inform("extraneous information in message %s's %s: field\n%*s(%s)",
862 ct->c_file, TYPE_FIELD, strlen(invo_name) + 2, "", cp);
863 }
864
865 return OK;
866 }
867
868
869 /*
870 * Parse out a Content-Disposition header. A lot of this is cribbed from
871 * get_ctinfo().
872 */
873 static int
874 get_dispo (char *cp, CT ct, int buildflag)
875 {
876 char *dp, *dispoheader;
877 char c;
878 int status;
879
880 /*
881 * Save the whole copy of the Content-Disposition header, unless we're
882 * processing a mhbuild directive. A NULL c_dispo will be a flag to
883 * mhbuild that the disposition header needs to be generated at that
884 * time.
885 */
886
887 dispoheader = cp = add(cp, NULL);
888
889 while (isspace ((unsigned char) *cp)) /* trim leading spaces */
890 cp++;
891
892 /* change newlines to spaces */
893 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
894 *dp++ = ' ';
895
896 /* trim trailing spaces */
897 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
898 if (!isspace ((unsigned char) *dp))
899 break;
900 *++dp = '\0';
901
902 if (debugsw)
903 fprintf (stderr, "%s: %s\n", DISPO_FIELD, cp);
904
905 if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) ==
906 NOTOK) {
907 free(dispoheader);
908 return NOTOK;
909 }
910
911 for (dp = cp; istoken (*dp); dp++)
912 continue;
913 c = *dp;
914 *dp = '\0';
915 ct->c_dispo_type = mh_xstrdup(cp); /* store disposition type */
916 *dp = c;
917 cp = dp;
918
919 if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) == NOTOK)
920 return NOTOK;
921
922 if ((status = parse_header_attrs (ct->c_file, DISPO_FIELD, &cp,
923 &ct->c_dispo_first, &ct->c_dispo_last,
924 NULL)) != OK) {
925 if (status == NOTOK) {
926 free(dispoheader);
927 return NOTOK;
928 }
929 } else if (*cp) {
930 inform("extraneous information in message %s's %s: field\n%*s(%s)",
931 ct->c_file, DISPO_FIELD, strlen(invo_name) + 2, "", cp);
932 }
933
934 if (buildflag)
935 free(dispoheader);
936 else
937 ct->c_dispo = dispoheader;
938
939 return OK;
940 }
941
942
943 static int
944 get_comment (const char *filename, const char *fieldname, char **ap,
945 char **commentp)
946 {
947 int i;
948 char *bp, *cp;
949 char c, buffer[BUFSIZ], *dp;
950
951 cp = *ap;
952 bp = buffer;
953 cp++;
954
955 for (i = 0;;) {
956 switch (c = *cp++) {
957 case '\0':
958 invalid:
959 inform("invalid comment in message %s's %s: field",
960 filename, fieldname);
961 return NOTOK;
962
963 case '\\':
964 *bp++ = c;
965 if ((c = *cp++) == '\0')
966 goto invalid;
967 *bp++ = c;
968 continue;
969
970 case '(':
971 i++;
972 /* FALLTHRU */
973 default:
974 *bp++ = c;
975 continue;
976
977 case ')':
978 if (--i < 0)
979 break;
980 *bp++ = c;
981 continue;
982 }
983 break;
984 }
985 *bp = '\0';
986
987 if (commentp) {
988 if ((dp = *commentp)) {
989 *commentp = concat (dp, " ", buffer, NULL);
990 free (dp);
991 } else {
992 *commentp = mh_xstrdup(buffer);
993 }
994 }
995
996 while (isspace ((unsigned char) *cp))
997 cp++;
998
999 *ap = cp;
1000 return OK;
1001 }
1002
1003
1004 /*
1005 * CONTENTS
1006 *
1007 * Handles content types audio, image, and video.
1008 * There's not much to do right here.
1009 */
1010
1011 static int
1012 InitGeneric (CT ct)
1013 {
1014 NMH_UNUSED (ct);
1015
1016 return OK; /* not much to do here */
1017 }
1018
1019
1020 /*
1021 * TEXT
1022 */
1023
1024 static int
1025 InitText (CT ct)
1026 {
1027 char buffer[BUFSIZ];
1028 char *chset = NULL;
1029 char *cp;
1030 PM pm;
1031 struct text *t;
1032 CI ci = &ct->c_ctinfo;
1033
1034 /* check for missing subtype */
1035 if (!*ci->ci_subtype)
1036 ci->ci_subtype = add ("plain", ci->ci_subtype);
1037
1038 /* match subtype */
1039 ct->c_subtype = ct_str_subtype (CT_TEXT, ci->ci_subtype);
1040
1041 /* allocate text character set structure */
1042 NEW0(t);
1043 ct->c_ctparams = (void *) t;
1044
1045 /* scan for charset parameter */
1046 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next)
1047 if (!strcasecmp (pm->pm_name, "charset"))
1048 break;
1049
1050 /* check if content specified a character set */
1051 if (pm) {
1052 chset = pm->pm_value;
1053 t->tx_charset = CHARSET_SPECIFIED;
1054 } else {
1055 t->tx_charset = CHARSET_UNSPECIFIED;
1056 }
1057
1058 /*
1059 * If we can not handle character set natively,
1060 * then check profile for string to modify the
1061 * terminal or display method.
1062 *
1063 * termproc is for mhshow, though mhlist -debug prints it, too.
1064 */
1065 if (chset != NULL && !check_charset (chset, strlen (chset))) {
1066 snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
1067 if ((cp = context_find (buffer)))
1068 ct->c_termproc = mh_xstrdup(cp);
1069 }
1070
1071 return OK;
1072 }
1073
1074
1075 /*
1076 * MULTIPART
1077 */
1078
1079 static int
1080 InitMultiPart (CT ct)
1081 {
1082 int inout;
1083 long last, pos;
1084 char *cp, *dp;
1085 PM pm;
1086 char *bp;
1087 char *bufp = NULL;
1088 size_t buflen;
1089 ssize_t gotlen;
1090 struct multipart *m;
1091 struct part *part, **next;
1092 CI ci = &ct->c_ctinfo;
1093 CT p;
1094 FILE *fp;
1095
1096 /*
1097 * The encoding for multipart messages must be either
1098 * 7bit, 8bit, or binary (per RFC 2045).
1099 */
1100 if (! skip_mp_cte_check && ct->c_encoding != CE_7BIT &&
1101 ct->c_encoding != CE_8BIT && ct->c_encoding != CE_BINARY) {
1102 /* Copy the Content-Transfer-Encoding header field body so we can
1103 remove any trailing whitespace and leading blanks from it. */
1104 char *cte = mh_xstrdup(ct->c_celine ? ct->c_celine : "(null)");
1105
1106 bp = cte + strlen (cte) - 1;
1107 while (bp >= cte && isspace ((unsigned char) *bp)) *bp-- = '\0';
1108 for (bp = cte; *bp && isblank ((unsigned char) *bp); ++bp) continue;
1109
1110 inform("\"%s/%s\" type in message %s must be encoded in\n"
1111 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1112 "mhfixmsg -fixcte can fix it, or\n"
1113 "manually edit the file and change the \"%s\"\n"
1114 "Content-Transfer-Encoding to one of those. For now, continuing...",
1115 ci->ci_type, ci->ci_subtype, ct->c_file, bp);
1116 free (cte);
1117
1118 return NOTOK;
1119 }
1120
1121 /* match subtype */
1122 ct->c_subtype = ct_str_subtype (CT_MULTIPART, ci->ci_subtype);
1123
1124 /*
1125 * Check for "boundary" parameter, which is
1126 * required for multipart messages.
1127 */
1128 bp = 0;
1129 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1130 if (!strcasecmp (pm->pm_name, "boundary")) {
1131 bp = pm->pm_value;
1132 break;
1133 }
1134 }
1135
1136 /* complain if boundary parameter is missing */
1137 if (!pm) {
1138 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1139 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1140 return NOTOK;
1141 }
1142
1143 /* allocate primary structure for multipart info */
1144 NEW0(m);
1145 ct->c_ctparams = (void *) m;
1146
1147 /* check if boundary parameter contains only whitespace characters */
1148 for (cp = bp; isspace ((unsigned char) *cp); cp++)
1149 continue;
1150 if (!*cp) {
1151 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1152 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1153 return NOTOK;
1154 }
1155
1156 /* remove trailing whitespace from boundary parameter */
1157 for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1158 if (!isspace ((unsigned char) *dp))
1159 break;
1160 *++dp = '\0';
1161
1162 /* record boundary separators */
1163 m->mp_start = concat (bp, "\n", NULL);
1164 m->mp_stop = concat (bp, "--\n", NULL);
1165
1166 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1167 advise (ct->c_file, "unable to open for reading");
1168 return NOTOK;
1169 }
1170
1171 fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1172 last = ct->c_end;
1173 next = &m->mp_parts;
1174 part = NULL;
1175 inout = 1;
1176
1177 while ((gotlen = getline(&bufp, &buflen, fp)) != -1) {
1178 if (pos > last)
1179 break;
1180
1181 pos += gotlen;
1182 if (bufp[0] != '-' || bufp[1] != '-')
1183 continue;
1184 if (inout) {
1185 if (strcmp (bufp + 2, m->mp_start))
1186 continue;
1187 next_part:
1188 NEW0(part);
1189 *next = part;
1190 next = &part->mp_next;
1191
1192 if (!(p = get_content (fp, ct->c_file,
1193 ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1194 free(bufp);
1195 ct->c_fp = NULL;
1196 return NOTOK;
1197 }
1198 p->c_fp = NULL;
1199 part->mp_part = p;
1200 pos = p->c_begin;
1201 fseek (fp, pos, SEEK_SET);
1202 inout = 0;
1203 } else {
1204 if (strcmp (bufp + 2, m->mp_start) == 0) {
1205 inout = 1;
1206 end_part:
1207 p = part->mp_part;
1208 p->c_end = ftell(fp) - (gotlen + 1);
1209 if (p->c_end < p->c_begin)
1210 p->c_begin = p->c_end;
1211 if (inout)
1212 goto next_part;
1213 goto last_part;
1214 }
1215 if (strcmp (bufp + 2, m->mp_stop) == 0)
1216 goto end_part;
1217 }
1218 }
1219
1220 if (! suppress_bogus_mp_content_warning) {
1221 inform("bogus multipart content in message %s", ct->c_file);
1222 }
1223 bogus_mp_content = 1;
1224
1225 if (!inout && part) {
1226 p = part->mp_part;
1227 p->c_end = ct->c_end;
1228
1229 if (p->c_begin >= p->c_end) {
1230 for (next = &m->mp_parts; *next != part;
1231 next = &((*next)->mp_next))
1232 continue;
1233 *next = NULL;
1234 free_content (p);
1235 free(part);
1236 }
1237 }
1238
1239 last_part:
1240 /* reverse the order of the parts for multipart/alternative */
1241 if (ct->c_subtype == MULTI_ALTERNATE) {
1242 reverse_parts (ct);
1243 prefer_parts (ct);
1244 }
1245
1246 /*
1247 * label all subparts with part number, and
1248 * then initialize the content of the subpart.
1249 */
1250 {
1251 int partnum;
1252 char *pp;
1253 char partnam[BUFSIZ];
1254
1255 if (ct->c_partno) {
1256 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1257 pp = partnam + strlen (partnam);
1258 } else {
1259 pp = partnam;
1260 }
1261
1262 for (part = m->mp_parts, partnum = 1; part;
1263 part = part->mp_next, partnum++) {
1264 p = part->mp_part;
1265
1266 sprintf (pp, "%d", partnum);
1267 p->c_partno = mh_xstrdup(partnam);
1268
1269 /* initialize the content of the subparts */
1270 if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1271 free(bufp);
1272 fclose (ct->c_fp);
1273 ct->c_fp = NULL;
1274 return NOTOK;
1275 }
1276 }
1277 }
1278
1279 get_leftover_mp_content (ct, 1);
1280 get_leftover_mp_content (ct, 0);
1281
1282 free(bufp);
1283 fclose (ct->c_fp);
1284 ct->c_fp = NULL;
1285 return OK;
1286 }
1287
1288
1289 /*
1290 * reverse the order of the parts of a multipart/alternative,
1291 * presumably to put the "most favored" alternative first, for
1292 * ease of choosing/displaying it later on. from a mail message on
1293 * nmh-workers, from kenh:
1294 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1295 * see code in mhn that did the same thing... According to the RCS
1296 * logs, that code was around from the initial checkin of mhn.c by
1297 * John Romine in 1992, which is as far back as we have."
1298 */
1299 static void
1300 reverse_parts (CT ct)
1301 {
1302 struct multipart *m = (struct multipart *) ct->c_ctparams;
1303 struct part *part;
1304 struct part *next;
1305
1306 /* Reverse the order of its parts by walking the mp_parts list
1307 and pushing each node to the front. */
1308 for (part = m->mp_parts, m->mp_parts = NULL; part; part = next) {
1309 next = part->mp_next;
1310 part->mp_next = m->mp_parts;
1311 m->mp_parts = part;
1312 }
1313 }
1314
1315 static void
1316 move_preferred_part (CT ct, char *type, char *subtype)
1317 {
1318 struct multipart *m = (struct multipart *) ct->c_ctparams;
1319 struct part *part, *prev, *head, *nhead, *ntail;
1320 struct part h, n;
1321 CI ci;
1322
1323 /* move the matching part(s) to the head of the list: walk the
1324 * list of parts, move matching parts to a new list (maintaining
1325 * their order), and finally, concatenate the old list onto the
1326 * new.
1327 */
1328
1329 head = &h;
1330 nhead = &n;
1331
1332 head->mp_next = m->mp_parts;
1333 nhead->mp_next = NULL;
1334 ntail = nhead;
1335
1336 prev = head;
1337 part = head->mp_next;
1338 while (part != NULL) {
1339 ci = &part->mp_part->c_ctinfo;
1340 if (!strcasecmp(ci->ci_type, type) &&
1341 (!subtype || !strcasecmp(ci->ci_subtype, subtype))) {
1342 prev->mp_next = part->mp_next;
1343 part->mp_next = NULL;
1344 ntail->mp_next = part;
1345 ntail = part;
1346 part = prev->mp_next;
1347 } else {
1348 prev = part;
1349 part = prev->mp_next;
1350 }
1351 }
1352 ntail->mp_next = head->mp_next;
1353 m->mp_parts = nhead->mp_next;
1354
1355 }
1356
1357 /*
1358 * move parts that match the user's preferences (-prefer) to the head
1359 * of the line. process preferences in reverse so first one given
1360 * ends up first in line
1361 */
1362 static void
1363 prefer_parts(CT ct)
1364 {
1365 int i;
1366 for (i = npreferred-1; i >= 0; i--)
1367 move_preferred_part(ct, preferred_types[i], preferred_subtypes[i]);
1368 }
1369
1370
1371
1372 /* parse_mime() arranges alternates in reverse (priority) order. This
1373 function can be used to reverse them back. This will put, for
1374 example, a text/plain part before a text/html part in a
1375 multipart/alternative part, for example, where it belongs. */
1376 void
1377 reverse_alternative_parts (CT ct) {
1378 if (ct->c_type == CT_MULTIPART) {
1379 struct multipart *m = (struct multipart *) ct->c_ctparams;
1380 struct part *part;
1381
1382 if (ct->c_subtype == MULTI_ALTERNATE) {
1383 reverse_parts (ct);
1384 }
1385
1386 /* And call recursively on each part of a multipart. */
1387 for (part = m->mp_parts; part; part = part->mp_next) {
1388 reverse_alternative_parts (part->mp_part);
1389 }
1390 }
1391 }
1392
1393
1394 /*
1395 * MESSAGE
1396 */
1397
1398 static int
1399 InitMessage (CT ct)
1400 {
1401 CI ci = &ct->c_ctinfo;
1402
1403 if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1404 inform("\"%s/%s\" type in message %s should be encoded in "
1405 "7bit or 8bit, continuing...", ci->ci_type, ci->ci_subtype,
1406 ct->c_file);
1407 return NOTOK;
1408 }
1409
1410 /* check for missing subtype */
1411 if (!*ci->ci_subtype)
1412 ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1413
1414 /* match subtype */
1415 ct->c_subtype = ct_str_subtype (CT_MESSAGE, ci->ci_subtype);
1416
1417 switch (ct->c_subtype) {
1418 case MESSAGE_RFC822:
1419 break;
1420
1421 case MESSAGE_PARTIAL:
1422 {
1423 PM pm;
1424 struct partial *p;
1425
1426 NEW0(p);
1427 ct->c_ctparams = (void *) p;
1428
1429 /* scan for parameters "id", "number", and "total" */
1430 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1431 if (!strcasecmp (pm->pm_name, "id")) {
1432 p->pm_partid = add (pm->pm_value, NULL);
1433 continue;
1434 }
1435 if (!strcasecmp (pm->pm_name, "number")) {
1436 if (sscanf (pm->pm_value, "%d", &p->pm_partno) != 1
1437 || p->pm_partno < 1) {
1438 invalid_param:
1439 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1440 pm->pm_name, ci->ci_type, ci->ci_subtype,
1441 ct->c_file, TYPE_FIELD);
1442 return NOTOK;
1443 }
1444 continue;
1445 }
1446 if (!strcasecmp (pm->pm_name, "total")) {
1447 if (sscanf (pm->pm_value, "%d", &p->pm_maxno) != 1
1448 || p->pm_maxno < 1)
1449 goto invalid_param;
1450 continue;
1451 }
1452 }
1453
1454 if (!p->pm_partid
1455 || !p->pm_partno
1456 || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1457 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1458 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1459 return NOTOK;
1460 }
1461 }
1462 break;
1463
1464 case MESSAGE_EXTERNAL:
1465 {
1466 int exresult;
1467 struct exbody *e;
1468 CT p;
1469 FILE *fp;
1470
1471 NEW0(e);
1472 ct->c_ctparams = (void *) e;
1473
1474 if (!ct->c_fp
1475 && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1476 advise (ct->c_file, "unable to open for reading");
1477 return NOTOK;
1478 }
1479
1480 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1481
1482 if (!(p = get_content (fp, ct->c_file, 0))) {
1483 ct->c_fp = NULL;
1484 return NOTOK;
1485 }
1486
1487 e->eb_parent = ct;
1488 e->eb_content = p;
1489 p->c_ctexbody = e;
1490 p->c_ceopenfnx = NULL;
1491 if ((exresult = params_external (ct, 0)) != NOTOK
1492 && p->c_ceopenfnx == openMail) {
1493 int cc, size;
1494 char *bp;
1495
1496 if ((size = ct->c_end - p->c_begin) <= 0) {
1497 if (!e->eb_subject)
1498 content_error (NULL, ct,
1499 "empty body for access-type=mail-server");
1500 goto no_body;
1501 }
1502
1503 e->eb_body = bp = mh_xmalloc ((unsigned) size);
1504 fseek (p->c_fp, p->c_begin, SEEK_SET);
1505 while (size > 0)
1506 switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1507 case NOTOK:
1508 adios ("failed", "fread");
1509
1510 case OK:
1511 adios (NULL, "unexpected EOF from fread");
1512
1513 default:
1514 bp += cc, size -= cc;
1515 break;
1516 }
1517 *bp = 0;
1518 }
1519 no_body:
1520 p->c_fp = NULL;
1521 p->c_end = p->c_begin;
1522
1523 fclose (ct->c_fp);
1524 ct->c_fp = NULL;
1525
1526 if (exresult == NOTOK)
1527 return NOTOK;
1528 if (e->eb_flags == NOTOK)
1529 return OK;
1530
1531 switch (p->c_type) {
1532 case CT_MULTIPART:
1533 break;
1534
1535 case CT_MESSAGE:
1536 if (p->c_subtype != MESSAGE_RFC822)
1537 break;
1538 /* FALLTHRU */
1539 default:
1540 e->eb_partno = ct->c_partno;
1541 if (p->c_ctinitfnx)
1542 (*p->c_ctinitfnx) (p);
1543 break;
1544 }
1545 }
1546 break;
1547
1548 default:
1549 break;
1550 }
1551
1552 return OK;
1553 }
1554
1555
1556 int
1557 params_external (CT ct, int composing)
1558 {
1559 PM pm;
1560 struct exbody *e = (struct exbody *) ct->c_ctparams;
1561 CI ci = &ct->c_ctinfo;
1562
1563 ct->c_ceopenfnx = NULL;
1564 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1565 if (!strcasecmp (pm->pm_name, "access-type")) {
1566 struct str2init *s2i;
1567 CT p = e->eb_content;
1568
1569 for (s2i = str2methods; s2i->si_key; s2i++)
1570 if (!strcasecmp (pm->pm_value, s2i->si_key))
1571 break;
1572 if (!s2i->si_key) {
1573 e->eb_access = pm->pm_value;
1574 e->eb_flags = NOTOK;
1575 p->c_encoding = CE_EXTERNAL;
1576 continue;
1577 }
1578 e->eb_access = s2i->si_key;
1579 e->eb_flags = s2i->si_val;
1580 p->c_encoding = CE_EXTERNAL;
1581
1582 /* Call the Init function for this external type */
1583 if ((*s2i->si_init)(p) == NOTOK)
1584 return NOTOK;
1585 continue;
1586 }
1587 if (!strcasecmp (pm->pm_name, "name")) {
1588 e->eb_name = pm->pm_value;
1589 continue;
1590 }
1591 if (!strcasecmp (pm->pm_name, "permission")) {
1592 e->eb_permission = pm->pm_value;
1593 continue;
1594 }
1595 if (!strcasecmp (pm->pm_name, "site")) {
1596 e->eb_site = pm->pm_value;
1597 continue;
1598 }
1599 if (!strcasecmp (pm->pm_name, "directory")) {
1600 e->eb_dir = pm->pm_value;
1601 continue;
1602 }
1603 if (!strcasecmp (pm->pm_name, "mode")) {
1604 e->eb_mode = pm->pm_value;
1605 continue;
1606 }
1607 if (!strcasecmp (pm->pm_name, "size")) {
1608 sscanf (pm->pm_value, "%lu", &e->eb_size);
1609 continue;
1610 }
1611 if (!strcasecmp (pm->pm_name, "server")) {
1612 e->eb_server = pm->pm_value;
1613 continue;
1614 }
1615 if (!strcasecmp (pm->pm_name, "subject")) {
1616 e->eb_subject = pm->pm_value;
1617 continue;
1618 }
1619 if (!strcasecmp (pm->pm_name, "url")) {
1620 /*
1621 * According to RFC 2017, we have to remove all whitespace from
1622 * the URL
1623 */
1624
1625 char *u, *p = pm->pm_value;
1626 e->eb_url = u = mh_xmalloc(strlen(pm->pm_value) + 1);
1627
1628 for (; *p != '\0'; p++) {
1629 if (! isspace((unsigned char) *p))
1630 *u++ = *p;
1631 }
1632
1633 *u = '\0';
1634 continue;
1635 }
1636 if (composing && !strcasecmp (pm->pm_name, "body")) {
1637 e->eb_body = getcpy (pm->pm_value);
1638 continue;
1639 }
1640 }
1641
1642 if (!e->eb_access) {
1643 inform("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 = mh_xstrdup(*file);
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 = mh_xstrdup(tempfile);
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(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 = mh_xstrdup(*file);
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 = mh_xstrdup(tempfile);
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';
2018 ep++;
2019
2020 for (; cp < ep; cp++) {
2021 if (quoted > 0) {
2022 /* in an escape sequence */
2023 if (quoted == 1) {
2024 /* at byte 1 of an escape sequence */
2025 mask = hex2nib[((unsigned char) *cp) & 0x7f];
2026 /* next is byte 2 */
2027 quoted = 2;
2028 } else {
2029 /* at byte 2 of an escape sequence */
2030 mask <<= 4;
2031 mask |= hex2nib[((unsigned char) *cp) & 0x7f];
2032 putc (mask, ce->ce_fp);
2033 if (digested)
2034 MD5Update (&mdContext, &mask, 1);
2035 if (ferror (ce->ce_fp)) {
2036 content_error (ce->ce_file, ct, "error writing to");
2037 goto clean_up;
2038 }
2039 /* finished escape sequence; next may be literal or a new
2040 * escape sequence */
2041 quoted = 0;
2042 }
2043 /* on to next byte */
2044 continue;
2045 }
2046
2047 /* not in an escape sequence */
2048 if (*cp == '=') {
2049 /* starting an escape sequence, or invalid '='? */
2050 if (cp + 1 < ep && cp[1] == '\n') {
2051 /* "=\n" soft line break, eat the \n */
2052 cp++;
2053 continue;
2054 }
2055 if (cp + 1 >= ep || cp + 2 >= ep) {
2056 /* We don't have 2 bytes left, so this is an invalid
2057 * escape sequence; just show the raw bytes (below). */
2058 } else if (isxdigit ((unsigned char) cp[1]) &&
2059 isxdigit ((unsigned char) cp[2])) {
2060 /* Next 2 bytes are hex digits, making this a valid escape
2061 * sequence; let's decode it (above). */
2062 quoted = 1;
2063 continue;
2064 }
2065 /* One or both of the next 2 is out of range, making this
2066 * an invalid escape sequence; just show the raw bytes
2067 * (below). */
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 = mh_xstrdup(*file);
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 = mh_xstrdup(tempfile);
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 = mh_xstrdup(*file);
2540 else if (caching)
2541 ce->ce_file = mh_xstrdup(cachefile);
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 = mh_xstrdup(tempfile);
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 inform("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 = mh_xstrdup(tempfile);
2738 ce->ce_unlink = 1;
2739 } else {
2740 ce->ce_file = mh_xstrdup(*file);
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 mh_xfree(ct->c_showproc);
2752 ct->c_showproc = mh_xstrdup("true");
2753
2754 fseek (ce->ce_fp, 0L, SEEK_SET);
2755 *file = ce->ce_file;
2756 return fileno (ce->ce_fp);
2757 }
2758
2759
2760 /*
2761 * URL
2762 */
2763
2764 static int
2765 InitURL (CT ct)
2766 {
2767 return init_encoding (ct, openURL);
2768 }
2769
2770
2771 static int
2772 openURL (CT ct, char **file)
2773 {
2774 struct exbody *e = ct->c_ctexbody;
2775 CE ce = &ct->c_cefile;
2776 char *urlprog, *program;
2777 char buffer[BUFSIZ], cachefile[BUFSIZ];
2778 int fd, caching, cachetype;
2779 struct msgs_array args = { 0, 0, NULL};
2780 pid_t child_id;
2781
2782 if ((urlprog = context_find(nmhaccessurl)) && *urlprog == '\0')
2783 urlprog = NULL;
2784
2785 if (! urlprog) {
2786 content_error(NULL, ct, "No entry for nmh-access-url in profile");
2787 return NOTOK;
2788 }
2789
2790 switch (openExternal(e->eb_parent, e->eb_content, ce, file, &fd)) {
2791 case NOTOK:
2792 return NOTOK;
2793
2794 case OK:
2795 break;
2796
2797 case DONE:
2798 return fd;
2799 }
2800
2801 if (!e->eb_url) {
2802 content_error(NULL, ct, "missing url parameter");
2803 return NOTOK;
2804 }
2805
2806 ce->ce_unlink = (*file == NULL);
2807 caching = 0;
2808 cachefile[0] = '\0';
2809
2810 if (find_cache(NULL, wcachesw, &cachetype, e->eb_content->c_id,
2811 cachefile, sizeof(cachefile)) != NOTOK) {
2812 if (*file == NULL) {
2813 ce->ce_unlink = 0;
2814 caching = 1;
2815 }
2816 }
2817
2818 if (*file)
2819 ce->ce_file = mh_xstrdup(*file);
2820 else if (caching)
2821 ce->ce_file = mh_xstrdup(cachefile);
2822 else {
2823 char *tempfile;
2824 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2825 adios(NULL, "unable to create temporary file in %s",
2826 get_temp_dir());
2827 }
2828 ce->ce_file = mh_xstrdup(tempfile);
2829 }
2830
2831 if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
2832 content_error(ce->ce_file, ct, "unable to fopen for read/writing");
2833 return NOTOK;
2834 }
2835
2836 switch (child_id = fork()) {
2837 case NOTOK:
2838 adios ("fork", "unable to");
2839 /* NOTREACHED */
2840
2841 case OK:
2842 argsplit_msgarg(&args, urlprog, &program);
2843 app_msgarg(&args, e->eb_url);
2844 app_msgarg(&args, NULL);
2845 dup2(fileno(ce->ce_fp), 1);
2846 close(fileno(ce->ce_fp));
2847 execvp(program, args.msgs);
2848 fprintf(stderr, "Unable to exec ");
2849 perror(program);
2850 _exit(-1);
2851 /* NOTREACHED */
2852
2853 default:
2854 if (pidXwait(child_id, NULL)) {
2855 ce->ce_unlink = 1;
2856 return NOTOK;
2857 }
2858 }
2859
2860 if (cachefile[0]) {
2861 if (caching)
2862 chmod(cachefile, cachetype ? m_gmprot() : 0444);
2863 else {
2864 int mask;
2865 FILE *fp;
2866
2867 mask = umask (cachetype ? ~m_gmprot() : 0222);
2868 if ((fp = fopen(cachefile, "w"))) {
2869 int cc;
2870 FILE *gp = ce->ce_fp;
2871
2872 fseeko(gp, 0, SEEK_SET);
2873
2874 while ((cc = fread(buffer, sizeof(*buffer),
2875 sizeof(buffer), gp)) > 0)
2876 if ((int) fwrite(buffer, sizeof(*buffer), cc, fp) < cc) {
2877 advise ("openURL", "fwrite");
2878 }
2879
2880 fflush(fp);
2881
2882 if (ferror(gp)) {
2883 admonish(ce->ce_file, "error reading");
2884 (void) m_unlink (cachefile);
2885 }
2886 }
2887 umask(mask);
2888 }
2889 }
2890
2891 fseeko(ce->ce_fp, 0, SEEK_SET);
2892 *file = ce->ce_file;
2893 return fd;
2894 }
2895
2896
2897 /*
2898 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2899 * has to be base64 decoded.
2900 */
2901 static int
2902 readDigest (CT ct, char *cp)
2903 {
2904 unsigned char *digest;
2905
2906 size_t len;
2907 if (decodeBase64 (cp, &digest, &len, 0, NULL) == OK) {
2908 const size_t maxlen = sizeof ct->c_digest / sizeof ct->c_digest[0];
2909
2910 if (strlen ((char *) digest) <= maxlen) {
2911 memcpy (ct->c_digest, digest, maxlen);
2912
2913 if (debugsw) {
2914 size_t i;
2915
2916 fprintf (stderr, "MD5 digest=");
2917 for (i = 0; i < maxlen; ++i) {
2918 fprintf (stderr, "%02x", ct->c_digest[i] & 0xff);
2919 }
2920 fprintf (stderr, "\n");
2921 }
2922
2923 return OK;
2924 }
2925 if (debugsw) {
2926 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2927 (int) strlen ((char *) digest));
2928 }
2929
2930 return NOTOK;
2931 }
2932
2933 return NOTOK;
2934 }
2935
2936
2937 /* Multipart parts might have content before the first subpart and/or
2938 after the last subpart that hasn't been stored anywhere else, so do
2939 that. */
2940 int
2941 get_leftover_mp_content (CT ct, int before /* or after */)
2942 {
2943 struct multipart *m = (struct multipart *) ct->c_ctparams;
2944 char *boundary;
2945 int found_boundary = 0;
2946 int max = BUFSIZ;
2947 char *bufp = NULL;
2948 size_t buflen;
2949 ssize_t gotlen;
2950 int read = 0;
2951 char *content = NULL;
2952
2953 if (! m) return NOTOK;
2954
2955 if (before) {
2956 if (! m->mp_parts || ! m->mp_parts->mp_part) return NOTOK;
2957
2958 /* Isolate the beginning of this part to the beginning of the
2959 first subpart and save any content between them. */
2960 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
2961 max = m->mp_parts->mp_part->c_begin - ct->c_begin;
2962 boundary = concat ("--", m->mp_start, NULL);
2963 } else {
2964 struct part *last_subpart = NULL;
2965 struct part *subpart;
2966
2967 /* Go to the last subpart to get its end position. */
2968 for (subpart = m->mp_parts; subpart; subpart = subpart->mp_next) {
2969 last_subpart = subpart;
2970 }
2971
2972 if (last_subpart == NULL) return NOTOK;
2973
2974 /* Isolate the end of the last subpart to the end of this part
2975 and save any content between them. */
2976 fseeko (ct->c_fp, last_subpart->mp_part->c_end, SEEK_SET);
2977 max = ct->c_end - last_subpart->mp_part->c_end;
2978 boundary = concat ("--", m->mp_stop, NULL);
2979 }
2980
2981 /* Back up by 1 to pick up the newline. */
2982 while ((gotlen = getline(&bufp, &buflen, ct->c_fp)) != -1) {
2983 read += gotlen;
2984 /* Don't look beyond beginning of first subpart (before) or
2985 next part (after). */
2986 if (read > max) bufp[read-max] = '\0';
2987
2988 if (before) {
2989 if (! strcmp (bufp, boundary)) {
2990 found_boundary = 1;
2991 }
2992 } else {
2993 if (! found_boundary && ! strcmp (bufp, boundary)) {
2994 found_boundary = 1;
2995 continue;
2996 }
2997 }
2998
2999 if ((before && ! found_boundary) || (! before && found_boundary)) {
3000 if (content) {
3001 char *old_content = content;
3002 content = concat (content, bufp, NULL);
3003 free (old_content);
3004 } else {
3005 content = before
3006 ? concat ("\n", bufp, NULL)
3007 : concat (bufp, NULL);
3008 }
3009 }
3010
3011 if (before) {
3012 if (found_boundary || read > max) break;
3013 } else {
3014 if (read > max) break;
3015 }
3016 }
3017
3018 /* Skip the newline if that's all there is. */
3019 if (content) {
3020 char *cp;
3021
3022 /* Remove trailing newline, except at EOF. */
3023 if ((before || ! feof (ct->c_fp)) &&
3024 (cp = content + strlen (content)) > content &&
3025 *--cp == '\n') {
3026 *cp = '\0';
3027 }
3028
3029 if (strlen (content) > 1) {
3030 if (before) {
3031 m->mp_content_before = content;
3032 } else {
3033 m->mp_content_after = content;
3034 }
3035 } else {
3036 free (content);
3037 }
3038 }
3039
3040 free (boundary);
3041 free (bufp);
3042
3043 return OK;
3044 }
3045
3046
3047 char *
3048 ct_type_str (int type) {
3049 switch (type) {
3050 case CT_APPLICATION:
3051 return "application";
3052 case CT_AUDIO:
3053 return "audio";
3054 case CT_IMAGE:
3055 return "image";
3056 case CT_MESSAGE:
3057 return "message";
3058 case CT_MULTIPART:
3059 return "multipart";
3060 case CT_TEXT:
3061 return "text";
3062 case CT_VIDEO:
3063 return "video";
3064 case CT_EXTENSION:
3065 return "extension";
3066 default:
3067 return "unknown_type";
3068 }
3069 }
3070
3071
3072 char *
3073 ct_subtype_str (int type, int subtype) {
3074 switch (type) {
3075 case CT_APPLICATION:
3076 switch (subtype) {
3077 case APPLICATION_OCTETS:
3078 return "octets";
3079 case APPLICATION_POSTSCRIPT:
3080 return "postscript";
3081 default:
3082 return "unknown_app_subtype";
3083 }
3084 case CT_MESSAGE:
3085 switch (subtype) {
3086 case MESSAGE_RFC822:
3087 return "rfc822";
3088 case MESSAGE_PARTIAL:
3089 return "partial";
3090 case MESSAGE_EXTERNAL:
3091 return "external";
3092 default:
3093 return "unknown_msg_subtype";
3094 }
3095 case CT_MULTIPART:
3096 switch (subtype) {
3097 case MULTI_MIXED:
3098 return "mixed";
3099 case MULTI_ALTERNATE:
3100 return "alternative";
3101 case MULTI_DIGEST:
3102 return "digest";
3103 case MULTI_PARALLEL:
3104 return "parallel";
3105 case MULTI_RELATED:
3106 return "related";
3107 default:
3108 return "unknown_multipart_subtype";
3109 }
3110 case CT_TEXT:
3111 switch (subtype) {
3112 case TEXT_PLAIN:
3113 return "plain";
3114 case TEXT_RICHTEXT:
3115 return "richtext";
3116 case TEXT_ENRICHED:
3117 return "enriched";
3118 default:
3119 return "unknown_text_subtype";
3120 }
3121 default:
3122 return "unknown_type";
3123 }
3124 }
3125
3126
3127 int
3128 ct_str_type (const char *type) {
3129 struct str2init *s2i;
3130
3131 for (s2i = str2cts; s2i->si_key; ++s2i) {
3132 if (! strcasecmp (type, s2i->si_key)) {
3133 break;
3134 }
3135 }
3136 if (! s2i->si_key && ! uprf (type, "X-")) {
3137 ++s2i;
3138 }
3139
3140 return s2i->si_val;
3141 }
3142
3143
3144 int
3145 ct_str_subtype (int type, const char *subtype) {
3146 struct k2v *kv;
3147
3148 switch (type) {
3149 case CT_APPLICATION:
3150 for (kv = SubApplication; kv->kv_key; ++kv) {
3151 if (! strcasecmp (subtype, kv->kv_key)) {
3152 break;
3153 }
3154 }
3155 return kv->kv_value;
3156 case CT_MESSAGE:
3157 for (kv = SubMessage; kv->kv_key; ++kv) {
3158 if (! strcasecmp (subtype, kv->kv_key)) {
3159 break;
3160 }
3161 }
3162 return kv->kv_value;
3163 case CT_MULTIPART:
3164 for (kv = SubMultiPart; kv->kv_key; ++kv) {
3165 if (! strcasecmp (subtype, kv->kv_key)) {
3166 break;
3167 }
3168 }
3169 return kv->kv_value;
3170 case CT_TEXT:
3171 for (kv = SubText; kv->kv_key; ++kv) {
3172 if (! strcasecmp (subtype, kv->kv_key)) {
3173 break;
3174 }
3175 }
3176 return kv->kv_value;
3177 default:
3178 return 0;
3179 }
3180 }
3181
3182
3183 /* Find the content type and InitFunc for the CT. */
3184 const struct str2init *
3185 get_ct_init (int type) {
3186 const struct str2init *sp;
3187
3188 for (sp = str2cts; sp->si_key; ++sp) {
3189 if (type == sp->si_val) {
3190 return sp;
3191 }
3192 }
3193
3194 return NULL;
3195 }
3196
3197 const char *
3198 ce_str (int encoding) {
3199 switch (encoding) {
3200 case CE_BASE64:
3201 return "base64";
3202 case CE_QUOTED:
3203 return "quoted-printable";
3204 case CE_8BIT:
3205 return "8bit";
3206 case CE_7BIT:
3207 return "7bit";
3208 case CE_BINARY:
3209 return "binary";
3210 case CE_EXTENSION:
3211 return "extension";
3212 case CE_EXTERNAL:
3213 return "external";
3214 default:
3215 return "unknown";
3216 }
3217 }
3218
3219 /* Find the content type and InitFunc for the content encoding method. */
3220 const struct str2init *
3221 get_ce_method (const char *method) {
3222 struct str2init *sp;
3223
3224 for (sp = str2ces; sp->si_key; ++sp) {
3225 if (! strcasecmp (method, sp->si_key)) {
3226 return sp;
3227 }
3228 }
3229
3230 return NULL;
3231 }
3232
3233 /*
3234 * Parse a series of MIME attributes (or parameters) given a header as
3235 * input.
3236 *
3237 * Arguments include:
3238 *
3239 * filename - Name of input file (for error messages)
3240 * fieldname - Name of field being processed
3241 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3242 * Updated to point to end of attributes when finished.
3243 * param_head - Pointer to head of parameter list
3244 * param_tail - Pointer to tail of parameter list
3245 * commentp - Pointer to header comment pointer (may be NULL)
3246 *
3247 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3248 * DONE to indicate a benign error (minor parsing error, but the program
3249 * should continue).
3250 */
3251
3252 static int
3253 parse_header_attrs (const char *filename, const char *fieldname,
3254 char **header_attrp, PM *param_head, PM *param_tail,
3255 char **commentp)
3256 {
3257 char *cp = *header_attrp;
3258 PM pm;
3259 struct sectlist {
3260 char *value;
3261 int index;
3262 int len;
3263 struct sectlist *next;
3264 } *sp, *sp2;
3265 struct parmlist {
3266 char *name;
3267 char *charset;
3268 char *lang;
3269 struct sectlist *sechead;
3270 struct parmlist *next;
3271 } *pp, *pp2, *phead = NULL;
3272
3273 while (*cp == ';') {
3274 char *dp, *vp, *up, *nameptr, *valptr, *charset = NULL, *lang = NULL;
3275 int encoded = 0, partial = 0, len = 0, index = 0;
3276
3277 cp++;
3278 while (isspace ((unsigned char) *cp))
3279 cp++;
3280
3281 if (*cp == '(' &&
3282 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3283 return NOTOK;
3284 }
3285
3286 if (*cp == 0) {
3287 if (! suppress_extraneous_trailing_semicolon_warning) {
3288 inform("extraneous trailing ';' in message %s's %s: "
3289 "parameter list", filename, fieldname);
3290 }
3291 return DONE;
3292 }
3293
3294 /* down case the attribute name */
3295 for (dp = cp; istoken ((unsigned char) *dp); dp++)
3296 *dp = tolower ((unsigned char) *dp);
3297
3298 for (up = dp; isspace ((unsigned char) *dp);)
3299 dp++;
3300 if (dp == cp || *dp != '=') {
3301 inform("invalid parameter in message %s's %s: "
3302 "field\n%*sparameter %s (error detected at offset %d)",
3303 filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp);
3304 return NOTOK;
3305 }
3306
3307 /*
3308 * To handle RFC 2231, we have to deal with the following extensions:
3309 *
3310 * name*=encoded-value
3311 * name*<N>=part-N-of-a-parameter-value
3312 * name*<N>*=encoded-part-N-of-a-parameter-value
3313 *
3314 * So the rule is:
3315 * If there's a * right before the equal sign, it's encoded.
3316 * If there's a * and one or more digits, then it's section N.
3317 *
3318 * Remember we can have one or the other, or both. cp points to
3319 * beginning of name, up points past the last character in the
3320 * parameter name.
3321 */
3322
3323 for (vp = cp; vp < up; vp++) {
3324 if (*vp == '*' && vp < up - 1) {
3325 partial = 1;
3326 continue;
3327 }
3328 if (*vp == '*' && vp == up - 1) {
3329 encoded = 1;
3330 } else if (partial) {
3331 if (isdigit((unsigned char) *vp))
3332 index = *vp - '0' + index * 10;
3333 else {
3334 inform("invalid parameter index in message %s's "
3335 "%s: field\n%*s(parameter %s)", filename,
3336 fieldname, strlen(invo_name) + 2, "", cp);
3337 return NOTOK;
3338 }
3339 } else {
3340 len++;
3341 }
3342 }
3343
3344 /*
3345 * Break out the parameter name and value sections and allocate
3346 * memory for each.
3347 */
3348
3349 nameptr = mh_xmalloc(len + 1);
3350 strncpy(nameptr, cp, len);
3351 nameptr[len] = '\0';
3352
3353 for (dp++; isspace ((unsigned char) *dp);)
3354 dp++;
3355
3356 if (encoded) {
3357 /*
3358 * Single quotes delimit the character set and language tag.
3359 * They are required on the first section (or a complete
3360 * parameter).
3361 */
3362 if (index == 0) {
3363 vp = dp;
3364 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3365 *vp != '\0')
3366 vp++;
3367 if (*vp == '\'') {
3368 if (vp != dp) {
3369 len = vp - dp;
3370 charset = mh_xmalloc(len + 1);
3371 strncpy(charset, dp, len);
3372 charset[len] = '\0';
3373 } else {
3374 charset = NULL;
3375 }
3376 vp++;
3377 } else {
3378 inform("missing charset in message %s's %s: "
3379 "field\n%*s(parameter %s)", filename, fieldname,
3380 strlen(invo_name) + 2, "", nameptr);
3381 free(nameptr);
3382 return NOTOK;
3383 }
3384 dp = vp;
3385
3386 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3387 *vp != '\0')
3388 vp++;
3389
3390 if (*vp == '\'') {
3391 if (vp != dp) {
3392 len = vp - dp;
3393 lang = mh_xmalloc(len + 1);
3394 strncpy(lang, dp, len);
3395 lang[len] = '\0';
3396 } else {
3397 lang = NULL;
3398 }
3399 vp++;
3400 } else {
3401 inform("missing language tag in message %s's %s: "
3402 "field\n%*s(parameter %s)", filename, fieldname,
3403 strlen(invo_name) + 2, "", nameptr);
3404 free(nameptr);
3405 mh_xfree(charset);
3406 return NOTOK;
3407 }
3408
3409 dp = vp;
3410 }
3411
3412 /*
3413 * At this point vp should be pointing at the beginning
3414 * of the encoded value/section. Continue until we reach
3415 * the end or get whitespace. But first, calculate the
3416 * length so we can allocate the correct buffer size.
3417 */
3418
3419 for (vp = dp, len = 0; istoken(*vp); vp++) {
3420 if (*vp == '%') {
3421 if (*(vp + 1) == '\0' ||
3422 !isxdigit((unsigned char) *(vp + 1)) ||
3423 *(vp + 2) == '\0' ||
3424 !isxdigit((unsigned char) *(vp + 2))) {
3425 inform("invalid encoded sequence in message "
3426 "%s's %s: field\n%*s(parameter %s)",
3427 filename, fieldname, strlen(invo_name) + 2,
3428 "", nameptr);
3429 free(nameptr);
3430 mh_xfree(charset);
3431 mh_xfree(lang);
3432 return NOTOK;
3433 }
3434 vp += 2;
3435 }
3436 len++;
3437 }
3438
3439 up = valptr = mh_xmalloc(len + 1);
3440
3441 for (vp = dp; istoken(*vp); vp++) {
3442 if (*vp == '%') {
3443 *up++ = decode_qp(*(vp + 1), *(vp + 2));
3444 vp += 2;
3445 } else {
3446 *up++ = *vp;
3447 }
3448 }
3449
3450 *up = '\0';
3451 cp = vp;
3452 } else {
3453 /*
3454 * A "normal" string. If it's got a leading quote, then we
3455 * strip the quotes out. Otherwise go until we reach the end
3456 * or get whitespace. Note we scan it twice; once to get the
3457 * length, then the second time copies it into the destination
3458 * buffer.
3459 */
3460
3461 len = 0;
3462
3463 if (*dp == '"') {
3464 for (cp = dp + 1;;) {
3465 switch (*cp++) {
3466 case '\0':
3467 bad_quote:
3468 inform("invalid quoted-string in message %s's %s: "
3469 "field\n%*s(parameter %s)", filename,
3470 fieldname, strlen(invo_name) + 2, "", nameptr);
3471 free(nameptr);
3472 mh_xfree(charset);
3473 mh_xfree(lang);
3474 return NOTOK;
3475 case '"':
3476 break;
3477
3478 case '\\':
3479 if (*++cp == '\0')
3480 goto bad_quote;
3481 /* FALLTHRU */
3482 default:
3483 len++;
3484 continue;
3485 }
3486 break;
3487 }
3488
3489 } else {
3490 for (cp = dp; istoken (*cp); cp++) {
3491 len++;
3492 }
3493 }
3494
3495 valptr = mh_xmalloc(len + 1);
3496
3497 if (*dp == '"') {
3498 int i;
3499 for (cp = dp + 1, vp = valptr, i = 0; i < len; i++) {
3500 if (*cp == '\\') {
3501 cp++;
3502 }
3503 *vp++ = *cp++;
3504 }
3505 cp++;
3506 } else {
3507 strncpy(valptr, cp = dp, len);
3508 cp += len;
3509 }
3510
3511 valptr[len] = '\0';
3512 }
3513
3514 /*
3515 * If 'partial' is set, we don't allocate a parameter now. We
3516 * put it on the parameter linked list to be reassembled later.
3517 *
3518 * "phead" points to a list of all parameters we need to reassemble.
3519 * Each parameter has a list of sections. We insert the sections in
3520 * order.
3521 */
3522
3523 if (partial) {
3524 for (pp = phead; pp != NULL; pp = pp->next) {
3525 if (strcasecmp(nameptr, pp->name) == 0) {
3526 free (nameptr);
3527 nameptr = pp->name;
3528 break;
3529 }
3530 }
3531
3532 if (pp == NULL) {
3533 NEW0(pp);
3534 pp->name = nameptr;
3535 pp->next = phead;
3536 phead = pp;
3537 }
3538
3539 /*
3540 * Insert this into the section linked list
3541 */
3542
3543 NEW0(sp);
3544 sp->value = valptr;
3545 sp->index = index;
3546 sp->len = len;
3547
3548 if (pp->sechead == NULL || pp->sechead->index > index) {
3549 sp->next = pp->sechead;
3550 pp->sechead = sp;
3551 } else {
3552 for (sp2 = pp->sechead; sp2 != NULL; sp2 = sp2->next) {
3553 if (sp2->index == sp->index) {
3554 inform("duplicate index (%d) in message "
3555 "%s's %s: field\n%*s(parameter %s)", sp->index,
3556 filename, fieldname, strlen(invo_name) + 2, "",
3557 nameptr);
3558 return NOTOK;
3559 }
3560 if (sp2->index < sp->index &&
3561 (sp2->next == NULL || sp2->next->index > sp->index)) {
3562 sp->next = sp2->next;
3563 sp2->next = sp;
3564 break;
3565 }
3566 }
3567
3568 if (sp2 == NULL) {
3569 inform("Internal error: cannot insert partial "
3570 "param in message %s's %s: field\n%*s(parameter %s)",
3571 filename, fieldname, strlen(invo_name) + 2, "",
3572 nameptr);
3573 return NOTOK;
3574 }
3575 }
3576
3577 /*
3578 * Save our charset and lang tags.
3579 */
3580
3581 if (index == 0 && encoded) {
3582 mh_xfree(pp->charset);
3583 pp->charset = charset;
3584 mh_xfree(pp->lang);
3585 pp->lang = lang;
3586 }
3587 } else {
3588 pm = add_param(param_head, param_tail, nameptr, valptr, 1);
3589 pm->pm_charset = charset;
3590 pm->pm_lang = lang;
3591 }
3592
3593 while (isspace ((unsigned char) *cp))
3594 cp++;
3595
3596 if (*cp == '(' &&
3597 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3598 return NOTOK;
3599 }
3600 }
3601
3602 /*
3603 * Now that we're done, reassemble all of the partial parameters.
3604 */
3605
3606 for (pp = phead; pp != NULL; ) {
3607 char *p, *q;
3608 size_t tlen = 0;
3609 int pindex = 0;
3610 for (sp = pp->sechead; sp != NULL; sp = sp->next) {
3611 if (sp->index != pindex++) {
3612 inform("missing section %d for parameter in "
3613 "message %s's %s: field\n%*s(parameter %s)", pindex - 1,
3614 filename, fieldname, strlen(invo_name) + 2, "",
3615 pp->name);
3616 return NOTOK;
3617 }
3618 tlen += sp->len;
3619 }
3620
3621 p = q = mh_xmalloc(tlen + 1);
3622 for (sp = pp->sechead; sp != NULL; ) {
3623 memcpy(q, sp->value, sp->len);
3624 q += sp->len;
3625 free(sp->value);
3626 sp2 = sp->next;
3627 free(sp);
3628 sp = sp2;
3629 }
3630
3631 p[tlen] = '\0';
3632
3633 pm = add_param(param_head, param_tail, pp->name, p, 1);
3634 pm->pm_charset = pp->charset;
3635 pm->pm_lang = pp->lang;
3636 pp2 = pp->next;
3637 free(pp);
3638 pp = pp2;
3639 }
3640
3641 *header_attrp = cp;
3642 return OK;
3643 }
3644
3645 /*
3646 * Return the charset for a particular content type.
3647 */
3648
3649 char *
3650 content_charset (CT ct) {
3651 char *ret_charset = NULL;
3652
3653 ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
3654
3655 return ret_charset ? ret_charset : mh_xstrdup("US-ASCII");
3656 }
3657
3658
3659 /*
3660 * Create a string based on a list of output parameters. Assume that this
3661 * parameter string will be appended to an existing header, so start out
3662 * with the separator (;). Perform RFC 2231 encoding when necessary.
3663 */
3664
3665 char *
3666 output_params(size_t initialwidth, PM params, int *offsetout, int external)
3667 {
3668 char *paramout = NULL;
3669 char line[CPERLIN * 2], *q;
3670 int curlen, index, cont, encode, i;
3671 size_t valoff, numchars;
3672
3673 while (params != NULL) {
3674 encode = 0;
3675 index = 0;
3676 valoff = 0;
3677 q = line;
3678
3679 if (external && strcasecmp(params->pm_name, "body") == 0)
3680 continue;
3681
3682 if (strlen(params->pm_name) > CPERLIN) {
3683 inform("Parameter name \"%s\" is too long", params->pm_name);
3684 mh_xfree(paramout);
3685 return NULL;
3686 }
3687
3688 curlen = param_len(params, index, valoff, &encode, &cont, &numchars);
3689
3690 /*
3691 * Loop until we get a parameter that fits within a line. We
3692 * assume new lines start with a tab, so check our overflow based
3693 * on that.
3694 */
3695
3696 while (cont) {
3697 *q++ = ';';
3698 *q++ = '\n';
3699 *q++ = '\t';
3700
3701 /*
3702 * At this point we're definitely continuing the line, so
3703 * be sure to include the parameter name and section index.
3704 */
3705
3706 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3707 params->pm_name, index);
3708
3709 /*
3710 * Both of these functions do a NUL termination
3711 */
3712
3713 if (encode)
3714 i = encode_param(params, q, sizeof(line) - (q - line),
3715 numchars, valoff, index);
3716 else
3717 i = normal_param(params, q, sizeof(line) - (q - line),
3718 numchars, valoff);
3719
3720 if (i == 0) {
3721 mh_xfree(paramout);
3722 return NULL;
3723 }
3724
3725 valoff += numchars;
3726 index++;
3727 curlen = param_len(params, index, valoff, &encode, &cont,
3728 &numchars);
3729 q = line;
3730
3731 /*
3732 * "line" starts with a ;\n\t, so that doesn't count against
3733 * the length. But add 8 since it starts with a tab; that's
3734 * how we end up with 5.
3735 */
3736
3737 initialwidth = strlen(line) + 5;
3738
3739 /*
3740 * At this point the line should be built, so add it to our
3741 * current output buffer.
3742 */
3743
3744 paramout = add(line, paramout);
3745 }
3746
3747 /*
3748 * If this won't fit on the line, start a new one. Save room in
3749 * case we need a semicolon on the end
3750 */
3751
3752 if (initialwidth + curlen > CPERLIN - 1) {
3753 *q++ = ';';
3754 *q++ = '\n';
3755 *q++ = '\t';
3756 initialwidth = 8;
3757 } else {
3758 *q++ = ';';
3759 *q++ = ' ';
3760 initialwidth += 2;
3761 }
3762
3763 /*
3764 * At this point, we're either finishing a continued parameter, or
3765 * we're working on a new one.
3766 */
3767
3768 if (index > 0) {
3769 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3770 params->pm_name, index);
3771 } else {
3772 strncpy(q, params->pm_name, sizeof(line) - (q - line));
3773 q += strlen(q);
3774 }
3775
3776 if (encode)
3777 i = encode_param(params, q, sizeof(line) - (q - line),
3778 strlen(params->pm_value + valoff), valoff, index);
3779 else
3780 i = normal_param(params, q, sizeof(line) - (q - line),
3781 strlen(params->pm_value + valoff), valoff);
3782
3783 if (i == 0) {
3784 mh_xfree(paramout);
3785 return NULL;
3786 }
3787
3788 paramout = add(line, paramout);
3789 initialwidth += strlen(line);
3790
3791 params = params->pm_next;
3792 }
3793
3794 if (offsetout)
3795 *offsetout = initialwidth;
3796
3797 return paramout;
3798 }
3799
3800 /*
3801 * Calculate the size of a parameter.
3802 *
3803 * Arguments include
3804 *
3805 * pm - The parameter being output
3806 * index - If continuing the parameter, the index of the section
3807 * we're on.
3808 * valueoff - The current offset into the parameter value that we're
3809 * working on (previous sections have consumed valueoff bytes).
3810 * encode - Set if we should perform encoding on this parameter section
3811 * (given that we're consuming bytesfit bytes).
3812 * cont - Set if the remaining data in value will not fit on a single
3813 * line and will need to be continued.
3814 * bytesfit - The number of bytes that we can consume from the parameter
3815 * value and still fit on a completely new line. The
3816 * calculation assumes the new line starts with a tab,
3817 * includes the parameter name and any encoding, and fits
3818 * within CPERLIN bytes. Will always be at least 1.
3819 */
3820
3821 static size_t
3822 param_len(PM pm, int index, size_t valueoff, int *encode, int *cont,
3823 size_t *bytesfit)
3824 {
3825 char *start = pm->pm_value + valueoff, *p, indexchar[32];
3826 size_t len = 0, fit = 0;
3827 int fitlimit = 0, eightbit, maxfit;
3828
3829 *encode = 0;
3830
3831 /*
3832 * Add up the length. First, start with the parameter name.
3833 */
3834
3835 len = strlen(pm->pm_name);
3836
3837 /*
3838 * Scan the parameter value and see if we need to do encoding for this
3839 * section.
3840 */
3841
3842 eightbit = contains8bit(start, NULL);
3843
3844 /*
3845 * Determine if we need to encode this section. Encoding is necessary if:
3846 *
3847 * - There are any 8-bit characters at all and we're on the first
3848 * section.
3849 * - There are 8-bit characters within N bytes of our section start.
3850 * N is calculated based on the number of bytes it would take to
3851 * reach CPERLIN. Specifically:
3852 * 8 (starting tab) +
3853 * strlen(param name) +
3854 * 4 ('* for section marker, '=', opening/closing '"')
3855 * strlen (index)
3856 * is the number of bytes used by everything that isn't part of the
3857 * value. So that gets subtracted from CPERLIN.
3858 */
3859
3860 snprintf(indexchar, sizeof(indexchar), "%d", index);
3861 maxfit = CPERLIN - (12 + len + strlen(indexchar));
3862 if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) {
3863 *encode = 1;
3864 }
3865
3866 len++; /* Add in equal sign */
3867
3868 if (*encode) {
3869 /*
3870 * We're using maxfit as a marker for how many characters we can
3871 * fit into the line. Bump it by two because we're not using quotes
3872 * when encoding.
3873 */
3874
3875 maxfit += 2;
3876
3877 /*
3878 * If we don't have a charset or language tag in this parameter,
3879 * add them now.
3880 */
3881
3882 if (! pm->pm_charset) {
3883 pm->pm_charset = mh_xstrdup(write_charset_8bit());
3884 if (strcasecmp(pm->pm_charset, "US-ASCII") == 0)
3885 adios(NULL, "8-bit characters in parameter \"%s\", but "
3886 "local character set is US-ASCII", pm->pm_name);
3887 }
3888 if (! pm->pm_lang)
3889 pm->pm_lang = mh_xstrdup(""); /* Default to a blank lang tag */
3890
3891 len++; /* For the encoding marker */
3892 maxfit--;
3893 if (index == 0) {
3894 int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
3895 len += enclen;
3896 maxfit-= enclen;
3897 } else {
3898 /*
3899 * We know we definitely need to include an index. maxfit already
3900 * includes the section marker.
3901 */
3902 len += strlen(indexchar);
3903 }
3904 for (p = start; *p != '\0'; p++) {
3905 if (isparamencode(*p)) {
3906 len += 3;
3907 maxfit -= 3;
3908 } else {
3909 len++;
3910 maxfit--;
3911 }
3912 /*
3913 * Just so there's no confusion: maxfit is counting OUTPUT
3914 * characters (post-encoding). fit is counting INPUT characters.
3915 */
3916 if (! fitlimit && maxfit >= 0)
3917 fit++;
3918 else if (! fitlimit)
3919 fitlimit++;
3920 }
3921 } else {
3922 /*
3923 * Calculate the string length, but add room for quoting \
3924 * and " if necessary. Also account for quotes at beginning
3925 * and end.
3926 */
3927 for (p = start; *p != '\0'; p++) {
3928 switch (*p) {
3929 case '"':
3930 case '\\':
3931 len++;
3932 maxfit--;
3933 /* FALLTHRU */
3934 default:
3935 len++;
3936 maxfit--;
3937 }
3938 if (! fitlimit && maxfit >= 0)
3939 fit++;
3940 else if (! fitlimit)
3941 fitlimit++;
3942 }
3943
3944 len += 2;
3945 }
3946
3947 if (fit < 1)
3948 fit = 1;
3949
3950 *cont = fitlimit;
3951 *bytesfit = fit;
3952
3953 return len;
3954 }
3955
3956 /*
3957 * Output an encoded parameter string.
3958 */
3959
3960 size_t
3961 encode_param(PM pm, char *output, size_t len, size_t valuelen,
3962 size_t valueoff, int index)
3963 {
3964 size_t outlen = 0, n;
3965 char *endptr = output + len, *p;
3966
3967 /*
3968 * First, output the marker for an encoded string.
3969 */
3970
3971 *output++ = '*';
3972 *output++ = '=';
3973 outlen += 2;
3974
3975 /*
3976 * If the index is 0, output the character set and language tag.
3977 * If theses were NULL, they should have already been filled in
3978 * by param_len().
3979 */
3980
3981 if (index == 0) {
3982 n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset,
3983 pm->pm_lang);
3984 output += n;
3985 outlen += n;
3986 if (output > endptr) {
3987 inform("Internal error: parameter buffer overflow");
3988 return 0;
3989 }
3990 }
3991
3992 /*
3993 * Copy over the value, encoding if necessary
3994 */
3995
3996 p = pm->pm_value + valueoff;
3997 while (valuelen-- > 0) {
3998 if (isparamencode(*p)) {
3999 n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++);
4000 output += n;
4001 outlen += n;
4002 } else {
4003 *output++ = *p++;
4004 outlen++;
4005 }
4006 if (output > endptr) {
4007 inform("Internal error: parameter buffer overflow");
4008 return 0;
4009 }
4010 }
4011
4012 *output = '\0';
4013
4014 return outlen;
4015 }
4016
4017 /*
4018 * Output a "normal" parameter, without encoding. Be sure to escape
4019 * quotes and backslashes if necessary.
4020 */
4021
4022 static size_t
4023 normal_param(PM pm, char *output, size_t len, size_t valuelen,
4024 size_t valueoff)
4025 {
4026 size_t outlen = 0;
4027 char *endptr = output + len, *p;
4028
4029 *output++ = '=';
4030 *output++ = '"';
4031 outlen += 2;
4032
4033 p = pm->pm_value + valueoff;
4034
4035 while (valuelen-- > 0) {
4036 switch (*p) {
4037 case '\\':
4038 case '"':
4039 *output++ = '\\';
4040 outlen++;
4041 /* FALLTHRU */
4042 default:
4043 *output++ = *p++;
4044 outlen++;
4045 }
4046 if (output > endptr) {
4047 inform("Internal error: parameter buffer overflow");
4048 return 0;
4049 }
4050 }
4051
4052 if (output - 2 > endptr) {
4053 inform("Internal error: parameter buffer overflow");
4054 return 0;
4055 }
4056
4057 *output++ = '"';
4058 *output++ = '\0';
4059
4060 return outlen + 1;
4061 }
4062
4063 /*
4064 * Add a parameter to the parameter linked list
4065 */
4066
4067 PM
4068 add_param(PM *first, PM *last, char *name, char *value, int nocopy)
4069 {
4070 PM pm;
4071
4072 NEW0(pm);
4073 pm->pm_name = nocopy ? name : getcpy(name);
4074 pm->pm_value = nocopy ? value : getcpy(value);
4075
4076 if (*first) {
4077 (*last)->pm_next = pm;
4078 *last = pm;
4079 } else {
4080 *first = pm;
4081 *last = pm;
4082 }
4083
4084 return pm;
4085 }
4086
4087 /*
4088 * Either replace a current parameter with a new value, or add the parameter
4089 * to the parameter linked list.
4090 */
4091
4092 PM
4093 replace_param(PM *first, PM *last, char *name, char *value, int nocopy)
4094 {
4095 PM pm;
4096
4097 for (pm = *first; pm != NULL; pm = pm->pm_next) {
4098 if (strcasecmp(name, pm->pm_name) == 0) {
4099 /*
4100 * If nocopy is set, it's assumed that we own both name
4101 * and value. We don't need name, so we discard it now.
4102 */
4103 if (nocopy)
4104 free(name);
4105 free(pm->pm_value);
4106 pm->pm_value = nocopy ? value : getcpy(value);
4107 return pm;
4108 }
4109 }
4110
4111 return add_param(first, last, name, value, nocopy);
4112 }
4113
4114 /*
4115 * Retrieve a parameter value from a parameter linked list. If the parameter
4116 * value needs converted to the local character set, do that now.
4117 */
4118
4119 char *
4120 get_param(PM first, const char *name, char replace, int fetchonly)
4121 {
4122 while (first != NULL) {
4123 if (strcasecmp(name, first->pm_name) == 0) {
4124 if (fetchonly)
4125 return first->pm_value;
4126 return getcpy(get_param_value(first, replace));
4127 }
4128 first = first->pm_next;
4129 }
4130
4131 return NULL;
4132 }
4133
4134 /*
4135 * Return a parameter value, converting to the local character set if
4136 * necessary
4137 */
4138
4139 char *get_param_value(PM pm, char replace)
4140 {
4141 static char buffer[4096]; /* I hope no parameters are larger */
4142 size_t bufsize = sizeof(buffer);
4143 #ifdef HAVE_ICONV
4144 size_t inbytes;
4145 int utf8;
4146 iconv_t cd;
4147 ICONV_CONST char *p;
4148 #else /* HAVE_ICONV */
4149 char *p;
4150 #endif /* HAVE_ICONV */
4151
4152 char *q;
4153
4154 /*
4155 * If we don't have a character set indicated, it's assumed to be
4156 * US-ASCII. If it matches our character set, we don't need to convert
4157 * anything.
4158 */
4159
4160 if (!pm->pm_charset || check_charset(pm->pm_charset,
4161 strlen(pm->pm_charset))) {
4162 return pm->pm_value;
4163 }
4164
4165 /*
4166 * In this case, we need to convert. If we have iconv support, use
4167 * that. Otherwise, go through and simply replace every non-ASCII
4168 * character with the substitution character.
4169 */
4170
4171 #ifdef HAVE_ICONV
4172 q = buffer;
4173 bufsize = sizeof(buffer);
4174 utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0;
4175
4176 cd = iconv_open(get_charset(), pm->pm_charset);
4177 if (cd == (iconv_t) -1) {
4178 goto noiconv;
4179 }
4180
4181 inbytes = strlen(pm->pm_value);
4182 p = pm->pm_value;
4183
4184 while (inbytes) {
4185 if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) {
4186 if (errno != EILSEQ) {
4187 iconv_close(cd);
4188 goto noiconv;
4189 }
4190 /*
4191 * Reset shift state, substitute our character,
4192 * try to restart conversion.
4193 */
4194
4195 iconv(cd, NULL, NULL, &q, &bufsize);
4196
4197 if (bufsize == 0) {
4198 iconv_close(cd);
4199 goto noiconv;
4200 }
4201 *q++ = replace;
4202 bufsize--;
4203 if (bufsize == 0) {
4204 iconv_close(cd);
4205 goto noiconv;
4206 }
4207 if (utf8) {
4208 for (++p, --inbytes;
4209 inbytes > 0 && (((unsigned char) *p) & 0xc0) == 0x80;
4210 ++p, --inbytes)
4211 continue;
4212 } else {
4213 p++;
4214 inbytes--;
4215 }
4216 }
4217 }
4218
4219 iconv_close(cd);
4220
4221 if (bufsize == 0)
4222 q--;
4223 *q = '\0';
4224
4225 return buffer;
4226
4227 noiconv:
4228 #endif /* HAVE_ICONV */
4229
4230 /*
4231 * Take everything non-ASCII and substitute the replacement character
4232 */
4233
4234 q = buffer;
4235 bufsize = sizeof(buffer);
4236 for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) {
4237 if (isascii((unsigned char) *p) && isprint((unsigned char) *p))
4238 *q = *p;
4239 else
4240 *q = replace;
4241 }
4242
4243 *q = '\0';
4244
4245 return buffer;
4246 }