]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Documented steps to build RPM from tarball, without using autogen.sh.
[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 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 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 * MESSAGE
1299 */
1300
1301 static int
1302 InitMessage (CT ct)
1303 {
1304 struct k2v *kv;
1305 CI ci = &ct->c_ctinfo;
1306
1307 if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1308 admonish (NULL,
1309 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1310 ci->ci_type, ci->ci_subtype, ct->c_file);
1311 return NOTOK;
1312 }
1313
1314 /* check for missing subtype */
1315 if (!*ci->ci_subtype)
1316 ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1317
1318 /* match subtype */
1319 for (kv = SubMessage; kv->kv_key; kv++)
1320 if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1321 break;
1322 ct->c_subtype = kv->kv_value;
1323
1324 switch (ct->c_subtype) {
1325 case MESSAGE_RFC822:
1326 break;
1327
1328 case MESSAGE_PARTIAL:
1329 {
1330 PM pm;
1331 struct partial *p;
1332
1333 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1334 adios (NULL, "out of memory");
1335 ct->c_ctparams = (void *) p;
1336
1337 /* scan for parameters "id", "number", and "total" */
1338 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1339 if (!strcasecmp (pm->pm_name, "id")) {
1340 p->pm_partid = add (pm->pm_value, NULL);
1341 continue;
1342 }
1343 if (!strcasecmp (pm->pm_name, "number")) {
1344 if (sscanf (pm->pm_value, "%d", &p->pm_partno) != 1
1345 || p->pm_partno < 1) {
1346 invalid_param:
1347 advise (NULL,
1348 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1349 pm->pm_name, ci->ci_type, ci->ci_subtype,
1350 ct->c_file, TYPE_FIELD);
1351 return NOTOK;
1352 }
1353 continue;
1354 }
1355 if (!strcasecmp (pm->pm_name, "total")) {
1356 if (sscanf (pm->pm_value, "%d", &p->pm_maxno) != 1
1357 || p->pm_maxno < 1)
1358 goto invalid_param;
1359 continue;
1360 }
1361 }
1362
1363 if (!p->pm_partid
1364 || !p->pm_partno
1365 || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1366 advise (NULL,
1367 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1368 ci->ci_type, ci->ci_subtype,
1369 ct->c_file, TYPE_FIELD);
1370 return NOTOK;
1371 }
1372 }
1373 break;
1374
1375 case MESSAGE_EXTERNAL:
1376 {
1377 int exresult;
1378 struct exbody *e;
1379 CT p;
1380 FILE *fp;
1381
1382 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1383 adios (NULL, "out of memory");
1384 ct->c_ctparams = (void *) e;
1385
1386 if (!ct->c_fp
1387 && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1388 advise (ct->c_file, "unable to open for reading");
1389 return NOTOK;
1390 }
1391
1392 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1393
1394 if (!(p = get_content (fp, ct->c_file, 0))) {
1395 ct->c_fp = NULL;
1396 return NOTOK;
1397 }
1398
1399 e->eb_parent = ct;
1400 e->eb_content = p;
1401 p->c_ctexbody = e;
1402 p->c_ceopenfnx = NULL;
1403 if ((exresult = params_external (ct, 0)) != NOTOK
1404 && p->c_ceopenfnx == openMail) {
1405 int cc, size;
1406 char *bp;
1407
1408 if ((size = ct->c_end - p->c_begin) <= 0) {
1409 if (!e->eb_subject)
1410 content_error (NULL, ct,
1411 "empty body for access-type=mail-server");
1412 goto no_body;
1413 }
1414
1415 e->eb_body = bp = mh_xmalloc ((unsigned) size);
1416 fseek (p->c_fp, p->c_begin, SEEK_SET);
1417 while (size > 0)
1418 switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1419 case NOTOK:
1420 adios ("failed", "fread");
1421
1422 case OK:
1423 adios (NULL, "unexpected EOF from fread");
1424
1425 default:
1426 bp += cc, size -= cc;
1427 break;
1428 }
1429 *bp = 0;
1430 }
1431 no_body:
1432 p->c_fp = NULL;
1433 p->c_end = p->c_begin;
1434
1435 fclose (ct->c_fp);
1436 ct->c_fp = NULL;
1437
1438 if (exresult == NOTOK)
1439 return NOTOK;
1440 if (e->eb_flags == NOTOK)
1441 return OK;
1442
1443 switch (p->c_type) {
1444 case CT_MULTIPART:
1445 break;
1446
1447 case CT_MESSAGE:
1448 if (p->c_subtype != MESSAGE_RFC822)
1449 break;
1450 /* else fall... */
1451 default:
1452 e->eb_partno = ct->c_partno;
1453 if (p->c_ctinitfnx)
1454 (*p->c_ctinitfnx) (p);
1455 break;
1456 }
1457 }
1458 break;
1459
1460 default:
1461 break;
1462 }
1463
1464 return OK;
1465 }
1466
1467
1468 int
1469 params_external (CT ct, int composing)
1470 {
1471 PM pm;
1472 struct exbody *e = (struct exbody *) ct->c_ctparams;
1473 CI ci = &ct->c_ctinfo;
1474
1475 ct->c_ceopenfnx = NULL;
1476 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1477 if (!strcasecmp (pm->pm_name, "access-type")) {
1478 struct str2init *s2i;
1479 CT p = e->eb_content;
1480
1481 for (s2i = str2methods; s2i->si_key; s2i++)
1482 if (!strcasecmp (pm->pm_value, s2i->si_key))
1483 break;
1484 if (!s2i->si_key) {
1485 e->eb_access = pm->pm_value;
1486 e->eb_flags = NOTOK;
1487 p->c_encoding = CE_EXTERNAL;
1488 continue;
1489 }
1490 e->eb_access = s2i->si_key;
1491 e->eb_flags = s2i->si_val;
1492 p->c_encoding = CE_EXTERNAL;
1493
1494 /* Call the Init function for this external type */
1495 if ((*s2i->si_init)(p) == NOTOK)
1496 return NOTOK;
1497 continue;
1498 }
1499 if (!strcasecmp (pm->pm_name, "name")) {
1500 e->eb_name = pm->pm_value;
1501 continue;
1502 }
1503 if (!strcasecmp (pm->pm_name, "permission")) {
1504 e->eb_permission = pm->pm_value;
1505 continue;
1506 }
1507 if (!strcasecmp (pm->pm_name, "site")) {
1508 e->eb_site = pm->pm_value;
1509 continue;
1510 }
1511 if (!strcasecmp (pm->pm_name, "directory")) {
1512 e->eb_dir = pm->pm_value;
1513 continue;
1514 }
1515 if (!strcasecmp (pm->pm_name, "mode")) {
1516 e->eb_mode = pm->pm_value;
1517 continue;
1518 }
1519 if (!strcasecmp (pm->pm_name, "size")) {
1520 sscanf (pm->pm_value, "%lu", &e->eb_size);
1521 continue;
1522 }
1523 if (!strcasecmp (pm->pm_name, "server")) {
1524 e->eb_server = pm->pm_value;
1525 continue;
1526 }
1527 if (!strcasecmp (pm->pm_name, "subject")) {
1528 e->eb_subject = pm->pm_value;
1529 continue;
1530 }
1531 if (!strcasecmp (pm->pm_name, "url")) {
1532 /*
1533 * According to RFC 2017, we have to remove all whitespace from
1534 * the URL
1535 */
1536
1537 char *u, *p = pm->pm_value;
1538 e->eb_url = u = mh_xmalloc(strlen(pm->pm_value) + 1);
1539
1540 for (; *p != '\0'; p++) {
1541 if (! isspace((unsigned char) *p))
1542 *u++ = *p;
1543 }
1544
1545 *u = '\0';
1546 continue;
1547 }
1548 if (composing && !strcasecmp (pm->pm_name, "body")) {
1549 e->eb_body = getcpy (pm->pm_value);
1550 continue;
1551 }
1552 }
1553
1554 if (!e->eb_access) {
1555 advise (NULL,
1556 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1557 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1558 return NOTOK;
1559 }
1560
1561 return OK;
1562 }
1563
1564
1565 /*
1566 * APPLICATION
1567 */
1568
1569 static int
1570 InitApplication (CT ct)
1571 {
1572 struct k2v *kv;
1573 CI ci = &ct->c_ctinfo;
1574
1575 /* match subtype */
1576 for (kv = SubApplication; kv->kv_key; kv++)
1577 if (!strcasecmp (ci->ci_subtype, kv->kv_key))
1578 break;
1579 ct->c_subtype = kv->kv_value;
1580
1581 return OK;
1582 }
1583
1584
1585 /*
1586 * TRANSFER ENCODINGS
1587 */
1588
1589 static int
1590 init_encoding (CT ct, OpenCEFunc openfnx)
1591 {
1592 ct->c_ceopenfnx = openfnx;
1593 ct->c_ceclosefnx = close_encoding;
1594 ct->c_cesizefnx = size_encoding;
1595
1596 return OK;
1597 }
1598
1599
1600 void
1601 close_encoding (CT ct)
1602 {
1603 CE ce = &ct->c_cefile;
1604
1605 if (ce->ce_fp) {
1606 fclose (ce->ce_fp);
1607 ce->ce_fp = NULL;
1608 }
1609 }
1610
1611
1612 static unsigned long
1613 size_encoding (CT ct)
1614 {
1615 int fd;
1616 unsigned long size;
1617 char *file;
1618 CE ce = &ct->c_cefile;
1619 struct stat st;
1620
1621 if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1622 return (long) st.st_size;
1623
1624 if (ce->ce_file) {
1625 if (stat (ce->ce_file, &st) != NOTOK)
1626 return (long) st.st_size;
1627 else
1628 return 0L;
1629 }
1630
1631 if (ct->c_encoding == CE_EXTERNAL)
1632 return (ct->c_end - ct->c_begin);
1633
1634 file = NULL;
1635 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1636 return (ct->c_end - ct->c_begin);
1637
1638 if (fstat (fd, &st) != NOTOK)
1639 size = (long) st.st_size;
1640 else
1641 size = 0L;
1642
1643 (*ct->c_ceclosefnx) (ct);
1644 return size;
1645 }
1646
1647
1648 /*
1649 * BASE64
1650 */
1651
1652 static unsigned char b642nib[0x80] = {
1653 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1654 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1655 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1656 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1657 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1658 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1659 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1660 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1661 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1662 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1663 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1664 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1665 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1666 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1667 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1668 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1669 };
1670
1671
1672 static int
1673 InitBase64 (CT ct)
1674 {
1675 return init_encoding (ct, openBase64);
1676 }
1677
1678
1679 static int
1680 openBase64 (CT ct, char **file)
1681 {
1682 int bitno, cc, digested;
1683 int fd, len, skip, own_ct_fp = 0, text = ct->c_type == CT_TEXT;
1684 uint32_t bits;
1685 unsigned char value, b;
1686 char *cp, *ep, buffer[BUFSIZ];
1687 /* sbeck -- handle suffixes */
1688 CI ci;
1689 CE ce = &ct->c_cefile;
1690 MD5_CTX mdContext;
1691
1692 if (ce->ce_fp) {
1693 fseek (ce->ce_fp, 0L, SEEK_SET);
1694 goto ready_to_go;
1695 }
1696
1697 if (ce->ce_file) {
1698 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1699 content_error (ce->ce_file, ct, "unable to fopen for reading");
1700 return NOTOK;
1701 }
1702 goto ready_to_go;
1703 }
1704
1705 if (*file == NULL) {
1706 ce->ce_unlink = 1;
1707 } else {
1708 ce->ce_file = add (*file, NULL);
1709 ce->ce_unlink = 0;
1710 }
1711
1712 /* sbeck@cise.ufl.edu -- handle suffixes */
1713 ci = &ct->c_ctinfo;
1714 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1715 invo_name, ci->ci_type, ci->ci_subtype);
1716 cp = context_find (buffer);
1717 if (cp == NULL || *cp == '\0') {
1718 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1719 ci->ci_type);
1720 cp = context_find (buffer);
1721 }
1722 if (cp != NULL && *cp != '\0') {
1723 if (ce->ce_unlink) {
1724 /* Create temporary file with filename extension. */
1725 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
1726 adios(NULL, "unable to create temporary file in %s",
1727 get_temp_dir());
1728 }
1729 } else {
1730 ce->ce_file = add (cp, ce->ce_file);
1731 }
1732 } else if (*file == NULL) {
1733 char *tempfile;
1734 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1735 adios(NULL, "unable to create temporary file in %s",
1736 get_temp_dir());
1737 }
1738 ce->ce_file = add (tempfile, NULL);
1739 }
1740
1741 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1742 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1743 return NOTOK;
1744 }
1745
1746 if ((len = ct->c_end - ct->c_begin) < 0)
1747 adios (NULL, "internal error(1)");
1748
1749 if (! ct->c_fp) {
1750 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1751 content_error (ct->c_file, ct, "unable to open for reading");
1752 return NOTOK;
1753 }
1754 own_ct_fp = 1;
1755 }
1756
1757 if ((digested = ct->c_digested))
1758 MD5Init (&mdContext);
1759
1760 bitno = 18;
1761 bits = 0L;
1762 skip = 0;
1763
1764 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1765 while (len > 0) {
1766 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1767 case NOTOK:
1768 content_error (ct->c_file, ct, "error reading from");
1769 goto clean_up;
1770
1771 case OK:
1772 content_error (NULL, ct, "premature eof");
1773 goto clean_up;
1774
1775 default:
1776 if (cc > len)
1777 cc = len;
1778 len -= cc;
1779
1780 for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1781 switch (*cp) {
1782 default:
1783 if (isspace ((unsigned char) *cp))
1784 break;
1785 if (skip || (((unsigned char) *cp) & 0x80)
1786 || (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) {
1787 if (debugsw) {
1788 fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1789 (unsigned char) *cp,
1790 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1791 skip);
1792 }
1793 content_error (NULL, ct,
1794 "invalid BASE64 encoding -- continuing");
1795 continue;
1796 }
1797
1798 bits |= value << bitno;
1799 test_end:
1800 if ((bitno -= 6) < 0) {
1801 b = (bits >> 16) & 0xff;
1802 if (!text || b != '\r')
1803 putc ((char) b, ce->ce_fp);
1804 if (digested)
1805 MD5Update (&mdContext, &b, 1);
1806 if (skip < 2) {
1807 b = (bits >> 8) & 0xff;
1808 if (! text || b != '\r')
1809 putc ((char) b, ce->ce_fp);
1810 if (digested)
1811 MD5Update (&mdContext, &b, 1);
1812 if (skip < 1) {
1813 b = bits & 0xff;
1814 if (! text || b != '\r')
1815 putc ((char) b, ce->ce_fp);
1816 if (digested)
1817 MD5Update (&mdContext, &b, 1);
1818 }
1819 }
1820
1821 if (ferror (ce->ce_fp)) {
1822 content_error (ce->ce_file, ct,
1823 "error writing to");
1824 goto clean_up;
1825 }
1826 bitno = 18, bits = 0L, skip = 0;
1827 }
1828 break;
1829
1830 case '=':
1831 if (++skip > 3)
1832 goto self_delimiting;
1833 goto test_end;
1834 }
1835 }
1836 }
1837 }
1838
1839 if (bitno != 18) {
1840 if (debugsw)
1841 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1842
1843 content_error (NULL, ct, "invalid BASE64 encoding");
1844 goto clean_up;
1845 }
1846
1847 self_delimiting:
1848 fseek (ct->c_fp, 0L, SEEK_SET);
1849
1850 if (fflush (ce->ce_fp)) {
1851 content_error (ce->ce_file, ct, "error writing to");
1852 goto clean_up;
1853 }
1854
1855 if (digested) {
1856 unsigned char digest[16];
1857
1858 MD5Final (digest, &mdContext);
1859 if (memcmp((char *) digest, (char *) ct->c_digest,
1860 sizeof(digest) / sizeof(digest[0])))
1861 content_error (NULL, ct,
1862 "content integrity suspect (digest mismatch) -- continuing");
1863 else
1864 if (debugsw)
1865 fprintf (stderr, "content integrity confirmed\n");
1866 }
1867
1868 fseek (ce->ce_fp, 0L, SEEK_SET);
1869
1870 ready_to_go:
1871 *file = ce->ce_file;
1872 if (own_ct_fp) {
1873 fclose (ct->c_fp);
1874 ct->c_fp = NULL;
1875 }
1876 return fileno (ce->ce_fp);
1877
1878 clean_up:
1879 if (own_ct_fp) {
1880 fclose (ct->c_fp);
1881 ct->c_fp = NULL;
1882 }
1883 free_encoding (ct, 0);
1884 return NOTOK;
1885 }
1886
1887
1888 /*
1889 * QUOTED PRINTABLE
1890 */
1891
1892 static char hex2nib[0x80] = {
1893 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1895 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1896 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1900 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1909 };
1910
1911
1912 static int
1913 InitQuoted (CT ct)
1914 {
1915 return init_encoding (ct, openQuoted);
1916 }
1917
1918
1919 static int
1920 openQuoted (CT ct, char **file)
1921 {
1922 int cc, digested, len, quoted, own_ct_fp = 0;
1923 char *cp, *ep;
1924 char buffer[BUFSIZ];
1925 char *bufp = NULL;
1926 size_t buflen;
1927 ssize_t gotlen;
1928 unsigned char mask;
1929 CE ce = &ct->c_cefile;
1930 /* sbeck -- handle suffixes */
1931 CI ci;
1932 MD5_CTX mdContext;
1933
1934 if (ce->ce_fp) {
1935 fseek (ce->ce_fp, 0L, SEEK_SET);
1936 goto ready_to_go;
1937 }
1938
1939 if (ce->ce_file) {
1940 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1941 content_error (ce->ce_file, ct, "unable to fopen for reading");
1942 return NOTOK;
1943 }
1944 goto ready_to_go;
1945 }
1946
1947 if (*file == NULL) {
1948 ce->ce_unlink = 1;
1949 } else {
1950 ce->ce_file = add (*file, NULL);
1951 ce->ce_unlink = 0;
1952 }
1953
1954 /* sbeck@cise.ufl.edu -- handle suffixes */
1955 ci = &ct->c_ctinfo;
1956 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1957 invo_name, ci->ci_type, ci->ci_subtype);
1958 cp = context_find (buffer);
1959 if (cp == NULL || *cp == '\0') {
1960 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1961 ci->ci_type);
1962 cp = context_find (buffer);
1963 }
1964 if (cp != NULL && *cp != '\0') {
1965 if (ce->ce_unlink) {
1966 /* Create temporary file with filename extension. */
1967 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
1968 adios(NULL, "unable to create temporary file in %s",
1969 get_temp_dir());
1970 }
1971 } else {
1972 ce->ce_file = add (cp, ce->ce_file);
1973 }
1974 } else if (*file == NULL) {
1975 char *tempfile;
1976 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1977 adios(NULL, "unable to create temporary file in %s",
1978 get_temp_dir());
1979 }
1980 ce->ce_file = add (tempfile, NULL);
1981 }
1982
1983 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1984 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1985 return NOTOK;
1986 }
1987
1988 if ((len = ct->c_end - ct->c_begin) < 0)
1989 adios (NULL, "internal error(2)");
1990
1991 if (! ct->c_fp) {
1992 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1993 content_error (ct->c_file, ct, "unable to open for reading");
1994 return NOTOK;
1995 }
1996 own_ct_fp = 1;
1997 }
1998
1999 if ((digested = ct->c_digested))
2000 MD5Init (&mdContext);
2001
2002 quoted = 0;
2003 #ifdef lint
2004 mask = 0;
2005 #endif
2006
2007 fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2008 while (len > 0) {
2009 if ((gotlen = getline(&bufp, &buflen, ct->c_fp)) == -1) {
2010 content_error (NULL, ct, "premature eof");
2011 goto clean_up;
2012 }
2013
2014 if ((cc = gotlen) > len)
2015 cc = len;
2016 len -= cc;
2017
2018 for (ep = (cp = bufp) + cc - 1; cp <= ep; ep--)
2019 if (!isspace ((unsigned char) *ep))
2020 break;
2021 *++ep = '\n', ep++;
2022
2023 for (; cp < ep; cp++) {
2024 if (quoted > 0) {
2025 /* in an escape sequence */
2026 if (quoted == 1) {
2027 /* at byte 1 of an escape sequence */
2028 mask = hex2nib[((unsigned char) *cp) & 0x7f];
2029 /* next is byte 2 */
2030 quoted = 2;
2031 } else {
2032 /* at byte 2 of an escape sequence */
2033 mask <<= 4;
2034 mask |= hex2nib[((unsigned char) *cp) & 0x7f];
2035 putc (mask, ce->ce_fp);
2036 if (digested)
2037 MD5Update (&mdContext, &mask, 1);
2038 if (ferror (ce->ce_fp)) {
2039 content_error (ce->ce_file, ct, "error writing to");
2040 goto clean_up;
2041 }
2042 /* finished escape sequence; next may be literal or a new
2043 * escape sequence */
2044 quoted = 0;
2045 }
2046 /* on to next byte */
2047 continue;
2048 }
2049
2050 /* not in an escape sequence */
2051 if (*cp == '=') {
2052 /* starting an escape sequence, or invalid '='? */
2053 if (cp + 1 < ep && cp[1] == '\n') {
2054 /* "=\n" soft line break, eat the \n */
2055 cp++;
2056 continue;
2057 }
2058 if (cp + 1 >= ep || cp + 2 >= ep) {
2059 /* We don't have 2 bytes left, so this is an invalid
2060 * escape sequence; just show the raw bytes (below). */
2061 } else if (isxdigit ((unsigned char) cp[1]) &&
2062 isxdigit ((unsigned char) cp[2])) {
2063 /* Next 2 bytes are hex digits, making this a valid escape
2064 * sequence; let's decode it (above). */
2065 quoted = 1;
2066 continue;
2067 } else {
2068 /* One or both of the next 2 is out of range, making this
2069 * an invalid escape sequence; just show the raw bytes
2070 * (below). */
2071 }
2072 }
2073
2074 /* Just show the raw byte. */
2075 putc (*cp, ce->ce_fp);
2076 if (digested) {
2077 if (*cp == '\n') {
2078 MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2079 } else {
2080 MD5Update (&mdContext, (unsigned char *) cp, 1);
2081 }
2082 }
2083 if (ferror (ce->ce_fp)) {
2084 content_error (ce->ce_file, ct, "error writing to");
2085 goto clean_up;
2086 }
2087 }
2088 }
2089 if (quoted) {
2090 content_error (NULL, ct,
2091 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2092 goto clean_up;
2093 }
2094
2095 fseek (ct->c_fp, 0L, SEEK_SET);
2096
2097 if (fflush (ce->ce_fp)) {
2098 content_error (ce->ce_file, ct, "error writing to");
2099 goto clean_up;
2100 }
2101
2102 if (digested) {
2103 unsigned char digest[16];
2104
2105 MD5Final (digest, &mdContext);
2106 if (memcmp((char *) digest, (char *) ct->c_digest,
2107 sizeof(digest) / sizeof(digest[0])))
2108 content_error (NULL, ct,
2109 "content integrity suspect (digest mismatch) -- continuing");
2110 else
2111 if (debugsw)
2112 fprintf (stderr, "content integrity confirmed\n");
2113 }
2114
2115 fseek (ce->ce_fp, 0L, SEEK_SET);
2116
2117 ready_to_go:
2118 *file = ce->ce_file;
2119 if (own_ct_fp) {
2120 fclose (ct->c_fp);
2121 ct->c_fp = NULL;
2122 }
2123 return fileno (ce->ce_fp);
2124
2125 clean_up:
2126 free_encoding (ct, 0);
2127 if (own_ct_fp) {
2128 fclose (ct->c_fp);
2129 ct->c_fp = NULL;
2130 }
2131 return NOTOK;
2132 }
2133
2134
2135 /*
2136 * 7BIT
2137 */
2138
2139 static int
2140 Init7Bit (CT ct)
2141 {
2142 if (init_encoding (ct, open7Bit) == NOTOK)
2143 return NOTOK;
2144
2145 ct->c_cesizefnx = NULL; /* no need to decode for real size */
2146 return OK;
2147 }
2148
2149
2150 int
2151 open7Bit (CT ct, char **file)
2152 {
2153 int cc, fd, len, own_ct_fp = 0;
2154 char buffer[BUFSIZ];
2155 /* sbeck -- handle suffixes */
2156 char *cp;
2157 CI ci;
2158 CE ce = &ct->c_cefile;
2159
2160 if (ce->ce_fp) {
2161 fseek (ce->ce_fp, 0L, SEEK_SET);
2162 goto ready_to_go;
2163 }
2164
2165 if (ce->ce_file) {
2166 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2167 content_error (ce->ce_file, ct, "unable to fopen for reading");
2168 return NOTOK;
2169 }
2170 goto ready_to_go;
2171 }
2172
2173 if (*file == NULL) {
2174 ce->ce_unlink = 1;
2175 } else {
2176 ce->ce_file = add (*file, NULL);
2177 ce->ce_unlink = 0;
2178 }
2179
2180 /* sbeck@cise.ufl.edu -- handle suffixes */
2181 ci = &ct->c_ctinfo;
2182 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2183 invo_name, ci->ci_type, ci->ci_subtype);
2184 cp = context_find (buffer);
2185 if (cp == NULL || *cp == '\0') {
2186 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2187 ci->ci_type);
2188 cp = context_find (buffer);
2189 }
2190 if (cp != NULL && *cp != '\0') {
2191 if (ce->ce_unlink) {
2192 /* Create temporary file with filename extension. */
2193 if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
2194 adios(NULL, "unable to create temporary file in %s",
2195 get_temp_dir());
2196 }
2197 } else {
2198 ce->ce_file = add (cp, ce->ce_file);
2199 }
2200 } else if (*file == NULL) {
2201 char *tempfile;
2202 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2203 adios(NULL, "unable to create temporary file in %s",
2204 get_temp_dir());
2205 }
2206 ce->ce_file = add (tempfile, NULL);
2207 }
2208
2209 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2210 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2211 return NOTOK;
2212 }
2213
2214 if (ct->c_type == CT_MULTIPART) {
2215 CI ci = &ct->c_ctinfo;
2216 char *buffer;
2217
2218 len = 0;
2219 fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2220 len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2221 + 1 + strlen (ci->ci_subtype);
2222 buffer = output_params(len, ci->ci_first_pm, &len, 0);
2223
2224 if (buffer) {
2225 fputs (buffer, ce->ce_fp);
2226 free(buffer);
2227 }
2228
2229 if (ci->ci_comment) {
2230 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2231 fputs ("\n\t", ce->ce_fp);
2232 len = 8;
2233 }
2234 else {
2235 putc (' ', ce->ce_fp);
2236 len++;
2237 }
2238 fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2239 len += cc;
2240 }
2241 fprintf (ce->ce_fp, "\n");
2242 if (ct->c_id)
2243 fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2244 if (ct->c_descr)
2245 fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2246 if (ct->c_dispo)
2247 fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2248 fprintf (ce->ce_fp, "\n");
2249 }
2250
2251 if ((len = ct->c_end - ct->c_begin) < 0)
2252 adios (NULL, "internal error(3)");
2253
2254 if (! ct->c_fp) {
2255 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2256 content_error (ct->c_file, ct, "unable to open for reading");
2257 return NOTOK;
2258 }
2259 own_ct_fp = 1;
2260 }
2261
2262 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2263 while (len > 0)
2264 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2265 case NOTOK:
2266 content_error (ct->c_file, ct, "error reading from");
2267 goto clean_up;
2268
2269 case OK:
2270 content_error (NULL, ct, "premature eof");
2271 goto clean_up;
2272
2273 default:
2274 if (cc > len)
2275 cc = len;
2276 len -= cc;
2277
2278 fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2279 if (ferror (ce->ce_fp)) {
2280 content_error (ce->ce_file, ct, "error writing to");
2281 goto clean_up;
2282 }
2283 }
2284
2285 fseek (ct->c_fp, 0L, SEEK_SET);
2286
2287 if (fflush (ce->ce_fp)) {
2288 content_error (ce->ce_file, ct, "error writing to");
2289 goto clean_up;
2290 }
2291
2292 fseek (ce->ce_fp, 0L, SEEK_SET);
2293
2294 ready_to_go:
2295 *file = ce->ce_file;
2296 if (own_ct_fp) {
2297 fclose (ct->c_fp);
2298 ct->c_fp = NULL;
2299 }
2300 return fileno (ce->ce_fp);
2301
2302 clean_up:
2303 free_encoding (ct, 0);
2304 if (own_ct_fp) {
2305 fclose (ct->c_fp);
2306 ct->c_fp = NULL;
2307 }
2308 return NOTOK;
2309 }
2310
2311
2312 /*
2313 * External
2314 */
2315
2316 static int
2317 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2318 {
2319 char cachefile[BUFSIZ];
2320
2321 if (ce->ce_fp) {
2322 fseek (ce->ce_fp, 0L, SEEK_SET);
2323 goto ready_already;
2324 }
2325
2326 if (ce->ce_file) {
2327 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2328 content_error (ce->ce_file, ct, "unable to fopen for reading");
2329 return NOTOK;
2330 }
2331 goto ready_already;
2332 }
2333
2334 if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2335 cachefile, sizeof(cachefile)) != NOTOK) {
2336 if ((ce->ce_fp = fopen (cachefile, "r"))) {
2337 ce->ce_file = getcpy (cachefile);
2338 ce->ce_unlink = 0;
2339 goto ready_already;
2340 } else {
2341 admonish (cachefile, "unable to fopen for reading");
2342 }
2343 }
2344
2345 return OK;
2346
2347 ready_already:
2348 *file = ce->ce_file;
2349 *fd = fileno (ce->ce_fp);
2350 return DONE;
2351 }
2352
2353 /*
2354 * File
2355 */
2356
2357 static int
2358 InitFile (CT ct)
2359 {
2360 return init_encoding (ct, openFile);
2361 }
2362
2363
2364 static int
2365 openFile (CT ct, char **file)
2366 {
2367 int fd, cachetype;
2368 char cachefile[BUFSIZ];
2369 struct exbody *e = ct->c_ctexbody;
2370 CE ce = &ct->c_cefile;
2371
2372 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2373 case NOTOK:
2374 return NOTOK;
2375
2376 case OK:
2377 break;
2378
2379 case DONE:
2380 return fd;
2381 }
2382
2383 if (!e->eb_name) {
2384 content_error (NULL, ct, "missing name parameter");
2385 return NOTOK;
2386 }
2387
2388 ce->ce_file = getcpy (e->eb_name);
2389 ce->ce_unlink = 0;
2390
2391 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2392 content_error (ce->ce_file, ct, "unable to fopen for reading");
2393 return NOTOK;
2394 }
2395
2396 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2397 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2398 cachefile, sizeof(cachefile)) != NOTOK) {
2399 int mask;
2400 FILE *fp;
2401
2402 mask = umask (cachetype ? ~m_gmprot () : 0222);
2403 if ((fp = fopen (cachefile, "w"))) {
2404 int cc;
2405 char buffer[BUFSIZ];
2406 FILE *gp = ce->ce_fp;
2407
2408 fseek (gp, 0L, SEEK_SET);
2409
2410 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2411 > 0)
2412 fwrite (buffer, sizeof(*buffer), cc, fp);
2413 fflush (fp);
2414
2415 if (ferror (gp)) {
2416 admonish (ce->ce_file, "error reading");
2417 (void) m_unlink (cachefile);
2418 }
2419 else
2420 if (ferror (fp)) {
2421 admonish (cachefile, "error writing");
2422 (void) m_unlink (cachefile);
2423 }
2424 fclose (fp);
2425 }
2426 umask (mask);
2427 }
2428
2429 fseek (ce->ce_fp, 0L, SEEK_SET);
2430 *file = ce->ce_file;
2431 return fileno (ce->ce_fp);
2432 }
2433
2434 /*
2435 * FTP
2436 */
2437
2438 static int
2439 InitFTP (CT ct)
2440 {
2441 return init_encoding (ct, openFTP);
2442 }
2443
2444
2445 static int
2446 openFTP (CT ct, char **file)
2447 {
2448 int cachetype, caching, fd;
2449 int len, buflen;
2450 char *bp, *ftp, *user, *pass;
2451 char buffer[BUFSIZ], cachefile[BUFSIZ];
2452 struct exbody *e;
2453 CE ce = &ct->c_cefile;
2454 static char *username = NULL;
2455 static char *password = NULL;
2456
2457 e = ct->c_ctexbody;
2458
2459 if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2460 ftp = NULL;
2461
2462 if (!ftp)
2463 return NOTOK;
2464
2465 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2466 case NOTOK:
2467 return NOTOK;
2468
2469 case OK:
2470 break;
2471
2472 case DONE:
2473 return fd;
2474 }
2475
2476 if (!e->eb_name || !e->eb_site) {
2477 content_error (NULL, ct, "missing %s parameter",
2478 e->eb_name ? "site": "name");
2479 return NOTOK;
2480 }
2481
2482 /* Get the buffer ready to go */
2483 bp = buffer;
2484 buflen = sizeof(buffer);
2485
2486 /*
2487 * Construct the query message for user
2488 */
2489 snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2490 len = strlen (bp);
2491 bp += len;
2492 buflen -= len;
2493
2494 if (e->eb_partno) {
2495 snprintf (bp, buflen, " (content %s)", e->eb_partno);
2496 len = strlen (bp);
2497 bp += len;
2498 buflen -= len;
2499 }
2500
2501 snprintf (bp, buflen, "\n using %sFTP from site %s",
2502 e->eb_flags ? "anonymous " : "", e->eb_site);
2503 len = strlen (bp);
2504 bp += len;
2505 buflen -= len;
2506
2507 if (e->eb_size > 0) {
2508 snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2509 len = strlen (bp);
2510 bp += len;
2511 buflen -= len;
2512 }
2513 snprintf (bp, buflen, "? ");
2514
2515 /*
2516 * Now, check the answer
2517 */
2518 if (!getanswer (buffer))
2519 return NOTOK;
2520
2521 if (e->eb_flags) {
2522 user = "anonymous";
2523 snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
2524 LocalName (1));
2525 pass = buffer;
2526 } else {
2527 ruserpass (e->eb_site, &username, &password);
2528 user = username;
2529 pass = password;
2530 }
2531
2532 ce->ce_unlink = (*file == NULL);
2533 caching = 0;
2534 cachefile[0] = '\0';
2535 if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
2536 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2537 cachefile, sizeof(cachefile)) != NOTOK) {
2538 if (*file == NULL) {
2539 ce->ce_unlink = 0;
2540 caching = 1;
2541 }
2542 }
2543
2544 if (*file)
2545 ce->ce_file = add (*file, NULL);
2546 else if (caching)
2547 ce->ce_file = add (cachefile, NULL);
2548 else {
2549 char *tempfile;
2550 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2551 adios(NULL, "unable to create temporary file in %s",
2552 get_temp_dir());
2553 }
2554 ce->ce_file = add (tempfile, NULL);
2555 }
2556
2557 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2558 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2559 return NOTOK;
2560 }
2561
2562 {
2563 int child_id, i, vecp;
2564 char *vec[9];
2565
2566 vecp = 0;
2567 vec[vecp++] = r1bindex (ftp, '/');
2568 vec[vecp++] = e->eb_site;
2569 vec[vecp++] = user;
2570 vec[vecp++] = pass;
2571 vec[vecp++] = e->eb_dir;
2572 vec[vecp++] = e->eb_name;
2573 vec[vecp++] = ce->ce_file,
2574 vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
2575 ? "ascii" : "binary";
2576 vec[vecp] = NULL;
2577
2578 fflush (stdout);
2579
2580 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2581 sleep (5);
2582 switch (child_id) {
2583 case NOTOK:
2584 adios ("fork", "unable to");
2585 /* NOTREACHED */
2586
2587 case OK:
2588 close (fileno (ce->ce_fp));
2589 execvp (ftp, vec);
2590 fprintf (stderr, "unable to exec ");
2591 perror (ftp);
2592 _exit (-1);
2593 /* NOTREACHED */
2594
2595 default:
2596 if (pidXwait (child_id, NULL)) {
2597 username = password = NULL;
2598 ce->ce_unlink = 1;
2599 return NOTOK;
2600 }
2601 break;
2602 }
2603 }
2604
2605 if (cachefile[0]) {
2606 if (caching)
2607 chmod (cachefile, cachetype ? m_gmprot () : 0444);
2608 else {
2609 int mask;
2610 FILE *fp;
2611
2612 mask = umask (cachetype ? ~m_gmprot () : 0222);
2613 if ((fp = fopen (cachefile, "w"))) {
2614 int cc;
2615 FILE *gp = ce->ce_fp;
2616
2617 fseek (gp, 0L, SEEK_SET);
2618
2619 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2620 > 0)
2621 fwrite (buffer, sizeof(*buffer), cc, fp);
2622 fflush (fp);
2623
2624 if (ferror (gp)) {
2625 admonish (ce->ce_file, "error reading");
2626 (void) m_unlink (cachefile);
2627 }
2628 else
2629 if (ferror (fp)) {
2630 admonish (cachefile, "error writing");
2631 (void) m_unlink (cachefile);
2632 }
2633 fclose (fp);
2634 }
2635 umask (mask);
2636 }
2637 }
2638
2639 fseek (ce->ce_fp, 0L, SEEK_SET);
2640 *file = ce->ce_file;
2641 return fileno (ce->ce_fp);
2642 }
2643
2644
2645 /*
2646 * Mail
2647 */
2648
2649 static int
2650 InitMail (CT ct)
2651 {
2652 return init_encoding (ct, openMail);
2653 }
2654
2655
2656 static int
2657 openMail (CT ct, char **file)
2658 {
2659 int child_id, fd, i, vecp;
2660 int len, buflen;
2661 char *bp, buffer[BUFSIZ], *vec[7];
2662 struct exbody *e = ct->c_ctexbody;
2663 CE ce = &ct->c_cefile;
2664
2665 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2666 case NOTOK:
2667 return NOTOK;
2668
2669 case OK:
2670 break;
2671
2672 case DONE:
2673 return fd;
2674 }
2675
2676 if (!e->eb_server) {
2677 content_error (NULL, ct, "missing server parameter");
2678 return NOTOK;
2679 }
2680
2681 /* Get buffer ready to go */
2682 bp = buffer;
2683 buflen = sizeof(buffer);
2684
2685 /* Now, construct query message */
2686 snprintf (bp, buflen, "Retrieve content");
2687 len = strlen (bp);
2688 bp += len;
2689 buflen -= len;
2690
2691 if (e->eb_partno) {
2692 snprintf (bp, buflen, " %s", e->eb_partno);
2693 len = strlen (bp);
2694 bp += len;
2695 buflen -= len;
2696 }
2697
2698 snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2699 e->eb_server,
2700 e->eb_subject ? e->eb_subject : e->eb_body);
2701
2702 /* Now, check answer */
2703 if (!getanswer (buffer))
2704 return NOTOK;
2705
2706 vecp = 0;
2707 vec[vecp++] = r1bindex (mailproc, '/');
2708 vec[vecp++] = e->eb_server;
2709 vec[vecp++] = "-subject";
2710 vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2711 vec[vecp++] = "-body";
2712 vec[vecp++] = e->eb_body;
2713 vec[vecp] = NULL;
2714
2715 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
2716 sleep (5);
2717 switch (child_id) {
2718 case NOTOK:
2719 advise ("fork", "unable to");
2720 return NOTOK;
2721
2722 case OK:
2723 execvp (mailproc, vec);
2724 fprintf (stderr, "unable to exec ");
2725 perror (mailproc);
2726 _exit (-1);
2727 /* NOTREACHED */
2728
2729 default:
2730 if (pidXwait (child_id, NULL) == OK)
2731 advise (NULL, "request sent");
2732 break;
2733 }
2734
2735 if (*file == NULL) {
2736 char *tempfile;
2737 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2738 adios(NULL, "unable to create temporary file in %s",
2739 get_temp_dir());
2740 }
2741 ce->ce_file = add (tempfile, NULL);
2742 ce->ce_unlink = 1;
2743 } else {
2744 ce->ce_file = add (*file, NULL);
2745 ce->ce_unlink = 0;
2746 }
2747
2748 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2749 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2750 return NOTOK;
2751 }
2752
2753 /* showproc is for mhshow and mhstore, though mhlist -debug
2754 * prints it, too. */
2755 if (ct->c_showproc)
2756 free (ct->c_showproc);
2757 ct->c_showproc = add ("true", NULL);
2758
2759 fseek (ce->ce_fp, 0L, SEEK_SET);
2760 *file = ce->ce_file;
2761 return fileno (ce->ce_fp);
2762 }
2763
2764
2765 /*
2766 * URL
2767 */
2768
2769 static int
2770 InitURL (CT ct)
2771 {
2772 return init_encoding (ct, openURL);
2773 }
2774
2775
2776 static int
2777 openURL (CT ct, char **file)
2778 {
2779 struct exbody *e = ct->c_ctexbody;
2780 CE ce = &ct->c_cefile;
2781 char *urlprog, *program;
2782 char buffer[BUFSIZ], cachefile[BUFSIZ];
2783 int fd, caching, cachetype;
2784 struct msgs_array args = { 0, 0, NULL};
2785 pid_t child_id;
2786
2787 if ((urlprog = context_find(nmhaccessurl)) && *urlprog == '\0')
2788 urlprog = NULL;
2789
2790 if (! urlprog) {
2791 content_error(NULL, ct, "No entry for nmh-access-url in profile");
2792 return NOTOK;
2793 }
2794
2795 switch (openExternal(e->eb_parent, e->eb_content, ce, file, &fd)) {
2796 case NOTOK:
2797 return NOTOK;
2798
2799 case OK:
2800 break;
2801
2802 case DONE:
2803 return fd;
2804 }
2805
2806 if (!e->eb_url) {
2807 content_error(NULL, ct, "missing url parameter");
2808 return NOTOK;
2809 }
2810
2811 ce->ce_unlink = (*file == NULL);
2812 caching = 0;
2813 cachefile[0] = '\0';
2814
2815 if (find_cache(NULL, wcachesw, &cachetype, e->eb_content->c_id,
2816 cachefile, sizeof(cachefile)) != NOTOK) {
2817 if (*file == NULL) {
2818 ce->ce_unlink = 0;
2819 caching = 1;
2820 }
2821 }
2822
2823 if (*file)
2824 ce->ce_file = add(*file, NULL);
2825 else if (caching)
2826 ce->ce_file = add(cachefile, NULL);
2827 else {
2828 char *tempfile;
2829 if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
2830 adios(NULL, "unable to create temporary file in %s",
2831 get_temp_dir());
2832 }
2833 ce->ce_file = add (tempfile, NULL);
2834 }
2835
2836 if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
2837 content_error(ce->ce_file, ct, "unable to fopen for read/writing");
2838 return NOTOK;
2839 }
2840
2841 switch (child_id = fork()) {
2842 case NOTOK:
2843 adios ("fork", "unable to");
2844 /* NOTREACHED */
2845
2846 case OK:
2847 argsplit_msgarg(&args, urlprog, &program);
2848 app_msgarg(&args, e->eb_url);
2849 app_msgarg(&args, NULL);
2850 dup2(fileno(ce->ce_fp), 1);
2851 close(fileno(ce->ce_fp));
2852 execvp(program, args.msgs);
2853 fprintf(stderr, "Unable to exec ");
2854 perror(program);
2855 _exit(-1);
2856 /* NOTREACHED */
2857
2858 default:
2859 if (pidXwait(child_id, NULL)) {
2860 ce->ce_unlink = 1;
2861 return NOTOK;
2862 }
2863 }
2864
2865 if (cachefile[0]) {
2866 if (caching)
2867 chmod(cachefile, cachetype ? m_gmprot() : 0444);
2868 else {
2869 int mask;
2870 FILE *fp;
2871
2872 mask = umask (cachetype ? ~m_gmprot() : 0222);
2873 if ((fp = fopen(cachefile, "w"))) {
2874 int cc;
2875 FILE *gp = ce->ce_fp;
2876
2877 fseeko(gp, 0, SEEK_SET);
2878
2879 while ((cc = fread(buffer, sizeof(*buffer),
2880 sizeof(buffer), gp)) > 0)
2881 fwrite(buffer, sizeof(*buffer), cc, fp);
2882
2883 fflush(fp);
2884
2885 if (ferror(gp)) {
2886 admonish(ce->ce_file, "error reading");
2887 (void) m_unlink (cachefile);
2888 }
2889 }
2890 umask(mask);
2891 }
2892 }
2893
2894 fseeko(ce->ce_fp, 0, SEEK_SET);
2895 *file = ce->ce_file;
2896 return fd;
2897 }
2898
2899 static int
2900 readDigest (CT ct, char *cp)
2901 {
2902 int bitno, skip;
2903 uint32_t bits;
2904 char *bp = cp;
2905 unsigned char *dp, value, *ep;
2906
2907 bitno = 18;
2908 bits = 0L;
2909 skip = 0;
2910
2911 for (ep = (dp = ct->c_digest)
2912 + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2913 switch (*cp) {
2914 default:
2915 if (skip
2916 || (*cp & 0x80)
2917 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2918 if (debugsw)
2919 fprintf (stderr, "invalid BASE64 encoding\n");
2920 return NOTOK;
2921 }
2922
2923 bits |= value << bitno;
2924 test_end:
2925 if ((bitno -= 6) < 0) {
2926 if (dp + (3 - skip) > ep)
2927 goto invalid_digest;
2928 *dp++ = (bits >> 16) & 0xff;
2929 if (skip < 2) {
2930 *dp++ = (bits >> 8) & 0xff;
2931 if (skip < 1)
2932 *dp++ = bits & 0xff;
2933 }
2934 bitno = 18;
2935 bits = 0L;
2936 skip = 0;
2937 }
2938 break;
2939
2940 case '=':
2941 if (++skip > 3)
2942 goto self_delimiting;
2943 goto test_end;
2944 }
2945 if (bitno != 18) {
2946 if (debugsw)
2947 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2948
2949 return NOTOK;
2950 }
2951 self_delimiting:
2952 if (dp != ep) {
2953 invalid_digest:
2954 if (debugsw) {
2955 while (*cp)
2956 cp++;
2957 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2958 (int)(cp - bp));
2959 }
2960
2961 return NOTOK;
2962 }
2963
2964 if (debugsw) {
2965 fprintf (stderr, "MD5 digest=");
2966 for (dp = ct->c_digest; dp < ep; dp++)
2967 fprintf (stderr, "%02x", *dp & 0xff);
2968 fprintf (stderr, "\n");
2969 }
2970
2971 return OK;
2972 }
2973
2974
2975 /* Multipart parts might have content before the first subpart and/or
2976 after the last subpart that hasn't been stored anywhere else, so do
2977 that. */
2978 int
2979 get_leftover_mp_content (CT ct, int before /* or after */)
2980 {
2981 struct multipart *m = (struct multipart *) ct->c_ctparams;
2982 char *boundary;
2983 int found_boundary = 0;
2984 int max = BUFSIZ;
2985 char *bufp = NULL;
2986 size_t buflen;
2987 ssize_t gotlen;
2988 int read = 0;
2989 char *content = NULL;
2990
2991 if (! m) return NOTOK;
2992
2993 if (before) {
2994 if (! m->mp_parts || ! m->mp_parts->mp_part) return NOTOK;
2995
2996 /* Isolate the beginning of this part to the beginning of the
2997 first subpart and save any content between them. */
2998 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
2999 max = m->mp_parts->mp_part->c_begin - ct->c_begin;
3000 boundary = concat ("--", m->mp_start, NULL);
3001 } else {
3002 struct part *last_subpart = NULL;
3003 struct part *subpart;
3004
3005 /* Go to the last subpart to get its end position. */
3006 for (subpart = m->mp_parts; subpart; subpart = subpart->mp_next) {
3007 last_subpart = subpart;
3008 }
3009
3010 if (last_subpart == NULL) return NOTOK;
3011
3012 /* Isolate the end of the last subpart to the end of this part
3013 and save any content between them. */
3014 fseeko (ct->c_fp, last_subpart->mp_part->c_end, SEEK_SET);
3015 max = ct->c_end - last_subpart->mp_part->c_end;
3016 boundary = concat ("--", m->mp_stop, NULL);
3017 }
3018
3019 /* Back up by 1 to pick up the newline. */
3020 while ((gotlen = getline(&bufp, &buflen, ct->c_fp)) != -1) {
3021 read += gotlen;
3022 /* Don't look beyond beginning of first subpart (before) or
3023 next part (after). */
3024 if (read > max) bufp[read-max] = '\0';
3025
3026 if (before) {
3027 if (! strcmp (bufp, boundary)) {
3028 found_boundary = 1;
3029 }
3030 } else {
3031 if (! found_boundary && ! strcmp (bufp, boundary)) {
3032 found_boundary = 1;
3033 continue;
3034 }
3035 }
3036
3037 if ((before && ! found_boundary) || (! before && found_boundary)) {
3038 if (content) {
3039 char *old_content = content;
3040 content = concat (content, bufp, NULL);
3041 free (old_content);
3042 } else {
3043 content = before
3044 ? concat ("\n", bufp, NULL)
3045 : concat (bufp, NULL);
3046 }
3047 }
3048
3049 if (before) {
3050 if (found_boundary || read > max) break;
3051 } else {
3052 if (read > max) break;
3053 }
3054 }
3055
3056 /* Skip the newline if that's all there is. */
3057 if (content) {
3058 char *cp;
3059
3060 /* Remove trailing newline, except at EOF. */
3061 if ((before || ! feof (ct->c_fp)) &&
3062 (cp = content + strlen (content)) > content &&
3063 *--cp == '\n') {
3064 *cp = '\0';
3065 }
3066
3067 if (strlen (content) > 1) {
3068 if (before) {
3069 m->mp_content_before = content;
3070 } else {
3071 m->mp_content_after = content;
3072 }
3073 } else {
3074 free (content);
3075 }
3076 }
3077
3078 free (boundary);
3079 free (bufp);
3080
3081 return OK;
3082 }
3083
3084
3085 char *
3086 ct_type_str (int type) {
3087 switch (type) {
3088 case CT_APPLICATION:
3089 return "application";
3090 case CT_AUDIO:
3091 return "audio";
3092 case CT_IMAGE:
3093 return "image";
3094 case CT_MESSAGE:
3095 return "message";
3096 case CT_MULTIPART:
3097 return "multipart";
3098 case CT_TEXT:
3099 return "text";
3100 case CT_VIDEO:
3101 return "video";
3102 case CT_EXTENSION:
3103 return "extension";
3104 default:
3105 return "unknown_type";
3106 }
3107 }
3108
3109
3110 char *
3111 ct_subtype_str (int type, int subtype) {
3112 switch (type) {
3113 case CT_APPLICATION:
3114 switch (subtype) {
3115 case APPLICATION_OCTETS:
3116 return "octets";
3117 case APPLICATION_POSTSCRIPT:
3118 return "postscript";
3119 default:
3120 return "unknown_app_subtype";
3121 }
3122 case CT_MESSAGE:
3123 switch (subtype) {
3124 case MESSAGE_RFC822:
3125 return "rfc822";
3126 case MESSAGE_PARTIAL:
3127 return "partial";
3128 case MESSAGE_EXTERNAL:
3129 return "external";
3130 default:
3131 return "unknown_msg_subtype";
3132 }
3133 case CT_MULTIPART:
3134 switch (subtype) {
3135 case MULTI_MIXED:
3136 return "mixed";
3137 case MULTI_ALTERNATE:
3138 return "alternative";
3139 case MULTI_DIGEST:
3140 return "digest";
3141 case MULTI_PARALLEL:
3142 return "parallel";
3143 default:
3144 return "unknown_multipart_subtype";
3145 }
3146 case CT_TEXT:
3147 switch (subtype) {
3148 case TEXT_PLAIN:
3149 return "plain";
3150 case TEXT_RICHTEXT:
3151 return "richtext";
3152 case TEXT_ENRICHED:
3153 return "enriched";
3154 default:
3155 return "unknown_text_subtype";
3156 }
3157 default:
3158 return "unknown_type";
3159 }
3160 }
3161
3162
3163 /* Find the content type and InitFunc for the CT. */
3164 const struct str2init *
3165 get_ct_init (int type) {
3166 const struct str2init *sp;
3167
3168 for (sp = str2cts; sp->si_key; ++sp) {
3169 if (type == sp->si_val) {
3170 return sp;
3171 }
3172 }
3173
3174 return NULL;
3175 }
3176
3177 const char *
3178 ce_str (int encoding) {
3179 switch (encoding) {
3180 case CE_BASE64:
3181 return "base64";
3182 case CE_QUOTED:
3183 return "quoted-printable";
3184 case CE_8BIT:
3185 return "8bit";
3186 case CE_7BIT:
3187 return "7bit";
3188 case CE_BINARY:
3189 return "binary";
3190 case CE_EXTENSION:
3191 return "extension";
3192 case CE_EXTERNAL:
3193 return "external";
3194 default:
3195 return "unknown";
3196 }
3197 }
3198
3199 /* Find the content type and InitFunc for the content encoding method. */
3200 const struct str2init *
3201 get_ce_method (const char *method) {
3202 struct str2init *sp;
3203
3204 for (sp = str2ces; sp->si_key; ++sp) {
3205 if (! strcasecmp (method, sp->si_key)) {
3206 return sp;
3207 }
3208 }
3209
3210 return NULL;
3211 }
3212
3213 /*
3214 * Parse a series of MIME attributes (or parameters) given a header as
3215 * input.
3216 *
3217 * Arguments include:
3218 *
3219 * filename - Name of input file (for error messages)
3220 * fieldname - Name of field being processed
3221 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3222 * Updated to point to end of attributes when finished.
3223 * param_head - Pointer to head of parameter list
3224 * param_tail - Pointer to tail of parameter list
3225 * commentp - Pointer to header comment pointer (may be NULL)
3226 *
3227 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3228 * DONE to indicate a benign error (minor parsing error, but the program
3229 * should continue).
3230 */
3231
3232 static int
3233 parse_header_attrs (const char *filename, const char *fieldname,
3234 char **header_attrp, PM *param_head, PM *param_tail,
3235 char **commentp)
3236 {
3237 char *cp = *header_attrp;
3238 PM pm;
3239 struct sectlist {
3240 char *value;
3241 int index;
3242 int len;
3243 struct sectlist *next;
3244 } *sp, *sp2;
3245 struct parmlist {
3246 char *name;
3247 char *charset;
3248 char *lang;
3249 struct sectlist *sechead;
3250 struct parmlist *next;
3251 } *pp, *pp2, *phead = NULL;
3252
3253 while (*cp == ';') {
3254 char *dp, *vp, *up, *nameptr, *valptr, *charset = NULL, *lang = NULL;
3255 int encoded = 0, partial = 0, len = 0, index = 0;
3256
3257 cp++;
3258 while (isspace ((unsigned char) *cp))
3259 cp++;
3260
3261 if (*cp == '(' &&
3262 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3263 return NOTOK;
3264 }
3265
3266 if (*cp == 0) {
3267 advise (NULL,
3268 "extraneous trailing ';' in message %s's %s: "
3269 "parameter list",
3270 filename, fieldname);
3271 return DONE;
3272 }
3273
3274 /* down case the attribute name */
3275 for (dp = cp; istoken ((unsigned char) *dp); dp++)
3276 if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
3277 *dp = tolower ((unsigned char) *dp);
3278
3279 for (up = dp; isspace ((unsigned char) *dp);)
3280 dp++;
3281 if (dp == cp || *dp != '=') {
3282 advise (NULL,
3283 "invalid parameter in message %s's %s: "
3284 "field\n%*sparameter %s (error detected at offset %d)",
3285 filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp);
3286 return NOTOK;
3287 }
3288
3289 /*
3290 * To handle RFC 2231, we have to deal with the following extensions:
3291 *
3292 * name*=encoded-value
3293 * name*<N>=part-N-of-a-parameter-value
3294 * name*<N>*=encoded-part-N-of-a-parameter-value
3295 *
3296 * So the rule is:
3297 * If there's a * right before the equal sign, it's encoded.
3298 * If there's a * and one or more digits, then it's section N.
3299 *
3300 * Remember we can have one or the other, or both. cp points to
3301 * beginning of name, up points past the last character in the
3302 * parameter name.
3303 */
3304
3305 for (vp = cp; vp < up; vp++) {
3306 if (*vp == '*' && vp < up - 1) {
3307 partial = 1;
3308 continue;
3309 } else if (*vp == '*' && vp == up - 1) {
3310 encoded = 1;
3311 } else if (partial) {
3312 if (isdigit((unsigned char) *vp))
3313 index = *vp - '0' + index * 10;
3314 else {
3315 advise (NULL, "invalid parameter index in message %s's "
3316 "%s: field\n%*s(parameter %s)", filename,
3317 fieldname, strlen(invo_name) + 2, "", cp);
3318 return NOTOK;
3319 }
3320 } else {
3321 len++;
3322 }
3323 }
3324
3325 /*
3326 * Break out the parameter name and value sections and allocate
3327 * memory for each.
3328 */
3329
3330 nameptr = mh_xmalloc(len + 1);
3331 strncpy(nameptr, cp, len);
3332 nameptr[len] = '\0';
3333
3334 for (dp++; isspace ((unsigned char) *dp);)
3335 dp++;
3336
3337 if (encoded) {
3338 /*
3339 * Single quotes delimit the character set and language tag.
3340 * They are required on the first section (or a complete
3341 * parameter).
3342 */
3343 if (index == 0) {
3344 vp = dp;
3345 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3346 *vp != '\0')
3347 vp++;
3348 if (*vp == '\'') {
3349 if (vp != dp) {
3350 len = vp - dp;
3351 charset = mh_xmalloc(len + 1);
3352 strncpy(charset, dp, len);
3353 charset[len] = '\0';
3354 } else {
3355 charset = NULL;
3356 }
3357 vp++;
3358 } else {
3359 advise(NULL, "missing charset in message %s's %s: "
3360 "field\n%*s(parameter %s)", filename, fieldname,
3361 strlen(invo_name) + 2, "", nameptr);
3362 free(nameptr);
3363 return NOTOK;
3364 }
3365 dp = vp;
3366
3367 while (*vp != '\'' && !isspace((unsigned char) *vp) &&
3368 *vp != '\0')
3369 vp++;
3370
3371 if (*vp == '\'') {
3372 if (vp != dp) {
3373 len = vp - dp;
3374 lang = mh_xmalloc(len + 1);
3375 strncpy(lang, dp, len);
3376 lang[len] = '\0';
3377 } else {
3378 lang = NULL;
3379 }
3380 vp++;
3381 } else {
3382 advise(NULL, "missing language tag in message %s's %s: "
3383 "field\n%*s(parameter %s)", filename, fieldname,
3384 strlen(invo_name) + 2, "", nameptr);
3385 free(nameptr);
3386 if (charset)
3387 free(charset);
3388 return NOTOK;
3389 }
3390
3391 dp = vp;
3392 }
3393
3394 /*
3395 * At this point vp should be pointing at the beginning
3396 * of the encoded value/section. Continue until we reach
3397 * the end or get whitespace. But first, calculate the
3398 * length so we can allocate the correct buffer size.
3399 */
3400
3401 for (vp = dp, len = 0; istoken(*vp); vp++) {
3402 if (*vp == '%') {
3403 if (*(vp + 1) == '\0' ||
3404 !isxdigit((unsigned char) *(vp + 1)) ||
3405 *(vp + 2) == '\0' ||
3406 !isxdigit((unsigned char) *(vp + 2))) {
3407 advise(NULL, "invalid encoded sequence in message "
3408 "%s's %s: field\n%*s(parameter %s)",
3409 filename, fieldname, strlen(invo_name) + 2,
3410 "", nameptr);
3411 free(nameptr);
3412 if (charset)
3413 free(charset);
3414 if (lang)
3415 free(lang);
3416 return NOTOK;
3417 }
3418 vp += 2;
3419 }
3420 len++;
3421 }
3422
3423 up = valptr = mh_xmalloc(len + 1);
3424
3425 for (vp = dp; istoken(*vp); vp++) {
3426 if (*vp == '%') {
3427 *up++ = decode_qp(*(vp + 1), *(vp + 2));
3428 vp += 2;
3429 } else {
3430 *up++ = *vp;
3431 }
3432 }
3433
3434 *up = '\0';
3435 cp = vp;
3436 } else {
3437 /*
3438 * A "normal" string. If it's got a leading quote, then we
3439 * strip the quotes out. Otherwise go until we reach the end
3440 * or get whitespace. Note we scan it twice; once to get the
3441 * length, then the second time copies it into the destination
3442 * buffer.
3443 */
3444
3445 len = 0;
3446
3447 if (*dp == '"') {
3448 for (cp = dp + 1;;) {
3449 switch (*cp++) {
3450 case '\0':
3451 bad_quote:
3452 advise (NULL,
3453 "invalid quoted-string in message %s's %s: "
3454 "field\n%*s(parameter %s)",
3455 filename, fieldname, strlen(invo_name) + 2, "",
3456 nameptr);
3457 free(nameptr);
3458 if (charset)
3459 free(charset);
3460 if (lang)
3461 free(lang);
3462 return NOTOK;
3463 case '"':
3464 break;
3465
3466 case '\\':
3467 if (*++cp == '\0')
3468 goto bad_quote;
3469 /* FALL THROUGH */
3470 default:
3471 len++;
3472 continue;
3473 }
3474 break;
3475 }
3476
3477 } else {
3478 for (cp = dp; istoken (*cp); cp++) {
3479 len++;
3480 }
3481 }
3482
3483 valptr = mh_xmalloc(len + 1);
3484
3485 if (*dp == '"') {
3486 int i;
3487 for (cp = dp + 1, vp = valptr, i = 0; i < len; i++) {
3488 if (*cp == '\\') {
3489 cp++;
3490 }
3491 *vp++ = *cp++;
3492 }
3493 cp++;
3494 } else {
3495 strncpy(valptr, cp = dp, len);
3496 cp += len;
3497 }
3498
3499 valptr[len] = '\0';
3500 }
3501
3502 /*
3503 * If 'partial' is set, we don't allocate a parameter now. We
3504 * put it on the parameter linked list to be reassembled later.
3505 *
3506 * "phead" points to a list of all parameters we need to reassemble.
3507 * Each parameter has a list of sections. We insert the sections in
3508 * order.
3509 */
3510
3511 if (partial) {
3512 for (pp = phead; pp != NULL; pp = pp->next) {
3513 if (strcasecmp(nameptr, pp->name) == 0)
3514 break;
3515 }
3516
3517 if (pp == NULL) {
3518 pp = mh_xmalloc(sizeof(*pp));
3519 memset(pp, 0, sizeof(*pp));
3520 pp->name = nameptr;
3521 pp->next = phead;
3522 phead = pp;
3523 }
3524
3525 /*
3526 * Insert this into the section linked list
3527 */
3528
3529 sp = mh_xmalloc(sizeof(*sp));
3530 memset(sp, 0, sizeof(*sp));
3531 sp->value = valptr;
3532 sp->index = index;
3533 sp->len = len;
3534
3535 if (pp->sechead == NULL || pp->sechead->index > index) {
3536 sp->next = pp->sechead;
3537 pp->sechead = sp;
3538 } else {
3539 for (sp2 = pp->sechead; sp2 != NULL; sp2 = sp2->next) {
3540 if (sp2->index == sp->index) {
3541 advise (NULL, "duplicate index (%d) in message "
3542 "%s's %s: field\n%*s(parameter %s)", sp->index,
3543 filename, fieldname, strlen(invo_name) + 2, "",
3544 nameptr);
3545 return NOTOK;
3546 }
3547 if (sp2->index < sp->index &&
3548 (sp2->next == NULL || sp2->next->index > sp->index)) {
3549 sp->next = sp2->next;
3550 sp2->next = sp;
3551 break;
3552 }
3553 }
3554
3555 if (sp2 == NULL) {
3556 advise(NULL, "Internal error: cannot insert partial "
3557 "param in message %s's %s: field\n%*s(parameter %s)",
3558 filename, fieldname, strlen(invo_name) + 2, "",
3559 nameptr);
3560 return NOTOK;
3561 }
3562 }
3563
3564 /*
3565 * Save our charset and lang tags.
3566 */
3567
3568 if (index == 0 && encoded) {
3569 if (pp->charset)
3570 free(pp->charset);
3571 pp->charset = charset;
3572 if (pp->lang)
3573 free(pp->lang);
3574 pp->lang = lang;
3575 }
3576 } else {
3577 pm = add_param(param_head, param_tail, nameptr, valptr, 1);
3578 pm->pm_charset = charset;
3579 pm->pm_lang = lang;
3580 }
3581
3582 while (isspace ((unsigned char) *cp))
3583 cp++;
3584
3585 if (*cp == '(' &&
3586 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3587 return NOTOK;
3588 }
3589 }
3590
3591 /*
3592 * Now that we're done, reassemble all of the partial parameters.
3593 */
3594
3595 for (pp = phead; pp != NULL; ) {
3596 char *p, *q;
3597 size_t tlen = 0;
3598 int pindex = 0;
3599 for (sp = pp->sechead; sp != NULL; sp = sp->next) {
3600 if (sp->index != pindex++) {
3601 advise(NULL, "missing section %d for parameter in "
3602 "message %s's %s: field\n%*s(parameter %s)", pindex - 1,
3603 filename, fieldname, strlen(invo_name) + 2, "",
3604 pp->name);
3605 return NOTOK;
3606 }
3607 tlen += sp->len;
3608 }
3609
3610 p = q = mh_xmalloc(tlen + 1);
3611 for (sp = pp->sechead; sp != NULL; ) {
3612 memcpy(q, sp->value, sp->len);
3613 q += sp->len;
3614 free(sp->value);
3615 sp2 = sp->next;
3616 free(sp);
3617 sp = sp2;
3618 }
3619
3620 p[tlen] = '\0';
3621
3622 pm = add_param(param_head, param_tail, pp->name, p, 1);
3623 pm->pm_charset = pp->charset;
3624 pm->pm_lang = pp->lang;
3625 pp2 = pp->next;
3626 free(pp);
3627 pp = pp2;
3628 }
3629
3630 *header_attrp = cp;
3631 return OK;
3632 }
3633
3634 /*
3635 * Return the charset for a particular content type. Return pointer is
3636 * only valid until the next call to content_charset().
3637 */
3638
3639 char *
3640 content_charset (CT ct) {
3641 static char *ret_charset = NULL;
3642
3643 if (ret_charset != NULL) {
3644 free(ret_charset);
3645 }
3646
3647 ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
3648
3649 return ret_charset ? ret_charset : "US-ASCII";
3650 }
3651
3652
3653 /*
3654 * Create a string based on a list of output parameters. Assume that this
3655 * parameter string will be appended to an existing header, so start out
3656 * with the separator (;). Perform RFC 2231 encoding when necessary.
3657 */
3658
3659 char *
3660 output_params(size_t initialwidth, PM params, int *offsetout, int external)
3661 {
3662 char *paramout = NULL;
3663 char line[CPERLIN * 2], *q;
3664 int curlen, index, cont, encode, i;
3665 size_t valoff, numchars;
3666
3667 while (params != NULL) {
3668 encode = 0;
3669 index = 0;
3670 valoff = 0;
3671 q = line;
3672
3673 if (external && strcasecmp(params->pm_name, "body") == 0)
3674 continue;
3675
3676 if (strlen(params->pm_name) > CPERLIN) {
3677 advise(NULL, "Parameter name \"%s\" is too long", params->pm_name);
3678 if (paramout)
3679 free(paramout);
3680 return NULL;
3681 }
3682
3683 curlen = param_len(params, index, valoff, &encode, &cont, &numchars);
3684
3685 /*
3686 * Loop until we get a parameter that fits within a line. We
3687 * assume new lines start with a tab, so check our overflow based
3688 * on that.
3689 */
3690
3691 while (cont) {
3692 *q++ = ';';
3693 *q++ = '\n';
3694 *q++ = '\t';
3695
3696 /*
3697 * At this point we're definitely continuing the line, so
3698 * be sure to include the parameter name and section index.
3699 */
3700
3701 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3702 params->pm_name, index);
3703
3704 /*
3705 * Both of these functions do a NUL termination
3706 */
3707
3708 if (encode)
3709 i = encode_param(params, q, sizeof(line) - (q - line),
3710 numchars, valoff, index);
3711 else
3712 i = normal_param(params, q, sizeof(line) - (q - line),
3713 numchars, valoff);
3714
3715 if (i == 0) {
3716 if (paramout)
3717 free(paramout);
3718 return NULL;
3719 }
3720
3721 valoff += numchars;
3722 index++;
3723 curlen = param_len(params, index, valoff, &encode, &cont,
3724 &numchars);
3725 q = line;
3726
3727 /*
3728 * "line" starts with a ;\n\t, so that doesn't count against
3729 * the length. But add 8 since it starts with a tab; that's
3730 * how we end up with 5.
3731 */
3732
3733 initialwidth = strlen(line) + 5;
3734
3735 /*
3736 * At this point the line should be built, so add it to our
3737 * current output buffer.
3738 */
3739
3740 paramout = add(line, paramout);
3741 }
3742
3743 /*
3744 * If this won't fit on the line, start a new one. Save room in
3745 * case we need a semicolon on the end
3746 */
3747
3748 if (initialwidth + curlen > CPERLIN - 1) {
3749 *q++ = ';';
3750 *q++ = '\n';
3751 *q++ = '\t';
3752 initialwidth = 8;
3753 } else {
3754 *q++ = ';';
3755 *q++ = ' ';
3756 initialwidth += 2;
3757 }
3758
3759 /*
3760 * At this point, we're either finishing a contined parameter, or
3761 * we're working on a new one.
3762 */
3763
3764 if (index > 0) {
3765 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3766 params->pm_name, index);
3767 } else {
3768 strncpy(q, params->pm_name, sizeof(line) - (q - line));
3769 q += strlen(q);
3770 }
3771
3772 if (encode)
3773 i = encode_param(params, q, sizeof(line) - (q - line),
3774 strlen(params->pm_value + valoff), valoff, index);
3775 else
3776 i = normal_param(params, q, sizeof(line) - (q - line),
3777 strlen(params->pm_value + valoff), valoff);
3778
3779 if (i == 0) {
3780 if (paramout)
3781 free(paramout);
3782 return NULL;
3783 }
3784
3785 paramout = add(line, paramout);
3786 initialwidth += strlen(line);
3787
3788 params = params->pm_next;
3789 }
3790
3791 if (offsetout)
3792 *offsetout = initialwidth;
3793
3794 return paramout;
3795 }
3796
3797 /*
3798 * Calculate the size of a parameter.
3799 *
3800 * Arguments include
3801 *
3802 * pm - The parameter being output
3803 * index - If continuing the parameter, the index of the section
3804 * we're on.
3805 * valueoff - The current offset into the parameter value that we're
3806 * working on (previous sections have consumed valueoff bytes).
3807 * encode - Set if we should perform encoding on this parameter section
3808 * (given that we're consuming bytesfit bytes).
3809 * cont - Set if the remaining data in value will not fit on a single
3810 * line and will need to be continued.
3811 * bytesfit - The number of bytes that we can consume from the parameter
3812 * value and still fit on a completely new line. The
3813 * calculation assumes the new line starts with a tab,
3814 * includes the parameter name and any encoding, and fits
3815 * within CPERLIN bytes. Will always be at least 1.
3816 */
3817
3818 static size_t
3819 param_len(PM pm, int index, size_t valueoff, int *encode, int *cont,
3820 size_t *bytesfit)
3821 {
3822 char *start = pm->pm_value + valueoff, *p, indexchar[32];
3823 size_t len = 0, fit = 0;
3824 int fitlimit = 0, eightbit, maxfit;
3825
3826 *encode = 0;
3827
3828 /*
3829 * Add up the length. First, start with the parameter name.
3830 */
3831
3832 len = strlen(pm->pm_name);
3833
3834 /*
3835 * Scan the parameter value and see if we need to do encoding for this
3836 * section.
3837 */
3838
3839 eightbit = contains8bit(start, NULL);
3840
3841 /*
3842 * Determine if we need to encode this section. Encoding is necessary if:
3843 *
3844 * - There are any 8-bit characters at all and we're on the first
3845 * section.
3846 * - There are 8-bit characters within N bytes of our section start.
3847 * N is calculated based on the number of bytes it would take to
3848 * reach CPERLIN. Specifically:
3849 * 8 (starting tab) +
3850 * strlen(param name) +
3851 * 4 ('* for section marker, '=', opening/closing '"')
3852 * strlen (index)
3853 * is the number of bytes used by everything that isn't part of the
3854 * value. So that gets subtracted from CPERLIN.
3855 */
3856
3857 snprintf(indexchar, sizeof(indexchar), "%d", index);
3858 maxfit = CPERLIN - (12 + len + strlen(indexchar));
3859 if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) {
3860 *encode = 1;
3861 }
3862
3863 len++; /* Add in equal sign */
3864
3865 if (*encode) {
3866 /*
3867 * We're using maxfit as a marker for how many characters we can
3868 * fit into the line. Bump it by two because we're not using quotes
3869 * when encoding.
3870 */
3871
3872 maxfit += 2;
3873
3874 /*
3875 * If we don't have a charset or language tag in this parameter,
3876 * add them now.
3877 */
3878
3879 if (! pm->pm_charset)
3880 pm->pm_charset = getcpy(write_charset_8bit());
3881 if (! pm->pm_lang)
3882 pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */
3883
3884 len++; /* For the encoding marker */
3885 maxfit--;
3886 if (index == 0) {
3887 int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
3888 len += enclen;
3889 maxfit-= enclen;
3890 } else {
3891 /*
3892 * We know we definitely need to include an index. maxfit already
3893 * includes the section marker.
3894 */
3895 len += strlen(indexchar);
3896 }
3897 for (p = start; *p != '\0'; p++) {
3898 if (isparamencode(*p)) {
3899 len += 3;
3900 maxfit -= 3;
3901 } else {
3902 len++;
3903 maxfit--;
3904 }
3905 /*
3906 * Just so there's no confusion: maxfit is counting OUTPUT
3907 * characters (post-encoding). fit is counting INPUT characters.
3908 */
3909 if (! fitlimit && maxfit >= 0)
3910 fit++;
3911 else if (! fitlimit)
3912 fitlimit++;
3913 }
3914 } else {
3915 /*
3916 * Calculate the string length, but add room for quoting \
3917 * and " if necessary. Also account for quotes at beginning
3918 * and end.
3919 */
3920 for (p = start; *p != '\0'; p++) {
3921 switch (*p) {
3922 case '"':
3923 case '\\':
3924 len++;
3925 maxfit--;
3926 /* FALL THROUGH */
3927 default:
3928 len++;
3929 maxfit--;
3930 }
3931 if (! fitlimit && maxfit >= 0)
3932 fit++;
3933 else if (! fitlimit)
3934 fitlimit++;
3935 }
3936
3937 len += 2;
3938 }
3939
3940 if (fit < 1)
3941 fit = 1;
3942
3943 *cont = fitlimit;
3944 *bytesfit = fit;
3945
3946 return len;
3947 }
3948
3949 /*
3950 * Output an encoded parameter string.
3951 */
3952
3953 static size_t
3954 encode_param(PM pm, char *output, size_t len, size_t valuelen,
3955 size_t valueoff, int index)
3956 {
3957 size_t outlen = 0, n;
3958 char *endptr = output + len, *p;
3959
3960 /*
3961 * First, output the marker for an encoded string.
3962 */
3963
3964 *output++ = '*';
3965 *output++ = '=';
3966 outlen += 2;
3967
3968 /*
3969 * If the index is 0, output the character set and language tag.
3970 * If theses were NULL, they should have already been filled in
3971 * by param_len().
3972 */
3973
3974 if (index == 0) {
3975 n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset,
3976 pm->pm_lang);
3977 output += n;
3978 outlen += n;
3979 if (output > endptr) {
3980 advise(NULL, "Internal error: parameter buffer overflow");
3981 return 0;
3982 }
3983 }
3984
3985 /*
3986 * Copy over the value, encoding if necessary
3987 */
3988
3989 p = pm->pm_value + valueoff;
3990 while (valuelen-- > 0) {
3991 if (isparamencode(*p)) {
3992 n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++);
3993 output += n;
3994 outlen += n;
3995 } else {
3996 *output++ = *p++;
3997 outlen++;
3998 }
3999 if (output > endptr) {
4000 advise(NULL, "Internal error: parameter buffer overflow");
4001 return 0;
4002 }
4003 }
4004
4005 *output = '\0';
4006
4007 return outlen;
4008 }
4009
4010 /*
4011 * Output a "normal" parameter, without encoding. Be sure to escape
4012 * quotes and backslashes if necessary.
4013 */
4014
4015 static size_t
4016 normal_param(PM pm, char *output, size_t len, size_t valuelen,
4017 size_t valueoff)
4018 {
4019 size_t outlen = 0;
4020 char *endptr = output + len, *p;
4021
4022 *output++ = '=';
4023 *output++ = '"';
4024 outlen += 2;
4025
4026 p = pm->pm_value + valueoff;
4027
4028 while (valuelen-- > 0) {
4029 switch (*p) {
4030 case '\\':
4031 case '"':
4032 *output++ = '\\';
4033 outlen++;
4034 default:
4035 *output++ = *p++;
4036 outlen++;
4037 }
4038 if (output > endptr) {
4039 advise(NULL, "Internal error: parameter buffer overflow");
4040 return 0;
4041 }
4042 }
4043
4044 if (output - 2 > endptr) {
4045 advise(NULL, "Internal error: parameter buffer overflow");
4046 return 0;
4047 }
4048
4049 *output++ = '"';
4050 *output++ = '\0';
4051
4052 return outlen + 1;
4053 }
4054
4055 /*
4056 * Add a parameter to the parameter linked list
4057 */
4058
4059 PM
4060 add_param(PM *first, PM *last, char *name, char *value, int nocopy)
4061 {
4062 PM pm = mh_xmalloc(sizeof(*pm));
4063
4064 memset(pm, 0, sizeof(*pm));
4065
4066 pm->pm_name = nocopy ? name : getcpy(name);
4067 pm->pm_value = nocopy ? value : getcpy(value);
4068
4069 if (*first) {
4070 (*last)->pm_next = pm;
4071 *last = pm;
4072 } else {
4073 *first = pm;
4074 *last = pm;
4075 }
4076
4077 return pm;
4078 }
4079
4080 /*
4081 * Either replace a current parameter with a new value, or add the parameter
4082 * to the parameter linked list.
4083 */
4084
4085 PM
4086 replace_param(PM *first, PM *last, char *name, char *value, int nocopy)
4087 {
4088 PM pm;
4089
4090 for (pm = *first; pm != NULL; pm = pm->pm_next) {
4091 if (strcasecmp(name, pm->pm_name) == 0) {
4092 /*
4093 * If nocopy is set, it's assumed that we own both name
4094 * and value. We don't need name, so we discard it now.
4095 */
4096 if (nocopy)
4097 free(name);
4098 free(pm->pm_value);
4099 pm->pm_value = nocopy ? value : getcpy(value);
4100 return pm;
4101 }
4102 }
4103
4104 return add_param(first, last, name, value, nocopy);
4105 }
4106
4107 /*
4108 * Retrieve a parameter value from a parameter linked list. If the parameter
4109 * value needs converted to the local character set, do that now.
4110 */
4111
4112 char *
4113 get_param(PM first, const char *name, char replace, int fetchonly)
4114 {
4115 while (first != NULL) {
4116 if (strcasecmp(name, first->pm_name) == 0) {
4117 if (fetchonly)
4118 return first->pm_value;
4119 else
4120 return getcpy(get_param_value(first, replace));
4121 }
4122 first = first->pm_next;
4123 }
4124
4125 return NULL;
4126 }
4127
4128 /*
4129 * Return a parameter value, converting to the local character set if
4130 * necessary
4131 */
4132
4133 char *get_param_value(PM pm, char replace)
4134 {
4135 static char buffer[4096]; /* I hope no parameters are larger */
4136 size_t bufsize = sizeof(buffer);
4137 #ifdef HAVE_ICONV
4138 size_t inbytes;
4139 int utf8;
4140 iconv_t cd;
4141 ICONV_CONST char *p;
4142 #else /* HAVE_ICONV */
4143 char *p;
4144 #endif /* HAVE_ICONV */
4145
4146 char *q;
4147
4148 /*
4149 * If we don't have a character set indicated, it's assumed to be
4150 * US-ASCII. If it matches our character set, we don't need to convert
4151 * anything.
4152 */
4153
4154 if (!pm->pm_charset || check_charset(pm->pm_charset,
4155 strlen(pm->pm_charset))) {
4156 return pm->pm_value;
4157 }
4158
4159 /*
4160 * In this case, we need to convert. If we have iconv support, use
4161 * that. Otherwise, go through and simply replace every non-ASCII
4162 * character with the substitution character.
4163 */
4164
4165 #ifdef HAVE_ICONV
4166 q = buffer;
4167 bufsize = sizeof(buffer);
4168 utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0;
4169
4170 cd = iconv_open(get_charset(), pm->pm_charset);
4171 if (cd == (iconv_t) -1) {
4172 goto noiconv;
4173 }
4174
4175 inbytes = strlen(pm->pm_value);
4176 p = pm->pm_value;
4177
4178 while (inbytes) {
4179 if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) {
4180 if (errno != EILSEQ) {
4181 iconv_close(cd);
4182 goto noiconv;
4183 }
4184 /*
4185 * Reset shift state, substitute our character,
4186 * try to restart conversion.
4187 */
4188
4189 iconv(cd, NULL, NULL, &q, &bufsize);
4190
4191 if (bufsize == 0) {
4192 iconv_close(cd);
4193 goto noiconv;
4194 }
4195 *q++ = replace;
4196 bufsize--;
4197 if (bufsize == 0) {
4198 iconv_close(cd);
4199 goto noiconv;
4200 }
4201 if (utf8) {
4202 for (++p, --inbytes;
4203 inbytes > 0 && (((unsigned char) *q) & 0xc0) == 0x80;
4204 ++p, --inbytes)
4205 continue;
4206 } else {
4207 p++;
4208 inbytes--;
4209 }
4210 }
4211 }
4212
4213 iconv_close(cd);
4214
4215 if (bufsize == 0)
4216 q--;
4217 *q = '\0';
4218
4219 return buffer;
4220
4221 noiconv:
4222 #endif /* HAVE_ICONV */
4223
4224 /*
4225 * Take everything non-ASCII and substituite the replacement character
4226 */
4227
4228 q = buffer;
4229 bufsize = sizeof(buffer);
4230 for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) {
4231 if (isascii((unsigned char) *p) && !iscntrl((unsigned char) *p))
4232 *q = *p;
4233 else
4234 *q = replace;
4235 }
4236
4237 *q = '\0';
4238
4239 return buffer;
4240 }