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