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