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