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