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