1 /*
2  * Heirloom mailx - a mail user agent derived from Berkeley Mail.
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  */
6 /*
7  * Copyright (c) 2000
8  *	Gunnar Ritter.  All rights reserved.
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 Gunnar Ritter
21  *	and his contributors.
22  * 4. Neither the name of Gunnar Ritter nor the names of his contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #ifndef lint
40 #ifdef	DOSCCS
41 static char copyright[]
42 = "@(#) Copyright (c) 2000, 2002 Gunnar Ritter. All rights reserved.\n";
43 static char sccsid[]  = "@(#)mime.c	2.69 (gritter) 6/29/08";
44 #endif /* DOSCCS */
45 #endif /* not lint */
46 
47 #include "rcv.h"
48 #include "extern.h"
49 #include <ctype.h>
50 #include <errno.h>
51 #ifdef	HAVE_WCTYPE_H
52 #include <wctype.h>
53 #endif	/* HAVE_WCTYPE_H */
54 
55 /*
56  * Mail -- a mail program
57  *
58  * MIME support functions.
59  */
60 
61 /*
62  * You won't guess what these are for.
63  */
64 static const char basetable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
65 static char *mimetypes_world = "/etc/mime.types";
66 static char *mimetypes_user = "~/.mime.types";
67 char *us_ascii = "us-ascii";
68 
69 static int mustquote_body(int c);
70 static int mustquote_hdr(const char *cp, int wordstart, int wordend);
71 static int mustquote_inhdrq(int c);
72 static size_t	delctrl(char *cp, size_t sz);
73 static char *getcharset(int isclean);
74 static int has_highbit(register const char *s);
75 #ifdef	HAVE_ICONV
76 static void uppercopy(char *dest, const char *src);
77 static void stripdash(char *p);
78 static size_t iconv_ft(iconv_t cd, char **inb, size_t *inbleft,
79 		char **outb, size_t *outbleft);
80 static void invalid_seq(int c);
81 #endif	/* HAVE_ICONV */
82 static int is_this_enc(const char *line, const char *encoding);
83 static char *mime_tline(char *x, char *l);
84 static char *mime_type(char *ext, char *filename);
85 static enum mimeclean mime_isclean(FILE *f);
86 static enum conversion gettextconversion(void);
87 static char *ctohex(int c, char *hex);
88 static size_t mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int));
89 static void mime_str_toqp(struct str *in, struct str *out,
90 		int (*mustquote)(int), int inhdr);
91 static void mime_fromqp(struct str *in, struct str *out, int ishdr);
92 static size_t mime_write_tohdr(struct str *in, FILE *fo);
93 static size_t convhdra(char *str, size_t len, FILE *fp);
94 static size_t mime_write_tohdr_a(struct str *in, FILE *f);
95 static void addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
96 static void addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len);
97 static size_t fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f,
98 		enum tdflags flags, char *prefix, size_t prefixlen);
99 
100 /*
101  * Check if c must be quoted inside a message's body.
102  */
103 static int
mustquote_body(int c)104 mustquote_body(int c)
105 {
106 	if (c != '\n' && (c < 040 || c == '=' || c >= 0177))
107 		return 1;
108 	return 0;
109 }
110 
111 /*
112  * Check if c must be quoted inside a message's header.
113  */
114 static int
mustquote_hdr(const char * cp,int wordstart,int wordend)115 mustquote_hdr(const char *cp, int wordstart, int wordend)
116 {
117 	int	c = *cp & 0377;
118 
119 	if (c != '\n' && (c < 040 || c >= 0177))
120 		return 1;
121 	if (wordstart && cp[0] == '=' && cp[1] == '?')
122 		return 1;
123 	if (cp[0] == '?' && cp[1] == '=' &&
124 			(wordend || cp[2] == '\0' || whitechar(cp[2]&0377)))
125 		return 1;
126 	return 0;
127 }
128 
129 /*
130  * Check if c must be quoted inside a quoting in a message's header.
131  */
132 static int
mustquote_inhdrq(int c)133 mustquote_inhdrq(int c)
134 {
135 	if (c != '\n'
136 		&& (c <= 040 || c == '=' || c == '?' || c == '_' || c >= 0177))
137 		return 1;
138 	return 0;
139 }
140 
141 static size_t
delctrl(char * cp,size_t sz)142 delctrl(char *cp, size_t sz)
143 {
144 	size_t	x = 0, y = 0;
145 
146 	while (x < sz) {
147 		if (!cntrlchar(cp[x]&0377))
148 			cp[y++] = cp[x];
149 		x++;
150 	}
151 	return y;
152 }
153 
154 /*
155  * Check if a name's address part contains invalid characters.
156  */
157 int
mime_name_invalid(char * name,int putmsg)158 mime_name_invalid(char *name, int putmsg)
159 {
160 	char *addr, *p;
161 	int in_quote = 0, in_domain = 0, err = 0, hadat = 0;
162 
163 	if (is_fileaddr(name))
164 		return 0;
165 	addr = skin(name);
166 
167 	if (addr == NULL || *addr == '\0')
168 		return 1;
169 	for (p = addr; *p != '\0'; p++) {
170 		if (*p == '\"') {
171 			in_quote = !in_quote;
172 		} else if (*p < 040 || (*p & 0377) >= 0177) {
173 			err = *p & 0377;
174 			break;
175 		} else if (in_domain == 2) {
176 			if ((*p == ']' && p[1] != '\0') || *p == '\0'
177 					|| *p == '\\' || whitechar(*p & 0377)) {
178 				err = *p & 0377;
179 				break;
180 			}
181 		} else if (in_quote && in_domain == 0) {
182 			/*EMPTY*/;
183 		} else if (*p == '\\' && p[1] != '\0') {
184 			p++;
185 		} else if (*p == '@') {
186 			if (hadat++) {
187 				if (putmsg) {
188 					fprintf(stderr, catgets(catd, CATSET,
189 								142,
190 					"%s contains invalid @@ sequence\n"),
191 						addr);
192 					putmsg = 0;
193 				}
194 				err = *p;
195 				break;
196 			}
197 			if (p[1] == '[')
198 				in_domain = 2;
199 			else
200 				in_domain = 1;
201 			continue;
202 		} else if (*p == '(' || *p == ')' || *p == '<' || *p == '>'
203 				|| *p == ',' || *p == ';' || *p == ':'
204 				|| *p == '\\' || *p == '[' || *p == ']') {
205 			err = *p & 0377;
206 			break;
207 		}
208 		hadat = 0;
209 	}
210 	if (err && putmsg) {
211 		fprintf(stderr, catgets(catd, CATSET, 143,
212 				"%s contains invalid character '"), addr);
213 #ifdef	HAVE_SETLOCALE
214 		if (isprint(err))
215 #else	/* !HAVE_SETLOCALE */
216 		if (err >= 040 && err <= 0177)
217 #endif	/* !HAVE_SETLOCALE */
218 			putc(err, stderr);
219 		else
220 			fprintf(stderr, "\\%03o", err);
221 		fprintf(stderr, catgets(catd, CATSET, 144, "'\n"));
222 	}
223 	return err;
224 }
225 
226 /*
227  * Check all addresses in np and delete invalid ones.
228  */
229 struct name *
checkaddrs(struct name * np)230 checkaddrs(struct name *np)
231 {
232 	struct name *n = np;
233 
234 	while (n != NULL) {
235 		if (mime_name_invalid(n->n_name, 1)) {
236 			if (n->n_blink)
237 				n->n_blink->n_flink = n->n_flink;
238 			if (n->n_flink)
239 				n->n_flink->n_blink = n->n_blink;
240 			if (n == np)
241 				np = n->n_flink;
242 		}
243 		n = n->n_flink;
244 	}
245 	return np;
246 }
247 
248 static char defcharset[] = "utf-8";
249 
250 /*
251  * Get the character set dependant on the conversion.
252  */
253 static char *
getcharset(int isclean)254 getcharset(int isclean)
255 {
256 	char *charset;
257 
258 	if (isclean & (MIME_CTRLCHAR|MIME_HASNUL))
259 		charset = NULL;
260 	else if (isclean & MIME_HIGHBIT) {
261 		charset = (wantcharset && wantcharset != (char *)-1) ?
262 			wantcharset : value("charset");
263 		if (charset == NULL) {
264 			charset = defcharset;
265 		}
266 	} else {
267 		/*
268 		 * This variable shall remain undocumented because
269 		 * only experts should change it.
270 		 */
271 		charset = value("charset7");
272 		if (charset == NULL) {
273 			charset = us_ascii;
274 		}
275 	}
276 	return charset;
277 }
278 
279 /*
280  * Get the setting of the terminal's character set.
281  */
282 char *
gettcharset(void)283 gettcharset(void)
284 {
285 	char *t;
286 
287 	if ((t = value("ttycharset")) == NULL)
288 		if ((t = value("charset")) == NULL)
289 			t = defcharset;
290 	return t;
291 }
292 
293 static int
has_highbit(const char * s)294 has_highbit(const char *s)
295 {
296 	if (s) {
297 		do
298 			if (*s & 0200)
299 				return 1;
300 		while (*s++ != '\0');
301 	}
302 	return 0;
303 }
304 
305 static int
name_highbit(struct name * np)306 name_highbit(struct name *np)
307 {
308 	while (np) {
309 		if (has_highbit(np->n_name) || has_highbit(np->n_fullname))
310 			return 1;
311 		np = np->n_flink;
312 	}
313 	return 0;
314 }
315 
316 char *
need_hdrconv(struct header * hp,enum gfield w)317 need_hdrconv(struct header *hp, enum gfield w)
318 {
319 	if (w & GIDENT) {
320 		if (hp->h_from && name_highbit(hp->h_from))
321 			goto needs;
322 		else if (has_highbit(myaddrs(hp)))
323 			goto needs;
324 		if (hp->h_organization && has_highbit(hp->h_organization))
325 			goto needs;
326 		else if (has_highbit(value("ORGANIZATION")))
327 			goto needs;
328 		if (hp->h_replyto && name_highbit(hp->h_replyto))
329 			goto needs;
330 		else if (has_highbit(value("replyto")))
331 			goto needs;
332 		if (hp->h_sender && name_highbit(hp->h_sender))
333 			goto needs;
334 		else if (has_highbit(value("sender")))
335 			goto needs;
336 	}
337 	if (w & GTO && name_highbit(hp->h_to))
338 		goto needs;
339 	if (w & GCC && name_highbit(hp->h_cc))
340 		goto needs;
341 	if (w & GBCC && name_highbit(hp->h_bcc))
342 		goto needs;
343 	if (w & GSUBJECT && has_highbit(hp->h_subject))
344 		goto needs;
345 	return NULL;
346 needs:	return getcharset(MIME_HIGHBIT);
347 }
348 
349 #ifdef	HAVE_ICONV
350 /*
351  * Convert a string, upper-casing the characters.
352  */
353 static void
uppercopy(char * dest,const char * src)354 uppercopy(char *dest, const char *src)
355 {
356 	do
357 		*dest++ = upperconv(*src & 0377);
358 	while (*src++);
359 }
360 
361 /*
362  * Strip dashes.
363  */
364 static void
stripdash(char * p)365 stripdash(char *p)
366 {
367 	char *q = p;
368 
369 	do
370 		if (*(q = p) != '-')
371 			q++;
372 	while (*p++);
373 }
374 
375 /*
376  * An iconv_open wrapper that tries to convert between character set
377  * naming conventions.
378  */
379 iconv_t
iconv_open_ft(const char * tocode,const char * fromcode)380 iconv_open_ft(const char *tocode, const char *fromcode)
381 {
382 	iconv_t id;
383 	char *t, *f;
384 
385 	/*
386 	 * On Linux systems, this call may succeed.
387 	 */
388 	if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
389 		return id;
390 	/*
391 	 * Remove the "iso-" prefixes for Solaris.
392 	 */
393 	if (ascncasecmp(tocode, "iso-", 4) == 0)
394 		tocode += 4;
395 	else if (ascncasecmp(tocode, "iso", 3) == 0)
396 		tocode += 3;
397 	if (ascncasecmp(fromcode, "iso-", 4) == 0)
398 		fromcode += 4;
399 	else if (ascncasecmp(fromcode, "iso", 3) == 0)
400 		fromcode += 3;
401 	if (*tocode == '\0' || *fromcode == '\0')
402 		return (iconv_t) -1;
403 	if ((id = iconv_open(tocode, fromcode)) != (iconv_t)-1)
404 		return id;
405 	/*
406 	 * Solaris prefers upper-case charset names. Don't ask...
407 	 */
408 	t = salloc(strlen(tocode) + 1);
409 	uppercopy(t, tocode);
410 	f = salloc(strlen(fromcode) + 1);
411 	uppercopy(f, fromcode);
412 	if ((id = iconv_open(t, f)) != (iconv_t)-1)
413 		return id;
414 	/*
415 	 * Strip dashes for UnixWare.
416 	 */
417 	stripdash(t);
418 	stripdash(f);
419 	if ((id = iconv_open(t, f)) != (iconv_t)-1)
420 		return id;
421 	/*
422 	 * Add your vendor's sillynesses here.
423 	 */
424 	/*
425 	 * If the encoding names are equal at this point, they
426 	 * are just not understood by iconv(), and we cannot
427 	 * sensibly use it in any way. We do not perform this
428 	 * as an optimization above since iconv() can otherwise
429 	 * be used to check the validity of the input even with
430 	 * identical encoding names.
431 	 */
432 	if (strcmp(t, f) == 0)
433 		errno = 0;
434 	return (iconv_t)-1;
435 }
436 
437 /*
438  * Fault-tolerant iconv() function.
439  */
440 static size_t
iconv_ft(iconv_t cd,char ** inb,size_t * inbleft,char ** outb,size_t * outbleft)441 iconv_ft(iconv_t cd, char **inb, size_t *inbleft, char **outb, size_t *outbleft)
442 {
443 	size_t sz = 0;
444 
445 	while ((sz = iconv(cd, inb, inbleft, outb, outbleft)) == (size_t)-1
446 			&& (errno == EILSEQ || errno == EINVAL)) {
447 		if (*inbleft > 0) {
448 			(*inb)++;
449 			(*inbleft)--;
450 		} else {
451 			**outb = '\0';
452 			break;
453 		}
454 		if (*outbleft > 0) {
455 			*(*outb)++ = '?';
456 			(*outbleft)--;
457 		} else {
458 			**outb = '\0';
459 			break;
460 		}
461 	}
462 	return sz;
463 }
464 
465 /*
466  * Print an error because of an invalid character sequence.
467  */
468 /*ARGSUSED*/
469 static void
invalid_seq(int c)470 invalid_seq(int c)
471 {
472 	/*fprintf(stderr, "iconv: cannot convert %c\n", c);*/
473 }
474 #endif	/* HAVE_ICONV */
475 
476 static int
is_this_enc(const char * line,const char * encoding)477 is_this_enc(const char *line, const char *encoding)
478 {
479 	int quoted = 0, c;
480 
481 	if (*line == '"')
482 		quoted = 1, line++;
483 	while (*line && *encoding)
484 		if (c = *line++, lowerconv(c) != *encoding++)
485 			return 0;
486 	if (quoted && *line == '"')
487 		return 1;
488 	if (*line == '\0' || whitechar(*line & 0377))
489 		return 1;
490 	return 0;
491 }
492 
493 /*
494  * Get the mime encoding from a Content-Transfer-Encoding header field.
495  */
496 enum mimeenc
mime_getenc(char * p)497 mime_getenc(char *p)
498 {
499 	if (is_this_enc(p, "7bit"))
500 		return MIME_7B;
501 	if (is_this_enc(p, "8bit"))
502 		return MIME_8B;
503 	if (is_this_enc(p, "base64"))
504 		return MIME_B64;
505 	if (is_this_enc(p, "binary"))
506 		return MIME_BIN;
507 	if (is_this_enc(p, "quoted-printable"))
508 		return MIME_QP;
509 	return MIME_NONE;
510 }
511 
512 /*
513  * Get the mime content from a Content-Type header field, other parameters
514  * already stripped.
515  */
516 int
mime_getcontent(char * s)517 mime_getcontent(char *s)
518 {
519 	if (strchr(s, '/') == NULL)	/* for compatibility with non-MIME */
520 		return MIME_TEXT;
521 	if (asccasecmp(s, "text/plain") == 0)
522 		return MIME_TEXT_PLAIN;
523 	if (asccasecmp(s, "text/html") == 0)
524 		return MIME_TEXT_HTML;
525 	if (ascncasecmp(s, "text/", 5) == 0)
526 		return MIME_TEXT;
527 	if (asccasecmp(s, "message/rfc822") == 0)
528 		return MIME_822;
529 	if (ascncasecmp(s, "message/", 8) == 0)
530 		return MIME_MESSAGE;
531 	if (asccasecmp(s, "multipart/alternative") == 0)
532 		return MIME_ALTERNATIVE;
533 	if (asccasecmp(s, "multipart/digest") == 0)
534 		return MIME_DIGEST;
535 	if (ascncasecmp(s, "multipart/", 10) == 0)
536 		return MIME_MULTI;
537 	if (asccasecmp(s, "application/x-pkcs7-mime") == 0 ||
538 				asccasecmp(s, "application/pkcs7-mime") == 0)
539 		return MIME_PKCS7;
540 	return MIME_UNKNOWN;
541 }
542 
543 /*
544  * Get a mime style parameter from a header line.
545  */
546 char *
mime_getparam(char * param,char * h)547 mime_getparam(char *param, char *h)
548 {
549 	char *p = h, *q, *r;
550 	int c;
551 	size_t sz;
552 
553 	sz = strlen(param);
554 	if (!whitechar(*p & 0377)) {
555 		c = '\0';
556 		while (*p && (*p != ';' || c == '\\')) {
557 			c = c == '\\' ? '\0' : *p;
558 			p++;
559 		}
560 		if (*p++ == '\0')
561 			return NULL;
562 	}
563 	for (;;) {
564 		while (whitechar(*p & 0377))
565 			p++;
566 		if (ascncasecmp(p, param, sz) == 0) {
567 			p += sz;
568 			while (whitechar(*p & 0377))
569 				p++;
570 			if (*p++ == '=')
571 				break;
572 		}
573 		c = '\0';
574 		while (*p && (*p != ';' || c == '\\')) {
575 			if (*p == '"' && c != '\\') {
576 				p++;
577 				while (*p && (*p != '"' || c == '\\')) {
578 					c = c == '\\' ? '\0' : *p;
579 					p++;
580 				}
581 				p++;
582 			} else {
583 				c = c == '\\' ? '\0' : *p;
584 				p++;
585 			}
586 		}
587 		if (*p++ == '\0')
588 			return NULL;
589 	}
590 	while (whitechar(*p & 0377))
591 		p++;
592 	q = p;
593 	c = '\0';
594 	if (*p == '"') {
595 		p++;
596 		if ((q = strchr(p, '"')) == NULL)
597 			return NULL;
598 	} else {
599 		q = p;
600 		while (*q && !whitechar(*q & 0377) && *q != ';')
601 			q++;
602 	}
603 	sz = q - p;
604 	r = salloc(q - p + 1);
605 	memcpy(r, p, sz);
606 	*(r + sz) = '\0';
607 	return r;
608 }
609 
610 /*
611  * Get the boundary out of a Content-Type: multipart/xyz header field.
612  */
613 char *
mime_getboundary(char * h)614 mime_getboundary(char *h)
615 {
616 	char *p, *q;
617 	size_t sz;
618 
619 	if ((p = mime_getparam("boundary", h)) == NULL)
620 		return NULL;
621 	sz = strlen(p);
622 	q = salloc(sz + 3);
623 	memcpy(q, "--", 2);
624 	memcpy(q + 2, p, sz);
625 	*(q + sz + 2) = '\0';
626 	return q;
627 }
628 
629 /*
630  * Get a line like "text/html html" and look if x matches the extension.
631  */
632 static char *
mime_tline(char * x,char * l)633 mime_tline(char *x, char *l)
634 {
635 	char *type, *n;
636 	int match = 0;
637 
638 	if ((*l & 0200) || alphachar(*l & 0377) == 0)
639 		return NULL;
640 	type = l;
641 	while (blankchar(*l & 0377) == 0 && *l != '\0')
642 		l++;
643 	if (*l == '\0')
644 		return NULL;
645 	*l++ = '\0';
646 	while (blankchar(*l & 0377) != 0 && *l != '\0')
647 		l++;
648 	if (*l == '\0')
649 		return NULL;
650 	while (*l != '\0') {
651 		n = l;
652 		while (whitechar(*l & 0377) == 0 && *l != '\0')
653 			l++;
654 		if (*l != '\0')
655 			*l++ = '\0';
656 		if (strcmp(x, n) == 0) {
657 			match = 1;
658 			break;
659 		}
660 		while (whitechar(*l & 0377) != 0 && *l != '\0')
661 			l++;
662 	}
663 	if (match != 0) {
664 		n = salloc(strlen(type) + 1);
665 		strcpy(n, type);
666 		return n;
667 	}
668 	return NULL;
669 }
670 
671 /*
672  * Check the given MIME type file for extension ext.
673  */
674 static char *
mime_type(char * ext,char * filename)675 mime_type(char *ext, char *filename)
676 {
677 	FILE *f;
678 	char *line = NULL;
679 	size_t linesize = 0;
680 	char *type = NULL;
681 
682 	if ((f = Fopen(filename, "r")) == NULL)
683 		return NULL;
684 	while (fgetline(&line, &linesize, NULL, NULL, f, 0)) {
685 		if ((type = mime_tline(ext, line)) != NULL)
686 			break;
687 	}
688 	Fclose(f);
689 	if (line)
690 		free(line);
691 	return type;
692 }
693 
694 /*
695  * Return the Content-Type matching the extension of name.
696  */
697 char *
mime_filecontent(char * name)698 mime_filecontent(char *name)
699 {
700 	char *ext, *content;
701 
702 	if ((ext = strrchr(name, '.')) == NULL || *++ext == '\0')
703 		return NULL;
704 	if ((content = mime_type(ext, expand(mimetypes_user))) != NULL)
705 		return content;
706 	if ((content = mime_type(ext, mimetypes_world)) != NULL)
707 		return content;
708 	return NULL;
709 }
710 
711 /*
712  * Check file contents.
713  */
714 static enum mimeclean
mime_isclean(FILE * f)715 mime_isclean(FILE *f)
716 {
717 	long initial_pos;
718 	unsigned curlen = 1, maxlen = 0, limit = 950;
719 	enum mimeclean isclean = 0;
720 	char	*cp;
721 	int c = EOF, lastc;
722 
723 	initial_pos = ftell(f);
724 	do {
725 		lastc = c;
726 		c = getc(f);
727 		curlen++;
728 		if (c == '\n' || c == EOF) {
729 			/*
730 			 * RFC 821 imposes a maximum line length of 1000
731 			 * characters including the terminating CRLF
732 			 * sequence. The configurable limit must not
733 			 * exceed that including a safety zone.
734 			 */
735 			if (curlen > maxlen)
736 				maxlen = curlen;
737 			curlen = 1;
738 		} else if (c & 0200) {
739 			isclean |= MIME_HIGHBIT;
740 		} else if (c == '\0') {
741 			isclean |= MIME_HASNUL;
742 			break;
743 		} else if ((c < 040 && (c != '\t' && c != '\f')) || c == 0177) {
744 			isclean |= MIME_CTRLCHAR;
745 		}
746 	} while (c != EOF);
747 	if (lastc != '\n')
748 		isclean |= MIME_NOTERMNL;
749 	clearerr(f);
750 	fseek(f, initial_pos, SEEK_SET);
751 	if ((cp = value("maximum-unencoded-line-length")) != NULL)
752 		limit = atoi(cp);
753 	if (limit < 0 || limit > 950)
754 		limit = 950;
755 	if (maxlen > limit)
756 		isclean |= MIME_LONGLINES;
757 	return isclean;
758 }
759 
760 /*
761  * Get the conversion that matches the encoding specified in the environment.
762  */
763 static enum conversion
gettextconversion(void)764 gettextconversion(void)
765 {
766 	char *p;
767 	int convert;
768 
769 	if ((p = value("encoding")) == NULL)
770 		return CONV_8BIT;
771 	if (equal(p, "quoted-printable"))
772 		convert = CONV_TOQP;
773 	else if (equal(p, "8bit"))
774 		convert = CONV_8BIT;
775 	else {
776 		fprintf(stderr, catgets(catd, CATSET, 177,
777 			"Warning: invalid encoding %s, using 8bit\n"), p);
778 		convert = CONV_8BIT;
779 	}
780 	return convert;
781 }
782 
783 int
get_mime_convert(FILE * fp,char ** contenttype,char ** charset,enum mimeclean * isclean,int dosign)784 get_mime_convert(FILE *fp, char **contenttype, char **charset,
785 		enum mimeclean *isclean, int dosign)
786 {
787 	int convert;
788 
789 	*isclean = mime_isclean(fp);
790 	if (*isclean & MIME_HASNUL ||
791 			*contenttype && ascncasecmp(*contenttype, "text/", 5) ||
792 			*contenttype == NULL && *isclean & MIME_CTRLCHAR) {
793 		convert = CONV_TOB64;
794 		if (*contenttype == NULL ||
795 				ascncasecmp(*contenttype, "text/", 5) == 0)
796 			*contenttype = "application/octet-stream";
797 		*charset = NULL;
798 	} else if (*isclean & (MIME_LONGLINES|MIME_CTRLCHAR|MIME_NOTERMNL) ||
799 			dosign)
800 		convert = CONV_TOQP;
801 	else if (*isclean & MIME_HIGHBIT)
802 		convert = gettextconversion();
803 	else
804 		convert = CONV_7BIT;
805 	if (*contenttype == NULL ||
806 			ascncasecmp(*contenttype, "text/", 5) == 0) {
807 		*charset = getcharset(*isclean);
808 		if (wantcharset == (char *)-1) {
809 			*contenttype = "application/octet-stream";
810 			*charset = NULL;
811 		} if (*isclean & MIME_CTRLCHAR) {
812 			/*
813 			 * RFC 2046 forbids control characters other than
814 			 * ^I or ^L in text/plain bodies. However, some
815 			 * obscure character sets actually contain these
816 			 * characters, so the content type can be set.
817 			 */
818 			if ((*contenttype = value("contenttype-cntrl")) == NULL)
819 				*contenttype = "application/octet-stream";
820 		} else if (*contenttype == NULL)
821 			*contenttype = "text/plain";
822 	}
823 	return convert;
824 }
825 
826 /*
827  * Convert c to a hexadecimal character string and store it in hex.
828  */
829 static char *
ctohex(int c,char * hex)830 ctohex(int c, char *hex)
831 {
832 	unsigned char d;
833 
834 	hex[2] = '\0';
835 	d = c % 16;
836 	hex[1] = basetable[d];
837 	if (c > d)
838 		hex[0] = basetable[(c - d) / 16];
839 	else
840 		hex[0] = basetable[0];
841 	return hex;
842 }
843 
844 /*
845  * Write to a file converting to quoted-printable.
846  * The mustquote function determines whether a character must be quoted.
847  */
848 static size_t
mime_write_toqp(struct str * in,FILE * fo,int (* mustquote)(int))849 mime_write_toqp(struct str *in, FILE *fo, int (*mustquote)(int))
850 {
851 	char *p, *upper, *h, hex[3];
852 	int l;
853 	size_t sz;
854 
855 	sz = in->l;
856 	upper = in->s + in->l;
857 	for (p = in->s, l = 0; p < upper; p++) {
858 		if (mustquote(*p&0377) ||
859 				p < upper-1 && p[1] == '\n' &&
860 					blankchar(p[0]&0377) ||
861 				p < upper-4 && l == 0 &&
862 					p[0] == 'F' && p[1] == 'r' &&
863 					p[2] == 'o' && p[3] == 'm' ||
864 				*p == '.' && l == 0 && p < upper-1 &&
865 					p[1] == '\n') {
866 			if (l >= 69) {
867 				sz += 2;
868 				fwrite("=\n", sizeof (char), 2, fo);
869 				l = 0;
870 			}
871 			sz += 2;
872 			putc('=', fo);
873 			h = ctohex(*p&0377, hex);
874 			fwrite(h, sizeof *h, 2, fo);
875 			l += 3;
876 		} else {
877 			if (*p == '\n')
878 				l = 0;
879 			else if (l >= 71) {
880 				sz += 2;
881 				fwrite("=\n", sizeof (char), 2, fo);
882 				l = 0;
883 			}
884 			putc(*p, fo);
885 			l++;
886 		}
887 	}
888 	return sz;
889 }
890 
891 /*
892  * Write to a stringstruct converting to quoted-printable.
893  * The mustquote function determines whether a character must be quoted.
894  */
895 static void
mime_str_toqp(struct str * in,struct str * out,int (* mustquote)(int),int inhdr)896 mime_str_toqp(struct str *in, struct str *out, int (*mustquote)(int), int inhdr)
897 {
898 	char *p, *q, *upper;
899 
900 	out->s = smalloc(in->l * 3 + 1);
901 	q = out->s;
902 	out->l = in->l;
903 	upper = in->s + in->l;
904 	for (p = in->s; p < upper; p++) {
905 		if (mustquote(*p&0377) || p+1 < upper && *(p + 1) == '\n' &&
906 				blankchar(*p & 0377)) {
907 			if (inhdr && *p == ' ') {
908 				*q++ = '_';
909 			} else {
910 				out->l += 2;
911 				*q++ = '=';
912 				ctohex(*p&0377, q);
913 				q += 2;
914 			}
915 		} else {
916 			*q++ = *p;
917 		}
918 	}
919 	*q = '\0';
920 }
921 
922 /*
923  * Write to a stringstruct converting from quoted-printable.
924  */
925 static void
mime_fromqp(struct str * in,struct str * out,int ishdr)926 mime_fromqp(struct str *in, struct str *out, int ishdr)
927 {
928 	char *p, *q, *upper;
929 	char quote[4];
930 
931 	out->l = in->l;
932 	out->s = smalloc(out->l + 1);
933 	upper = in->s + in->l;
934 	for (p = in->s, q = out->s; p < upper; p++) {
935 		if (*p == '=') {
936 			do {
937 				p++;
938 				out->l--;
939 			} while (blankchar(*p & 0377) && p < upper);
940 			if (p == upper)
941 				break;
942 			if (*p == '\n') {
943 				out->l--;
944 				continue;
945 			}
946 			if (p + 1 >= upper)
947 				break;
948 			quote[0] = *p++;
949 			quote[1] = *p;
950 			quote[2] = '\0';
951 			*q = (char)strtol(quote, NULL, 16);
952 			q++;
953 			out->l--;
954 		} else if (ishdr && *p == '_')
955 			*q++ = ' ';
956 		else
957 			*q++ = *p;
958 	}
959 	return;
960 }
961 
962 #define	mime_fromhdr_inc(inc) { \
963 		size_t diff = q - out->s; \
964 		out->s = srealloc(out->s, (maxstor += inc) + 1); \
965 		q = &(out->s)[diff]; \
966 	}
967 /*
968  * Convert header fields from RFC 1522 format
969  */
970 void
mime_fromhdr(struct str * in,struct str * out,enum tdflags flags)971 mime_fromhdr(struct str *in, struct str *out, enum tdflags flags)
972 {
973 	char *p, *q, *op, *upper, *cs, *cbeg, *tcs, *lastwordend = NULL;
974 	struct str cin, cout;
975 	int convert;
976 	size_t maxstor, lastoutl = 0;
977 #ifdef	HAVE_ICONV
978 	iconv_t fhicd = (iconv_t)-1;
979 #endif
980 
981 	tcs = gettcharset();
982 	maxstor = in->l;
983 	out->s = smalloc(maxstor + 1);
984 	out->l = 0;
985 	upper = in->s + in->l;
986 	for (p = in->s, q = out->s; p < upper; p++) {
987 		op = p;
988 		if (*p == '=' && *(p + 1) == '?') {
989 			p += 2;
990 			cbeg = p;
991 			while (p < upper && *p != '?')
992 				p++;	/* strip charset */
993 			if (p >= upper)
994 				goto notmime;
995 			cs = salloc(++p - cbeg);
996 			memcpy(cs, cbeg, p - cbeg - 1);
997 			cs[p - cbeg - 1] = '\0';
998 #ifdef	HAVE_ICONV
999 			if (fhicd != (iconv_t)-1)
1000 				iconv_close(fhicd);
1001 			if (strcmp(cs, tcs))
1002 				fhicd = iconv_open_ft(tcs, cs);
1003 			else
1004 				fhicd = (iconv_t)-1;
1005 #endif
1006 			switch (*p) {
1007 			case 'B': case 'b':
1008 				convert = CONV_FROMB64;
1009 				break;
1010 			case 'Q': case 'q':
1011 				convert = CONV_FROMQP;
1012 				break;
1013 			default:	/* invalid, ignore */
1014 				goto notmime;
1015 			}
1016 			if (*++p != '?')
1017 				goto notmime;
1018 			cin.s = ++p;
1019 			cin.l = 1;
1020 			for (;;) {
1021 				if (p == upper)
1022 					goto fromhdr_end;
1023 				if (*p++ == '?' && *p == '=')
1024 					break;
1025 				cin.l++;
1026 			}
1027 			cin.l--;
1028 			switch (convert) {
1029 				case CONV_FROMB64:
1030 					mime_fromb64(&cin, &cout, 1);
1031 					break;
1032 				case CONV_FROMQP:
1033 					mime_fromqp(&cin, &cout, 1);
1034 					break;
1035 			}
1036 			if (lastwordend) {
1037 				q = lastwordend;
1038 				out->l = lastoutl;
1039 			}
1040 #ifdef	HAVE_ICONV
1041 			if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
1042 				char *iptr, *mptr, *nptr, *uptr;
1043 				size_t inleft, outleft;
1044 
1045 			again:	inleft = cout.l;
1046 				outleft = maxstor - out->l;
1047 				mptr = nptr = q;
1048 				uptr = nptr + outleft;
1049 				iptr = cout.s;
1050 				if (iconv_ft(fhicd, &iptr, &inleft,
1051 						&nptr, &outleft) != 0 &&
1052 						errno == E2BIG) {
1053 					iconv(fhicd, NULL, NULL, NULL, NULL);
1054 					mime_fromhdr_inc(inleft);
1055 					goto again;
1056 				}
1057 				out->l += uptr - mptr - outleft;
1058 				q += uptr - mptr - outleft;
1059 			} else {
1060 #endif
1061 				while (cout.l > maxstor - out->l)
1062 					mime_fromhdr_inc(cout.l -
1063 							(maxstor - out->l));
1064 				memcpy(q, cout.s, cout.l);
1065 				q += cout.l;
1066 				out->l += cout.l;
1067 #ifdef	HAVE_ICONV
1068 			}
1069 #endif
1070 			free(cout.s);
1071 			lastwordend = q;
1072 			lastoutl = out->l;
1073 		} else {
1074 notmime:
1075 			p = op;
1076 			while (out->l >= maxstor)
1077 				mime_fromhdr_inc(16);
1078 			*q++ = *p;
1079 			out->l++;
1080 			if (!blankchar(*p&0377))
1081 				lastwordend = NULL;
1082 		}
1083 	}
1084 fromhdr_end:
1085 	*q = '\0';
1086 	if (flags & TD_ISPR) {
1087 		struct str	new;
1088 		makeprint(out, &new);
1089 		free(out->s);
1090 		*out = new;
1091 	}
1092 	if (flags & TD_DELCTRL)
1093 		out->l = delctrl(out->s, out->l);
1094 #ifdef	HAVE_ICONV
1095 	if (fhicd != (iconv_t)-1)
1096 		iconv_close(fhicd);
1097 #endif
1098 	return;
1099 }
1100 
1101 /*
1102  * Convert header fields to RFC 1522 format and write to the file fo.
1103  */
1104 static size_t
mime_write_tohdr(struct str * in,FILE * fo)1105 mime_write_tohdr(struct str *in, FILE *fo)
1106 {
1107 	char *upper, *wbeg, *wend, *charset, *lastwordend = NULL, *lastspc, b,
1108 		*charset7;
1109 	struct str cin, cout;
1110 	size_t sz = 0, col = 0, wr, charsetlen, charset7len;
1111 	int quoteany, mustquote, broken,
1112 		maxcol = 65 /* there is the header field's name, too */;
1113 
1114 	upper = in->s + in->l;
1115 	charset = getcharset(MIME_HIGHBIT);
1116 	if ((charset7 = value("charset7")) == NULL)
1117 		charset7 = us_ascii;
1118 	charsetlen = strlen(charset);
1119 	charset7len = strlen(charset7);
1120 	charsetlen = smax(charsetlen, charset7len);
1121 	b = 0;
1122 	for (wbeg = in->s, quoteany = 0; wbeg < upper; wbeg++) {
1123 		b |= *wbeg;
1124 		if (mustquote_hdr(wbeg, wbeg == in->s, wbeg == &upper[-1]))
1125 			quoteany++;
1126 	}
1127 	if (2 * quoteany > in->l) {
1128 		/*
1129 		 * Print the entire field in base64.
1130 		 */
1131 		for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1132 			wend = upper;
1133 			cin.s = wbeg;
1134 			for (;;) {
1135 				cin.l = wend - wbeg;
1136 				if (cin.l * 4/3 + 7 + charsetlen
1137 						< maxcol - col) {
1138 					fprintf(fo, "=?%s?B?",
1139 						b&0200 ? charset : charset7);
1140 					wr = mime_write_tob64(&cin, fo, 1);
1141 					fwrite("?=", sizeof (char), 2, fo);
1142 					wr += 7 + charsetlen;
1143 					sz += wr, col += wr;
1144 					if (wend < upper) {
1145 						fwrite("\n ", sizeof (char),
1146 								2, fo);
1147 						sz += 2;
1148 						col = 0;
1149 						maxcol = 76;
1150 					}
1151 					break;
1152 				} else {
1153 					if (col) {
1154 						fprintf(fo, "\n ");
1155 						sz += 2;
1156 						col = 0;
1157 						maxcol = 76;
1158 					} else
1159 						wend -= 4;
1160 				}
1161 			}
1162 		}
1163 	} else {
1164 		/*
1165 		 * Print the field word-wise in quoted-printable.
1166 		 */
1167 		broken = 0;
1168 		for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1169 			lastspc = NULL;
1170 			while (wbeg < upper && whitechar(*wbeg & 0377)) {
1171 				lastspc = lastspc ? lastspc : wbeg;
1172 				wbeg++;
1173 				col++;
1174 				broken = 0;
1175 			}
1176 			if (wbeg == upper) {
1177 				if (lastspc)
1178 					while (lastspc < wbeg) {
1179 						putc(*lastspc&0377, fo);
1180 							lastspc++,
1181 							sz++;
1182 						}
1183 				break;
1184 			}
1185 			mustquote = 0;
1186 			b = 0;
1187 			for (wend = wbeg;
1188 				wend < upper && !whitechar(*wend & 0377);
1189 					wend++) {
1190 				b |= *wend;
1191 				if (mustquote_hdr(wend, wend == wbeg,
1192 							wbeg == &upper[-1]))
1193 					mustquote++;
1194 			}
1195 			if (mustquote || broken || (wend - wbeg) >= 74 &&
1196 					quoteany) {
1197 				for (;;) {
1198 					cin.s = lastwordend ? lastwordend :
1199 						wbeg;
1200 					cin.l = wend - cin.s;
1201 					mime_str_toqp(&cin, &cout,
1202 							mustquote_inhdrq, 1);
1203 					if ((wr = cout.l + charsetlen + 7)
1204 							< maxcol - col) {
1205 						if (lastspc)
1206 							while (lastspc < wbeg) {
1207 								putc(*lastspc
1208 									&0377,
1209 									fo);
1210 								lastspc++,
1211 								sz++;
1212 							}
1213 						fprintf(fo, "=?%s?Q?", b&0200 ?
1214 							charset : charset7);
1215 						fwrite(cout.s, sizeof *cout.s,
1216 								cout.l, fo);
1217 						fwrite("?=", 1, 2, fo);
1218 						sz += wr, col += wr;
1219 						free(cout.s);
1220 						break;
1221 					} else {
1222 						broken = 1;
1223 						if (col) {
1224 							putc('\n', fo);
1225 							sz++;
1226 							col = 0;
1227 							maxcol = 76;
1228 							if (lastspc == NULL) {
1229 								putc(' ', fo);
1230 								sz++;
1231 								maxcol--;
1232 							} else
1233 								maxcol -= wbeg -
1234 									lastspc;
1235 						} else {
1236 							wend -= 4;
1237 						}
1238 						free(cout.s);
1239 					}
1240 				}
1241 				lastwordend = wend;
1242 			} else {
1243 				if (col && wend - wbeg > maxcol - col) {
1244 					putc('\n', fo);
1245 					sz++;
1246 					col = 0;
1247 					maxcol = 76;
1248 					if (lastspc == NULL) {
1249 						putc(' ', fo);
1250 						sz++;
1251 						maxcol--;
1252 					} else
1253 						maxcol -= wbeg - lastspc;
1254 				}
1255 				if (lastspc)
1256 					while (lastspc < wbeg) {
1257 						putc(*lastspc&0377, fo);
1258 						lastspc++, sz++;
1259 					}
1260 				wr = fwrite(wbeg, sizeof *wbeg,
1261 						wend - wbeg, fo);
1262 				sz += wr, col += wr;
1263 				lastwordend = NULL;
1264 			}
1265 		}
1266 	}
1267 	return sz;
1268 }
1269 
1270 /*
1271  * Write len characters of the passed string to the passed file,
1272  * doing charset and header conversion.
1273  */
1274 static size_t
convhdra(char * str,size_t len,FILE * fp)1275 convhdra(char *str, size_t len, FILE *fp)
1276 {
1277 #ifdef	HAVE_ICONV
1278 	char	*ip, *op;
1279 	size_t	isz, osz;
1280 #endif
1281 	struct str	cin;
1282 	size_t	cbufsz;
1283 	char	*cbuf;
1284 	size_t	sz;
1285 
1286 	cbuf = ac_alloc(cbufsz = 1);
1287 #ifdef	HAVE_ICONV
1288 	if (iconvd == (iconv_t)-1) {
1289 #endif
1290 		cin.s = str;
1291 		cin.l = len;
1292 #ifdef	HAVE_ICONV
1293 	} else {
1294 	again:	ip = str;
1295 		isz = len;
1296 		op = cbuf;
1297 		osz = cbufsz;
1298 		if (iconv(iconvd, &ip, &isz, &op, &osz) != 0) {
1299 			if (errno != E2BIG) {
1300 				ac_free(cbuf);
1301 				return 0;
1302 			}
1303 			cbuf = ac_alloc(cbufsz += isz);
1304 			goto again;
1305 		}
1306 		cin.s = cbuf;
1307 		cin.l = cbufsz - osz;
1308 	}
1309 #endif	/* HAVE_ICONV */
1310 	sz = mime_write_tohdr(&cin, fp);
1311 	ac_free(cbuf);
1312 	return sz;
1313 }
1314 
1315 
1316 /*
1317  * Write an address to a header field.
1318  */
1319 static size_t
mime_write_tohdr_a(struct str * in,FILE * f)1320 mime_write_tohdr_a(struct str *in, FILE *f)
1321 {
1322 	char	*cp, *lastcp;
1323 	size_t	sz = 0;
1324 
1325 	in->s[in->l] = '\0';
1326 	lastcp = in->s;
1327 	if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
1328 		sz += convhdra(lastcp, cp - lastcp, f);
1329 		lastcp = cp;
1330 	} else
1331 		cp = in->s;
1332 	for ( ; *cp; cp++) {
1333 		switch (*cp) {
1334 		case '(':
1335 			sz += fwrite(lastcp, 1, cp - lastcp + 1, f);
1336 			lastcp = ++cp;
1337 			cp = skip_comment(cp);
1338 			if (--cp > lastcp)
1339 				sz += convhdra(lastcp, cp - lastcp, f);
1340 			lastcp = cp;
1341 			break;
1342 		case '"':
1343 			while (*cp) {
1344 				if (*++cp == '"')
1345 					break;
1346 				if (*cp == '\\' && cp[1])
1347 					cp++;
1348 			}
1349 			break;
1350 		}
1351 	}
1352 	if (cp > lastcp)
1353 		sz += fwrite(lastcp, 1, cp - lastcp, f);
1354 	return sz;
1355 }
1356 
1357 static void
addstr(char ** buf,size_t * sz,size_t * pos,char * str,size_t len)1358 addstr(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
1359 {
1360 	*buf = srealloc(*buf, *sz += len);
1361 	memcpy(&(*buf)[*pos], str, len);
1362 	*pos += len;
1363 }
1364 
1365 static void
addconv(char ** buf,size_t * sz,size_t * pos,char * str,size_t len)1366 addconv(char **buf, size_t *sz, size_t *pos, char *str, size_t len)
1367 {
1368 	struct str	in, out;
1369 
1370 	in.s = str;
1371 	in.l = len;
1372 	mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1373 	addstr(buf, sz, pos, out.s, out.l);
1374 	free(out.s);
1375 }
1376 
1377 /*
1378  * Interpret MIME strings in parts of an address field.
1379  */
1380 char *
mime_fromaddr(char * name)1381 mime_fromaddr(char *name)
1382 {
1383 	char	*cp, *lastcp;
1384 	char	*res = NULL;
1385 	size_t	ressz = 1, rescur = 0;
1386 
1387 	if (name == NULL || *name == '\0')
1388 		return name;
1389 	if ((cp = routeaddr(name)) != NULL && cp > name) {
1390 		addconv(&res, &ressz, &rescur, name, cp - name);
1391 		lastcp = cp;
1392 	} else
1393 		cp = lastcp = name;
1394 	for ( ; *cp; cp++) {
1395 		switch (*cp) {
1396 		case '(':
1397 			addstr(&res, &ressz, &rescur, lastcp, cp - lastcp + 1);
1398 			lastcp = ++cp;
1399 			cp = skip_comment(cp);
1400 			if (--cp > lastcp)
1401 				addconv(&res, &ressz, &rescur, lastcp,
1402 						cp - lastcp);
1403 			lastcp = cp;
1404 			break;
1405 		case '"':
1406 			while (*cp) {
1407 				if (*++cp == '"')
1408 					break;
1409 				if (*cp == '\\' && cp[1])
1410 					cp++;
1411 			}
1412 			break;
1413 		}
1414 	}
1415 	if (cp > lastcp)
1416 		addstr(&res, &ressz, &rescur, lastcp, cp - lastcp);
1417 	res[rescur] = '\0';
1418 	cp = savestr(res);
1419 	free(res);
1420 	return cp;
1421 }
1422 
1423 /*
1424  * fwrite whilst adding prefix, if present.
1425  */
1426 size_t
prefixwrite(void * ptr,size_t size,size_t nmemb,FILE * f,char * prefix,size_t prefixlen)1427 prefixwrite(void *ptr, size_t size, size_t nmemb, FILE *f,
1428 		char *prefix, size_t prefixlen)
1429 {
1430 	static FILE *lastf;
1431 	static char lastc = '\n';
1432 	size_t rsz, wsz = 0;
1433 	char *p = ptr;
1434 
1435 	if (nmemb == 0)
1436 		return 0;
1437 	if (prefix == NULL) {
1438 		lastf = f;
1439 		lastc = ((char *)ptr)[size * nmemb - 1];
1440 		return fwrite(ptr, size, nmemb, f);
1441 	}
1442 	if (f != lastf || lastc == '\n') {
1443 		if (*p == '\n' || *p == '\0')
1444 			wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1445 		else {
1446 			fputs(prefix, f);
1447 			wsz += strlen(prefix);
1448 		}
1449 	}
1450 	lastf = f;
1451 	for (rsz = size * nmemb; rsz; rsz--, p++, wsz++) {
1452 		putc(*p, f);
1453 		if (*p != '\n' || rsz == 1) {
1454 			continue;
1455 		}
1456 		if (p[1] == '\n' || p[1] == '\0')
1457 			wsz += fwrite(prefix, sizeof *prefix, prefixlen, f);
1458 		else {
1459 			fputs(prefix, f);
1460 			wsz += strlen(prefix);
1461 		}
1462 	}
1463 	lastc = p[-1];
1464 	return wsz;
1465 }
1466 
1467 /*
1468  * fwrite while checking for displayability.
1469  */
1470 static size_t
fwrite_td(void * ptr,size_t size,size_t nmemb,FILE * f,enum tdflags flags,char * prefix,size_t prefixlen)1471 fwrite_td(void *ptr, size_t size, size_t nmemb, FILE *f, enum tdflags flags,
1472 		char *prefix, size_t prefixlen)
1473 {
1474 	char *upper;
1475 	size_t sz, csize;
1476 #ifdef	HAVE_ICONV
1477 	char *iptr, *nptr;
1478 	size_t inleft, outleft;
1479 #endif
1480 	char *mptr, *xmptr, *mlptr = NULL;
1481 	size_t mptrsz;
1482 
1483 	csize = size * nmemb;
1484 	mptrsz = csize;
1485 	mptr = xmptr = ac_alloc(mptrsz + 1);
1486 #ifdef	HAVE_ICONV
1487 	if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
1488 	again:	inleft = csize;
1489 		outleft = mptrsz;
1490 		nptr = mptr;
1491 		iptr = ptr;
1492 		if (iconv_ft(iconvd, &iptr, &inleft, &nptr, &outleft) != 0 &&
1493 				errno == E2BIG) {
1494 			iconv(iconvd, NULL, NULL, NULL, NULL);
1495 			ac_free(mptr);
1496 			mptrsz += inleft;
1497 			mptr = ac_alloc(mptrsz + 1);
1498 			goto again;
1499 		}
1500 		nmemb = mptrsz - outleft;
1501 		size = sizeof (char);
1502 		ptr = mptr;
1503 		csize = size * nmemb;
1504 	} else
1505 #endif
1506 	{
1507 		memcpy(mptr, ptr, csize);
1508 	}
1509 	upper = mptr + csize;
1510 	*upper = '\0';
1511 	if (flags & TD_ISPR) {
1512 		struct str	in, out;
1513 		in.s = mptr;
1514 		in.l = csize;
1515 		makeprint(&in, &out);
1516 		mptr = mlptr = out.s;
1517 		csize = out.l;
1518 	}
1519 	if (flags & TD_DELCTRL)
1520 		csize = delctrl(mptr, csize);
1521 	sz = prefixwrite(mptr, sizeof *mptr, csize, f, prefix, prefixlen);
1522 	ac_free(xmptr);
1523 	free(mlptr);
1524 	return sz;
1525 }
1526 
1527 /*
1528  * fwrite performing the given MIME conversion.
1529  */
1530 size_t
mime_write(void * ptr,size_t size,FILE * f,enum conversion convert,enum tdflags dflags,char * prefix,size_t prefixlen,char ** restp,size_t * restsizep)1531 mime_write(void *ptr, size_t size, FILE *f,
1532 		enum conversion convert, enum tdflags dflags,
1533 		char *prefix, size_t prefixlen,
1534 		char **restp, size_t *restsizep)
1535 {
1536 	struct str in, out;
1537 	size_t sz, csize;
1538 	int is_text = 0;
1539 #ifdef	HAVE_ICONV
1540 	char mptr[LINESIZE * 6];
1541 	char *iptr, *nptr;
1542 	size_t inleft, outleft;
1543 #endif
1544 
1545 	if (size == 0)
1546 		return 0;
1547 	csize = size;
1548 #ifdef	HAVE_ICONV
1549 	if (csize < sizeof mptr && (dflags & TD_ICONV)
1550 			&& iconvd != (iconv_t)-1
1551 			&& (convert == CONV_TOQP || convert == CONV_8BIT ||
1552 				convert == CONV_TOB64 ||
1553 				convert == CONV_TOHDR)) {
1554 		inleft = csize;
1555 		outleft = sizeof mptr;
1556 		nptr = mptr;
1557 		iptr = ptr;
1558 		if (iconv(iconvd, &iptr, &inleft,
1559 				&nptr, &outleft) != (size_t)-1) {
1560 			in.l = sizeof mptr - outleft;
1561 			in.s = mptr;
1562 		} else {
1563 			if (errno == EILSEQ || errno == EINVAL)
1564 				invalid_seq(*iptr);
1565 			return 0;
1566 		}
1567 	} else {
1568 #endif
1569 		in.s = ptr;
1570 		in.l = csize;
1571 #ifdef	HAVE_ICONV
1572 	}
1573 #endif
1574 	switch (convert) {
1575 	case CONV_FROMQP:
1576 		mime_fromqp(&in, &out, 0);
1577 		sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
1578 				prefix, prefixlen);
1579 		free(out.s);
1580 		break;
1581 	case CONV_TOQP:
1582 		sz = mime_write_toqp(&in, f, mustquote_body);
1583 		break;
1584 	case CONV_8BIT:
1585 		sz = prefixwrite(in.s, sizeof *in.s, in.l, f,
1586 				prefix, prefixlen);
1587 		break;
1588 	case CONV_FROMB64_T:
1589 		is_text = 1;
1590 		/*FALLTHROUGH*/
1591 	case CONV_FROMB64:
1592 		mime_fromb64_b(&in, &out, is_text, f);
1593 		if (is_text && out.s[out.l-1] != '\n' && restp && restsizep) {
1594 			*restp = ptr;
1595 			*restsizep = size;
1596 			sz = 0;
1597 		} else {
1598 			sz = fwrite_td(out.s, sizeof *out.s, out.l, f, dflags,
1599 				prefix, prefixlen);
1600 		}
1601 		free(out.s);
1602 		break;
1603 	case CONV_TOB64:
1604 		sz = mime_write_tob64(&in, f, 0);
1605 		break;
1606 	case CONV_FROMHDR:
1607 		mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1608 		sz = fwrite_td(out.s, sizeof *out.s, out.l, f,
1609 				dflags&TD_DELCTRL, prefix, prefixlen);
1610 		free(out.s);
1611 		break;
1612 	case CONV_TOHDR:
1613 		sz = mime_write_tohdr(&in, f);
1614 		break;
1615 	case CONV_TOHDR_A:
1616 		sz = mime_write_tohdr_a(&in, f);
1617 		break;
1618 	default:
1619 		sz = fwrite_td(in.s, sizeof *in.s, in.l, f, dflags,
1620 				prefix, prefixlen);
1621 	}
1622 	return sz;
1623 }
1624