]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Fixed test negations in test-version-check to work with make distcheck.
[nmh] / uip / mhparse.c
1
2 /*
3 * mhparse.c -- routines to parse the contents of MIME messages
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
8 */
9
10 #include <h/mh.h>
11 #include <fcntl.h>
12 #include <h/md5.h>
13 #include <h/mts.h>
14 #include <h/tws.h>
15 #include <h/mime.h>
16 #include <h/mhparse.h>
17 #include <h/utils.h>
18 #ifdef HAVE_ICONV
19 # include <iconv.h>
20 #endif /* HAVE_ICONV */
21
22
23 extern int debugsw;
24
25 /* cache policies */
26 extern int rcachesw; /* mhcachesbr.c */
27 extern int wcachesw; /* mhcachesbr.c */
28
29 int checksw = 0; /* check Content-MD5 field */
30
31 /*
32 * These are for mhfixmsg to:
33 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
34 * in a multipart.
35 * 2) Suppress the warning about bogus multipart content, and report it.
36 * 3) Suppress the warning about extraneous trailing ';' in header parameter
37 * lists, and report it.
38 */
39 int skip_mp_cte_check;
40 int suppress_bogus_mp_content_warning;
41 int bogus_mp_content;
42 int suppress_extraneous_trailing_semicolon_warning;
43 int extraneous_trailing_semicolon;
44 int suppress_multiple_mime_version_warning = 1;
45
46 /* list of preferred type/subtype pairs, for -prefer */
47 char *preferred_types[NPREFS],
48 *preferred_subtypes[NPREFS];
49 int npreferred;
50
51
52 /*
53 * Structures for TEXT messages
54 */
55 struct k2v SubText[] = {
56 { "plain", TEXT_PLAIN },
57 { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */
58 { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */
59 { NULL, TEXT_UNKNOWN } /* this one must be last! */
60 };
61
62 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
63
64 /*
65 * Structures for MULTIPART messages
66 */
67 struct k2v SubMultiPart[] = {
68 { "mixed", MULTI_MIXED },
69 { "alternative", MULTI_ALTERNATE },
70 { "digest", MULTI_DIGEST },
71 { "parallel", MULTI_PARALLEL },
72 { "related", MULTI_RELATED },
73 { NULL, MULTI_UNKNOWN } /* this one must be last! */
74 };
75
76 /*
77 * Structures for MESSAGE messages
78 */
79 struct k2v SubMessage[] = {
80 { "rfc822", MESSAGE_RFC822 },
81 { "partial", MESSAGE_PARTIAL },
82 { "external-body", MESSAGE_EXTERNAL },
83 { NULL, MESSAGE_UNKNOWN } /* this one must be last! */
84 };
85
86 /*
87 * Structure for APPLICATION messages
88 */
89 struct k2v SubApplication[] = {
90 { "octet-stream", APPLICATION_OCTETS },
91 { "postscript", APPLICATION_POSTSCRIPT },
92 { NULL, APPLICATION_UNKNOWN } /* this one must be last! */
93 };
94
95 /*
96 * Mapping of names of CTE types in mhbuild directives
97 */
98 static struct k2v EncodingType[] = {
99 { "8bit", CE_8BIT },
100 { "qp", CE_QUOTED },
101 { "q-p", CE_QUOTED },
102 { "quoted-printable", CE_QUOTED },
103 { "b64", CE_BASE64 },
104 { "base64", CE_BASE64 },
105 { NULL, 0 },
106 };
107
108
109 /* mhcachesbr.c */
110 int find_cache (CT, int, int *, char *, char *, int);
111
112 /* mhmisc.c */
113 int part_ok (CT);
114 int type_ok (CT, int);
115 void content_error (char *, CT, char *, ...);
116
117 /* mhfree.c */
118 void free_encoding (CT, int);
119
120 /*
121 * static prototypes
122 */
123 static CT get_content (FILE *, char *, int);
124 static int get_comment (const char *, const char *, char **, char **);
125
126 static int InitGeneric (CT);
127 static int InitText (CT);
128 static int InitMultiPart (CT);
129 static void reverse_parts (CT);
130 static void prefer_parts(CT ct);
131 static int InitMessage (CT);
132 static int InitApplication (CT);
133 static int init_encoding (CT, OpenCEFunc);
134 static unsigned long size_encoding (CT);
135 static int InitBase64 (CT);
136 static int openBase64 (CT, char **);
137 static int InitQuoted (CT);
138 static int openQuoted (CT, char **);
139 static int Init7Bit (CT);
140 static int openExternal (CT, CT, CE, char **, int *);
141 static int InitFile (CT);
142 static int openFile (CT, char **);
143 static int InitFTP (CT);
144 static int openFTP (CT, char **);
145 static int InitMail (CT);
146 static int openMail (CT, char **);
147 static int readDigest (CT, char *);
148 static int get_leftover_mp_content (CT, int);
149 static int InitURL (CT);
150 static int openURL (CT, char **);
151 static int parse_header_attrs (const char *, const char *, char **, PM *,
152 PM *, char **);
153 static size_t param_len(PM, int, size_t, int *, int *, size_t *);
154 static size_t normal_param(PM, char *, size_t, size_t, size_t);
155 static int get_dispo (char *, CT, int);
156
157 struct str2init str2cts[] = {
158 { "application", CT_APPLICATION, InitApplication },
159 { "audio", CT_AUDIO, InitGeneric },
160 { "image", CT_IMAGE, InitGeneric },
161 { "message", CT_MESSAGE, InitMessage },
162 { "multipart", CT_MULTIPART, InitMultiPart },
163 { "text", CT_TEXT, InitText },
164 { "video", CT_VIDEO, InitGeneric },
165 { NULL, CT_EXTENSION, NULL }, /* these two must be last! */
166 { NULL, CT_UNKNOWN, NULL },
167 };
168
169 struct str2init str2ces[] = {
170 { "base64", CE_BASE64, InitBase64 },
171 { "quoted-printable", CE_QUOTED, InitQuoted },
172 { "8bit", CE_8BIT, Init7Bit },
173 { "7bit", CE_7BIT, Init7Bit },
174 { "binary", CE_BINARY, Init7Bit },
175 { NULL, CE_EXTENSION, NULL }, /* these two must be last! */
176 { NULL, CE_UNKNOWN, NULL },
177 };
178
179 /*
180 * NOTE WELL: si_key MUST NOT have value of NOTOK
181 *
182 * si_key is 1 if access method is anonymous.
183 */
184 struct str2init str2methods[] = {
185 { "afs", 1, InitFile },
186 { "anon-ftp", 1, InitFTP },
187 { "ftp", 0, InitFTP },
188 { "local-file", 0, InitFile },
189 { "mail-server", 0, InitMail },
190 { "url", 0, InitURL },
191 { NULL, 0, NULL }
192 };
193
194
195 /*
196 * Main entry point for parsing a MIME message or file.
197 * It returns the Content structure for the top level
198 * entity in the file.
199 */
200
201 CT
202 parse_mime (char *file)
203 {
204 int is_stdin;
205 char buffer[BUFSIZ];
206 FILE *fp;
207 CT ct;
208 size_t n;
209
210 /*
211 * Check if file is actually standard input
212 */
213 if ((is_stdin = !(strcmp (file, "-")))) {
214 char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
215 if (tfile == NULL) {
216 advise("mhparse", "unable to create temporary file in %s",
217 get_temp_dir());
218 return NULL;
219 }
220 file = add (tfile, NULL);
221
222 while ((n = fread(buffer, 1, sizeof(buffer), stdin)) > 0) {
223 if (fwrite(buffer, 1, n, fp) != n) {
224 (void) m_unlink (file);
225 advise (file, "error copying to temporary file");
226 return NULL;
227 }
228 }
229 fflush (fp);
230
231 if (ferror (stdin)) {
232 (void) m_unlink (file);
233 advise ("stdin", "error reading");
234 return NULL;
235 }
236 if (ferror (fp)) {
237 (void) m_unlink (file);
238 advise (file, "error writing");
239 return NULL;
240 }
241 fseek (fp, 0L, SEEK_SET);
242 } else if ((fp = fopen (file, "r")) == NULL) {
243 advise (file, "unable to read");
244 return NULL;
245 }
246
247 if (!(ct = get_content (fp, file, 1))) {
248 if (is_stdin)
249 (void) m_unlink (file);
250 advise (NULL, "unable to decode %s", file);
251 return NULL;
252 }
253
254 if (is_stdin)
255 ct->c_unlink = 1; /* temp file to remove */
256
257 ct->c_fp = NULL;
258
259 if (ct->c_end == 0L) {
260 fseek (fp, 0L, SEEK_END);
261 ct->c_end = ftell (fp);
262 }
263
264 if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
265 fclose (fp);
266 free_content (ct);
267 return NULL;
268 }
269
270 fclose (fp);
271 return ct;
272 }
273
274
275 /*
276 * Main routine for reading/parsing the headers
277 * of a message content.
278 *
279 * toplevel = 1 # we are at the top level of the message
280 * toplevel = 0 # we are inside message type or multipart type
281 * # other than multipart/digest
282 * toplevel = -1 # we are inside multipart/digest
283 * NB: on failure we will fclose(in)!
284 */
285
286 static CT
287 get_content (FILE *in, char *file, int toplevel)
288 {
289 int compnum, state;
290 char buf[BUFSIZ], name[NAMESZ];
291 char *np, *vp;
292 CT ct;
293 HF hp;
294 m_getfld_state_t gstate = 0;
295
296 /* allocate the content structure */
297 if (!(ct = (CT) mh_xcalloc (1, sizeof(*ct))))
298 adios (NULL, "out of memory");
299
300 ct->c_fp = in;
301 ct->c_file = add (file, NULL);
302 ct->c_begin = ftell (ct->c_fp) + 1;
303
304 /*
305 * Parse the header fields for this
306 * content into a linked list.
307 */
308 m_getfld_track_filepos (&gstate, in);
309 for (compnum = 1;;) {
310 int bufsz = sizeof buf;
311 switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
312 case FLD:
313 case FLDPLUS:
314 compnum++;
315
316 /* get copies of the buffers */
317 np = add (name, NULL);
318 vp = add (buf, NULL);
319
320 /* if necessary, get rest of field */
321 while (state == FLDPLUS) {
322 bufsz = sizeof buf;
323 state = m_getfld (&gstate, name, buf, &bufsz, in);
324 vp = add (buf, vp); /* add to previous value */
325 }
326
327 /* Now add the header data to the list */
328 add_header (ct, np, vp);
329
330 /* continue, to see if this isn't the last header field */
331 ct->c_begin = ftell (in) + 1;
332 continue;
333
334 case BODY:
335 ct->c_begin = ftell (in) - strlen (buf);
336 break;
337
338 case FILEEOF:
339 ct->c_begin = ftell (in);
340 break;
341
342 case LENERR:
343 case FMTERR:
344 adios (NULL, "message format error in component #%d", compnum);
345
346 default:
347 adios (NULL, "getfld() returned %d", state);
348 }
349
350 /* break out of the loop */
351 break;
352 }
353 m_getfld_state_destroy (&gstate);
354
355 /*
356 * Read the content headers. We will parse the
357 * MIME related header fields into their various
358 * structures and set internal flags related to
359 * content type/subtype, etc.
360 */
361
362 hp = ct->c_first_hf; /* start at first header field */
363 while (hp) {
364 /* Get MIME-Version field */
365 if (!strcasecmp (hp->name, VRSN_FIELD)) {
366 int ucmp;
367 char c, *cp, *dp;
368 char *vrsn;
369
370 vrsn = add (hp->value, NULL);
371
372 /* Now, cleanup this field */
373 cp = vrsn;
374
375 while (isspace ((unsigned char) *cp))
376 cp++;
377 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
378 *dp++ = ' ';
379 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
380 if (!isspace ((unsigned char) *dp))
381 break;
382 *++dp = '\0';
383 if (debugsw)
384 fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
385
386 if (*cp == '(' &&
387 get_comment (ct->c_file, VRSN_FIELD, &cp, NULL) == NOTOK)
388 goto out;
389
390 for (dp = cp; istoken (*dp); dp++)
391 continue;
392 c = *dp;
393 *dp = '\0';
394 ucmp = !strcasecmp (cp, VRSN_VALUE);
395 *dp = c;
396 if (!ucmp) {
397 admonish (NULL, "message %s has unknown value for %s: field (%s)",
398 ct->c_file, VRSN_FIELD, cp);
399 }
400 if (!ct->c_vrsn) {
401 ct->c_vrsn = vrsn;
402 } else {
403 if (! suppress_multiple_mime_version_warning)
404 advise (NULL, "message %s has multiple %s: fields",
405 ct->c_file, VRSN_FIELD);
406 free(vrsn);
407 }
408 }
409 else if (!strcasecmp (hp->name, TYPE_FIELD)) {
410 /* Get Content-Type field */
411 struct str2init *s2i;
412 CI ci = &ct->c_ctinfo;
413
414 /* Check if we've already seen a Content-Type header */
415 if (ct->c_ctline) {
416 advise (NULL, "message %s has multiple %s: fields",
417 ct->c_file, TYPE_FIELD);
418 goto next_header;
419 }
420
421 /* Parse the Content-Type field */
422 if (get_ctinfo (hp->value, ct, 0) == NOTOK)
423 goto out;
424
425 /*
426 * Set the Init function and the internal
427 * flag for this content type.
428 */
429 for (s2i = str2cts; s2i->si_key; s2i++)
430 if (!strcasecmp (ci->ci_type, s2i->si_key))
431 break;
432 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
433 s2i++;
434 ct->c_type = s2i->si_val;
435 ct->c_ctinitfnx = s2i->si_init;
436 }
437 else if (!strcasecmp (hp->name, ENCODING_FIELD)) {
438 /* Get Content-Transfer-Encoding field */
439 char c, *cp, *dp;
440 struct str2init *s2i;
441
442 /*
443 * Check if we've already seen the
444 * Content-Transfer-Encoding field
445 */
446 if (ct->c_celine) {
447 advise (NULL, "message %s has multiple %s: fields",
448 ct->c_file, ENCODING_FIELD);
449 goto next_header;
450 }
451
452 /* get copy of this field */
453 ct->c_celine = cp = add (hp->value, NULL);
454
455 while (isspace ((unsigned char) *cp))
456 cp++;
457 for (dp = cp; istoken (*dp); dp++)
458 continue;
459 c = *dp;
460 *dp = '\0';
461
462 /*
463 * Find the internal flag and Init function
464 * for this transfer encoding.
465 */
466 for (s2i = str2ces; s2i->si_key; s2i++)
467 if (!strcasecmp (cp, s2i->si_key))
468 break;
469 if (!s2i->si_key && !uprf (cp, "X-"))
470 s2i++;
471 *dp = c;
472 ct->c_encoding = s2i->si_val;
473
474 /* Call the Init function for this encoding */
475 if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
476 goto out;
477 }
478 else if (!strcasecmp (hp->name, MD5_FIELD)) {
479 /* Get Content-MD5 field */
480 char *cp, *dp, *ep;
481
482 if (!checksw)
483 goto next_header;
484
485 if (ct->c_digested) {
486 advise (NULL, "message %s has multiple %s: fields",
487 ct->c_file, MD5_FIELD);
488 goto next_header;
489 }
490
491 ep = cp = add (hp->value, NULL); /* get a copy */
492
493 while (isspace ((unsigned char) *cp))
494 cp++;
495 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
496 *dp++ = ' ';
497 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
498 if (!isspace ((unsigned char) *dp))
499 break;
500 *++dp = '\0';
501 if (debugsw)
502 fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
503
504 if (*cp == '(' &&
505 get_comment (ct->c_file, MD5_FIELD, &cp, NULL) == NOTOK) {
506 free (ep);
507 goto out;
508 }
509
510 for (dp = cp; *dp && !isspace ((unsigned char) *dp); dp++)
511 continue;
512 *dp = '\0';
513
514 readDigest (ct, cp);
515 free (ep);
516 ct->c_digested++;
517 }
518 else if (!strcasecmp (hp->name, ID_FIELD)) {
519 /* Get Content-ID field */
520 ct->c_id = add (hp->value, ct->c_id);
521 }
522 else if (!strcasecmp (hp->name, DESCR_FIELD)) {
523 /* Get Content-Description field */
524 ct->c_descr = add (hp->value, ct->c_descr);
525 }
526 else if (!strcasecmp (hp->name, DISPO_FIELD)) {
527 /* Get Content-Disposition field */
528 if (get_dispo(hp->value, ct, 0) == NOTOK)
529 goto out;
530 }
531
532 next_header:
533 hp = hp->next; /* next header field */
534 }
535
536 /*
537 * Check if we saw a Content-Type field.
538 * If not, then assign a default value for
539 * it, and the Init function.
540 */
541 if (!ct->c_ctline) {
542 /*
543 * If we are inside a multipart/digest message,
544 * so default type is message/rfc822
545 */
546 if (toplevel < 0) {
547 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
548 goto out;
549 ct->c_type = CT_MESSAGE;
550 ct->c_ctinitfnx = InitMessage;
551 } else {
552 /*
553 * Else default type is text/plain
554 */
555 if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
556 goto out;
557 ct->c_type = CT_TEXT;
558 ct->c_ctinitfnx = InitText;
559 }
560 }
561
562 /* Use default Transfer-Encoding, if necessary */
563 if (!ct->c_celine) {
564 ct->c_encoding = CE_7BIT;
565 Init7Bit (ct);
566 }
567
568 return ct;
569
570 out:
571 free_content (ct);
572 return NULL;
573 }
574
575
576 /*
577 * small routine to add header field to list
578 */
579
580 int
581 add_header (CT ct, char *name, char *value)
582 {
583 HF hp;
584
585 /* allocate header field structure */
586 hp = mh_xmalloc (sizeof(*hp));
587
588 /* link data into header structure */
589 hp->name = name;
590 hp->value = value;
591 hp->next = NULL;
592
593 /* link header structure into the list */
594 if (ct->c_first_hf == NULL) {
595 ct->c_first_hf = hp; /* this is the first */
596 ct->c_last_hf = hp;
597 } else {
598 ct->c_last_hf->next = hp; /* add it to the end */
599 ct->c_last_hf = hp;
600 }
601
602 return 0;
603 }
604
605
606 /*
607 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
608 * directives. Fills in the information of the CTinfo structure.
609 */
610 int
611 get_ctinfo (char *cp, CT ct, int magic)
612 {
613 char *dp;
614 char c;
615 CI ci;
616 int status;
617
618 ci = &ct->c_ctinfo;
619
620 /* store copy of Content-Type line */
621 cp = ct->c_ctline = add (cp, NULL);
622
623 while (isspace ((unsigned char) *cp)) /* trim leading spaces */
624 cp++;
625
626 /* change newlines to spaces */
627 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
628 *dp++ = ' ';
629
630 /* trim trailing spaces */
631 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
632 if (!isspace ((unsigned char) *dp))
633 break;
634 *++dp = '\0';
635
636 if (debugsw)
637 fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
638
639 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
640 &ci->ci_comment) == NOTOK)
641 return NOTOK;
642
643 for (dp = cp; istoken (*dp); dp++)
644 continue;
645 c = *dp, *dp = '\0';
646 ci->ci_type = add (cp, NULL); /* store content type */
647 *dp = c, cp = dp;
648
649 if (!*ci->ci_type) {
650 advise (NULL, "invalid %s: field in message %s (empty type)",
651 TYPE_FIELD, ct->c_file);
652 return NOTOK;
653 }
654
655 /* down case the content type string */
656 for (dp = ci->ci_type; *dp; dp++)
657 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
658 *dp = tolower ((unsigned char) *dp);
659
660 while (isspace ((unsigned char) *cp))
661 cp++;
662
663 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
664 &ci->ci_comment) == NOTOK)
665 return NOTOK;
666
667 if (*cp != '/') {
668 if (!magic)
669 ci->ci_subtype = add ("", NULL);
670 goto magic_skip;
671 }
672
673 cp++;
674 while (isspace ((unsigned char) *cp))
675 cp++;
676
677 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
678 &ci->ci_comment) == NOTOK)
679 return NOTOK;
680
681 for (dp = cp; istoken (*dp); dp++)
682 continue;
683 c = *dp, *dp = '\0';
684 ci->ci_subtype = add (cp, NULL); /* store the content subtype */
685 *dp = c, cp = dp;
686
687 if (!*ci->ci_subtype) {
688 advise (NULL,
689 "invalid %s: field in message %s (empty subtype for \"%s\")",
690 TYPE_FIELD, ct->c_file, ci->ci_type);
691 return NOTOK;
692 }
693
694 /* down case the content subtype string */
695 for (dp = ci->ci_subtype; *dp; dp++)
696 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
697 *dp = tolower ((unsigned char) *dp);
698
699 magic_skip:
700 while (isspace ((unsigned char) *cp))
701 cp++;
702
703 if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
704 &ci->ci_comment) == NOTOK)
705 return NOTOK;
706
707 if ((status = parse_header_attrs (ct->c_file, TYPE_FIELD, &cp,
708 &ci->ci_first_pm, &ci->ci_last_pm,
709 &ci->ci_comment)) != OK) {
710 return status == NOTOK ? NOTOK : OK;
711 }
712
713 /*
714 * Get any <Content-Id> given in buffer
715 */
716 if (magic && *cp == '<') {
717 if (ct->c_id) {
718 free (ct->c_id);
719 ct->c_id = NULL;
720 }
721 if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
722 advise (NULL, "invalid ID in message %s", ct->c_file);
723 return NOTOK;
724 }
725 c = *dp;
726 *dp = '\0';
727 if (*ct->c_id)
728 ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
729 else
730 ct->c_id = NULL;
731 *dp++ = c;
732 cp = dp;
733
734 while (isspace ((unsigned char) *cp))
735 cp++;
736 }
737
738 /*
739 * Get any [Content-Description] given in buffer.
740 */
741 if (magic && *cp == '[') {
742 ct->c_descr = ++cp;
743 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
744 if (*dp == ']')
745 break;
746 if (dp < cp) {
747 advise (NULL, "invalid description in message %s", ct->c_file);
748 ct->c_descr = NULL;
749 return NOTOK;
750 }
751
752 c = *dp;
753 *dp = '\0';
754 if (*ct->c_descr)
755 ct->c_descr = concat (ct->c_descr, "\n", NULL);
756 else
757 ct->c_descr = NULL;
758 *dp++ = c;
759 cp = dp;
760
761 while (isspace ((unsigned char) *cp))
762 cp++;
763 }
764
765 /*
766 * Get any {Content-Disposition} given in buffer.
767 */
768 if (magic && *cp == '{') {
769 ++cp;
770 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
771 if (*dp == '}')
772 break;
773 if (dp < cp) {
774 advise (NULL, "invalid disposition in message %s", ct->c_file);
775 ct->c_dispo = NULL;
776 return NOTOK;
777 }
778
779 c = *dp;
780 *dp = '\0';
781
782 if (get_dispo(cp, ct, 1) != OK)
783 return NOTOK;
784
785 *dp++ = c;
786 cp = dp;
787
788 while (isspace ((unsigned char) *cp))
789 cp++;
790 }
791
792 /*
793 * Get any extension directives (right now just the content transfer
794 * encoding, but maybe others) that we care about.
795 */
796
797 if (magic && *cp == '*') {
798 /*
799 * See if it's a CTE we match on
800 */
801 struct k2v *kv;
802
803 dp = ++cp;
804 while (*cp != '\0' && ! isspace((unsigned char) *cp))
805 cp++;
806
807 if (dp == cp) {
808 advise (NULL, "invalid null transfer encoding specification");
809 return NOTOK;
810 }
811
812 if (*cp != '\0')
813 *cp++ = '\0';
814
815 ct->c_reqencoding = CE_UNKNOWN;
816
817 for (kv = EncodingType; kv->kv_key; kv++) {
818 if (strcasecmp(kv->kv_key, dp) == 0) {
819 ct->c_reqencoding = kv->kv_value;
820 break;
821 }
822 }
823
824 if (ct->c_reqencoding == CE_UNKNOWN) {
825 advise (NULL, "invalid CTE specification: \"%s\"", dp);
826 return NOTOK;
827 }
828
829 while (isspace ((unsigned char) *cp))
830 cp++;
831 }
832
833 /*
834 * Check if anything is left over
835 */
836 if (*cp) {
837 if (magic) {
838 ci->ci_magic = add (cp, NULL);
839
840 /* If there is a Content-Disposition header and it doesn't
841 have a *filename=, extract it from the magic contents.
842 The r1bindex call skips any leading directory
843 components. */
844 if (ct->c_dispo_type &&
845 !get_param(ct->c_dispo_first, "filename", '_', 1)) {
846 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename",
847 r1bindex(ci->ci_magic, '/'), 0);
848 }
849 }
850 else
851 advise (NULL,
852 "extraneous information in message %s's %s: field\n%*s(%s)",
853 ct->c_file, TYPE_FIELD, strlen(invo_name) + 2, "", cp);
854 }
855
856 return OK;
857 }
858
859
860 /*
861 * Parse out a Content-Disposition header. A lot of this is cribbed from
862 * get_ctinfo().
863 */
864 static int
865 get_dispo (char *cp, CT ct, int buildflag)
866 {
867 char *dp, *dispoheader;
868 char c;
869 int status;
870
871 /*
872 * Save the whole copy of the Content-Disposition header, unless we're
873 * processing a mhbuild directive. A NULL c_dispo will be a flag to
874 * mhbuild that the disposition header needs to be generated at that
875 * time.
876 */
877
878 dispoheader = cp = add(cp, NULL);
879
880 while (isspace ((unsigned char) *cp)) /* trim leading spaces */
881 cp++;
882
883 /* change newlines to spaces */
884 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
885 *dp++ = ' ';
886
887 /* trim trailing spaces */
888 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
889 if (!isspace ((unsigned char) *dp))
890 break;
891 *++dp = '\0';
892
893 if (debugsw)
894 fprintf (stderr, "%s: %s\n", DISPO_FIELD, cp);
895
896 if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) ==
897 NOTOK) {
898 free(dispoheader);
899 return NOTOK;
900 }
901
902 for (dp = cp; istoken (*dp); dp++)
903 continue;
904 c = *dp, *dp = '\0';
905 ct->c_dispo_type = add (cp, NULL); /* store disposition type */
906 *dp = c, cp = dp;
907
908 if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) == NOTOK)
909 return NOTOK;
910
911 if ((status = parse_header_attrs (ct->c_file, DISPO_FIELD, &cp,
912 &ct->c_dispo_first, &ct->c_dispo_last,
913 NULL)) != OK) {
914 if (status == NOTOK) {
915 free(dispoheader);
916 return NOTOK;
917 }
918 } else if (*cp) {
919 advise (NULL,
920 "extraneous information in message %s's %s: field\n%*s(%s)",
921 ct->c_file, DISPO_FIELD, strlen(invo_name) + 2, "", cp);
922 }
923
924 if (buildflag)
925 free(dispoheader);
926 else
927 ct->c_dispo = dispoheader;
928
929 return OK;
930 }
931
932
933 static int
934 get_comment (const char *filename, const char *fieldname, char **ap,
935 char **commentp)
936 {
937 int i;
938 char *bp, *cp;
939 char c, buffer[BUFSIZ], *dp;
940
941 cp = *ap;
942 bp = buffer;
943 cp++;
944
945 for (i = 0;;) {
946 switch (c = *cp++) {
947 case '\0':
948 invalid:
949 advise (NULL, "invalid comment in message %s's %s: field",
950 filename, fieldname);
951 return NOTOK;
952
953 case '\\':
954 *bp++ = c;
955 if ((c = *cp++) == '\0')
956 goto invalid;
957 *bp++ = c;
958 continue;
959
960 case '(':
961 i++;
962 /* and fall... */
963 default:
964 *bp++ = c;
965 continue;
966
967 case ')':
968 if (--i < 0)
969 break;
970 *bp++ = c;
971 continue;
972 }
973 break;
974 }
975 *bp = '\0';
976
977 if (commentp) {
978 if ((dp = *commentp)) {
979 *commentp = concat (dp, " ", buffer, NULL);
980 free (dp);
981 } else {
982 *commentp = add (buffer, NULL);
983 }
984 }
985
986 while (isspace ((unsigned char) *cp))
987 cp++;
988
989 *ap = cp;
990 return OK;
991 }
992
993
994 /*
995 * CONTENTS
996 *
997 * Handles content types audio, image, and video.
998 * There's not much to do right here.
999 */
1000
1001 static int
1002 InitGeneric (CT ct)
1003 {
1004 NMH_UNUSED (ct);
1005
1006 return OK; /* not much to do here */
1007 }
1008
1009
1010 /*
1011 * TEXT
1012 */
1013
1014 static int
1015 InitText (CT ct)
1016 {
1017 char buffer[BUFSIZ];
1018 char *chset = NULL;
1019 char *cp;
1020 PM pm;
1021 struct text *t;
1022 CI ci = &ct->c_ctinfo;
1023
1024 /* check for missing subtype */
1025 if (!*ci->ci_subtype)
1026 ci->ci_subtype = add ("plain", ci->ci_subtype);
1027
1028 /* match subtype */
1029 ct->c_subtype = ct_str_subtype (CT_TEXT, ci->ci_subtype);
1030
1031 /* allocate text character set structure */
1032 if ((t = (struct text *) mh_xcalloc (1, sizeof(*t))) == NULL)
1033 adios (NULL, "out of memory");
1034 ct->c_ctparams = (void *) t;
1035
1036 /* scan for charset parameter */
1037 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next)
1038 if (!strcasecmp (pm->pm_name, "charset"))
1039 break;
1040
1041 /* check if content specified a character set */
1042 if (pm) {
1043 chset = pm->pm_value;
1044 t->tx_charset = CHARSET_SPECIFIED;
1045 } else {
1046 t->tx_charset = CHARSET_UNSPECIFIED;
1047 }
1048
1049 /*
1050 * If we can not handle character set natively,
1051 * then check profile for string to modify the
1052 * terminal or display method.
1053 *
1054 * termproc is for mhshow, though mhlist -debug prints it, too.
1055 */
1056 if (chset != NULL && !check_charset (chset, strlen (chset))) {
1057 snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
1058 if ((cp = context_find (buffer)))
1059 ct->c_termproc = getcpy (cp);
1060 }
1061
1062 return OK;
1063 }
1064
1065
1066 /*
1067 * MULTIPART
1068 */
1069
1070 static int
1071 InitMultiPart (CT ct)
1072 {
1073 int inout;
1074 long last, pos;
1075 char *cp, *dp;
1076 PM pm;
1077 char *bp;
1078 char *bufp = NULL;
1079 size_t buflen;
1080 ssize_t gotlen;
1081 struct multipart *m;
1082 struct part *part, **next;
1083 CI ci = &ct->c_ctinfo;
1084 CT p;
1085 FILE *fp;
1086
1087 /*
1088 * The encoding for multipart messages must be either
1089 * 7bit, 8bit, or binary (per RFC2045).
1090 */
1091 if (! skip_mp_cte_check && ct->c_encoding != CE_7BIT &&
1092 ct->c_encoding != CE_8BIT && ct->c_encoding != CE_BINARY) {
1093 /* Copy the Content-Transfer-Encoding header field body so we can
1094 remove any trailing whitespace and leading blanks from it. */
1095 char *cte = add (ct->c_celine ? ct->c_celine : "(null)", NULL);
1096
1097 bp = cte + strlen (cte) - 1;
1098 while (bp >= cte && isspace ((unsigned char) *bp)) *bp-- = '\0';
1099 for (bp = cte; *bp && isblank ((unsigned char) *bp); ++bp) continue;
1100
1101 admonish (NULL,
1102 "\"%s/%s\" type in message %s must be encoded in\n"
1103 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1104 "mhfixmsg -fixcte can fix it, or\n"
1105 "manually edit the file and change the \"%s\"\n"
1106 "Content-Transfer-Encoding to one of those. For now",
1107 ci->ci_type, ci->ci_subtype, ct->c_file, bp);
1108 free (cte);
1109
1110 return NOTOK;
1111 }
1112
1113 /* match subtype */
1114 ct->c_subtype = ct_str_subtype (CT_MULTIPART, ci->ci_subtype);
1115
1116 /*
1117 * Check for "boundary" parameter, which is
1118 * required for multipart messages.
1119 */
1120 bp = 0;
1121 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1122 if (!strcasecmp (pm->pm_name, "boundary")) {
1123 bp = pm->pm_value;
1124 break;
1125 }
1126 }
1127
1128 /* complain if boundary parameter is missing */
1129 if (!pm) {
1130 advise (NULL,
1131 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1132 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1133 return NOTOK;
1134 }
1135
1136 /* allocate primary structure for multipart info */
1137 if ((m = (struct multipart *) mh_xcalloc (1, sizeof(*m))) == NULL)
1138 adios (NULL, "out of memory");
1139 ct->c_ctparams = (void *) m;
1140
1141 /* check if boundary parameter contains only whitespace characters */
1142 for (cp = bp; isspace ((unsigned char) *cp); cp++)
1143 continue;
1144 if (!*cp) {
1145 advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1146 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1147 return NOTOK;
1148 }
1149
1150 /* remove trailing whitespace from boundary parameter */
1151 for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1152 if (!isspace ((unsigned char) *dp))
1153 break;
1154 *++dp = '\0';
1155
1156 /* record boundary separators */
1157 m->mp_start = concat (bp, "\n", NULL);
1158 m->mp_stop = concat (bp, "--\n", NULL);
1159
1160 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1161 advise (ct->c_file, "unable to open for reading");
1162 return NOTOK;
1163 }
1164
1165 fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1166 last = ct->c_end;
1167 next = &m->mp_parts;
1168 part = NULL;
1169 inout = 1;
1170
1171 while ((gotlen = getline(&bufp, &buflen, fp)) != -1) {
1172 if (pos > last)
1173 break;
1174
1175 pos += gotlen;
1176 if (bufp[0] != '-' || bufp[1] != '-')
1177 continue;
1178 if (inout) {
1179 if (strcmp (bufp + 2, m->mp_start))
1180 continue;
1181 next_part:
1182 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
1183 adios (NULL, "out of memory");
1184 *next = part;
1185 next = &part->mp_next;
1186
1187 if (!(p = get_content (fp, ct->c_file,
1188 ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1189 free(bufp);
1190 ct->c_fp = NULL;
1191 return NOTOK;
1192 }
1193 p->c_fp = NULL;
1194 part->mp_part = p;
1195 pos = p->c_begin;
1196 fseek (fp, pos, SEEK_SET);
1197 inout = 0;
1198 } else {
1199 if (strcmp (bufp + 2, m->mp_start) == 0) {
1200 inout = 1;
1201 end_part:
1202 p = part->mp_part;
1203 p->c_end = ftell(fp) - (gotlen + 1);
1204 if (p->c_end < p->c_begin)
1205 p->c_begin = p->c_end;
1206 if (inout)
1207 goto next_part;
1208 goto last_part;
1209 } else {
1210 if (strcmp (bufp + 2, m->mp_stop) == 0)
1211 goto end_part;
1212 }
1213 }
1214 }
1215
1216 if (! suppress_bogus_mp_content_warning) {
1217 advise (NULL, "bogus multipart content in message %s", ct->c_file);
1218 }
1219 bogus_mp_content = 1;
1220
1221 if (!inout && part) {
1222 p = part->mp_part;
1223 p->c_end = ct->c_end;
1224
1225 if (p->c_begin >= p->c_end) {
1226 for (next = &m->mp_parts; *next != part;
1227 next = &((*next)->mp_next))
1228 continue;
1229 *next = NULL;
1230 free_content (p);
1231 free ((char *) part);
1232 }
1233 }
1234
1235 last_part:
1236 /* reverse the order of the parts for multipart/alternative */
1237 if (ct->c_subtype == MULTI_ALTERNATE) {
1238 reverse_parts (ct);
1239 prefer_parts (ct);
1240 }
1241
1242 /*
1243 * label all subparts with part number, and
1244 * then initialize the content of the subpart.
1245 */
1246 {
1247 int partnum;
1248 char *pp;
1249 char partnam[BUFSIZ];
1250
1251 if (ct->c_partno) {
1252 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1253 pp = partnam + strlen (partnam);
1254 } else {
1255 pp = partnam;
1256 }
1257
1258 for (part = m->mp_parts, partnum = 1; part;
1259 part = part->mp_next, partnum++) {
1260 p = part->mp_part;
1261
1262 sprintf (pp, "%d", partnum);
1263 p->c_partno = add (partnam, NULL);
1264
1265 /* initialize the content of the subparts */
1266 if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1267 free(bufp);
1268 fclose (ct->c_fp);
1269 ct->c_fp = NULL;
1270 return NOTOK;
1271 }
1272 }
1273 }
1274
1275 get_leftover_mp_content (ct, 1);
1276 get_leftover_mp_content (ct, 0);
1277
1278 free(bufp);
1279 fclose (ct->c_fp);
1280 ct->c_fp = NULL;
1281 return OK;
1282 }
1283
1284
1285 /*
1286 * reverse the order of the parts of a multipart/alternative,
1287 * presumably to put the "most favored" alternative first, for
1288 * ease of choosing/displaying it later on. from a mail message on
1289 * nmh-workers, from kenh:
1290 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1291 * see code in mhn that did the same thing... Acccording to the RCS
1292 * logs, that code was around from the initial checkin of mhn.c by
1293 * John Romine in 1992, which is as far back as we have."
1294 */
1295 static void
1296 reverse_parts (CT ct)
1297 {
1298 struct multipart *m = (struct multipart *) ct->c_ctparams;
1299 struct part *part;
1300 struct part *next;
1301
1302 /* Reverse the order of its parts by walking the mp_parts list
1303 and pushing each node to the front. */
1304 for (part = m->mp_parts, m->mp_parts = NULL; part; part = next) {
1305 next = part->mp_next;
1306 part->mp_next = m->mp_parts;
1307 m->mp_parts = part;
1308 }
1309 }
1310
1311 static void
1312 move_preferred_part (CT ct, char *type, char *subtype)
1313 {
1314 struct multipart *m = (struct multipart *) ct->c_ctparams;
1315 struct part *part, *prev, *head, *nhead, *ntail;
1316 struct part h, n;
1317 CI ci;
1318
1319 /* move the matching part(s) to the head of the list: walk the
1320 * list of parts, move matching parts to a new list (maintaining
1321 * their order), and finally, concatenate the old list onto the
1322 * new.
1323 */
1324
1325 head = &h;
1326 nhead = &n;
1327
1328 head->mp_next = m->mp_parts;
1329 nhead->mp_next = NULL;
1330 ntail = nhead;
1331
1332 prev = head;
1333 part = head->mp_next;
1334 while (part != NULL) {
1335 ci = &part->mp_part->c_ctinfo;
1336 if (!strcasecmp(ci->ci_type, type) &&
1337 (!subtype || !strcasecmp(ci->ci_subtype, subtype))) {
1338 prev->mp_next = part->mp_next;
1339 part->mp_next = NULL;
1340 ntail->mp_next = part;
1341 ntail = part;
1342 part = prev->mp_next;
1343 } else {
1344 prev = part;
1345 part = prev->mp_next;
1346 }
1347 }
1348 ntail->mp_next = head->mp_next;
1349 m->mp_parts = nhead->mp_next;
1350
1351 }
1352
1353 /*
1354 * move parts that match the user's preferences (-prefer) to the head
1355 * of the line. process preferences in reverse so first one given
1356 * ends up first in line
1357 */
1358 static void
1359 prefer_parts(CT ct)
1360 {
1361 int i;
1362 for (i = npreferred-1; i >= 0; i--)
1363 move_preferred_part(ct, preferred_types[i], preferred_subtypes[i]);
1364 }
1365
1366
1367
1368 /* parse_mime() arranges alternates in reverse (priority) order. This
1369 function can be used to reverse them back. This will put, for
1370 example, a text/plain part before a text/html part in a
1371 multipart/alternative part, for example, where it belongs. */
1372 void
1373 reverse_alternative_parts (CT ct) {
1374 if (ct->c_type == CT_MULTIPART) {
1375 struct multipart *m = (struct multipart *) ct->c_ctparams;
1376 struct part *part;
1377
1378 if (ct->c_subtype == MULTI_ALTERNATE) {
1379 reverse_parts (ct);
1380 }
1381
1382 /* And call recursively on each part of a multipart. */
1383 for (part = m->mp_parts; part; part = part->mp_next) {
1384 reverse_alternative_parts (part->mp_part);
1385 }
1386 }
1387 }
1388
1389
1390 /*
1391 * MESSAGE
1392 */
1393
1394 static int
1395 InitMessage (CT ct)
1396 {
1397 CI ci = &ct->c_ctinfo;
1398
1399 if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1400 admonish (NULL,
1401 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1402 ci->ci_type, ci->ci_subtype, ct->c_file);
1403 return NOTOK;
1404 }
1405
1406 /* check for missing subtype */
1407 if (!*ci->ci_subtype)
1408 ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1409
1410 /* match subtype */
1411 ct->c_subtype = ct_str_subtype (CT_MESSAGE, ci->ci_subtype);
1412
1413 switch (ct->c_subtype) {
1414 case MESSAGE_RFC822:
1415 break;
1416
1417 case MESSAGE_PARTIAL:
1418 {
1419 PM pm;
1420 struct partial *p;
1421
1422 if ((p = (struct partial *) mh_xcalloc (1, sizeof(*p))) == NULL)
1423 adios (NULL, "out of memory");
1424 ct->c_ctparams = (void *) p;
1425
1426 /* scan for parameters "id", "number", and "total" */
1427 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1428 if (!strcasecmp (pm->pm_name, "id")) {
1429 p->pm_partid = add (pm->pm_value, NULL);
1430 continue;
1431 }
1432 if (!strcasecmp (pm->pm_name, "number")) {
1433 if (sscanf (pm->pm_value, "%d", &p->pm_partno) != 1
1434 || p->pm_partno < 1) {
1435 invalid_param:
1436 advise (NULL,
1437 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1438 pm->pm_name, ci->ci_type, ci->ci_subtype,
1439 ct->c_file, TYPE_FIELD);
1440 return NOTOK;
1441 }
1442 continue;
1443 }
1444 if (!strcasecmp (pm->pm_name, "total")) {
1445 if (sscanf (pm->pm_value, "%d", &p->pm_maxno) != 1
1446 || p->pm_maxno < 1)
1447 goto invalid_param;
1448 continue;
1449 }
1450 }
1451
1452 if (!p->pm_partid
1453 || !p->pm_partno
1454 || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1455 advise (NULL,
1456 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1457 ci->ci_type, ci->ci_subtype,
1458 ct->c_file, TYPE_FIELD);
1459 return NOTOK;
1460 }
1461 }
1462 break;
1463
1464 case MESSAGE_EXTERNAL:
1465 {
1466 int exresult;
1467 struct exbody *e;
1468 CT p;
1469 FILE *fp;
1470
1471 if ((e = (struct exbody *) mh_xcalloc (1, sizeof(*e))) == NULL)
1472 adios (NULL, "out of memory");
1473 ct->c_ctparams = (void *) e;
1474
1475 if (!ct->c_fp
1476 && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1477 advise (ct->c_file, "unable to open for reading");
1478 return NOTOK;
1479 }
1480
1481 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1482
1483 if (!(p = get_content (fp, ct->c_file, 0))) {
1484 ct->c_fp = NULL;
1485 return NOTOK;
1486 }
1487
1488 e->eb_parent = ct;
1489 e->eb_content = p;
1490 p->c_ctexbody = e;
1491 p->c_ceopenfnx = NULL;
1492 if ((exresult = params_external (ct, 0)) != NOTOK
1493 && p->c_ceopenfnx == openMail) {
1494 int cc, size;
1495 char *bp;
1496
1497 if ((size = ct->c_end - p->c_begin) <= 0) {
1498 if (!e->eb_subject)
1499 content_error (NULL, ct,
1500 "empty body for access-type=mail-server");
1501 goto no_body;
1502 }
1503
1504 e->eb_body = bp = mh_xmalloc ((unsigned) size);
1505 fseek (p->c_fp, p->c_begin, SEEK_SET);
1506 while (size > 0)
1507 switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1508 case NOTOK:
1509 adios ("failed", "fread");
1510
1511 case OK:
1512 adios (NULL, "unexpected EOF from fread");
1513
1514 default:
1515 bp += cc, size -= cc;
1516 break;
1517 }
1518 *bp = 0;
1519 }
1520 no_body:
1521 p->c_fp = NULL;
1522 p->c_end = p->c_begin;
1523
1524 fclose (ct->c_fp);
1525 ct->c_fp = NULL;
1526
1527 if (exresult == NOTOK)
1528 return NOTOK;
1529 if (e->eb_flags == NOTOK)
1530 return OK;
1531
1532 switch (p->c_type) {
1533 case CT_MULTIPART:
1534 break;
1535
1536 case CT_MESSAGE:
1537 if (p->c_subtype != MESSAGE_RFC822)
1538 break;
1539 /* else fall... */
1540 default:
1541 e->eb_partno = ct->c_partno;
1542 if (p->c_ctinitfnx)
1543 (*p->c_ctinitfnx) (p);
1544 break;
1545 }
1546 }
1547 break;
1548
1549 default:
1550 break;
1551 }
1552
1553 return OK;
1554 }
1555
1556
1557 int
1558 params_external (CT ct, int composing)
1559 {
1560 PM pm;
1561 struct exbody *e = (struct exbody *) ct->c_ctparams;
1562 CI ci = &ct->c_ctinfo;
1563
1564 ct->c_ceopenfnx = NULL;
1565 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1566 if (!strcasecmp (pm->pm_name, "access-type")) {
1567 struct str2init *s2i;
1568 CT p = e->eb_content;
1569
1570 for (s2i = str2methods; s2i->si_key; s2i++)
1571 if (!strcasecmp (pm->pm_value, s2i->si_key))
1572 break;
1573 if (!s2i->si_key) {
1574 e->eb_access = pm->pm_value;
1575 e->eb_flags = NOTOK;
1576 p->c_encoding = CE_EXTERNAL;
1577 continue;
1578 }
1579 e->eb_access = s2i->si_key;
1580 e->eb_flags = s2i->si_val;
1581 p->c_encoding = CE_EXTERNAL;
1582
1583 /* Call the Init function for this external type */
1584 if ((*s2i->si_init)(p) == NOTOK)
1585 return NOTOK;
1586 continue;
1587 }
1588 if (!strcasecmp (pm->pm_name, "name")) {
1589 e->eb_name = pm->pm_value;
1590 continue;
1591 }
1592 if (!strcasecmp (pm->pm_name, "permission")) {
1593 e->eb_permission = pm->pm_value;
1594 continue;
1595 }
1596 if (!strcasecmp (pm->pm_name, "site")) {
1597 e->eb_site = pm->pm_value;
1598 continue;
1599 }
1600 if (!strcasecmp (pm->pm_name, "directory")) {
1601 e->eb_dir = pm->pm_value;
1602 continue;
1603 }
1604 if (!strcasecmp (pm->pm_name, "mode")) {
1605 e->eb_mode = pm->pm_value;
1606 continue;
1607 }
1608 if (!strcasecmp (pm->pm_name, "size")) {
1609 sscanf (pm->pm_value, "%lu", &e->eb_size);
1610 continue;
1611 }
1612 if (!strcasecmp (pm->pm_name, "server")) {
1613 e->eb_server = pm->pm_value;
1614 continue;
1615 }
1616 if (!strcasecmp (pm->pm_name, "subject")) {
1617 e->eb_subject = pm->pm_value;
1618 continue;
1619 }
1620 if (!strcasecmp (pm->pm_name, "url")) {
1621 /*
1622 * According to RFC 2017, we have to remove all whitespace from
1623 * the URL
1624 */
1625
1626 char *u, *p = pm->pm_value;
1627 e->eb_url = u = mh_xmalloc(strlen(pm->pm_value) + 1);
1628
1629 for (; *p != '\0'; p++) {
1630 if (! isspace((unsigned char) *p))
1631 *u++ = *p;
1632 }
1633
1634 *u = '\0';
1635 continue;
1636 }
1637 if (composing && !strcasecmp (pm->pm_name, "body")) {
1638 e->eb_body = getcpy (pm->pm_value);
1639 continue;
1640 }
1641 }
1642
1643 if (!e->eb_access) {
1644 advise (NULL,
1645 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1646 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1647 return NOTOK;
1648 }
1649
1650 return OK;
1651 }
1652
1653
1654 /*
1655 * APPLICATION
1656 */
1657
1658 static int
1659 InitApplication (CT ct)
1660 {
1661 CI ci = &ct->c_ctinfo;
1662
1663 /* match subtype */
1664 ct->c_subtype = ct_str_subtype (CT_APPLICATION, ci->ci_subtype);
1665
1666 return OK;
1667 }
1668
1669
1670 /*
1671 * TRANSFER ENCODINGS
1672 */
1673
1674 static int
1675 init_encoding (CT ct, OpenCEFunc openfnx)
1676 {
1677 ct->c_ceopenfnx = openfnx;
1678 ct->c_ceclosefnx = close_encoding;
1679 ct->c_cesizefnx = size_encoding;
1680
1681 return OK;
1682 }
1683
1684
1685 void
1686 close_encoding (CT ct)
1687 {
1688 CE ce = &ct->c_cefile;
1689
1690 if (ce->ce_fp) {
1691 fclose (ce->ce_fp);
1692 ce->ce_fp = NULL;
1693 }
1694 }
1695
1696
1697 static unsigned long
1698 size_encoding (CT ct)
1699 {
1700 int fd;
1701 unsigned long size;
1702 char *file;
1703 CE ce = &ct->c_cefile;
1704 struct stat st;
1705
1706 if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1707 return (long) st.st_size;
1708
1709 if (ce->ce_file) {
1710 if (stat (ce->ce_file, &st) != NOTOK)
1711 return (long) st.st_size;
1712 else
1713 return 0L;
1714 }
1715
1716 if (ct->c_encoding == CE_EXTERNAL)
1717 return (ct->c_end - ct->c_begin);
1718
1719 file = NULL;
1720 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1721 return (ct->c_end - ct->c_begin);
1722
1723 if (fstat (fd, &st) != NOTOK)
1724 size = (long) st.st_size;
1725 else
1726 size = 0L;
1727
1728 (*ct->c_ceclosefnx) (ct);
1729 return size;
1730 }
1731
1732
1733 /*
1734 * BASE64
1735 */
1736
1737 static int
1738 InitBase64 (CT ct)
1739 {
1740 return init_encoding (ct, openBase64);
1741 }
1742
1743
1744 static int
1745 openBase64 (CT ct, char **file)
1746 {
1747 ssize_t cc, len;
1748 int fd, own_ct_fp = 0;
1749 char *cp, *buffer = NULL;
1750 /* sbeck -- handle suffixes */
1751 CI ci;
1752 CE ce = &ct->c_cefile;
1753 unsigned char *decoded;
1754 size_t decoded_len;
1755 unsigned char digest[16];
1756
1757 if (ce->ce_fp) {
1758 fseek (ce->ce_fp, 0L, SEEK_SET);
1759 goto ready_to_go;
1760 }
1761
1762 if (ce->ce_file) {
1763 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1764 content_error (ce->ce_file, ct, "unable to fopen for reading");
1765 return NOTOK;
1766 }
1767 goto ready_to_go;
1768 }
1769
1770 if (*file == NULL) {
1771 ce->ce_unlink = 1;
1772 } else {
1773 ce->ce_file = add (*file, NULL);
1774 ce->ce_unlink = 0;
1775 }
1776
1777 /* sbeck@cise.ufl.edu -- handle suffixes */
1778 ci = &ct->c_ctinfo;
1779 if ((cp = context_find_by_type ("suffix", ci->ci_type, ci->ci_subtype))) {
1780 if (ce->ce_unlink) {
1781 /* Create temporary file with filename extension. */
1782 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
1783 adios(NULL, "unable to create temporary file in %s",
1784 get_temp_dir());
1785 }
1786 } else {
1787 ce->ce_file = add (cp, ce->ce_file);
1788 }
1789 } else if (*file == NULL) {
1790 char *tempfile;
1791 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1792 adios(NULL, "unable to create temporary file in %s",
1793 get_temp_dir());
1794 }
1795 ce->ce_file = add (tempfile, NULL);
1796 }
1797
1798 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1799 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1800 return NOTOK;
1801 }
1802
1803 if ((len = ct->c_end - ct->c_begin) < 0)
1804 adios (NULL, "internal error(1)");
1805
1806 buffer = mh_xmalloc (len + 1);
1807
1808 if (! ct->c_fp) {
1809 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1810 content_error (ct->c_file, ct, "unable to open for reading");
1811 return NOTOK;
1812 }
1813 own_ct_fp = 1;
1814 }
1815
1816 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1817 cp = buffer;
1818 while (len > 0) {
1819 switch (cc = read (fd, cp, len)) {
1820 case NOTOK:
1821 content_error (ct->c_file, ct, "error reading from");
1822 goto clean_up;
1823
1824 case OK:
1825 content_error (NULL, ct, "premature eof");
1826 goto clean_up;
1827
1828 default:
1829 if (cc > len)
1830 cc = len;
1831 len -= cc;
1832 cp += cc;
1833 }
1834 }
1835
1836 /* decodeBase64() requires null-terminated input. */
1837 *cp = '\0';
1838
1839 if (decodeBase64 (buffer, &decoded, &decoded_len, ct->c_type == CT_TEXT,
1840 ct->c_digested ? digest : NULL) == OK) {
1841 size_t i;
1842 unsigned char *decoded_p = decoded;
1843 for (i = 0; i < decoded_len; ++i) {
1844 putc (*decoded_p++, ce->ce_fp);
1845 }
1846 free ((char *) decoded);
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 unsigned 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 ((char *) 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 ((char *) 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 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 }