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