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