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