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