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