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