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