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