xref: /netbsd-src/usr.bin/mail/mime_codecs.c (revision 77a1ad5f0039ea2bc6af76846debc20728bae498)
1*77a1ad5fSkamil /*	$NetBSD: mime_codecs.c,v 1.12 2019/10/24 18:18:00 kamil Exp $	*/
28207b28aSchristos 
38207b28aSchristos /*-
48207b28aSchristos  * Copyright (c) 2006 The NetBSD Foundation, Inc.
58207b28aSchristos  * All rights reserved.
68207b28aSchristos  *
78207b28aSchristos  * This code is derived from software contributed to The NetBSD Foundation
88207b28aSchristos  * by Anon Ymous.
98207b28aSchristos  *
108207b28aSchristos  * Redistribution and use in source and binary forms, with or without
118207b28aSchristos  * modification, are permitted provided that the following conditions
128207b28aSchristos  * are met:
138207b28aSchristos  * 1. Redistributions of source code must retain the above copyright
148207b28aSchristos  *    notice, this list of conditions and the following disclaimer.
158207b28aSchristos  * 2. Redistributions in binary form must reproduce the above copyright
168207b28aSchristos  *    notice, this list of conditions and the following disclaimer in the
178207b28aSchristos  *    documentation and/or other materials provided with the distribution.
188207b28aSchristos  *
198207b28aSchristos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
208207b28aSchristos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
218207b28aSchristos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
228207b28aSchristos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
238207b28aSchristos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
248207b28aSchristos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
258207b28aSchristos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
268207b28aSchristos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
278207b28aSchristos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
288207b28aSchristos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
298207b28aSchristos  * POSSIBILITY OF SUCH DAMAGE.
308207b28aSchristos  */
318207b28aSchristos 
328207b28aSchristos /*
338207b28aSchristos  * This module contains all mime related codecs.  Typically there are
348207b28aSchristos  * two versions: one operating on buffers and one operating on files.
358207b28aSchristos  * All exported routines have a "mime_" prefix.  The file oriented
368207b28aSchristos  * routines have a "mime_f" prefix replacing the "mime_" prefix of the
378207b28aSchristos  * equivalent buffer based version.
388207b28aSchristos  *
398207b28aSchristos  * The file based API should be:
408207b28aSchristos  *
418207b28aSchristos  *   mime_f<name>_{encode,decode}(FILE *in, FILE *out, void *cookie)
428207b28aSchristos  *
438207b28aSchristos  * XXX - currently this naming convention has not been adheared to.
448207b28aSchristos  *
458207b28aSchristos  * where the cookie is a generic way to pass arguments to the routine.
468207b28aSchristos  * This way these routines can be run by run_function() in mime.c.
478207b28aSchristos  *
488207b28aSchristos  * The buffer based API is not as rigid.
498207b28aSchristos  */
508207b28aSchristos 
518207b28aSchristos #ifdef MIME_SUPPORT
528207b28aSchristos 
538207b28aSchristos #include <sys/cdefs.h>
548207b28aSchristos #ifndef __lint__
55*77a1ad5fSkamil __RCSID("$NetBSD: mime_codecs.c,v 1.12 2019/10/24 18:18:00 kamil Exp $");
568207b28aSchristos #endif /* not __lint__ */
578207b28aSchristos 
588207b28aSchristos #include <assert.h>
598207b28aSchristos #include <iconv.h>
608207b28aSchristos #include <stdio.h>
618207b28aSchristos #include <stdlib.h>
628207b28aSchristos #include <util.h>
638207b28aSchristos 
648207b28aSchristos #include "def.h"
658207b28aSchristos #include "extern.h"
668207b28aSchristos #include "mime_codecs.h"
678207b28aSchristos 
688207b28aSchristos 
698207b28aSchristos #ifdef CHARSET_SUPPORT
708207b28aSchristos /************************************************************************
718207b28aSchristos  * Core character set conversion routines.
728207b28aSchristos  *
738207b28aSchristos  */
748207b28aSchristos 
758207b28aSchristos /*
768207b28aSchristos  * Fault-tolerant iconv() function.
778207b28aSchristos  *
788207b28aSchristos  * This routine was borrowed from nail-11.25/mime.c and modified.  It
798207b28aSchristos  * tries to handle errno == EILSEQ by restarting at the next input
808207b28aSchristos  * byte (is this a good idea?).  All other errors are handled by the
818207b28aSchristos  * caller.
828207b28aSchristos  */
838207b28aSchristos PUBLIC size_t
mime_iconv(iconv_t cd,const char ** inb,size_t * inbleft,char ** outb,size_t * outbleft)848207b28aSchristos mime_iconv(iconv_t cd, const char **inb, size_t *inbleft, char **outb, size_t *outbleft)
858207b28aSchristos {
868207b28aSchristos 	size_t sz = 0;
878207b28aSchristos 
88*77a1ad5fSkamil 	while ((sz = iconv(cd, __UNCONST(inb), inbleft, outb, outbleft))
89*77a1ad5fSkamil 		   == (size_t)-1
908207b28aSchristos 			&& errno == EILSEQ) {
918207b28aSchristos 		if (*outbleft > 0) {
928207b28aSchristos 			*(*outb)++ = '?';
938207b28aSchristos 			(*outbleft)--;
948207b28aSchristos 		} else {
958207b28aSchristos 			**outb = '\0';
968207b28aSchristos 			return E2BIG;
978207b28aSchristos 		}
988207b28aSchristos 		if (*inbleft > 0) {
998207b28aSchristos 			(*inb)++;
1008207b28aSchristos 			(*inbleft)--;
1018207b28aSchristos 		} else {
1028207b28aSchristos 			**outb = '\0';
1038207b28aSchristos 			break;
1048207b28aSchristos 		}
1058207b28aSchristos 	}
1068207b28aSchristos 	return sz;
1078207b28aSchristos }
1088207b28aSchristos 
1098207b28aSchristos /*
1108207b28aSchristos  * This routine was mostly borrowed from src/usr.bin/iconv/iconv.c.
1118207b28aSchristos  * We don't care about the invalid character count, so don't bother
1128207b28aSchristos  * with __iconv().  We do care about robustness, so call iconv_ft()
1138207b28aSchristos  * above to try to recover from errors.
1148207b28aSchristos  */
1158207b28aSchristos #define INBUFSIZE 1024
1168207b28aSchristos #define OUTBUFSIZE (INBUFSIZE * 2)
1178207b28aSchristos 
1188207b28aSchristos PUBLIC void
mime_ficonv(FILE * fi,FILE * fo,void * cookie)1198207b28aSchristos mime_ficonv(FILE *fi, FILE *fo, void *cookie)
1208207b28aSchristos {
1218207b28aSchristos 	char inbuf[INBUFSIZE], outbuf[OUTBUFSIZE], *out;
1228207b28aSchristos 	const char *in;
1238207b28aSchristos 	size_t inbytes, outbytes, ret;
1248207b28aSchristos 	iconv_t cd;
1258207b28aSchristos 
1268207b28aSchristos 	/*
1278207b28aSchristos 	 * NOTE: iconv_t is actually a pointer typedef, so this
1288207b28aSchristos 	 * conversion is not what it appears to be!
1298207b28aSchristos 	 */
1308207b28aSchristos 	cd = (iconv_t)cookie;
1318207b28aSchristos 
1328207b28aSchristos 	while ((inbytes = fread(inbuf, 1, INBUFSIZE, fi)) > 0) {
1338207b28aSchristos 		in = inbuf;
1348207b28aSchristos 		while (inbytes > 0) {
1358207b28aSchristos 			out = outbuf;
1368207b28aSchristos 			outbytes = OUTBUFSIZE;
1378207b28aSchristos 			ret = mime_iconv(cd, &in, &inbytes, &out, &outbytes);
1388207b28aSchristos 			if (ret == (size_t)-1 && errno != E2BIG) {
1398207b28aSchristos 				if (errno != EINVAL || in == inbuf) {
1408207b28aSchristos 					/* XXX - what is proper here?
1418207b28aSchristos 					 * Just copy out the remains? */
1428207b28aSchristos 					(void)fprintf(fo,
1438207b28aSchristos 					    "\n\t[ iconv truncated message: %s ]\n\n",
1448207b28aSchristos 					    strerror(errno));
1458207b28aSchristos 					return;
1468207b28aSchristos 				}
1478207b28aSchristos 				/*
1488207b28aSchristos 				 * If here: errno == EINVAL && in != inbuf
1498207b28aSchristos 				 */
1508207b28aSchristos 				/* incomplete input character */
1518207b28aSchristos 				(void)memmove(inbuf, in, inbytes);
1528207b28aSchristos 				ret = fread(inbuf + inbytes, 1,
1538207b28aSchristos 				    INBUFSIZE - inbytes, fi);
1548207b28aSchristos 				if (ret == 0) {
1558207b28aSchristos 					if (feof(fi)) {
1568207b28aSchristos 						(void)fprintf(fo,
1578207b28aSchristos 						    "\n\t[ unexpected end of file; "
1588207b28aSchristos 						    "the last character is "
1598207b28aSchristos 						    "incomplete. ]\n\n");
1608207b28aSchristos 						return;
1618207b28aSchristos 					}
1628207b28aSchristos 					(void)fprintf(fo,
1638207b28aSchristos 					    "\n\t[ fread(): %s ]\n\n",
1648207b28aSchristos 					    strerror(errno));
1658207b28aSchristos 					return;
1668207b28aSchristos 				}
1678207b28aSchristos 				in = inbuf;
1688207b28aSchristos 				inbytes += ret;
1698207b28aSchristos 
1708207b28aSchristos 			}
1718207b28aSchristos 			if (outbytes < OUTBUFSIZE)
1728207b28aSchristos 				(void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, fo);
1738207b28aSchristos 		}
1748207b28aSchristos 	}
1758207b28aSchristos 	/* reset the shift state of the output buffer */
1768207b28aSchristos 	outbytes = OUTBUFSIZE;
1778207b28aSchristos 	out = outbuf;
1788207b28aSchristos 	ret = iconv(cd, NULL, NULL, &out, &outbytes);
1798207b28aSchristos 	if (ret == (size_t)-1) {
1808207b28aSchristos 		(void)fprintf(fo, "\n\t[ iconv(): %s ]\n\n",
1818207b28aSchristos 		    strerror(errno));
1828207b28aSchristos 		return;
1838207b28aSchristos 	}
1848207b28aSchristos 	if (outbytes < OUTBUFSIZE)
1858207b28aSchristos 		(void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, fo);
1868207b28aSchristos }
1878207b28aSchristos 
1888207b28aSchristos #endif	/* CHARSET_SUPPORT */
1898207b28aSchristos 
1908207b28aSchristos 
1918207b28aSchristos 
1928207b28aSchristos /************************************************************************
1938207b28aSchristos  * Core base64 routines
1948207b28aSchristos  *
1958207b28aSchristos  * Defined in sec 6.8 of RFC 2045.
1968207b28aSchristos  */
1978207b28aSchristos 
1988207b28aSchristos /*
1998207b28aSchristos  * Decode a base64 buffer.
2008207b28aSchristos  *
2018207b28aSchristos  *   bin:  buffer to hold the decoded (binary) result (see note 1).
2028207b28aSchristos  *   b64:  buffer holding the encoded (base64) source.
2038207b28aSchristos  *   cnt:  number of bytes in the b64 buffer to decode (see note 2).
2048207b28aSchristos  *
2058207b28aSchristos  * Return: the number of bytes written to the 'bin' buffer or -1 on
2068207b28aSchristos  *         error.
2078207b28aSchristos  * NOTES:
2088207b28aSchristos  *   1) It is the callers responsibility to ensure that bin is large
2098207b28aSchristos  *      enough to hold the result.
2108207b28aSchristos  *   2) The b64 buffer should always contain a multiple of 4 bytes of
2118207b28aSchristos  *      data!
2128207b28aSchristos  */
2138207b28aSchristos PUBLIC ssize_t
mime_b64tobin(char * bin,const char * b64,size_t cnt)2148207b28aSchristos mime_b64tobin(char *bin, const char *b64, size_t cnt)
2158207b28aSchristos {
2168207b28aSchristos 	static const signed char b64index[] = {
2178207b28aSchristos 		-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
2188207b28aSchristos 		-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
2198207b28aSchristos 		-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
2208207b28aSchristos 		52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-2,-1,-1,
2218207b28aSchristos 		-1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
2228207b28aSchristos 		15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
2238207b28aSchristos 		-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
2248207b28aSchristos 		41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
2258207b28aSchristos 	};
2268207b28aSchristos 	unsigned char *p;
227f1aa39b3Schristos 	const unsigned char *q, *end;
2288207b28aSchristos 
2298207b28aSchristos #define EQU	(unsigned)-2
2308207b28aSchristos #define BAD	(unsigned)-1
231c172e3b9Slukem #define uchar64(c)  ((c) >= sizeof(b64index) ? BAD : (unsigned)b64index[(c)])
2328207b28aSchristos 
2338207b28aSchristos 	p = (unsigned char *)bin;
234f1aa39b3Schristos 	q = (const unsigned char *)b64;
235f1aa39b3Schristos 	for (end = q + cnt; q < end; q += 4) {
236f1aa39b3Schristos 		unsigned a = uchar64(q[0]);
237f1aa39b3Schristos 		unsigned b = uchar64(q[1]);
238f1aa39b3Schristos 		unsigned c = uchar64(q[2]);
239f1aa39b3Schristos 		unsigned d = uchar64(q[3]);
2408207b28aSchristos 
24179abd5ecSchristos 		if (a == BAD || a == EQU || b == BAD || b == EQU ||
24279abd5ecSchristos 		    c == BAD || d == BAD)
24379abd5ecSchristos 			return -1;
24479abd5ecSchristos 
2458207b28aSchristos 		*p++ = ((a << 2) | ((b & 0x30) >> 4));
2468207b28aSchristos 		if (c == EQU)	{ /* got '=' */
2478207b28aSchristos 			if (d != EQU)
2488207b28aSchristos 				return -1;
2498207b28aSchristos 			break;
2508207b28aSchristos 		}
2518207b28aSchristos 		*p++ = (((b & 0x0f) << 4) | ((c & 0x3c) >> 2));
2528207b28aSchristos 		if (d == EQU) { /* got '=' */
2538207b28aSchristos 			break;
2548207b28aSchristos 		}
2558207b28aSchristos 		*p++ = (((c & 0x03) << 6) | d);
2568207b28aSchristos 	}
2578207b28aSchristos 
258f1aa39b3Schristos #undef uchar64
2598207b28aSchristos #undef EQU
2608207b28aSchristos #undef BAD
2618207b28aSchristos 
2628207b28aSchristos 	return p - (unsigned char*)bin;
2638207b28aSchristos }
2648207b28aSchristos 
2658207b28aSchristos /*
2668207b28aSchristos  * Encode a buffer as a base64 result.
2678207b28aSchristos  *
2688207b28aSchristos  *   b64:  buffer to hold the encoded (base64) result (see note).
2698207b28aSchristos  *   bin:  buffer holding the binary source.
2708207b28aSchristos  *   cnt:  number of bytes in the bin buffer to encode.
2718207b28aSchristos  *
2728207b28aSchristos  * NOTE: it is the callers responsibility to ensure that 'b64' is
2738207b28aSchristos  *       large enough to hold the result.
2748207b28aSchristos  */
2758207b28aSchristos PUBLIC void
mime_bintob64(char * b64,const char * bin,size_t cnt)2768207b28aSchristos mime_bintob64(char *b64, const char *bin, size_t cnt)
2778207b28aSchristos {
2788207b28aSchristos 	static const char b64table[] =
2798207b28aSchristos 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2808207b28aSchristos 	const unsigned char *p = (const unsigned char*)bin;
281ca13337dSchristos 	ssize_t i;
2828207b28aSchristos 
2838207b28aSchristos 	for (i = cnt; i > 0; i -= 3) {
2848207b28aSchristos 		unsigned a = p[0];
2858207b28aSchristos 		unsigned b = p[1];
2868207b28aSchristos 		unsigned c = p[2];
2878207b28aSchristos 
2888207b28aSchristos 		b64[0] = b64table[a >> 2];
2898207b28aSchristos 		switch(i) {
2908207b28aSchristos 		case 1:
2918207b28aSchristos 			b64[1] = b64table[((a & 0x3) << 4)];
2928207b28aSchristos 			b64[2] = '=';
2938207b28aSchristos 			b64[3] = '=';
2948207b28aSchristos 			break;
2958207b28aSchristos 		case 2:
2968207b28aSchristos 			b64[1] = b64table[((a & 0x3) << 4) | ((b & 0xf0) >> 4)];
2978207b28aSchristos 			b64[2] = b64table[((b & 0xf) << 2)];
2988207b28aSchristos 			b64[3] = '=';
2998207b28aSchristos 			break;
3008207b28aSchristos 		default:
3018207b28aSchristos 			b64[1] = b64table[((a & 0x3) << 4) | ((b & 0xf0) >> 4)];
3028207b28aSchristos 			b64[2] = b64table[((b & 0xf) << 2) | ((c & 0xc0) >> 6)];
3038207b28aSchristos 			b64[3] = b64table[c & 0x3f];
3048207b28aSchristos 			break;
3058207b28aSchristos 		}
3068207b28aSchristos 		p   += 3;
3078207b28aSchristos 		b64 += 4;
3088207b28aSchristos 	}
3098207b28aSchristos }
3108207b28aSchristos 
3118207b28aSchristos 
3128207b28aSchristos #define MIME_BASE64_LINE_MAX	(4 * 19)  /* max line length is 76: see RFC2045 sec 6.8 */
3138207b28aSchristos 
3148207b28aSchristos static void
mime_fB64_encode(FILE * fi,FILE * fo,void * cookie __unused)3158207b28aSchristos mime_fB64_encode(FILE *fi, FILE *fo, void *cookie __unused)
3168207b28aSchristos {
3178207b28aSchristos 	static char b64[MIME_BASE64_LINE_MAX];
3188207b28aSchristos 	static char mem[3 * (MIME_BASE64_LINE_MAX / 4)];
319ca13337dSchristos 	size_t cnt;
3208207b28aSchristos 	char *cp;
3218207b28aSchristos 	size_t limit;
3228207b28aSchristos #ifdef __lint__
3238207b28aSchristos 	cookie = cookie;
3248207b28aSchristos #endif
3258207b28aSchristos 	limit = 0;
3268207b28aSchristos 	if ((cp = value(ENAME_MIME_B64_LINE_MAX)) != NULL)
3278207b28aSchristos 		limit = (size_t)atoi(cp);
3288207b28aSchristos 	if (limit == 0 || limit > sizeof(b64))
3298207b28aSchristos 		limit = sizeof(b64);
3308207b28aSchristos 
3318207b28aSchristos 	limit = 3 * roundup(limit, 4) / 4;
3328207b28aSchristos 	if (limit < 3)
3338207b28aSchristos 		limit = 3;
3348207b28aSchristos 
3358207b28aSchristos 	while ((cnt = fread(mem, sizeof(*mem), limit, fi)) > 0) {
3368207b28aSchristos 		mime_bintob64(b64, mem, (size_t)cnt);
3378207b28aSchristos 		(void)fwrite(b64, sizeof(*b64), (size_t)4 * roundup(cnt, 3) / 3, fo);
3388207b28aSchristos 		(void)putc('\n', fo);
3398207b28aSchristos 	}
3408207b28aSchristos }
3418207b28aSchristos 
3428207b28aSchristos static void
mime_fB64_decode(FILE * fi,FILE * fo,void * add_lf)343933195acSchristos mime_fB64_decode(FILE *fi, FILE *fo, void *add_lf)
3448207b28aSchristos {
3458207b28aSchristos 	char *line;
3468207b28aSchristos 	size_t len;
3478207b28aSchristos 	char *buf;
3488207b28aSchristos 	size_t buflen;
3498207b28aSchristos 
3508207b28aSchristos 	buflen = 3 * (MIME_BASE64_LINE_MAX / 4);
3518207b28aSchristos 	buf = emalloc(buflen);
3528207b28aSchristos 
3538207b28aSchristos 	while ((line = fgetln(fi, &len)) != NULL) {
3548207b28aSchristos 		ssize_t binlen;
3558207b28aSchristos 		if (line[len-1] == '\n') /* forget the trailing newline */
3568207b28aSchristos 			len--;
3578207b28aSchristos 
3588207b28aSchristos 		/* trash trailing white space */
359d727506fSchristos 		for (/*EMPTY*/; len > 0 && is_WSP(line[len-1]); len--)
3608207b28aSchristos 			continue;
3618207b28aSchristos 
3628207b28aSchristos 		/* skip leading white space */
363d727506fSchristos 		for (/*EMPTY*/; len > 0 && is_WSP(line[0]); len--, line++)
3648207b28aSchristos 			continue;
3658207b28aSchristos 
3668207b28aSchristos 		if (len == 0)
3678207b28aSchristos 			break;
3688207b28aSchristos 
3698207b28aSchristos 		if (3 * len > 4 * buflen) {
3708207b28aSchristos 			buflen *= 2;
3718207b28aSchristos 			buf = erealloc(buf, buflen);
3728207b28aSchristos 		}
3738207b28aSchristos 
3748207b28aSchristos 		binlen = mime_b64tobin(buf, line, len);
3758207b28aSchristos 
3768207b28aSchristos 		if (binlen <= 0) {
3778207b28aSchristos 			(void)fprintf(fo, "WARN: invalid base64 encoding\n");
3788207b28aSchristos 			break;
3798207b28aSchristos 		}
3808207b28aSchristos 		(void)fwrite(buf, 1, (size_t)binlen, fo);
3818207b28aSchristos 	}
3828207b28aSchristos 
3838207b28aSchristos 	free(buf);
3848207b28aSchristos 
385933195acSchristos 	if (add_lf)
3868207b28aSchristos 		(void)fputc('\n', fo);
3878207b28aSchristos }
3888207b28aSchristos 
3898207b28aSchristos 
3908207b28aSchristos /************************************************************************
3918207b28aSchristos  * Core quoted-printable routines.
3928207b28aSchristos  *
393ba2b5111Schristos  * Defined in sec 6.7 of RFC 2045.
3948207b28aSchristos  */
3958207b28aSchristos 
396ba2b5111Schristos /*
397ba2b5111Schristos  * strtol(3), but inline and with easy error indication.
398ba2b5111Schristos  */
399ba2b5111Schristos static inline int
_qp_cfromhex(char const * hex)400ba2b5111Schristos _qp_cfromhex(char const *hex)
401ba2b5111Schristos {
402ba2b5111Schristos 	/* Be robust, allow lowercase hexadecimal letters, too */
403ba2b5111Schristos 	static unsigned char const atoi16[] = {
404ba2b5111Schristos 		0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */
405ba2b5111Schristos 		0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */
406ba2b5111Schristos 		0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */
407ba2b5111Schristos 		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */
408ba2b5111Schristos 		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */
409ba2b5111Schristos 		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */
410ba2b5111Schristos 		0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF  /* 0x60-0x67 */
411ba2b5111Schristos 	};
412ba2b5111Schristos 	unsigned char i1, i2;
413ba2b5111Schristos 	int r;
414ba2b5111Schristos 
415ba2b5111Schristos 	if ((i1 = (unsigned char)hex[0] - '0') >= __arraycount(atoi16) ||
416ba2b5111Schristos 	    (i2 = (unsigned char)hex[1] - '0') >= __arraycount(atoi16))
417ba2b5111Schristos 		goto jerr;
418ba2b5111Schristos 	i1 = atoi16[i1];
419ba2b5111Schristos 	i2 = atoi16[i2];
420ba2b5111Schristos 	if ((i1 | i2) & 0xF0)
421ba2b5111Schristos 		goto jerr;
422ba2b5111Schristos 	r = i1;
423ba2b5111Schristos 	r <<= 4;
424ba2b5111Schristos 	r += i2;
425ba2b5111Schristos jleave:
426ba2b5111Schristos 	return r;
427ba2b5111Schristos jerr:
428ba2b5111Schristos 	r = -1;
429ba2b5111Schristos 	goto jleave;
430ba2b5111Schristos }
431ba2b5111Schristos 
432ba2b5111Schristos /*
433ba2b5111Schristos  * Header specific "quoted-printable" decode!
434ba2b5111Schristos  * Differences with body QP decoding (see rfc 2047, sec 4.2):
435ba2b5111Schristos  * 1) '=' occurs _only_ when followed by two hex digits (FWS is not allowed).
436ba2b5111Schristos  * 2) Spaces can be encoded as '_' in headers for readability.
437ba2b5111Schristos  */
438ba2b5111Schristos static ssize_t
mime_QPh_decode(char * outbuf,size_t outlen,const char * inbuf,size_t inlen)439ba2b5111Schristos mime_QPh_decode(char *outbuf, size_t outlen, const char *inbuf, size_t inlen)
440ba2b5111Schristos {
441ba2b5111Schristos 	const char *p, *inend;
442ba2b5111Schristos 	char *outend;
443ba2b5111Schristos 	char *q;
444ba2b5111Schristos 
445ba2b5111Schristos 	outend = outbuf + outlen;
446ba2b5111Schristos 	inend = inbuf + inlen;
447ba2b5111Schristos 	q = outbuf;
448ba2b5111Schristos 	for (p = inbuf; p < inend; p++) {
449ba2b5111Schristos 		if (q >= outend)
450ba2b5111Schristos 			return -1;
451ba2b5111Schristos 		if (*p == '=') {
452ba2b5111Schristos 			p++;
453ba2b5111Schristos 			if (p + 1 < inend) {
454ba2b5111Schristos 				int c = _qp_cfromhex(p++);
455ba2b5111Schristos 				if (c < 0)
456ba2b5111Schristos 					return -1;
457ba2b5111Schristos 				*q++ = (char)c;
458ba2b5111Schristos 			}
459ba2b5111Schristos 			else
460ba2b5111Schristos 				return -1;
461ba2b5111Schristos 		}
462ba2b5111Schristos 		else if (*p == '_')  /* header's may encode ' ' as '_' */
463ba2b5111Schristos 			*q++ = ' ';
464ba2b5111Schristos 		else
465ba2b5111Schristos 			*q++ = *p;
466ba2b5111Schristos 	}
467ba2b5111Schristos 	return q - outbuf;
468ba2b5111Schristos }
469ba2b5111Schristos 
470ba2b5111Schristos 
4718207b28aSchristos static int
mustquote(unsigned char * p,unsigned char * end,size_t l)4728207b28aSchristos mustquote(unsigned char *p, unsigned char *end, size_t l)
4738207b28aSchristos {
4748207b28aSchristos #define N	0	/* do not quote */
4758207b28aSchristos #define Q	1	/* must quote */
4768207b28aSchristos #define SP	2	/* white space */
4778207b28aSchristos #define XF	3	/* special character 'F' - maybe quoted */
4788207b28aSchristos #define XD	4	/* special character '.' - maybe quoted */
4798207b28aSchristos #define EQ	Q	/* '=' must be quoted */
4808207b28aSchristos #define TB	SP	/* treat '\t' as a space */
4818207b28aSchristos #define NL	N	/* don't quote '\n' (NL) - XXX - quoting here breaks the line length algorithm */
4828207b28aSchristos #define CR	Q	/* always quote a '\r' (CR) - it occurs only in a CRLF combo */
4838207b28aSchristos 
4848207b28aSchristos 	static const signed char quotetab[] = {
4858207b28aSchristos   		 Q, Q, Q, Q,  Q, Q, Q, Q,  Q,TB,NL, Q,  Q,CR, Q, Q,
4868207b28aSchristos 		 Q, Q, Q, Q,  Q, Q, Q, Q,  Q, Q, Q, Q,  Q, Q, Q, Q,
4878207b28aSchristos 		SP, N, N, N,  N, N, N, N,  N, N, N, N,  N, N,XD, N,
4888207b28aSchristos 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N,EQ, N, N,
4898207b28aSchristos 
4908207b28aSchristos 		 N, N, N, N,  N, N,XF, N,  N, N, N, N,  N, N, N, N,
4918207b28aSchristos 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, N,
4928207b28aSchristos 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, N,
4938207b28aSchristos 		 N, N, N, N,  N, N, N, N,  N, N, N, N,  N, N, N, Q,
4948207b28aSchristos 	};
4958207b28aSchristos 	int flag = *p > 0x7f ? Q : quotetab[*p];
4968207b28aSchristos 
4978207b28aSchristos 	if (flag == N)
4988207b28aSchristos 		return 0;
4998207b28aSchristos 	if (flag == Q)
5008207b28aSchristos 		return 1;
5018207b28aSchristos 	if (flag == SP)
502f3098750Schristos 		return p + 1 < end && p[1] == '\n'; /* trailing white space */
5038207b28aSchristos 
5048207b28aSchristos 	/* The remainder are special start-of-line cases. */
5058207b28aSchristos 	if (l != 0)
5068207b28aSchristos 		return 0;
5078207b28aSchristos 
5088207b28aSchristos 	if (flag == XF)	/* line may start with "From" */
509f3098750Schristos 		return p + 4 < end && p[1] == 'r' && p[2] == 'o' && p[3] == 'm';
5108207b28aSchristos 
5118207b28aSchristos 	if (flag == XD)	/* line may consist of a single dot */
512f3098750Schristos 		return p + 1 < end && p[1] == '\n';
5138207b28aSchristos 
514f3098750Schristos 	errx(EXIT_FAILURE,
515f3098750Schristos 	    "mustquote: invalid logic: *p=0x%x (%d) flag=%d, l=%zu\n",
5168207b28aSchristos 	    *p, *p, flag, l);
5178207b28aSchristos 	/* NOT REACHED */
5188207b28aSchristos 	return 0;	/* appease GCC */
5198207b28aSchristos 
5208207b28aSchristos #undef N
5218207b28aSchristos #undef Q
5228207b28aSchristos #undef SP
5238207b28aSchristos #undef XX
5248207b28aSchristos #undef EQ
5258207b28aSchristos #undef TB
5268207b28aSchristos #undef NL
5278207b28aSchristos #undef CR
5288207b28aSchristos }
5298207b28aSchristos 
5308207b28aSchristos 
5318207b28aSchristos #define MIME_QUOTED_LINE_MAX	76  /* QP max length: see RFC2045 sec 6.7 */
5328207b28aSchristos 
5338207b28aSchristos static void
fput_quoted_line(FILE * fo,char * line,size_t len,size_t limit)5348207b28aSchristos fput_quoted_line(FILE *fo, char *line, size_t len, size_t limit)
5358207b28aSchristos {
5368207b28aSchristos 	size_t l;	/* length of current output line */
5378207b28aSchristos 	unsigned char *beg;
5388207b28aSchristos 	unsigned char *end;
5398207b28aSchristos 	unsigned char *p;
5408207b28aSchristos 
5418207b28aSchristos 	assert(limit <= MIME_QUOTED_LINE_MAX);
5428207b28aSchristos 
5438207b28aSchristos 	beg = (unsigned char*)line;
5448207b28aSchristos 	end = beg + len;
5458207b28aSchristos 	l = 0;
5468207b28aSchristos 	for (p = (unsigned char*)line; p < end; p++) {
5478207b28aSchristos 		if (mustquote(p, end, l)) {
5488207b28aSchristos 			if (l + 4 > limit) {
5498207b28aSchristos 				(void)fputs("=\n", fo);
5508207b28aSchristos 				l = 0;
5518207b28aSchristos 			}
5528207b28aSchristos 			(void)fprintf(fo, "=%02X", *p);
5538207b28aSchristos 			l += 3;
5548207b28aSchristos 		}
5558207b28aSchristos 		else {
5568207b28aSchristos 			if (*p == '\n') {
557ba2b5111Schristos 				if (p > beg && p[-1] == '\r') {
558ba2b5111Schristos 					if (l + 4 > limit)
559ba2b5111Schristos 						(void)fputs("=\n", fo);
5608207b28aSchristos 					(void)fputs("=0A=", fo);
561ba2b5111Schristos 				}
5628207b28aSchristos 				l = (size_t)-1;
5638207b28aSchristos 			}
5648207b28aSchristos 			else if (l + 2 > limit) {
5658207b28aSchristos 				(void)fputs("=\n", fo);
5668207b28aSchristos 				l = 0;
5678207b28aSchristos 			}
5688207b28aSchristos 			(void)putc(*p, fo);
5698207b28aSchristos 			l++;
5708207b28aSchristos 		}
5718207b28aSchristos 	}
5728207b28aSchristos 	/*
5738207b28aSchristos 	 * Lines ending in a blank must escape the newline.
5748207b28aSchristos 	 */
575d727506fSchristos 	if (len && is_WSP(p[-1]))
5768207b28aSchristos 		(void)fputs("=\n", fo);
5778207b28aSchristos }
5788207b28aSchristos 
5798207b28aSchristos static void
mime_fQP_encode(FILE * fi,FILE * fo,void * cookie __unused)5808207b28aSchristos mime_fQP_encode(FILE *fi, FILE *fo, void *cookie __unused)
5818207b28aSchristos {
5828207b28aSchristos 	char *line;
5838207b28aSchristos 	size_t len;
5848207b28aSchristos 	char *cp;
5858207b28aSchristos 	size_t limit;
5868207b28aSchristos 
5878207b28aSchristos #ifdef __lint__
5888207b28aSchristos 	cookie = cookie;
5898207b28aSchristos #endif
5908207b28aSchristos 	limit = 0;
5918207b28aSchristos 	if ((cp = value(ENAME_MIME_QP_LINE_MAX)) != NULL)
5928207b28aSchristos 		limit = (size_t)atoi(cp);
5938207b28aSchristos 	if (limit == 0 || limit > MIME_QUOTED_LINE_MAX)
5948207b28aSchristos 		limit = MIME_QUOTED_LINE_MAX;
5958207b28aSchristos 	if (limit < 4)
5968207b28aSchristos 		limit = 4;
5978207b28aSchristos 
5988207b28aSchristos 	while ((line = fgetln(fi, &len)) != NULL)
5998207b28aSchristos 		fput_quoted_line(fo, line, len, limit);
6008207b28aSchristos }
6018207b28aSchristos 
6028207b28aSchristos static void
mime_fQP_decode(FILE * fi,FILE * fo,void * cookie __unused)6038207b28aSchristos mime_fQP_decode(FILE *fi, FILE *fo, void *cookie __unused)
6048207b28aSchristos {
6058207b28aSchristos 	char *line;
6068207b28aSchristos 	size_t len;
6078207b28aSchristos 
6088207b28aSchristos #ifdef __lint__
6098207b28aSchristos 	cookie = cookie;
6108207b28aSchristos #endif
6118207b28aSchristos 	while ((line = fgetln(fi, &len)) != NULL) {
6128207b28aSchristos 		char *p;
6138207b28aSchristos 		char *end;
614ca13337dSchristos 
6158207b28aSchristos 		end = line + len;
6168207b28aSchristos 		for (p = line; p < end; p++) {
6178207b28aSchristos 			if (*p == '=') {
6188207b28aSchristos 				p++;
619d727506fSchristos 				while (p < end && is_WSP(*p))
6208207b28aSchristos 					p++;
6218207b28aSchristos 				if (*p != '\n' && p + 1 < end) {
622ba2b5111Schristos 					int c = _qp_cfromhex(p++);
623ba2b5111Schristos 					if (c >= 0)
6248207b28aSchristos 						(void)fputc(c, fo);
625ba2b5111Schristos 					else
626ba2b5111Schristos 						(void)fputs("[?]", fo);
6278207b28aSchristos 				}
6288207b28aSchristos 			}
6298207b28aSchristos 			else
6308207b28aSchristos 				(void)fputc(*p, fo);
6318207b28aSchristos 		}
6328207b28aSchristos 	}
6338207b28aSchristos }
6348207b28aSchristos 
6358207b28aSchristos 
6368207b28aSchristos /************************************************************************
6378207b28aSchristos  * Routines to select the codec by name.
6388207b28aSchristos  */
6398207b28aSchristos 
6408207b28aSchristos PUBLIC void
mime_fio_copy(FILE * fi,FILE * fo,void * cookie __unused)6418207b28aSchristos mime_fio_copy(FILE *fi, FILE *fo, void *cookie __unused)
6428207b28aSchristos {
6438207b28aSchristos 	int c;
6448207b28aSchristos 
6458207b28aSchristos #ifdef __lint__
6468207b28aSchristos 	cookie = cookie;
6478207b28aSchristos #endif
6488207b28aSchristos 	while ((c = getc(fi)) != EOF)
6498207b28aSchristos 		(void)putc(c, fo);
6508207b28aSchristos 
6518207b28aSchristos 	(void)fflush(fo);
6528207b28aSchristos 	if (ferror(fi)) {
6538207b28aSchristos 		warn("read");
6548207b28aSchristos 		rewind(fi);
6558207b28aSchristos 		return;
6568207b28aSchristos 	}
6578207b28aSchristos 	if (ferror(fo)) {
6588207b28aSchristos 		warn("write");
6598207b28aSchristos 		(void)Fclose(fo);
6608207b28aSchristos 		rewind(fi);
6618207b28aSchristos 		return;
6628207b28aSchristos 	}
6638207b28aSchristos }
6648207b28aSchristos 
6658207b28aSchristos 
6668207b28aSchristos static const struct transfer_encoding_s {
6678207b28aSchristos 	const char 	*name;
6688207b28aSchristos 	mime_codec_t	enc;
6698207b28aSchristos 	mime_codec_t	dec;
6708207b28aSchristos } transfer_encoding_tbl[] = {
6718207b28aSchristos 	{ MIME_TRANSFER_7BIT,	mime_fio_copy,	    mime_fio_copy },
6728207b28aSchristos 	{ MIME_TRANSFER_8BIT, 	mime_fio_copy,	    mime_fio_copy },
6738207b28aSchristos 	{ MIME_TRANSFER_BINARY,	mime_fio_copy,	    mime_fio_copy },
6748207b28aSchristos 	{ MIME_TRANSFER_QUOTED, mime_fQP_encode,    mime_fQP_decode },
6758207b28aSchristos 	{ MIME_TRANSFER_BASE64, mime_fB64_encode,   mime_fB64_decode },
6768207b28aSchristos 	{ NULL,			NULL,		    NULL },
6778207b28aSchristos };
6788207b28aSchristos 
6798207b28aSchristos 
6808207b28aSchristos PUBLIC mime_codec_t
mime_fio_encoder(const char * ename)6818207b28aSchristos mime_fio_encoder(const char *ename)
6828207b28aSchristos {
6838207b28aSchristos 	const struct transfer_encoding_s *tep = NULL;
6848207b28aSchristos 
6858207b28aSchristos 	if (ename == NULL)
6868207b28aSchristos 		return NULL;
6878207b28aSchristos 
6888207b28aSchristos 	for (tep = transfer_encoding_tbl; tep->name; tep++)
6898207b28aSchristos 		if (strcasecmp(tep->name, ename) == 0)
6908207b28aSchristos 			break;
6918207b28aSchristos 	return tep->enc;
6928207b28aSchristos }
6938207b28aSchristos 
6948207b28aSchristos PUBLIC mime_codec_t
mime_fio_decoder(const char * ename)6958207b28aSchristos mime_fio_decoder(const char *ename)
6968207b28aSchristos {
6978207b28aSchristos 	const struct transfer_encoding_s *tep = NULL;
6988207b28aSchristos 
6998207b28aSchristos 	if (ename == NULL)
7008207b28aSchristos 		return NULL;
7018207b28aSchristos 
7028207b28aSchristos 	for (tep = transfer_encoding_tbl; tep->name; tep++)
7038207b28aSchristos 		if (strcasecmp(tep->name, ename) == 0)
7048207b28aSchristos 			break;
7058207b28aSchristos 	return tep->dec;
7068207b28aSchristos }
7078207b28aSchristos 
7088207b28aSchristos /*
709ba2b5111Schristos  * Decode a RFC 2047 extended message header *encoded-word*.
710ba2b5111Schristos  * *encoding* is the corresponding character of the *encoded-word*.
711ba2b5111Schristos  */
712ba2b5111Schristos PUBLIC ssize_t
mime_rfc2047_decode(char encoding,char * outbuf,size_t outlen,const char * inbuf,size_t inlen)713ba2b5111Schristos mime_rfc2047_decode(char encoding, char *outbuf, size_t outlen,
714ba2b5111Schristos 	const char *inbuf, size_t inlen)
715ba2b5111Schristos {
716ba2b5111Schristos 	ssize_t declen = -1;
717ba2b5111Schristos 
718ba2b5111Schristos 	if (encoding == 'B' || encoding == 'b') {
719ba2b5111Schristos 		if (outlen >= 3 * roundup(inlen, 4) / 4)
720ba2b5111Schristos 			declen = mime_b64tobin(outbuf, inbuf, inlen);
721ba2b5111Schristos 	} else if (encoding == 'Q' || encoding == 'q')
722ba2b5111Schristos 		declen = mime_QPh_decode(outbuf, outlen, inbuf, inlen);
723ba2b5111Schristos 	return declen;
724ba2b5111Schristos }
725ba2b5111Schristos 
726ba2b5111Schristos /*
7278207b28aSchristos  * This is for use in complete.c and mime.c to get the list of
7288207b28aSchristos  * encoding names without exposing the transfer_encoding_tbl[].  The
7298207b28aSchristos  * first name is returned if called with a pointer to a NULL pointer.
7308207b28aSchristos  * Subsequent calls with the same cookie give successive names.  A
7318207b28aSchristos  * NULL return indicates the end of the list.
7328207b28aSchristos  */
7338207b28aSchristos PUBLIC const char *
mime_next_encoding_name(const void ** cookie)7348207b28aSchristos mime_next_encoding_name(const void **cookie)
7358207b28aSchristos {
7368207b28aSchristos 	const struct transfer_encoding_s *tep;
7378207b28aSchristos 
7388207b28aSchristos 	tep = *cookie;
7398207b28aSchristos 	if (tep == NULL)
7408207b28aSchristos 		tep = transfer_encoding_tbl;
7418207b28aSchristos 
7428207b28aSchristos 	*cookie = tep->name ? &tep[1] : NULL;
7438207b28aSchristos 
7448207b28aSchristos 	return tep->name;
7458207b28aSchristos }
7468207b28aSchristos 
7478207b28aSchristos #endif /* MIME_SUPPORT */
748