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