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