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