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