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