xref: /netbsd-src/usr.bin/mail/mime_decode.c (revision c0179c282a5968435315a82f4128c61372c68fc3)
1 /*	$NetBSD: mime_decode.c,v 1.3 2006/11/01 16:42:27 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 2006 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Anon Ymous.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 
40 #ifdef MIME_SUPPORT
41 
42 #include <sys/cdefs.h>
43 #ifndef __lint__
44 __RCSID("$NetBSD: mime_decode.c,v 1.3 2006/11/01 16:42:27 christos Exp $");
45 #endif /* not __lint__ */
46 
47 #include <assert.h>
48 #include <err.h>
49 #include <fcntl.h>
50 #include <libgen.h>
51 #include <setjmp.h>
52 #include <signal.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <iconv.h>
58 
59 #include "def.h"
60 #include "extern.h"
61 #ifdef USE_EDITLINE
62 #include "complete.h"
63 #endif
64 #ifdef MIME_SUPPORT
65 #include "mime.h"
66 #include "mime_child.h"
67 #include "mime_codecs.h"
68 #include "mime_header.h"
69 #endif
70 #include "glob.h"
71 
72 
73 /************************************************
74  * The fundametal data structure for this module!
75  */
76 struct mime_info {
77 	struct mime_info *mi_blink;
78 	struct mime_info *mi_flink;
79 
80 	/* sendmessage -> decoder -> filter -> pager */
81 
82 	FILE *mi_fo;		/* output file handle pointing to PAGER */
83 	FILE *mi_pipe_end;	/* initial end of pipe */
84 	FILE *mi_head_end;	/* close to here at start of body */
85 
86 	int mi_partnum;		/* part number displayed (if nonzero) */
87 
88 	const char *mi_version;
89 	const char *mi_type;
90 	const char *mi_subtype;
91 	const char *mi_boundary;
92 	const char *mi_charset;
93 	const char *mi_encoding;
94 
95 	struct message *mp;		/* MP for this message regarded as a part. */
96 	struct {
97 		struct mime_info *mip;	/* parent of part of multipart message */
98 		struct message *mp;
99 	} mi_parent;
100 
101 	const char *mi_command_hook;	/* alternate command used to process this message */
102 };
103 
104 
105 #if 0
106 #ifndef __lint__
107 /*
108  * XXX - This block for debugging only and eventually should go away.
109  */
110 static void
111 show_one_mime_info(FILE *fp, struct mime_info *mip)
112 {
113 #define XX(a) (a) ? (a) : "<null>"
114 
115 	(void)fprintf(fp, ">> --------\n");
116 	(void)fprintf(fp, "mip %d:\n", mip->mi_partnum);
117 	(void)fprintf(fp, "** Version: %s\n",  XX(mip->mi_version));
118 	(void)fprintf(fp, "** type: %s\n",     XX(mip->mi_type));
119 	(void)fprintf(fp, "** subtype: %s\n",  XX(mip->mi_subtype));
120 	(void)fprintf(fp, "** charset: %s\n",  XX(mip->mi_charset));
121 	(void)fprintf(fp, "** encoding: %s\n", XX(mip->mi_encoding));
122 	(void)fprintf(fp, "** boundary: %s\n", XX(mip->mi_boundary));
123 	(void)fprintf(fp, "** %p: flag: 0x%x, block: %ld, offset: %d, size: %lld, lines: %ld:%ld\n",
124 	    mip->mp,
125 	    mip->mp->m_flag,
126 	    mip->mp->m_block, mip->mp->m_offset, mip->mp->m_size,
127 	    mip->mp->m_lines, mip->mp->m_blines);
128 	(void)fprintf(fp, "** mip: %p\n", mip);
129 	(void)fprintf(fp, "** mi_flink: %p\n", mip->mi_flink);
130 	(void)fprintf(fp, "** mi_blink: %p\n", mip->mi_blink);
131 	(void)fprintf(fp, "** mip %p, mp %p,  parent_mip %p, parent_mp %p\n",
132 	    mip, mip->mp, mip->mi_parent.mip, mip->mi_parent.mp);
133 
134 	(void)fprintf(fp, "** mi_fo %p, mi_head_end %p, mi_pipe_end %p\n",
135 	    mip->mi_fo, mip->mi_head_end, mip->mi_pipe_end);
136 
137 	(void)fprintf(fp, "** mi_partnum: %d\n", mip->mi_partnum);
138 
139 	(void)fflush(fp);
140 
141 #undef XX
142 }
143 
144 __unused
145 static void
146 show_mime_info(FILE *fp, struct mime_info *mip, struct mime_info *end_mip)
147 {
148 	for (/* EMTPY */; mip != end_mip; mip = mip->mi_flink)
149 		show_one_mime_info(fp, mip);
150 
151 	(void)fprintf(fp, "++ =========\n");
152 	(void)fflush(fp);
153 }
154 #endif /* __lint__ */
155 #endif /* #if */
156 
157 
158 /*
159  * Our interface to the file registry in popen.c
160  */
161 static FILE *
162 pipe_end(struct mime_info *mip)
163 {
164 	FILE *fp;
165 	fp = last_registered_file(1);	/* get last registered pipe */
166 	if (fp == NULL)
167 		fp = mip->mi_fo;
168 	return fp;
169 }
170 
171 /*
172  * Copy the first ';' delimited substring from 'src' (null terminated)
173  * into 'dst', expanding quotes and removing comments (as per RFC
174  * 822).  Returns a pointer in src to the next non-white character
175  * following ';'.  The caller is responsible for ensuring 'dst' is
176  * sufficiently large to hold the result.
177  */
178 static char *
179 get_param(char *dst, char *src)
180 {
181 	char *lastq;
182 	char *cp;
183 	char *cp2;
184 	int nesting;
185 
186 	cp2 = dst;
187 	lastq = dst;
188 	for (cp = src; *cp && *cp != ';'; cp++) {
189 		switch (*cp) {
190 		case '"':	/* start of quoted string */
191 			for (cp++; *cp; cp++) {
192 				if (*cp == '"')
193 					break;
194 				if (*cp == '\\' && cp[1] != '\0')
195 					++cp;
196 				*cp2++ = *cp;
197 			}
198 			lastq = cp2-1;
199 			break;
200 		case '(':	/* start of comment */
201 			nesting = 1;
202 			while (nesting > 0 && *++cp) {
203 				if (*cp == '\\' && cp[1] != '\0')
204 					cp++;
205 				if (*cp == '(')
206 					nesting++;
207 				if (*cp == ')')
208 					nesting--;
209 			}
210 			break;
211 		default:
212 			*cp2++ = *cp;
213 			break;
214 		}
215 	}
216 	/* remove trailing white space */
217 	while (cp2 > lastq && isblank((unsigned char)cp2[-1]))
218 		cp2--;
219 	*cp2 = '\0';
220 	if (*cp == ';')
221 		cp++;
222 	cp = skip_white(cp);
223 	return cp;
224 }
225 
226 /*
227  * Content parameter
228  *    if field is NULL, return the content "specifier".
229  */
230 static char*
231 cparam(const char field[], char *src, int downcase)
232 {
233 	char *cp;
234 	char *dst;
235 
236 	if (src == NULL)
237 		return NULL;
238 
239 	dst = salloc(strlen(src) + 1); /* large enough for any param in src */
240 	cp = skip_white(src);
241 	cp = get_param(dst, cp);
242 
243 	if (field == NULL)
244 		return dst;
245 
246 	while (*cp != '\0') {
247 		size_t len = strlen(field);
248 		cp = get_param(dst, cp);
249 		if (strncasecmp(dst, field, len) == 0 && dst[len] == '=') {
250 			char *cp2;
251 			cp2 = dst + len + 1;
252 			if (downcase)
253 				istrcpy(cp2, cp2);
254 			return cp2;
255 		}
256 	}
257 	return NULL;
258 }
259 
260 
261 static void
262 get_content(struct mime_info *mip)
263 {
264 	char *mime_type_field;
265 	struct message *mp;
266 	char *cp;
267 
268 	mp = mip->mp;
269 	mip->mi_version  = cparam(NULL, hfield(MIME_HDR_VERSION, mp), 0);
270 	mip->mi_encoding = cparam(NULL, hfield(MIME_HDR_ENCODING, mp), 1);
271 
272 	mime_type_field = hfield(MIME_HDR_TYPE, mp);
273 	mip->mi_type = cparam(NULL, mime_type_field, 1);
274 	if (mip->mi_type) {
275 		cp = strchr(mip->mi_type, '/');
276 		if (cp)
277 			*cp++ = '\0';
278 		mip->mi_subtype = cp;
279 	}
280 	mip->mi_charset = cparam("charset", mime_type_field, 1);
281 	mip->mi_boundary = cparam("boundary", mime_type_field, 0);
282 }
283 
284 
285 static struct message *
286 salloc_message(int flag, long block, short offset)
287 {
288 	struct message *mp;
289 	/* use csalloc in case someone adds a field someday! */
290 	mp = csalloc(1, sizeof(*mp));
291 	mp->m_flag   = flag;
292 	mp->m_block  = block;
293 	mp->m_offset = offset;
294 #if 0
295 	mp->m_lines  = 0;
296 	mp->m_size   = 0;
297 	mp->m_blines = 0;
298 #endif
299 	return mp;
300 }
301 
302 static struct mime_info *
303 insert_new_mip(struct mime_info *this_mip)
304 {
305 	struct mime_info *new_mip;
306 	new_mip = csalloc(1, sizeof(*new_mip));
307 	new_mip->mi_blink = this_mip;
308 	new_mip->mi_flink = this_mip->mi_flink;
309 	this_mip->mi_flink = new_mip;
310 	this_mip = new_mip;
311 	return new_mip;
312 }
313 
314 static void
315 split_multipart(struct mime_info *top_mip)
316 {
317 	FILE *fp;
318 	struct message *top_mp;
319 	struct message *this_mp;
320 	struct mime_info *this_mip;
321 	off_t beg_pos;
322 	const char *boundary;
323 	size_t boundary_len;
324 	long lines_left;	/* must be signed and same size as m_lines */
325 	int partnum;
326 	int in_header;
327 
328 	top_mp = top_mip->mp;
329 	this_mp = salloc_message(top_mp->m_flag, top_mp->m_block, top_mp->m_offset);
330 	this_mip = top_mip;
331 	this_mip->mp = this_mp;
332 
333 	partnum = 1;
334 /*	top_mip->mi_partnum = partnum++;  */ /* Keep the number set by the caller */
335 	in_header = 1;
336 	boundary = top_mip->mi_boundary;
337 	boundary_len = boundary ? strlen(boundary) : 0;
338 
339 	fp = setinput(top_mp);
340 	beg_pos = ftello(fp);
341 
342 	for (lines_left = top_mp->m_lines - 1; lines_left >= 0; lines_left--) {
343 		char *line;
344 		size_t line_len;
345 
346 		line = fgetln(fp, &line_len);
347 
348 		this_mp->m_lines++;		/* count the message lines */
349 
350 		if (!in_header)
351 			this_mp->m_blines++;	/* count the body lines */
352 
353 		if (lines_left == 0 || (
354 			    !in_header &&
355 			    line_len >= boundary_len + 2 &&
356 			    line[0] == '-' && line[1] == '-' &&
357 			    strncmp(line + 2, boundary, boundary_len) == 0)) {
358 			off_t cur_pos;
359 			off_t end_pos;
360 
361 			cur_pos = ftello(fp);
362 
363 			/* the boundary belongs to the next part */
364 			end_pos = cur_pos - line_len;
365 			this_mp->m_lines  -= 1;
366 			this_mp->m_blines -= 1;
367 
368 			this_mp->m_size = end_pos - beg_pos;
369 
370 			if (line[boundary_len + 2] == '-' &&
371 			    line[boundary_len + 3] == '-') {/* end of multipart */
372 				/* do a sanity check on the EOM */
373 				if (lines_left) {
374 					/*
375 					 * XXX - this can happen!
376 					 * Should we display the
377 					 * trailing garbage or check
378 					 * that it is blank or just
379 					 * ignore it?
380 					 */
381 /*					(void)printf("EOM: lines left: %ld\n", lines_left); */
382 				}
383 				break;	/* XXX - stop at this point or grab the rest? */
384 			}
385 
386 			this_mip = insert_new_mip(this_mip);
387 			this_mp = salloc_message(top_mp->m_flag,
388 			    (long)blockof(end_pos), offsetof(end_pos));
389 			this_mip->mp = this_mp;
390 			this_mip->mi_parent.mip = top_mip;
391 			this_mip->mi_parent.mp = top_mp;
392 			this_mip->mi_partnum = partnum++;
393 
394 			beg_pos = end_pos;
395 			in_header = 1;
396 		}
397 
398 		if (line_len == 1)
399 			in_header = 0;
400 	}
401 }
402 
403 static void
404 split_message(struct mime_info *top_mip)
405 {
406 	struct mime_info *this_mip;
407 	struct message *top_mp;
408 	struct message *this_mp;
409 	FILE *fp;
410 	off_t beg_pos;
411 	long lines_left;	/* must be same size as m_lines */
412 	int in_header;
413 
414 	top_mp = top_mip->mp;
415 	this_mp = salloc_message(top_mp->m_flag, top_mp->m_block, top_mp->m_offset);
416 	this_mip = top_mip;
417 	this_mip->mp = this_mp;
418 
419 	in_header = 1;
420 
421 	fp = setinput(top_mp);
422 	beg_pos = ftello(fp);
423 
424 	for (lines_left = top_mp->m_lines; lines_left > 0; lines_left--) {
425 		size_t line_len;
426 
427 		(void)fgetln(fp, &line_len);
428 
429 		this_mp->m_lines++;		/* count the message lines */
430 		if (!in_header)
431 			this_mp->m_blines++;	/* count the body lines */
432 
433 		if (in_header && line_len == 1) { /* end of header */
434 			off_t end_pos;
435 			end_pos = ftello(fp);
436 			this_mp->m_size = end_pos - beg_pos;
437 
438 			this_mip = insert_new_mip(this_mip);
439 			this_mp = salloc_message(top_mp->m_flag,
440 			    (long)blockof(end_pos), offsetof(end_pos));
441 			this_mip->mp = this_mp;
442 			this_mip->mi_parent.mip = top_mip;
443 			this_mip->mi_parent.mp = top_mp;
444 			this_mip->mi_partnum = 0;  /* no partnum displayed */
445 
446 			beg_pos = end_pos;
447 			in_header = 0;	/* never in header again */
448 		}
449 	}
450 
451 	/* close the last message */
452 	this_mp->m_size = ftello(fp) - beg_pos;
453 }
454 
455 
456 static const char *
457 get_command_hook(struct mime_info *mip, const char *domain)
458 {
459 	char *key;
460 	char *cmd;
461 
462 	if (mip->mi_type == NULL)
463 		return NULL;
464 
465 	/* XXX - should we use easprintf() here?  We are probably
466 	 * hosed elsewhere if this fails anyway. */
467 
468 	cmd = NULL;
469 	if (mip->mi_subtype) {
470 		if (asprintf(&key, "mime%s-%s-%s",
471 			domain,	mip->mi_type, mip->mi_subtype) == -1) {
472 			warn("get_command_hook: subtupe: asprintf");
473 			return NULL;
474 		}
475 		cmd = value(key);
476 		free(key);
477 	}
478 	if (cmd == NULL) {
479 		if (asprintf(&key, "mime%s-%s", domain, mip->mi_type) == -1) {
480 			warn("get_command_hook: type: asprintf");
481 			return NULL;
482 		}
483 		cmd = value(key);
484 		free(key);
485 	}
486 	return cmd;
487 }
488 
489 
490 static int
491 is_basic_alternative(struct mime_info *mip)
492 {
493 	return
494 	    strcasecmp(mip->mi_type, "text") == 0 &&
495 	    strcasecmp(mip->mi_subtype, "plain") == 0;
496 }
497 
498 static struct mime_info *
499 select_alternative(struct mime_info *top_mip, struct mime_info *end_mip)
500 {
501 	struct mime_info *the_mip;	/* the chosen alternate */
502 	struct mime_info *this_mip;
503 	/*
504 	 * The alternates are supposed to occur in order of
505 	 * increasing "complexity".  So: if there is at least
506 	 * one alternate of type "text/plain", use the last
507 	 * one, otherwise default to the first alternate.
508 	 */
509 	the_mip = top_mip->mi_flink;
510 	for (this_mip = top_mip->mi_flink;
511 	     this_mip != end_mip;
512 	     this_mip = this_mip->mi_flink) {
513 		const char *cmd;
514 
515 		if (this_mip->mi_type == NULL ||
516 		    this_mip->mi_subtype == NULL)
517 			continue;
518 
519 		if (is_basic_alternative(this_mip))
520 			the_mip = this_mip;
521 		else if (
522 			(cmd = get_command_hook(this_mip, "-hook")) ||
523 			(cmd = get_command_hook(this_mip, "-head")) ||
524 			(cmd = get_command_hook(this_mip, "-body"))) {
525 			int flags;
526 			/* just get the flags. */
527 			flags = mime_run_command(cmd, NULL);
528 			if ((flags & CMD_FLAG_ALTERNATIVE) != 0)
529 				the_mip = this_mip;
530 		}
531 	}
532 	return the_mip;
533 }
534 
535 
536 static inline int
537 is_multipart(struct mime_info *mip)
538 {
539 	return mip->mi_type &&
540 	    strcasecmp("multipart", mip->mi_type) == 0;
541 }
542 static inline int
543 is_message(struct mime_info *mip)
544 {
545 	return mip->mi_type &&
546 	    strcasecmp("message", mip->mi_type) == 0;
547 }
548 
549 static inline int
550 is_alternative(struct mime_info *mip)
551 {
552 	return mip->mi_subtype &&
553 	    strcasecmp("alternative", mip->mi_subtype) == 0;
554 }
555 
556 
557 /*
558  * Take a mime_info pointer and expand it recursively into all its
559  * mime parts.  Only "multipart" and "message" types recursed into;
560  * they are handled separately.
561  */
562 static struct mime_info *
563 expand_mip(struct mime_info *top_mip)
564 {
565 	struct mime_info *this_mip;
566 	struct mime_info *next_mip;
567 
568 	next_mip = top_mip->mi_flink;
569 
570 	if (is_multipart(top_mip)) {
571 		split_multipart(top_mip);
572 
573 		for (this_mip = top_mip->mi_flink;
574 		     this_mip != next_mip;
575 		     this_mip = this_mip->mi_flink) {
576 			get_content(this_mip);
577 		}
578 		if (is_alternative(top_mip)) {
579 			this_mip = select_alternative(top_mip, next_mip);
580 			this_mip->mi_partnum = 0; /* suppress partnum display */
581 			this_mip->mi_flink = next_mip;
582 			this_mip->mi_blink = top_mip;
583 			top_mip->mi_flink  = this_mip;
584 		}
585 		/*
586 		 * Recurse into each part.
587 		 */
588 		for (this_mip = top_mip->mi_flink;
589 		     this_mip != next_mip;
590 		     this_mip = expand_mip(this_mip))
591 			continue;
592 	}
593 	else if (is_message(top_mip)) {
594 		split_message(top_mip);
595 
596 		this_mip = top_mip->mi_flink;
597 		if (this_mip) {
598 			get_content(this_mip);
599 			/*
600 			 * If the one part is MIME encoded, recurse into it.
601 			 * XXX - Should this be conditional on subtype "rcs822"?
602 			 */
603 			if (this_mip->mi_type &&
604 			    this_mip->mi_version &&
605 			    equal(this_mip->mi_version, MIME_VERSION)) {
606 				this_mip->mi_partnum = 0;
607 				(void)expand_mip(this_mip);
608 			}
609 		}
610 	}
611 
612 	return next_mip;
613 }
614 
615 
616 static int
617 show_partnum(FILE *fp, struct mime_info *mip)
618 {
619 	int need_dot;
620 	need_dot = 0;
621 	if (mip->mi_parent.mip && mip->mi_parent.mip->mi_parent.mip)
622 		need_dot = show_partnum(fp, mip->mi_parent.mip);
623 
624 	if (mip->mi_partnum) {
625 		(void)fprintf(fp, "%s%d", need_dot ? "." : "",  mip->mi_partnum);
626 		need_dot = 1;
627 	}
628 	return need_dot;
629 }
630 
631 
632 PUBLIC struct mime_info *
633 mime_decode_open(struct message *mp)
634 {
635 	struct mime_info *mip;
636 
637 	mip = csalloc(1, sizeof(*mip));
638 	mip->mp = salloc(sizeof(*mip->mp));
639 	*mip->mp = *mp;		/* copy this so we can change its m_lines */
640 
641 	get_content(mip);
642 
643 	/* RFC 2049 - sec 2 item 1 */
644 	if (mip->mi_version == NULL ||
645 	    !equal(mip->mi_version, MIME_VERSION))
646 		return NULL;
647 
648 	if (mip->mi_type)
649 		(void)expand_mip(mip);
650 
651 /*	show_mime_info(stderr, mip, NULL); */
652 
653 	return mip;
654 }
655 
656 
657 PUBLIC void
658 mime_decode_close(struct mime_info *mip)
659 {
660 	if (mip)
661 		close_top_files(mip->mi_pipe_end);
662 }
663 
664 
665 struct prefix_line_args_s {
666 	const char *prefix;
667 	size_t prefixlen;
668 };
669 
670 static void
671 prefix_line(FILE *fi, FILE *fo, void *cookie)
672 {
673 	struct prefix_line_args_s *args;
674 	const char *line;
675 	const char *prefix;
676 	size_t prefixlen;
677 	size_t length;
678 
679 	args = cookie;
680 	prefix    = args->prefix;
681 	prefixlen = args->prefixlen;
682 
683 	while ((line = fgetln(fi, &length)) != NULL) {
684 		if (length > 1)
685 			(void)fputs(prefix, fo);
686 		else
687 			(void)fwrite(prefix, sizeof *prefix,
688 			    prefixlen, fo);
689 		(void)fwrite(line, sizeof(*line), length, fo);
690 	}
691 	(void)fflush(fo);
692 }
693 
694 PUBLIC int
695 mime_sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign,
696     const char *prefix, struct mime_info *mip)
697 {
698 	int error;
699 	FILE *end_of_pipe;
700 	FILE *end_of_prefix;
701 
702 	if (mip == NULL)
703 		return sendmessage(mp, obuf, doign ? ignore : 0, prefix, NULL);
704 
705 	/*
706 	 * Set these early so pipe_end() and mime_decode_close() work!
707 	 * XXX - Do this before anything that can block, like
708 	 *       fflush()!  Shouldn't we move these settings to
709 	 *       mime_decode_open() to avoid a race condition?
710 	 *       Especially, mip->mi_pipe_end?
711 	 */
712 	mip->mi_fo = obuf;
713 	mip->mi_pipe_end = last_registered_file(0);
714 
715 	(void)fflush(obuf);  /* Be safe and flush!  XXX - necessary? */
716 
717 	/*
718 	 * Handle the prefix as a pipe stage so it doesn't get seen by
719 	 * any decoding or hooks.
720 	 */
721 	if (prefix != NULL) {
722 		static struct prefix_line_args_s prefix_line_args;
723 		const char *dp, *dp2 = NULL;
724 		for (dp = prefix; *dp; dp++)
725 			if (*dp != ' ' && *dp != '\t')
726 				dp2 = dp;
727 		prefix_line_args.prefixlen = dp2 == 0 ? 0 : dp2 - prefix + 1;
728 		prefix_line_args.prefix = prefix;
729 		mime_run_function(prefix_line, pipe_end(mip), (void*)&prefix_line_args);
730 	}
731 
732 	end_of_pipe   = mip->mi_pipe_end;
733 	end_of_prefix = last_registered_file(0);
734 	error = 0;
735 	for (/* EMPTY */; mip; mip = mip->mi_flink) {
736 		mip->mi_fo = obuf;
737 		mip->mi_pipe_end = end_of_pipe;
738 		error |= sendmessage(mip->mp, pipe_end(mip), doign ? ignore : 0, NULL, mip);
739 		close_top_files(end_of_prefix);	/* don't close the prefixer! */
740 	}
741 	return error;
742 }
743 
744 
745 #ifdef CHARSET_SUPPORT
746 /**********************************************
747  * higher level interface to run mime_ficonv().
748  *
749  */
750 static void
751 run_mime_ficonv(struct mime_info *mip, const char *charset)
752 {
753 	FILE *fo;
754 	iconv_t cd;
755 
756 	fo = pipe_end(mip);
757 
758 	if (charset == NULL ||
759 	    mip->mi_charset == NULL ||
760 	    strcasecmp(mip->mi_charset, charset) == 0 ||
761 	    strcasecmp(mip->mi_charset, "unknown") == 0)
762 		return;
763 
764 	cd = iconv_open(charset, mip->mi_charset);
765 	if (cd == (iconv_t)-1) {
766 		(void)fprintf(fo, "\t [ iconv_open failed: %s ]\n\n",
767 		    strerror(errno));
768 		(void)fflush(fo);	/* flush here or see double! */
769 		return;
770 	}
771 
772 	if (value(ENAME_MIME_CHARSET_VERBOSE))
773 		(void)fprintf(fo, "\t[ converting %s -> %s ]\n\n", mip->mi_charset, charset);
774 
775 	mime_run_function(mime_ficonv, fo, cd);
776 
777 	iconv_close(cd);
778 }
779 #endif /* CHARSET_SUPPORT */
780 
781 
782 static void
783 run_decoder(struct mime_info *mip, void(*fn)(FILE*, FILE*, void *))
784 {
785 #ifdef CHARSET_SUPPORT
786 	char *charset;
787 
788 	charset = value(ENAME_MIME_CHARSET);
789 	if (charset && mip->mi_type && strcasecmp(mip->mi_type, "text") == 0)
790 		run_mime_ficonv(mip, charset);
791 #endif /* CHARSET_SUPPORT */
792 
793 	if (fn == mime_fio_copy)/* XXX - avoid an extra unnecessary pipe stage */
794 		return;
795 
796 	mime_run_function(fn, pipe_end(mip), (void*)1);
797 }
798 
799 
800 /*
801  * Determine how to handle the display based on the type and subtype
802  * fields.
803  */
804 enum dispmode_e {
805 	DM_IGNORE	= 0x00,	/* silently ignore part - must be zero! */
806 	DM_DISPLAY,		/* decode and display the part */
807 	DM_UNKNOWN,		/* unknown display */
808 	DM_BINARY,		/* indicate binary data */
809 	DM_PGPSIGN,		/* OpenPGP signed part */
810 	DM_PGPENCR,		/* OpenPGP encrypted part */
811 	DM_PGPKEYS		/* OpenPGP keys part */
812 };
813 #define APPLICATION_OCTET_STREAM	DM_BINARY
814 
815 static enum dispmode_e
816 get_display_mode(struct mime_info *mip, mime_codec_t dec)
817 {
818 	struct mime_subtype_s {
819 		const char *st_name;
820 		enum dispmode_e st_dispmode;
821 	};
822 	struct mime_type_s {
823 		const char *mt_type;
824 		const struct mime_subtype_s *mt_subtype;
825 		enum dispmode_e mt_dispmode;	/* default if NULL subtype */
826 	};
827 	static const struct mime_subtype_s text_subtype_tbl[] = {
828 		{ "plain",		DM_DISPLAY },
829 		{ "html", 		DM_DISPLAY },	/* rfc2854 */
830 		{ "rfc822-headers",	DM_DISPLAY },
831 		{ "css",		DM_DISPLAY },	/* rfc2318 */
832 		{ "enriched",		DM_DISPLAY },	/* rfc1523/rfc1563/rfc1896 */
833 		{ "graphics",		DM_DISPLAY },	/* rfc0553 */
834 		{ "nroff",		DM_DISPLAY },	/* rfc4263 */
835 		{ "red",		DM_DISPLAY },	/* rfc4102 */
836 		{ NULL,			DM_DISPLAY }	/* default */
837 	};
838 	static const struct mime_subtype_s image_subtype_tbl[] = {
839 		{ "tiff",		DM_BINARY },	/* rfc2302/rfc3302 */
840 		{ "tiff-fx",		DM_BINARY },	/* rfc3250/rfc3950 */
841 		{ "t38",		DM_BINARY },	/* rfc3362 */
842 		{ NULL,			DM_BINARY }	/* default */
843 	};
844 	static const struct mime_subtype_s audio_subtype_tbl[] = {
845 		{ "mpeg",		DM_BINARY },	/* rfc3003 */
846 		{ "t38",		DM_BINARY },	/* rfc4612 */
847 		{ NULL,			DM_BINARY }	/* default */
848 	};
849 	static const struct mime_subtype_s video_subtype_tbl[] = {
850 		{ NULL,			DM_BINARY }	/* default */
851 	};
852 	static const struct mime_subtype_s application_subtype_tbl[] = {
853 		{ "octet-stream",	APPLICATION_OCTET_STREAM },
854                 { "pgp-encrypted",      DM_PGPENCR },   /* rfc3156 */
855 		{ "pgp-keys",           DM_PGPKEYS },   /* rfc3156 */
856 		{ "pgp-signature",      DM_PGPSIGN },   /* rfc3156 */
857 		{ "pdf",		DM_BINARY },	/* rfc3778 */
858 		{ "whoispp-query",	DM_UNKNOWN },	/* rfc2957 */
859 		{ "whoispp-response",	DM_UNKNOWN },	/* rfc2958 */
860 		{ "font-tdpfr",		DM_UNKNOWN },	/* rfc3073 */
861 		{ "xhtml+xml",		DM_UNKNOWN },	/* rfc3236 */
862 		{ "ogg",		DM_UNKNOWN },	/* rfc3534 */
863 		{ "rdf+xml",		DM_UNKNOWN },	/* rfc3870 */
864 		{ "soap+xml",		DM_UNKNOWN },	/* rfc3902 */
865 		{ "mbox",		DM_UNKNOWN },	/* rfc4155 */
866 		{ "xv+xml",		DM_UNKNOWN },	/* rfc4374 */
867 		{ "smil",		DM_UNKNOWN },	/* rfc4536 */
868 		{ "smil+xml",		DM_UNKNOWN },	/* rfc4536 */
869 		{ "json",		DM_UNKNOWN },	/* rfc4627 */
870 		{ "voicexml+xml",	DM_UNKNOWN },	/* rfc4267 */
871 		{ "ssml+xml",		DM_UNKNOWN },	/* rfc4267 */
872 		{ "srgs",		DM_UNKNOWN },	/* rfc4267 */
873 		{ "srgs+xml",		DM_UNKNOWN },	/* rfc4267 */
874 		{ "ccxml+xml",		DM_UNKNOWN },	/* rfc4267 */
875 		{ "pls+xml.",		DM_UNKNOWN },	/* rfc4267 */
876 		{ NULL,			APPLICATION_OCTET_STREAM } /* default */
877 	};
878 	static const struct mime_type_s mime_type_tbl[] = {
879 		{ "text",	 text_subtype_tbl,		DM_DISPLAY },
880 		{ "image",	 image_subtype_tbl,		DM_IGNORE },
881 		{ "audio",	 audio_subtype_tbl,		DM_IGNORE },
882 		{ "video",	 video_subtype_tbl,		DM_IGNORE },
883 		{ "application", application_subtype_tbl,	APPLICATION_OCTET_STREAM },
884 		{ "NULL",	 NULL,				DM_UNKNOWN }, /* default */
885 	};
886 	const struct mime_type_s *mtp;
887 	const struct mime_subtype_s *stp;
888 	const char *mi_type;
889 	const char *mi_subtype;
890 
891 	/*
892 	 * Silently ignore all multipart bodies.
893 	 * 1) In the case of "multipart" types, this typically
894 	 *    contains a message for non-mime enabled mail readers.
895 	 * 2) In the case of "message" type, there should be no body.
896 	 */
897 	if (is_multipart(mip) || is_message(mip))
898 		return DM_IGNORE;
899 
900 	/*
901 	 * If the encoding type given but not recognized, treat block
902 	 * as "application/octet-stream".  rfc 2049 sec 2 part 2.
903 	 */
904 	if (mip->mi_encoding && dec == NULL)
905 		return APPLICATION_OCTET_STREAM;
906 
907 	mi_type    = mip->mi_type;
908 	mi_subtype = mip->mi_type ? mip->mi_subtype : NULL;
909 
910 	/*
911 	 * If there was no type specified, display anyway so we don't
912 	 * miss anything.  (The encoding type is known.)
913 	 */
914 	if (mi_type == NULL)
915 		return DM_DISPLAY;	/* XXX - default to something safe! */
916 
917 	for (mtp = mime_type_tbl; mtp->mt_type; mtp++) {
918 		if (strcasecmp(mtp->mt_type, mi_type) == 0) {
919 			if (mi_subtype == NULL)
920 				return mtp->mt_dispmode;
921 			for (stp = mtp->mt_subtype; stp->st_name; stp++) {
922 				if (strcasecmp(stp->st_name, mi_subtype) == 0)
923 					return stp->st_dispmode;
924 			}
925 			return stp->st_dispmode;
926 		}
927 	}
928 	return mtp->mt_dispmode;
929 }
930 
931 
932 PUBLIC FILE *
933 mime_decode_body(struct mime_info *mip)
934 {
935 	static enum dispmode_e dispmode;
936 	mime_codec_t dec;
937 	const char *cmd;
938 
939 	/* close anything left over from mime_decode_head() */
940 	close_top_files(mip->mi_head_end);
941 
942 	/*
943 	 * Make sure we flush everything down the pipe so children
944 	 * don't see it.
945 	 */
946 	(void)fflush(pipe_end(mip));
947 
948 	cmd = NULL;
949 	if (mip->mi_command_hook == NULL)
950 		cmd = get_command_hook(mip, "-body");
951 
952 	dec = mime_fio_decoder(mip->mi_encoding);
953 	/*
954 	 * If there is a filter running, we need to send the message
955 	 * to it.  Otherwise, get the default display mode for this body.
956 	 */
957 	dispmode = cmd || mip->mi_command_hook ? DM_DISPLAY : get_display_mode(mip, dec);
958 
959 	if (dec == NULL)	/* make sure we have a usable decoder */
960 		dec = mime_fio_decoder(MIME_TRANSFER_7BIT);
961 
962 	if (dispmode == DM_DISPLAY) {
963 		int flags;
964 		if (cmd == NULL)
965 			/* just get the flags */
966 			flags = mime_run_command(mip->mi_command_hook, NULL);
967 		else
968 			flags = mime_run_command(cmd, pipe_end(mip));
969 		if ((flags & CMD_FLAG_NO_DECODE) == 0)
970 			run_decoder(mip, dec);
971 		return pipe_end(mip);
972 	}
973 	else {
974 		static const struct msg_tbl_s {
975 			enum dispmode_e dm;
976 			const char *msg;
977 		} msg_tbl[] = {
978 			{ DM_BINARY,	"binary content"	},
979 			{ DM_PGPSIGN,	"OpenPGP signature"	},
980 			{ DM_PGPENCR,	"OpenPGP encrypted"	},
981 			{ DM_PGPKEYS,	"OpenPGP keys"		},
982 			{ DM_UNKNOWN,	"unknown data"		},
983 			{ DM_IGNORE,	NULL			},
984 			{ -1,		NULL			},
985 		};
986 		const struct msg_tbl_s *mp;
987 
988 		for (mp = msg_tbl; mp->dm != -1; mp++)
989 			if (mp->dm == dispmode)
990 				break;
991 
992 		assert(mp->dm != -1);	/* msg_tbl is short if this happens! */
993 
994 		if (mp->msg)
995 			(void)fprintf(pipe_end(mip), "  [%s]\n\n", mp->msg);
996 
997 		return NULL;
998 	}
999 }
1000 
1001 
1002 
1003 
1004 /************************************************************************
1005  * Higher level header decoding interface.
1006  *
1007  * The core routines are in mime_header.c.
1008  */
1009 
1010 PUBLIC char *
1011 mime_decode_hfield(char *linebuf, size_t bufsize, char *hdrstr)
1012 {
1013 	hfield_decoder_t decode;
1014 	decode = mime_hfield_decoder(hdrstr);
1015 	if (decode) {
1016 		decode(linebuf, bufsize, hdrstr);
1017 		return linebuf;
1018 	}
1019 	return hdrstr;
1020 }
1021 
1022 /*
1023  * Return the next header field found in the given message.
1024  * Return >= 0 if something found, < 0 elsewise.
1025  * "colon" is set to point to the colon in the header.
1026  */
1027 static int
1028 get_folded_hfield(FILE *f, char *linebuf, size_t bufsize, int rem, char **colon)
1029 {
1030 	char *cp, *cp2;
1031 	char *line;
1032 	size_t len;
1033 
1034 	for (;;) {
1035 		if (--rem <= 0)
1036 			return -1;
1037 		if ((cp = fgetln(f, &len)) == NULL)
1038 			return -1;
1039 		for (cp2 = cp; isprint((unsigned char)*cp2) &&
1040 			 !isblank((unsigned char)*cp2) && *cp2 != ':'; cp2++)
1041 			continue;
1042 		len = MIN(bufsize - 1, len);
1043 		bufsize -= len;
1044 		(void)memcpy(linebuf, cp, len);
1045 		*colon = *cp2 == ':' ? linebuf + (cp2 - cp) : NULL;
1046 		line = linebuf + len;
1047 		for (/*EMPTY*/; rem > 0; rem--) {
1048 			int c;
1049 			(void)ungetc(c = getc(f), f);
1050 			if (c == EOF || !isblank((unsigned char)c))
1051 				break;
1052 
1053 			if ((cp = fgetln(f, &len)) == NULL)
1054 				break;
1055 			len = MIN(bufsize - 1, len);
1056 			bufsize -= len;
1057 			if (len == 0)
1058 			    break;
1059 			(void)memcpy(line, cp, len);
1060 			line += len;
1061 		}
1062 		*line = 0;
1063 		return rem;
1064 		/* NOTREACHED */
1065 	}
1066 }
1067 
1068 static void
1069 decode_header(FILE *fi, FILE *fo, void *cookie __unused)
1070 {
1071 	char linebuf[LINESIZE];
1072 	char *colon;
1073 #ifdef __lint__
1074 	cookie = cookie;
1075 #endif
1076 	while(get_folded_hfield(fi, linebuf, sizeof(linebuf), INT_MAX, &colon) >= 0) {
1077 		char decbuf[LINESIZE];
1078 		char *hdrstr;
1079 		hdrstr = linebuf;
1080 		if (colon)
1081 			hdrstr = mime_decode_hfield(decbuf, sizeof(decbuf), hdrstr);
1082 		(void)fprintf(fo, hdrstr);
1083 	}
1084 }
1085 
1086 
1087 PUBLIC FILE *
1088 mime_decode_header(struct mime_info *mip)
1089 {
1090 	int flags;
1091 	const char *cmd;
1092 	FILE *fo;
1093 
1094 	fo = pipe_end(mip);
1095 	/*
1096 	 * Make sure we flush everything down the pipe so children
1097 	 * don't see it.
1098 	 */
1099 
1100 	if (mip->mi_partnum) {
1101 		(void)fprintf(fo, "----- Part ");
1102 		(void)show_partnum(fo, mip);
1103 		(void)fprintf(fo, " -----\n");
1104 	}
1105 	(void)fflush(fo);
1106 
1107 	/*
1108 	 * install the message hook before the head hook.
1109 	 */
1110 	cmd = get_command_hook(mip, "-hook");
1111 	mip->mi_command_hook = cmd;
1112 	if (cmd) {
1113 		flags = mime_run_command(cmd, pipe_end(mip));
1114 		mip->mi_head_end = last_registered_file(0);
1115 	}
1116 	else {
1117 		cmd = get_command_hook(mip, "-head");
1118 		mip->mi_head_end = last_registered_file(0);
1119 		flags = mime_run_command(cmd, pipe_end(mip));
1120 	}
1121 
1122 	if (value(ENAME_MIME_DECODE_HDR) && (flags & CMD_FLAG_NO_DECODE) == 0)
1123 		mime_run_function(decode_header, pipe_end(mip), NULL);
1124 
1125 	return pipe_end(mip);
1126 }
1127 
1128 #endif /* MIME_SUPPORT */
1129