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