]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Replace getcpy() with mh_xstrdup() where the string isn't NULL.
[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 NEW0(pp);
3541 pp->name = nameptr;
3542 pp->next = phead;
3543 phead = pp;
3544 }
3545
3546 /*
3547 * Insert this into the section linked list
3548 */
3549
3550 NEW0(sp);
3551 sp->value = valptr;
3552 sp->index = index;
3553 sp->len = len;
3554
3555 if (pp->sechead == NULL || pp->sechead->index > index) {
3556 sp->next = pp->sechead;
3557 pp->sechead = sp;
3558 } else {
3559 for (sp2 = pp->sechead; sp2 != NULL; sp2 = sp2->next) {
3560 if (sp2->index == sp->index) {
3561 advise (NULL, "duplicate index (%d) in message "
3562 "%s's %s: field\n%*s(parameter %s)", sp->index,
3563 filename, fieldname, strlen(invo_name) + 2, "",
3564 nameptr);
3565 free (nameptr);
3566 return NOTOK;
3567 }
3568 if (sp2->index < sp->index &&
3569 (sp2->next == NULL || sp2->next->index > sp->index)) {
3570 sp->next = sp2->next;
3571 sp2->next = sp;
3572 break;
3573 }
3574 }
3575
3576 if (sp2 == NULL) {
3577 advise(NULL, "Internal error: cannot insert partial "
3578 "param in message %s's %s: field\n%*s(parameter %s)",
3579 filename, fieldname, strlen(invo_name) + 2, "",
3580 nameptr);
3581 free (nameptr);
3582 return NOTOK;
3583 }
3584 }
3585
3586 /*
3587 * Save our charset and lang tags.
3588 */
3589
3590 if (index == 0 && encoded) {
3591 if (pp->charset)
3592 free(pp->charset);
3593 pp->charset = charset;
3594 if (pp->lang)
3595 free(pp->lang);
3596 pp->lang = lang;
3597 }
3598 } else {
3599 pm = add_param(param_head, param_tail, nameptr, valptr, 1);
3600 pm->pm_charset = charset;
3601 pm->pm_lang = lang;
3602 }
3603
3604 while (isspace ((unsigned char) *cp))
3605 cp++;
3606
3607 if (*cp == '(' &&
3608 get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
3609 return NOTOK;
3610 }
3611 }
3612
3613 /*
3614 * Now that we're done, reassemble all of the partial parameters.
3615 */
3616
3617 for (pp = phead; pp != NULL; ) {
3618 char *p, *q;
3619 size_t tlen = 0;
3620 int pindex = 0;
3621 for (sp = pp->sechead; sp != NULL; sp = sp->next) {
3622 if (sp->index != pindex++) {
3623 advise(NULL, "missing section %d for parameter in "
3624 "message %s's %s: field\n%*s(parameter %s)", pindex - 1,
3625 filename, fieldname, strlen(invo_name) + 2, "",
3626 pp->name);
3627 return NOTOK;
3628 }
3629 tlen += sp->len;
3630 }
3631
3632 p = q = mh_xmalloc(tlen + 1);
3633 for (sp = pp->sechead; sp != NULL; ) {
3634 memcpy(q, sp->value, sp->len);
3635 q += sp->len;
3636 free(sp->value);
3637 sp2 = sp->next;
3638 free(sp);
3639 sp = sp2;
3640 }
3641
3642 p[tlen] = '\0';
3643
3644 pm = add_param(param_head, param_tail, pp->name, p, 1);
3645 pm->pm_charset = pp->charset;
3646 pm->pm_lang = pp->lang;
3647 pp2 = pp->next;
3648 free(pp);
3649 pp = pp2;
3650 }
3651
3652 *header_attrp = cp;
3653 return OK;
3654 }
3655
3656 /*
3657 * Return the charset for a particular content type.
3658 */
3659
3660 char *
3661 content_charset (CT ct) {
3662 char *ret_charset = NULL;
3663
3664 ret_charset = get_param(ct->c_ctinfo.ci_first_pm, "charset", '?', 0);
3665
3666 return ret_charset ? ret_charset : mh_xstrdup("US-ASCII");
3667 }
3668
3669
3670 /*
3671 * Create a string based on a list of output parameters. Assume that this
3672 * parameter string will be appended to an existing header, so start out
3673 * with the separator (;). Perform RFC 2231 encoding when necessary.
3674 */
3675
3676 char *
3677 output_params(size_t initialwidth, PM params, int *offsetout, int external)
3678 {
3679 char *paramout = NULL;
3680 char line[CPERLIN * 2], *q;
3681 int curlen, index, cont, encode, i;
3682 size_t valoff, numchars;
3683
3684 while (params != NULL) {
3685 encode = 0;
3686 index = 0;
3687 valoff = 0;
3688 q = line;
3689
3690 if (external && strcasecmp(params->pm_name, "body") == 0)
3691 continue;
3692
3693 if (strlen(params->pm_name) > CPERLIN) {
3694 advise(NULL, "Parameter name \"%s\" is too long", params->pm_name);
3695 if (paramout)
3696 free(paramout);
3697 return NULL;
3698 }
3699
3700 curlen = param_len(params, index, valoff, &encode, &cont, &numchars);
3701
3702 /*
3703 * Loop until we get a parameter that fits within a line. We
3704 * assume new lines start with a tab, so check our overflow based
3705 * on that.
3706 */
3707
3708 while (cont) {
3709 *q++ = ';';
3710 *q++ = '\n';
3711 *q++ = '\t';
3712
3713 /*
3714 * At this point we're definitely continuing the line, so
3715 * be sure to include the parameter name and section index.
3716 */
3717
3718 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3719 params->pm_name, index);
3720
3721 /*
3722 * Both of these functions do a NUL termination
3723 */
3724
3725 if (encode)
3726 i = encode_param(params, q, sizeof(line) - (q - line),
3727 numchars, valoff, index);
3728 else
3729 i = normal_param(params, q, sizeof(line) - (q - line),
3730 numchars, valoff);
3731
3732 if (i == 0) {
3733 if (paramout)
3734 free(paramout);
3735 return NULL;
3736 }
3737
3738 valoff += numchars;
3739 index++;
3740 curlen = param_len(params, index, valoff, &encode, &cont,
3741 &numchars);
3742 q = line;
3743
3744 /*
3745 * "line" starts with a ;\n\t, so that doesn't count against
3746 * the length. But add 8 since it starts with a tab; that's
3747 * how we end up with 5.
3748 */
3749
3750 initialwidth = strlen(line) + 5;
3751
3752 /*
3753 * At this point the line should be built, so add it to our
3754 * current output buffer.
3755 */
3756
3757 paramout = add(line, paramout);
3758 }
3759
3760 /*
3761 * If this won't fit on the line, start a new one. Save room in
3762 * case we need a semicolon on the end
3763 */
3764
3765 if (initialwidth + curlen > CPERLIN - 1) {
3766 *q++ = ';';
3767 *q++ = '\n';
3768 *q++ = '\t';
3769 initialwidth = 8;
3770 } else {
3771 *q++ = ';';
3772 *q++ = ' ';
3773 initialwidth += 2;
3774 }
3775
3776 /*
3777 * At this point, we're either finishing a contined parameter, or
3778 * we're working on a new one.
3779 */
3780
3781 if (index > 0) {
3782 q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
3783 params->pm_name, index);
3784 } else {
3785 strncpy(q, params->pm_name, sizeof(line) - (q - line));
3786 q += strlen(q);
3787 }
3788
3789 if (encode)
3790 i = encode_param(params, q, sizeof(line) - (q - line),
3791 strlen(params->pm_value + valoff), valoff, index);
3792 else
3793 i = normal_param(params, q, sizeof(line) - (q - line),
3794 strlen(params->pm_value + valoff), valoff);
3795
3796 if (i == 0) {
3797 if (paramout)
3798 free(paramout);
3799 return NULL;
3800 }
3801
3802 paramout = add(line, paramout);
3803 initialwidth += strlen(line);
3804
3805 params = params->pm_next;
3806 }
3807
3808 if (offsetout)
3809 *offsetout = initialwidth;
3810
3811 return paramout;
3812 }
3813
3814 /*
3815 * Calculate the size of a parameter.
3816 *
3817 * Arguments include
3818 *
3819 * pm - The parameter being output
3820 * index - If continuing the parameter, the index of the section
3821 * we're on.
3822 * valueoff - The current offset into the parameter value that we're
3823 * working on (previous sections have consumed valueoff bytes).
3824 * encode - Set if we should perform encoding on this parameter section
3825 * (given that we're consuming bytesfit bytes).
3826 * cont - Set if the remaining data in value will not fit on a single
3827 * line and will need to be continued.
3828 * bytesfit - The number of bytes that we can consume from the parameter
3829 * value and still fit on a completely new line. The
3830 * calculation assumes the new line starts with a tab,
3831 * includes the parameter name and any encoding, and fits
3832 * within CPERLIN bytes. Will always be at least 1.
3833 */
3834
3835 static size_t
3836 param_len(PM pm, int index, size_t valueoff, int *encode, int *cont,
3837 size_t *bytesfit)
3838 {
3839 char *start = pm->pm_value + valueoff, *p, indexchar[32];
3840 size_t len = 0, fit = 0;
3841 int fitlimit = 0, eightbit, maxfit;
3842
3843 *encode = 0;
3844
3845 /*
3846 * Add up the length. First, start with the parameter name.
3847 */
3848
3849 len = strlen(pm->pm_name);
3850
3851 /*
3852 * Scan the parameter value and see if we need to do encoding for this
3853 * section.
3854 */
3855
3856 eightbit = contains8bit(start, NULL);
3857
3858 /*
3859 * Determine if we need to encode this section. Encoding is necessary if:
3860 *
3861 * - There are any 8-bit characters at all and we're on the first
3862 * section.
3863 * - There are 8-bit characters within N bytes of our section start.
3864 * N is calculated based on the number of bytes it would take to
3865 * reach CPERLIN. Specifically:
3866 * 8 (starting tab) +
3867 * strlen(param name) +
3868 * 4 ('* for section marker, '=', opening/closing '"')
3869 * strlen (index)
3870 * is the number of bytes used by everything that isn't part of the
3871 * value. So that gets subtracted from CPERLIN.
3872 */
3873
3874 snprintf(indexchar, sizeof(indexchar), "%d", index);
3875 maxfit = CPERLIN - (12 + len + strlen(indexchar));
3876 if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) {
3877 *encode = 1;
3878 }
3879
3880 len++; /* Add in equal sign */
3881
3882 if (*encode) {
3883 /*
3884 * We're using maxfit as a marker for how many characters we can
3885 * fit into the line. Bump it by two because we're not using quotes
3886 * when encoding.
3887 */
3888
3889 maxfit += 2;
3890
3891 /*
3892 * If we don't have a charset or language tag in this parameter,
3893 * add them now.
3894 */
3895
3896 if (! pm->pm_charset) {
3897 pm->pm_charset = getcpy(write_charset_8bit());
3898 if (strcasecmp(pm->pm_charset, "US-ASCII") == 0)
3899 adios(NULL, "8-bit characters in parameter \"%s\", but "
3900 "local character set is US-ASCII", pm->pm_name);
3901 }
3902 if (! pm->pm_lang)
3903 pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */
3904
3905 len++; /* For the encoding marker */
3906 maxfit--;
3907 if (index == 0) {
3908 int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
3909 len += enclen;
3910 maxfit-= enclen;
3911 } else {
3912 /*
3913 * We know we definitely need to include an index. maxfit already
3914 * includes the section marker.
3915 */
3916 len += strlen(indexchar);
3917 }
3918 for (p = start; *p != '\0'; p++) {
3919 if (isparamencode(*p)) {
3920 len += 3;
3921 maxfit -= 3;
3922 } else {
3923 len++;
3924 maxfit--;
3925 }
3926 /*
3927 * Just so there's no confusion: maxfit is counting OUTPUT
3928 * characters (post-encoding). fit is counting INPUT characters.
3929 */
3930 if (! fitlimit && maxfit >= 0)
3931 fit++;
3932 else if (! fitlimit)
3933 fitlimit++;
3934 }
3935 } else {
3936 /*
3937 * Calculate the string length, but add room for quoting \
3938 * and " if necessary. Also account for quotes at beginning
3939 * and end.
3940 */
3941 for (p = start; *p != '\0'; p++) {
3942 switch (*p) {
3943 case '"':
3944 case '\\':
3945 len++;
3946 maxfit--;
3947 /* FALL THROUGH */
3948 default:
3949 len++;
3950 maxfit--;
3951 }
3952 if (! fitlimit && maxfit >= 0)
3953 fit++;
3954 else if (! fitlimit)
3955 fitlimit++;
3956 }
3957
3958 len += 2;
3959 }
3960
3961 if (fit < 1)
3962 fit = 1;
3963
3964 *cont = fitlimit;
3965 *bytesfit = fit;
3966
3967 return len;
3968 }
3969
3970 /*
3971 * Output an encoded parameter string.
3972 */
3973
3974 size_t
3975 encode_param(PM pm, char *output, size_t len, size_t valuelen,
3976 size_t valueoff, int index)
3977 {
3978 size_t outlen = 0, n;
3979 char *endptr = output + len, *p;
3980
3981 /*
3982 * First, output the marker for an encoded string.
3983 */
3984
3985 *output++ = '*';
3986 *output++ = '=';
3987 outlen += 2;
3988
3989 /*
3990 * If the index is 0, output the character set and language tag.
3991 * If theses were NULL, they should have already been filled in
3992 * by param_len().
3993 */
3994
3995 if (index == 0) {
3996 n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset,
3997 pm->pm_lang);
3998 output += n;
3999 outlen += n;
4000 if (output > endptr) {
4001 advise(NULL, "Internal error: parameter buffer overflow");
4002 return 0;
4003 }
4004 }
4005
4006 /*
4007 * Copy over the value, encoding if necessary
4008 */
4009
4010 p = pm->pm_value + valueoff;
4011 while (valuelen-- > 0) {
4012 if (isparamencode(*p)) {
4013 n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++);
4014 output += n;
4015 outlen += n;
4016 } else {
4017 *output++ = *p++;
4018 outlen++;
4019 }
4020 if (output > endptr) {
4021 advise(NULL, "Internal error: parameter buffer overflow");
4022 return 0;
4023 }
4024 }
4025
4026 *output = '\0';
4027
4028 return outlen;
4029 }
4030
4031 /*
4032 * Output a "normal" parameter, without encoding. Be sure to escape
4033 * quotes and backslashes if necessary.
4034 */
4035
4036 static size_t
4037 normal_param(PM pm, char *output, size_t len, size_t valuelen,
4038 size_t valueoff)
4039 {
4040 size_t outlen = 0;
4041 char *endptr = output + len, *p;
4042
4043 *output++ = '=';
4044 *output++ = '"';
4045 outlen += 2;
4046
4047 p = pm->pm_value + valueoff;
4048
4049 while (valuelen-- > 0) {
4050 switch (*p) {
4051 case '\\':
4052 case '"':
4053 *output++ = '\\';
4054 outlen++;
4055 default:
4056 *output++ = *p++;
4057 outlen++;
4058 }
4059 if (output > endptr) {
4060 advise(NULL, "Internal error: parameter buffer overflow");
4061 return 0;
4062 }
4063 }
4064
4065 if (output - 2 > endptr) {
4066 advise(NULL, "Internal error: parameter buffer overflow");
4067 return 0;
4068 }
4069
4070 *output++ = '"';
4071 *output++ = '\0';
4072
4073 return outlen + 1;
4074 }
4075
4076 /*
4077 * Add a parameter to the parameter linked list
4078 */
4079
4080 PM
4081 add_param(PM *first, PM *last, char *name, char *value, int nocopy)
4082 {
4083 PM pm;
4084
4085 NEW0(pm);
4086 pm->pm_name = nocopy ? name : getcpy(name);
4087 pm->pm_value = nocopy ? value : getcpy(value);
4088
4089 if (*first) {
4090 (*last)->pm_next = pm;
4091 *last = pm;
4092 } else {
4093 *first = pm;
4094 *last = pm;
4095 }
4096
4097 return pm;
4098 }
4099
4100 /*
4101 * Either replace a current parameter with a new value, or add the parameter
4102 * to the parameter linked list.
4103 */
4104
4105 PM
4106 replace_param(PM *first, PM *last, char *name, char *value, int nocopy)
4107 {
4108 PM pm;
4109
4110 for (pm = *first; pm != NULL; pm = pm->pm_next) {
4111 if (strcasecmp(name, pm->pm_name) == 0) {
4112 /*
4113 * If nocopy is set, it's assumed that we own both name
4114 * and value. We don't need name, so we discard it now.
4115 */
4116 if (nocopy)
4117 free(name);
4118 free(pm->pm_value);
4119 pm->pm_value = nocopy ? value : getcpy(value);
4120 return pm;
4121 }
4122 }
4123
4124 return add_param(first, last, name, value, nocopy);
4125 }
4126
4127 /*
4128 * Retrieve a parameter value from a parameter linked list. If the parameter
4129 * value needs converted to the local character set, do that now.
4130 */
4131
4132 char *
4133 get_param(PM first, const char *name, char replace, int fetchonly)
4134 {
4135 while (first != NULL) {
4136 if (strcasecmp(name, first->pm_name) == 0) {
4137 if (fetchonly)
4138 return first->pm_value;
4139 else
4140 return getcpy(get_param_value(first, replace));
4141 }
4142 first = first->pm_next;
4143 }
4144
4145 return NULL;
4146 }
4147
4148 /*
4149 * Return a parameter value, converting to the local character set if
4150 * necessary
4151 */
4152
4153 char *get_param_value(PM pm, char replace)
4154 {
4155 static char buffer[4096]; /* I hope no parameters are larger */
4156 size_t bufsize = sizeof(buffer);
4157 #ifdef HAVE_ICONV
4158 size_t inbytes;
4159 int utf8;
4160 iconv_t cd;
4161 ICONV_CONST char *p;
4162 #else /* HAVE_ICONV */
4163 char *p;
4164 #endif /* HAVE_ICONV */
4165
4166 char *q;
4167
4168 /*
4169 * If we don't have a character set indicated, it's assumed to be
4170 * US-ASCII. If it matches our character set, we don't need to convert
4171 * anything.
4172 */
4173
4174 if (!pm->pm_charset || check_charset(pm->pm_charset,
4175 strlen(pm->pm_charset))) {
4176 return pm->pm_value;
4177 }
4178
4179 /*
4180 * In this case, we need to convert. If we have iconv support, use
4181 * that. Otherwise, go through and simply replace every non-ASCII
4182 * character with the substitution character.
4183 */
4184
4185 #ifdef HAVE_ICONV
4186 q = buffer;
4187 bufsize = sizeof(buffer);
4188 utf8 = strcasecmp(pm->pm_charset, "UTF-8") == 0;
4189
4190 cd = iconv_open(get_charset(), pm->pm_charset);
4191 if (cd == (iconv_t) -1) {
4192 goto noiconv;
4193 }
4194
4195 inbytes = strlen(pm->pm_value);
4196 p = pm->pm_value;
4197
4198 while (inbytes) {
4199 if (iconv(cd, &p, &inbytes, &q, &bufsize) == (size_t)-1) {
4200 if (errno != EILSEQ) {
4201 iconv_close(cd);
4202 goto noiconv;
4203 }
4204 /*
4205 * Reset shift state, substitute our character,
4206 * try to restart conversion.
4207 */
4208
4209 iconv(cd, NULL, NULL, &q, &bufsize);
4210
4211 if (bufsize == 0) {
4212 iconv_close(cd);
4213 goto noiconv;
4214 }
4215 *q++ = replace;
4216 bufsize--;
4217 if (bufsize == 0) {
4218 iconv_close(cd);
4219 goto noiconv;
4220 }
4221 if (utf8) {
4222 for (++p, --inbytes;
4223 inbytes > 0 && (((unsigned char) *p) & 0xc0) == 0x80;
4224 ++p, --inbytes)
4225 continue;
4226 } else {
4227 p++;
4228 inbytes--;
4229 }
4230 }
4231 }
4232
4233 iconv_close(cd);
4234
4235 if (bufsize == 0)
4236 q--;
4237 *q = '\0';
4238
4239 return buffer;
4240
4241 noiconv:
4242 #endif /* HAVE_ICONV */
4243
4244 /*
4245 * Take everything non-ASCII and substituite the replacement character
4246 */
4247
4248 q = buffer;
4249 bufsize = sizeof(buffer);
4250 for (p = pm->pm_value; *p != '\0' && bufsize > 1; p++, q++, bufsize--) {
4251 if (isascii((unsigned char) *p) && !iscntrl((unsigned char) *p))
4252 *q = *p;
4253 else
4254 *q = replace;
4255 }
4256
4257 *q = '\0';
4258
4259 return buffer;
4260 }