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