xref: /netbsd-src/usr.bin/mail/mime_codecs.c (revision 77a1ad5f0039ea2bc6af76846debc20728bae498)
1 /*	$NetBSD: mime_codecs.c,v 1.12 2019/10/24 18:18:00 kamil 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  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * This module contains all mime related codecs.  Typically there are
34  * two versions: one operating on buffers and one operating on files.
35  * All exported routines have a "mime_" prefix.  The file oriented
36  * routines have a "mime_f" prefix replacing the "mime_" prefix of the
37  * equivalent buffer based version.
38  *
39  * The file based API should be:
40  *
41  *   mime_f<name>_{encode,decode}(FILE *in, FILE *out, void *cookie)
42  *
43  * XXX - currently this naming convention has not been adheared to.
44  *
45  * where the cookie is a generic way to pass arguments to the routine.
46  * This way these routines can be run by run_function() in mime.c.
47  *
48  * The buffer based API is not as rigid.
49  */
50 
51 #ifdef MIME_SUPPORT
52 
53 #include <sys/cdefs.h>
54 #ifndef __lint__
55 __RCSID("$NetBSD: mime_codecs.c,v 1.12 2019/10/24 18:18:00 kamil Exp $");
56 #endif /* not __lint__ */
57 
58 #include <assert.h>
59 #include <iconv.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <util.h>
63 
64 #include "def.h"
65 #include "extern.h"
66 #include "mime_codecs.h"
67 
68 
69 #ifdef CHARSET_SUPPORT
70 /************************************************************************
71  * Core character set conversion routines.
72  *
73  */
74 
75 /*
76  * Fault-tolerant iconv() function.
77  *
78  * This routine was borrowed from nail-11.25/mime.c and modified.  It
79  * tries to handle errno == EILSEQ by restarting at the next input
80  * byte (is this a good idea?).  All other errors are handled by the
81  * caller.
82  */
83 PUBLIC size_t
mime_iconv(iconv_t cd,const char ** inb,size_t * inbleft,char ** outb,size_t * outbleft)84 mime_iconv(iconv_t cd, const char **inb, size_t *inbleft, char **outb, size_t *outbleft)
85 {
86 	size_t sz = 0;
87 
88 	while ((sz = iconv(cd, __UNCONST(inb), inbleft, outb, outbleft))
89 		   == (size_t)-1
90 			&& errno == EILSEQ) {
91 		if (*outbleft > 0) {
92 			*(*outb)++ = '?';
93 			(*outbleft)--;
94 		} else {
95 			**outb = '\0';
96 			return E2BIG;
97 		}
98 		if (*inbleft > 0) {
99 			(*inb)++;
100 			(*inbleft)--;
101 		} else {
102 			**outb = '\0';
103 			break;
104 		}
105 	}
106 	return sz;
107 }
108 
109 /*
110  * This routine was mostly borrowed from src/usr.bin/iconv/iconv.c.
111  * We don't care about the invalid character count, so don't bother
112  * with __iconv().  We do care about robustness, so call iconv_ft()
113  * above to try to recover from errors.
114  */
115 #define INBUFSIZE 1024
116 #define OUTBUFSIZE (INBUFSIZE * 2)
117 
118 PUBLIC void
mime_ficonv(FILE * fi,FILE * fo,void * cookie)119 mime_ficonv(FILE *fi, FILE *fo, void *cookie)
120 {
121 	char inbuf[INBUFSIZE], outbuf[OUTBUFSIZE], *out;
122 	const char *in;
123 	size_t inbytes, outbytes, ret;
124 	iconv_t cd;
125 
126 	/*
127 	 * NOTE: iconv_t is actually a pointer typedef, so this
128 	 * conversion is not what it appears to be!
129 	 */
130 	cd = (iconv_t)cookie;
131 
132 	while ((inbytes = fread(inbuf, 1, INBUFSIZE, fi)) > 0) {
133 		in = inbuf;
134 		while (inbytes > 0) {
135 			out = outbuf;
136 			outbytes = OUTBUFSIZE;
137 			ret = mime_iconv(cd, &in, &inbytes, &out, &outbytes);
138 			if (ret == (size_t)-1 && errno != E2BIG) {
139 				if (errno != EINVAL || in == inbuf) {
140 					/* XXX - what is proper here?
141 					 * Just copy out the remains? */
142 					(void)fprintf(fo,
143 					    "\n\t[ iconv truncated message: %s ]\n\n",
144 					    strerror(errno));
145 					return;
146 				}
147 				/*
148 				 * If here: errno == EINVAL && in != inbuf
149 				 */
150 				/* incomplete input character */
151 				(void)memmove(inbuf, in, inbytes);
152 				ret = fread(inbuf + inbytes, 1,
153 				    INBUFSIZE - inbytes, fi);
154 				if (ret == 0) {
155 					if (feof(fi)) {
156 						(void)fprintf(fo,
157 						    "\n\t[ unexpected end of file; "
158 						    "the last character is "
159 						    "incomplete. ]\n\n");
160 						return;
161 					}
162 					(void)fprintf(fo,
163 					    "\n\t[ fread(): %s ]\n\n",
164 					    strerror(errno));
165 					return;
166 				}
167 				in = inbuf;
168 				inbytes += ret;
169 
170 			}
171 			if (outbytes < OUTBUFSIZE)
172 				(void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, fo);
173 		}
174 	}
175 	/* reset the shift state of the output buffer */
176 	outbytes = OUTBUFSIZE;
177 	out = outbuf;
178 	ret = iconv(cd, NULL, NULL, &out, &outbytes);
179 	if (ret == (size_t)-1) {
180 		(void)fprintf(fo, "\n\t[ iconv(): %s ]\n\n",
181 		    strerror(errno));
182 		return;
183 	}
184 	if (outbytes < OUTBUFSIZE)
185 		(void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, fo);
186 }
187 
188 #endif	/* CHARSET_SUPPORT */
189 
190 
191 
192 /************************************************************************
193  * Core base64 routines
194  *
195  * Defined in sec 6.8 of RFC 2045.
196  */
197 
198 /*
199  * Decode a base64 buffer.
200  *
201  *   bin:  buffer to hold the decoded (binary) result (see note 1).
202  *   b64:  buffer holding the encoded (base64) source.
203  *   cnt:  number of bytes in the b64 buffer to decode (see note 2).
204  *
205  * Return: the number of bytes written to the 'bin' buffer or -1 on
206  *         error.
207  * NOTES:
208  *   1) It is the callers responsibility to ensure that bin is large
209  *      enough to hold the result.
210  *   2) The b64 buffer should always contain a multiple of 4 bytes of
211  *      data!
212  */
213 PUBLIC ssize_t
mime_b64tobin(char * bin,const char * b64,size_t cnt)214 mime_b64tobin(char *bin, const char *b64, size_t cnt)
215 {
216 	static const signed char b64index[] = {
217 		-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
218 		-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
219 		-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
220 		52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-2,-1,-1,
221 		-1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
222 		15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
223 		-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
224 		41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
225 	};
226 	unsigned char *p;
227 	const unsigned char *q, *end;
228 
229 #define EQU	(unsigned)-2
230 #define BAD	(unsigned)-1
231 #define uchar64(c)  ((c) >= sizeof(b64index) ? BAD : (unsigned)b64index[(c)])
232 
233 	p = (unsigned char *)bin;
234 	q = (const unsigned char *)b64;
235 	for (end = q + cnt; q < end; q += 4) {
236 		unsigned a = uchar64(q[0]);
237 		unsigned b = uchar64(q[1]);
238 		unsigned c = uchar64(q[2]);
239 		unsigned d = uchar64(q[3]);
240 
241 		if (a == BAD || a == EQU || b == BAD || b == EQU ||
242 		    c == BAD || d == BAD)
243 			return -1;
244 
245 		*p++ = ((a << 2) | ((b & 0x30) >> 4));
246 		if (c == EQU)	{ /* got '=' */
247 			if (d != EQU)
248 				return -1;
249 			break;
250 		}
251 		*p++ = (((b & 0x0f) << 4) | ((c & 0x3c) >> 2));
252 		if (d == EQU) { /* got '=' */
253 			break;
254 		}
255 		*p++ = (((c & 0x03) << 6) | d);
256 	}
257 
258 #undef uchar64
259 #undef EQU
260 #undef BAD
261 
262 	return p - (unsigned char*)bin;
263 }
264 
265 /*
266  * Encode a buffer as a base64 result.
267  *
268  *   b64:  buffer to hold the encoded (base64) result (see note).
269  *   bin:  buffer holding the binary source.
270  *   cnt:  number of bytes in the bin buffer to encode.
271  *
272  * NOTE: it is the callers responsibility to ensure that 'b64' is
273  *       large enough to hold the result.
274  */
275 PUBLIC void
mime_bintob64(char * b64,const char * bin,size_t cnt)276 mime_bintob64(char *b64, const char *bin, size_t cnt)
277 {
278 	static const char b64table[] =
279 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
280 	const unsigned char *p = (const unsigned char*)bin;
281 	ssize_t i;
282 
283 	for (i = cnt; i > 0; i -= 3) {
284 		unsigned a = p[0];
285 		unsigned b = p[1];
286 		unsigned c = p[2];
287 
288 		b64[0] = b64table[a >> 2];
289 		switch(i) {
290 		case 1:
291 			b64[1] = b64table[((a & 0x3) << 4)];
292 			b64[2] = '=';
293 			b64[3] = '=';
294 			break;
295 		case 2:
296 			b64[1] = b64table[((a & 0x3) << 4) | ((b & 0xf0) >> 4)];
297 			b64[2] = b64table[((b & 0xf) << 2)];
298 			b64[3] = '=';
299 			break;
300 		default:
301 			b64[1] = b64table[((a & 0x3) << 4) | ((b & 0xf0) >> 4)];
302 			b64[2] = b64table[((b & 0xf) << 2) | ((c & 0xc0) >> 6)];
303 			b64[3] = b64table[c & 0x3f];
304 			break;
305 		}
306 		p   += 3;
307 		b64 += 4;
308 	}
309 }
310 
311 
312 #define MIME_BASE64_LINE_MAX	(4 * 19)  /* max line length is 76: see RFC2045 sec 6.8 */
313 
314 static void
mime_fB64_encode(FILE * fi,FILE * fo,void * cookie __unused)315 mime_fB64_encode(FILE *fi, FILE *fo, void *cookie __unused)
316 {
317 	static char b64[MIME_BASE64_LINE_MAX];
318 	static char mem[3 * (MIME_BASE64_LINE_MAX / 4)];
319 	size_t cnt;
320 	char *cp;
321 	size_t limit;
322 #ifdef __lint__
323 	cookie = cookie;
324 #endif
325 	limit = 0;
326 	if ((cp = value(ENAME_MIME_B64_LINE_MAX)) != NULL)
327 		limit = (size_t)atoi(cp);
328 	if (limit == 0 || limit > sizeof(b64))
329 		limit = sizeof(b64);
330 
331 	limit = 3 * roundup(limit, 4) / 4;
332 	if (limit < 3)
333 		limit = 3;
334 
335 	while ((cnt = fread(mem, sizeof(*mem), limit, fi)) > 0) {
336 		mime_bintob64(b64, mem, (size_t)cnt);
337 		(void)fwrite(b64, sizeof(*b64), (size_t)4 * roundup(cnt, 3) / 3, fo);
338 		(void)putc('\n', fo);
339 	}
340 }
341 
342 static void
mime_fB64_decode(FILE * fi,FILE * fo,void * add_lf)343 mime_fB64_decode(FILE *fi, FILE *fo, void *add_lf)
344 {
345 	char *line;
346 	size_t len;
347 	char *buf;
348 	size_t buflen;
349 
350 	buflen = 3 * (MIME_BASE64_LINE_MAX / 4);
351 	buf = emalloc(buflen);
352 
353 	while ((line = fgetln(fi, &len)) != NULL) {
354 		ssize_t binlen;
355 		if (line[len-1] == '\n') /* forget the trailing newline */
356 			len--;
357 
358 		/* trash trailing white space */
359 		for (/*EMPTY*/; len > 0 && is_WSP(line[len-1]); len--)
360 			continue;
361 
362 		/* skip leading white space */
363 		for (/*EMPTY*/; len > 0 && is_WSP(line[0]); len--, line++)
364 			continue;
365 
366 		if (len == 0)
367 			break;
368 
369 		if (3 * len > 4 * buflen) {
370 			buflen *= 2;
371 			buf = erealloc(buf, buflen);
372 		}
373 
374 		binlen = mime_b64tobin(buf, line, len);
375 
376 		if (binlen <= 0) {
377 			(void)fprintf(fo, "WARN: invalid base64 encoding\n");
378 			break;
379 		}
380 		(void)fwrite(buf, 1, (size_t)binlen, fo);
381 	}
382 
383 	free(buf);
384 
385 	if (add_lf)
386 		(void)fputc('\n', fo);
387 }
388 
389 
390 /************************************************************************
391  * Core quoted-printable routines.
392  *
393  * Defined in sec 6.7 of RFC 2045.
394  */
395 
396 /*
397  * strtol(3), but inline and with easy error indication.
398  */
399 static inline int
_qp_cfromhex(char const * hex)400 _qp_cfromhex(char const *hex)
401 {
402 	/* Be robust, allow lowercase hexadecimal letters, too */
403 	static unsigned char const atoi16[] = {
404 		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */
405 		0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */
406 		0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */
407 		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */
408 		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */
409 		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */
410 		0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF  /* 0x60-0x67 */
411 	};
412 	unsigned char i1, i2;
413 	int r;
414 
415 	if ((i1 = (unsigned char)hex[0] - '0') >= __arraycount(atoi16) ||
416 	    (i2 = (unsigned char)hex[1] - '0') >= __arraycount(atoi16))
417 		goto jerr;
418 	i1 = atoi16[i1];
419 	i2 = atoi16[i2];
420 	if ((i1 | i2) & 0xF0)
421 		goto jerr;
422 	r = i1;
423 	r <<= 4;
424 	r += i2;
425 jleave:
426 	return r;
427 jerr:
428 	r = -1;
429 	goto jleave;
430 }
431 
432 /*
433  * Header specific "quoted-printable" decode!
434  * Differences with body QP decoding (see rfc 2047, sec 4.2):
435  * 1) '=' occurs _only_ when followed by two hex digits (FWS is not allowed).
436  * 2) Spaces can be encoded as '_' in headers for readability.
437  */
438 static ssize_t
mime_QPh_decode(char * outbuf,size_t outlen,const char * inbuf,size_t inlen)439 mime_QPh_decode(char *outbuf, size_t outlen, const char *inbuf, size_t inlen)
440 {
441 	const char *p, *inend;
442 	char *outend;
443 	char *q;
444 
445 	outend = outbuf + outlen;
446 	inend = inbuf + inlen;
447 	q = outbuf;
448 	for (p = inbuf; p < inend; p++) {
449 		if (q >= outend)
450 			return -1;
451 		if (*p == '=') {
452 			p++;
453 			if (p + 1 < inend) {
454 				int c = _qp_cfromhex(p++);
455 				if (c < 0)
456 					return -1;
457 				*q++ = (char)c;
458 			}
459 			else
460 				return -1;
461 		}
462 		else if (*p == '_')  /* header's may encode ' ' as '_' */
463 			*q++ = ' ';
464 		else
465 			*q++ = *p;
466 	}
467 	return q - outbuf;
468 }
469 
470 
471 static int
mustquote(unsigned char * p,unsigned char * end,size_t l)472 mustquote(unsigned char *p, unsigned char *end, size_t l)
473 {
474 #define N	0	/* do not quote */
475 #define Q	1	/* must quote */
476 #define SP	2	/* white space */
477 #define XF	3	/* special character 'F' - maybe quoted */
478 #define XD	4	/* special character '.' - maybe quoted */
479 #define EQ	Q	/* '=' must be quoted */
480 #define TB	SP	/* treat '\t' as a space */
481 #define NL	N	/* don't quote '\n' (NL) - XXX - quoting here breaks the line length algorithm */
482 #define CR	Q	/* always quote a '\r' (CR) - it occurs only in a CRLF combo */
483 
484 	static const signed char quotetab[] = {
485   		 Q, Q, Q, Q,  Q, Q, Q, Q,  Q,TB,NL, Q,  Q,CR, Q, Q,
486 		 Q, Q, Q, Q,  Q, Q, Q, Q,  Q, Q, Q, Q,  Q, Q, Q, Q,
487 		SP, N, N, N,  N, N, N, N,  N, N, N, N,  N, N,XD, N,
488 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N,EQ, N, N,
489 
490 		 N, N, N, N,  N, N,XF, N,  N, N, N, N,  N, N, N, N,
491 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, N,
492 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, N,
493 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, Q,
494 	};
495 	int flag = *p > 0x7f ? Q : quotetab[*p];
496 
497 	if (flag == N)
498 		return 0;
499 	if (flag == Q)
500 		return 1;
501 	if (flag == SP)
502 		return p + 1 < end && p[1] == '\n'; /* trailing white space */
503 
504 	/* The remainder are special start-of-line cases. */
505 	if (l != 0)
506 		return 0;
507 
508 	if (flag == XF)	/* line may start with "From" */
509 		return p + 4 < end && p[1] == 'r' && p[2] == 'o' && p[3] == 'm';
510 
511 	if (flag == XD)	/* line may consist of a single dot */
512 		return p + 1 < end && p[1] == '\n';
513 
514 	errx(EXIT_FAILURE,
515 	    "mustquote: invalid logic: *p=0x%x (%d) flag=%d, l=%zu\n",
516 	    *p, *p, flag, l);
517 	/* NOT REACHED */
518 	return 0;	/* appease GCC */
519 
520 #undef N
521 #undef Q
522 #undef SP
523 #undef XX
524 #undef EQ
525 #undef TB
526 #undef NL
527 #undef CR
528 }
529 
530 
531 #define MIME_QUOTED_LINE_MAX	76  /* QP max length: see RFC2045 sec 6.7 */
532 
533 static void
fput_quoted_line(FILE * fo,char * line,size_t len,size_t limit)534 fput_quoted_line(FILE *fo, char *line, size_t len, size_t limit)
535 {
536 	size_t l;	/* length of current output line */
537 	unsigned char *beg;
538 	unsigned char *end;
539 	unsigned char *p;
540 
541 	assert(limit <= MIME_QUOTED_LINE_MAX);
542 
543 	beg = (unsigned char*)line;
544 	end = beg + len;
545 	l = 0;
546 	for (p = (unsigned char*)line; p < end; p++) {
547 		if (mustquote(p, end, l)) {
548 			if (l + 4 > limit) {
549 				(void)fputs("=\n", fo);
550 				l = 0;
551 			}
552 			(void)fprintf(fo, "=%02X", *p);
553 			l += 3;
554 		}
555 		else {
556 			if (*p == '\n') {
557 				if (p > beg && p[-1] == '\r') {
558 					if (l + 4 > limit)
559 						(void)fputs("=\n", fo);
560 					(void)fputs("=0A=", fo);
561 				}
562 				l = (size_t)-1;
563 			}
564 			else if (l + 2 > limit) {
565 				(void)fputs("=\n", fo);
566 				l = 0;
567 			}
568 			(void)putc(*p, fo);
569 			l++;
570 		}
571 	}
572 	/*
573 	 * Lines ending in a blank must escape the newline.
574 	 */
575 	if (len && is_WSP(p[-1]))
576 		(void)fputs("=\n", fo);
577 }
578 
579 static void
mime_fQP_encode(FILE * fi,FILE * fo,void * cookie __unused)580 mime_fQP_encode(FILE *fi, FILE *fo, void *cookie __unused)
581 {
582 	char *line;
583 	size_t len;
584 	char *cp;
585 	size_t limit;
586 
587 #ifdef __lint__
588 	cookie = cookie;
589 #endif
590 	limit = 0;
591 	if ((cp = value(ENAME_MIME_QP_LINE_MAX)) != NULL)
592 		limit = (size_t)atoi(cp);
593 	if (limit == 0 || limit > MIME_QUOTED_LINE_MAX)
594 		limit = MIME_QUOTED_LINE_MAX;
595 	if (limit < 4)
596 		limit = 4;
597 
598 	while ((line = fgetln(fi, &len)) != NULL)
599 		fput_quoted_line(fo, line, len, limit);
600 }
601 
602 static void
mime_fQP_decode(FILE * fi,FILE * fo,void * cookie __unused)603 mime_fQP_decode(FILE *fi, FILE *fo, void *cookie __unused)
604 {
605 	char *line;
606 	size_t len;
607 
608 #ifdef __lint__
609 	cookie = cookie;
610 #endif
611 	while ((line = fgetln(fi, &len)) != NULL) {
612 		char *p;
613 		char *end;
614 
615 		end = line + len;
616 		for (p = line; p < end; p++) {
617 			if (*p == '=') {
618 				p++;
619 				while (p < end && is_WSP(*p))
620 					p++;
621 				if (*p != '\n' && p + 1 < end) {
622 					int c = _qp_cfromhex(p++);
623 					if (c >= 0)
624 						(void)fputc(c, fo);
625 					else
626 						(void)fputs("[?]", fo);
627 				}
628 			}
629 			else
630 				(void)fputc(*p, fo);
631 		}
632 	}
633 }
634 
635 
636 /************************************************************************
637  * Routines to select the codec by name.
638  */
639 
640 PUBLIC void
mime_fio_copy(FILE * fi,FILE * fo,void * cookie __unused)641 mime_fio_copy(FILE *fi, FILE *fo, void *cookie __unused)
642 {
643 	int c;
644 
645 #ifdef __lint__
646 	cookie = cookie;
647 #endif
648 	while ((c = getc(fi)) != EOF)
649 		(void)putc(c, fo);
650 
651 	(void)fflush(fo);
652 	if (ferror(fi)) {
653 		warn("read");
654 		rewind(fi);
655 		return;
656 	}
657 	if (ferror(fo)) {
658 		warn("write");
659 		(void)Fclose(fo);
660 		rewind(fi);
661 		return;
662 	}
663 }
664 
665 
666 static const struct transfer_encoding_s {
667 	const char 	*name;
668 	mime_codec_t	enc;
669 	mime_codec_t	dec;
670 } transfer_encoding_tbl[] = {
671 	{ MIME_TRANSFER_7BIT,	mime_fio_copy,	    mime_fio_copy },
672 	{ MIME_TRANSFER_8BIT, 	mime_fio_copy,	    mime_fio_copy },
673 	{ MIME_TRANSFER_BINARY,	mime_fio_copy,	    mime_fio_copy },
674 	{ MIME_TRANSFER_QUOTED, mime_fQP_encode,    mime_fQP_decode },
675 	{ MIME_TRANSFER_BASE64, mime_fB64_encode,   mime_fB64_decode },
676 	{ NULL,			NULL,		    NULL },
677 };
678 
679 
680 PUBLIC mime_codec_t
mime_fio_encoder(const char * ename)681 mime_fio_encoder(const char *ename)
682 {
683 	const struct transfer_encoding_s *tep = NULL;
684 
685 	if (ename == NULL)
686 		return NULL;
687 
688 	for (tep = transfer_encoding_tbl; tep->name; tep++)
689 		if (strcasecmp(tep->name, ename) == 0)
690 			break;
691 	return tep->enc;
692 }
693 
694 PUBLIC mime_codec_t
mime_fio_decoder(const char * ename)695 mime_fio_decoder(const char *ename)
696 {
697 	const struct transfer_encoding_s *tep = NULL;
698 
699 	if (ename == NULL)
700 		return NULL;
701 
702 	for (tep = transfer_encoding_tbl; tep->name; tep++)
703 		if (strcasecmp(tep->name, ename) == 0)
704 			break;
705 	return tep->dec;
706 }
707 
708 /*
709  * Decode a RFC 2047 extended message header *encoded-word*.
710  * *encoding* is the corresponding character of the *encoded-word*.
711  */
712 PUBLIC ssize_t
mime_rfc2047_decode(char encoding,char * outbuf,size_t outlen,const char * inbuf,size_t inlen)713 mime_rfc2047_decode(char encoding, char *outbuf, size_t outlen,
714 	const char *inbuf, size_t inlen)
715 {
716 	ssize_t declen = -1;
717 
718 	if (encoding == 'B' || encoding == 'b') {
719 		if (outlen >= 3 * roundup(inlen, 4) / 4)
720 			declen = mime_b64tobin(outbuf, inbuf, inlen);
721 	} else if (encoding == 'Q' || encoding == 'q')
722 		declen = mime_QPh_decode(outbuf, outlen, inbuf, inlen);
723 	return declen;
724 }
725 
726 /*
727  * This is for use in complete.c and mime.c to get the list of
728  * encoding names without exposing the transfer_encoding_tbl[].  The
729  * first name is returned if called with a pointer to a NULL pointer.
730  * Subsequent calls with the same cookie give successive names.  A
731  * NULL return indicates the end of the list.
732  */
733 PUBLIC const char *
mime_next_encoding_name(const void ** cookie)734 mime_next_encoding_name(const void **cookie)
735 {
736 	const struct transfer_encoding_s *tep;
737 
738 	tep = *cookie;
739 	if (tep == NULL)
740 		tep = transfer_encoding_tbl;
741 
742 	*cookie = tep->name ? &tep[1] : NULL;
743 
744 	return tep->name;
745 }
746 
747 #endif /* MIME_SUPPORT */
748