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