Home | History | Annotate | Line # | Download | only in mail
mime_header.c revision 1.8
      1  1.8  christos /*	$NetBSD: mime_header.c,v 1.8 2009/04/10 13:08:25 christos 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 /*
     34  1.1  christos  * This module contains the core MIME header decoding routines.
     35  1.1  christos  * Please refer to RFC 2047 and RFC 2822.
     36  1.1  christos  */
     37  1.1  christos 
     38  1.1  christos #ifdef MIME_SUPPORT
     39  1.1  christos 
     40  1.1  christos #include <sys/cdefs.h>
     41  1.1  christos #ifndef __lint__
     42  1.8  christos __RCSID("$NetBSD: mime_header.c,v 1.8 2009/04/10 13:08:25 christos Exp $");
     43  1.1  christos #endif /* not __lint__ */
     44  1.1  christos 
     45  1.8  christos #include <assert.h>
     46  1.1  christos #include <stdio.h>
     47  1.1  christos #include <stdlib.h>
     48  1.1  christos #include <string.h>
     49  1.1  christos 
     50  1.1  christos #include "def.h"
     51  1.1  christos #include "extern.h"
     52  1.1  christos #include "mime.h"
     53  1.1  christos #include "mime_header.h"
     54  1.1  christos #include "mime_codecs.h"
     55  1.1  christos 
     56  1.1  christos /*
     57  1.1  christos  * Our interface to mime_b64tobin()
     58  1.1  christos  *
     59  1.1  christos  * XXX - This should move to mime_codecs.c.
     60  1.1  christos  */
     61  1.1  christos static ssize_t
     62  1.1  christos mime_B64_decode(char *outbuf, size_t outlen, const char *inbuf, size_t inlen)
     63  1.1  christos {
     64  1.1  christos 	if (outlen < 3 * roundup(inlen, 4) / 4)
     65  1.1  christos 		return -1;
     66  1.1  christos 
     67  1.1  christos 	return mime_b64tobin(outbuf, inbuf, inlen);
     68  1.1  christos }
     69  1.1  christos 
     70  1.1  christos 
     71  1.1  christos /*
     72  1.1  christos  * Header specific "quoted-printable" decode!
     73  1.1  christos  * Differences with body QP decoding (see rfc 2047, sec 4.2):
     74  1.1  christos  * 1) '=' occurs _only_ when followed by two hex digits (FWS is not allowed).
     75  1.1  christos  * 2) Spaces can be encoded as '_' in headers for readability.
     76  1.1  christos  *
     77  1.1  christos  * XXX - This should move to mime_codecs.c.
     78  1.1  christos  */
     79  1.1  christos static ssize_t
     80  1.1  christos mime_QPh_decode(char *outbuf, size_t outlen, const char *inbuf, size_t inlen)
     81  1.1  christos {
     82  1.1  christos 	const char *p, *inend;
     83  1.1  christos 	char *outend;
     84  1.1  christos 	char *q;
     85  1.1  christos 
     86  1.1  christos 	outend = outbuf + outlen;
     87  1.1  christos 	inend = inbuf + inlen;
     88  1.1  christos 	q = outbuf;
     89  1.1  christos 	for (p = inbuf; p < inend; p++) {
     90  1.1  christos 		if (q >= outend)
     91  1.1  christos 			return -1;
     92  1.1  christos 		if (*p == '=') {
     93  1.1  christos 			p++;
     94  1.1  christos 			if (p + 1 < inend) {
     95  1.8  christos 				size_t c;
     96  1.1  christos 				char *bufend;
     97  1.1  christos 				char buf[3];
     98  1.8  christos 
     99  1.1  christos 				buf[0] = *p++;
    100  1.1  christos 				buf[1] = *p;
    101  1.1  christos 				buf[2] = '\0';
    102  1.1  christos 				c = strtol(buf, &bufend, 16);
    103  1.1  christos 				if (bufend != &buf[2])
    104  1.1  christos 					return -1;
    105  1.8  christos 				*q++ = (char)c;
    106  1.1  christos 			}
    107  1.1  christos 			else
    108  1.1  christos 				return -1;
    109  1.1  christos 		}
    110  1.1  christos 		else if (*p == '_')  /* header's may encode ' ' as '_' */
    111  1.1  christos 			*q++ = ' ';
    112  1.1  christos 		else
    113  1.1  christos 			*q++ = *p;
    114  1.1  christos 	}
    115  1.1  christos 	return q - outbuf;
    116  1.1  christos }
    117  1.1  christos 
    118  1.1  christos static const char *
    119  1.1  christos grab_charset(char *from_cs, size_t from_cs_len, const char *p)
    120  1.1  christos {
    121  1.1  christos 	char *q;
    122  1.1  christos 	q = from_cs;
    123  1.1  christos 	for (/*EMPTY*/; *p != '?'; p++) {
    124  1.1  christos 		if (*p == '\0' || q >= from_cs + from_cs_len - 1)
    125  1.1  christos 			return NULL;
    126  1.1  christos 		*q++ = *p;
    127  1.1  christos 	}
    128  1.1  christos 	*q = '\0';
    129  1.1  christos 	return ++p;	/* if here, then we got the '?' */
    130  1.1  christos }
    131  1.1  christos 
    132  1.1  christos /*
    133  1.1  christos  * An encoded word is a string of at most 75 non-white space
    134  1.1  christos  * characters of the following form:
    135  1.1  christos  *
    136  1.1  christos  *  =?charset?X?encoding?=
    137  1.1  christos  *
    138  1.1  christos  * where:
    139  1.1  christos  *   'charset'	is the original character set of the unencoded string.
    140  1.1  christos  *
    141  1.1  christos  *   'X'	is the encoding type 'B' or 'Q' for "base64" or
    142  1.1  christos  *              "quoted-printable", respectively,
    143  1.1  christos  *   'encoding'	is the encoded string.
    144  1.1  christos  *
    145  1.1  christos  * Both 'charset' and 'X' are case independent and 'encoding' cannot
    146  1.1  christos  * contain any whitespace or '?' characters.  The 'encoding' must also
    147  1.1  christos  * be fully contained within the encoded words, i.e., it cannot be
    148  1.1  christos  * split between encoded words.
    149  1.1  christos  *
    150  1.1  christos  * Note: the 'B' encoding is a slightly modified "quoted-printable"
    151  1.1  christos  * encoding.  In particular, spaces (' ') may be encoded as '_' to
    152  1.1  christos  * improve undecoded readability.
    153  1.1  christos  */
    154  1.1  christos static int
    155  1.1  christos decode_word(const char **ibuf, char **obuf, char *oend, const char *to_cs)
    156  1.1  christos {
    157  1.1  christos 	ssize_t declen;
    158  1.1  christos 	size_t enclen, dstlen;
    159  1.1  christos 	char decword[LINESIZE];
    160  1.1  christos 	char from_cs[LINESIZE];
    161  1.1  christos 	const char *encword, *iend, *p;
    162  1.1  christos 	char *dstend;
    163  1.1  christos 	char enctype;
    164  1.1  christos 
    165  1.1  christos 	p = *ibuf;
    166  1.1  christos 	if (p[0] != '=' && p[1] != '?')
    167  1.1  christos 		return -1;
    168  1.1  christos 	if (strlen(p) <  2 + 1 + 3 + 1 + 2)
    169  1.1  christos 		return -1;
    170  1.1  christos 	p = grab_charset(from_cs, sizeof(from_cs), p + 2);
    171  1.1  christos 	if (p == NULL)
    172  1.1  christos 		return -1;
    173  1.1  christos 	enctype = *p++;
    174  1.1  christos 	if (*p++ != '?')
    175  1.1  christos 		return -1;
    176  1.1  christos 	encword = p;
    177  1.1  christos 	p = strchr(p, '?');
    178  1.1  christos 	if (p == NULL || p[1] != '=')
    179  1.1  christos 		return -1;
    180  1.1  christos 	enclen = p - encword;	/* length of encoded substring */
    181  1.1  christos 	iend = p + 2;
    182  1.1  christos 	/* encoded words are at most 75 characters (RFC 2047, sec 2) */
    183  1.1  christos 	if (iend > *ibuf + 75)
    184  1.1  christos 		return -1;
    185  1.1  christos 
    186  1.8  christos 	if (oend < *obuf + 1) {
    187  1.8  christos 		assert(/*CONSTCOND*/ 0);	/* We have a coding error! */
    188  1.8  christos 		return -1;
    189  1.8  christos 	}
    190  1.1  christos 	dstend = to_cs ? decword : *obuf;
    191  1.7     lukem 	dstlen = (to_cs ? sizeof(decword) : (size_t)(oend - *obuf)) - 1;
    192  1.1  christos 
    193  1.1  christos 	if (enctype == 'B' || enctype == 'b')
    194  1.1  christos 		declen = mime_B64_decode(dstend, dstlen, encword, enclen);
    195  1.1  christos 	else if (enctype == 'Q' || enctype == 'q')
    196  1.1  christos 		declen = mime_QPh_decode(dstend, dstlen, encword, enclen);
    197  1.1  christos 	else
    198  1.1  christos 		return -1;
    199  1.1  christos 
    200  1.1  christos 	if (declen == -1)
    201  1.1  christos 		return -1;
    202  1.1  christos 
    203  1.1  christos 	dstend += declen;
    204  1.1  christos #ifdef CHARSET_SUPPORT
    205  1.1  christos 	if (to_cs != NULL) {
    206  1.1  christos 		iconv_t cd;
    207  1.1  christos 		const char *src;
    208  1.1  christos 		size_t srclen;
    209  1.1  christos 		size_t cnt;
    210  1.1  christos 
    211  1.1  christos 		cd = iconv_open(to_cs, from_cs);
    212  1.1  christos 		if (cd == (iconv_t)-1)
    213  1.1  christos 			return -1;
    214  1.1  christos 
    215  1.1  christos 		src = decword;
    216  1.1  christos 		srclen = declen;
    217  1.1  christos 		dstend = *obuf;
    218  1.1  christos 		dstlen = oend - *obuf - 1;
    219  1.1  christos 		cnt = mime_iconv(cd, &src, &srclen, &dstend, &dstlen);
    220  1.4  christos 
    221  1.1  christos 		(void)iconv_close(cd);
    222  1.1  christos 		if (cnt == (size_t)-1)
    223  1.1  christos 			return -1;
    224  1.1  christos 	}
    225  1.1  christos #endif /* CHARSET_SUPPORT */
    226  1.1  christos 	*dstend = '\0';
    227  1.1  christos 	*ibuf = iend;
    228  1.1  christos 	*obuf = dstend;
    229  1.1  christos 	return 0;
    230  1.1  christos }
    231  1.1  christos 
    232  1.1  christos 
    233  1.1  christos /*
    234  1.1  christos  * Folding White Space.  See RFC 2822.
    235  1.4  christos  *
    236  1.4  christos  * Note: RFC 2822 specifies that '\n' and '\r' only occur as CRLF
    237  1.4  christos  * pairs (i.e., "\r\n") and never separately.  However, by the time
    238  1.4  christos  * mail(1) sees the messages, all CRLF pairs have been converted to
    239  1.4  christos  * '\n' characters.
    240  1.4  christos  *
    241  1.4  christos  * XXX - pull is_FWS() and skip_FWS() up to def.h?
    242  1.1  christos  */
    243  1.1  christos static inline int
    244  1.1  christos is_FWS(int c)
    245  1.1  christos {
    246  1.4  christos 	return c == ' ' || c == '\t' || c == '\n';
    247  1.1  christos }
    248  1.1  christos 
    249  1.1  christos static inline const char *
    250  1.1  christos skip_FWS(const char *p)
    251  1.1  christos {
    252  1.4  christos 	while (is_FWS(*p))
    253  1.1  christos 		p++;
    254  1.1  christos 	return p;
    255  1.1  christos }
    256  1.1  christos 
    257  1.1  christos static inline void
    258  1.1  christos copy_skipped_FWS(char **dst, char *dstend, const char **src, const char *srcend)
    259  1.1  christos {
    260  1.1  christos 	const char *p, *pend;
    261  1.1  christos 	char *q, *qend;
    262  1.1  christos 
    263  1.1  christos 	p = *src;
    264  1.1  christos 	q = *dst;
    265  1.1  christos 	pend = srcend;
    266  1.1  christos 	qend = dstend;
    267  1.1  christos 
    268  1.1  christos 	if (p) {  /* copy any skipped linear-white-space */
    269  1.1  christos 		while (p < pend && q < qend)
    270  1.1  christos 			*q++ = *p++;
    271  1.1  christos 		*dst = q;
    272  1.1  christos 		*src = NULL;
    273  1.1  christos 	}
    274  1.1  christos }
    275  1.1  christos 
    276  1.1  christos /*
    277  1.1  christos  * Decode an unstructured field.
    278  1.1  christos  *
    279  1.1  christos  * See RFC 2822 Sec 2.2.1 and 3.6.5.
    280  1.1  christos  * Encoded words may occur anywhere in unstructured fields provided
    281  1.1  christos  * they are separated from any other text or encoded words by at least
    282  1.1  christos  * one linear-white-space character. (See RFC 2047 sec 5.1.)  If two
    283  1.1  christos  * encoded words occur sequentially (separated by only FWS) then the
    284  1.1  christos  * separating FWS is removed.
    285  1.1  christos  *
    286  1.1  christos  * NOTE: unstructured fields cannot contain 'quoted-pairs' (see
    287  1.1  christos  * RFC2822 sec 3.2.6 and RFC 2047), but that is no problem as a '\\'
    288  1.1  christos  * (or any non-whitespace character) immediately before an
    289  1.1  christos  * encoded-word will prevent it from being decoded.
    290  1.1  christos  *
    291  1.1  christos  * hstring should be a NULL terminated string.
    292  1.1  christos  * outbuf should be sufficiently large to hold the result.
    293  1.1  christos  */
    294  1.1  christos static void
    295  1.1  christos mime_decode_usfield(char *outbuf, size_t outsize, const char *hstring)
    296  1.1  christos {
    297  1.1  christos 	const char *p, *p0;
    298  1.1  christos 	char *q, *qend;
    299  1.1  christos 	int lastc;
    300  1.1  christos 	const char *charset;
    301  1.1  christos 
    302  1.1  christos 	charset = value(ENAME_MIME_CHARSET);
    303  1.1  christos 	qend = outbuf + outsize - 1; /* Make sure there is room for the trailing NULL! */
    304  1.1  christos 	q = outbuf;
    305  1.1  christos 	p = hstring;
    306  1.1  christos 	p0 = NULL;
    307  1.1  christos 	lastc = (unsigned char)' ';
    308  1.1  christos 	while (*p && q < qend) {
    309  1.1  christos 		const char *p1;
    310  1.1  christos 		char *q1;
    311  1.1  christos 		if (is_FWS(lastc) && p[0] == '=' && p[1] == '?' &&
    312  1.1  christos 		    decode_word((p1 = p, &p1), (q1 = q, &q1), qend, charset) == 0 &&
    313  1.4  christos 		    (*p1 == '\0' || is_FWS(*p1))) {
    314  1.1  christos 			p0 = p1;  /* pointer to first character after encoded word */
    315  1.1  christos 			q = q1;
    316  1.1  christos 			p = skip_FWS(p1);
    317  1.1  christos 			lastc = (unsigned char)*p0;
    318  1.1  christos 		}
    319  1.1  christos 		else {
    320  1.1  christos 			copy_skipped_FWS(&q, qend, &p0, p);
    321  1.1  christos 			lastc = (unsigned char)*p;
    322  1.1  christos 			if (q < qend)
    323  1.1  christos 				*q++ = *p++;
    324  1.1  christos 		}
    325  1.1  christos 	}
    326  1.1  christos 	copy_skipped_FWS(&q, qend, &p0, p);
    327  1.1  christos 	*q = '\0';
    328  1.1  christos }
    329  1.1  christos 
    330  1.1  christos /*
    331  1.1  christos  * Decode a field comment.
    332  1.1  christos  *
    333  1.1  christos  * Comments only occur in structured fields, can be nested (rfc 2822,
    334  1.1  christos  * sec 3.2.3), and can contain 'encoded-words' and 'quoted-pairs'.
    335  1.1  christos  * Otherwise, they can be regarded as unstructured fields that are
    336  1.1  christos  * bounded by '(' and ')' characters.
    337  1.1  christos  */
    338  1.1  christos static int
    339  1.1  christos decode_comment(char **obuf, char *oend, const char **ibuf, const char *iend, const char *charset)
    340  1.1  christos {
    341  1.1  christos 	const char *p, *pend, *p0;
    342  1.1  christos 	char *q, *qend;
    343  1.1  christos 	int lastc;
    344  1.1  christos 
    345  1.1  christos 	p = *ibuf;
    346  1.1  christos 	q = *obuf;
    347  1.1  christos 	pend = iend;
    348  1.1  christos 	qend = oend;
    349  1.4  christos 	lastc = ' ';
    350  1.1  christos 	p0 = NULL;
    351  1.1  christos 	while (p < pend && q < qend) {
    352  1.1  christos 		const char *p1;
    353  1.1  christos 		char *q1;
    354  1.1  christos 
    355  1.1  christos 		if (is_FWS(lastc) && p[0] == '=' && p[1] == '?' &&
    356  1.1  christos 		    decode_word((p1 = p, &p1), (q1 = q, &q1), qend, charset) == 0 &&
    357  1.4  christos 		    (*p1 == ')' || is_FWS(*p1))) {
    358  1.1  christos 			lastc = (unsigned char)*p1;
    359  1.1  christos 			p0 = p1;
    360  1.1  christos 			q = q1;
    361  1.1  christos 			p = skip_FWS(p1);
    362  1.1  christos 			/*
    363  1.1  christos 			 * XXX - this check should be unnecessary as *pend should
    364  1.1  christos 			 * be '\0' which will stop skip_FWS()
    365  1.1  christos 			 */
    366  1.1  christos 			if (p > pend)
    367  1.1  christos 				p = pend;
    368  1.1  christos 		}
    369  1.1  christos 		else {
    370  1.1  christos 			copy_skipped_FWS(&q, qend, &p0, p);
    371  1.1  christos 			if (q >= qend)	/* XXX - q > qend cannot happen */
    372  1.1  christos 				break;
    373  1.1  christos 
    374  1.1  christos 			if (*p == ')') {
    375  1.1  christos 				*q++ = *p++;	/* copy the closing ')' */
    376  1.1  christos 				break;		/* and get out of here! */
    377  1.1  christos 			}
    378  1.1  christos 
    379  1.1  christos 			if (*p == '(') {
    380  1.1  christos 				*q++ = *p++;	/* copy the opening '(' */
    381  1.1  christos 				if (decode_comment(&q, qend, &p, pend, charset) == -1)
    382  1.1  christos 					return -1;	/* is this right or should we update? */
    383  1.1  christos 				lastc = ')';
    384  1.1  christos 			}
    385  1.1  christos 			else if (*p == '\\' && p + 1 < pend) {	/* quoted-pair */
    386  1.1  christos 				if (p[1] == '(' || p[1] == ')' || p[1] == '\\') /* need quoted-pair*/
    387  1.1  christos 					*q++ = *p;
    388  1.1  christos 				p++;
    389  1.1  christos 				lastc = (unsigned char)*p;
    390  1.1  christos 				if (q < qend)
    391  1.1  christos 					*q++ = *p++;
    392  1.1  christos 			}
    393  1.1  christos 			else {
    394  1.1  christos 				lastc = (unsigned char)*p;
    395  1.1  christos 				*q++ = *p++;
    396  1.1  christos 			}
    397  1.1  christos 		}
    398  1.1  christos 	}
    399  1.1  christos 	*ibuf = p;
    400  1.1  christos 	*obuf = q;
    401  1.1  christos 	return 0;
    402  1.1  christos }
    403  1.1  christos 
    404  1.1  christos /*
    405  1.1  christos  * Decode a quoted-string or no-fold-quote.
    406  1.1  christos  *
    407  1.1  christos  * These cannot contain encoded words.  They can contain quoted-pairs,
    408  1.1  christos  * making '\\' special.  They have no other structure.  See RFC 2822
    409  1.1  christos  * sec 3.2.5 and 3.6.4.
    410  1.1  christos  */
    411  1.1  christos static void
    412  1.1  christos decode_quoted_string(char **obuf, char *oend, const char **ibuf, const char *iend)
    413  1.1  christos {
    414  1.1  christos 	const char *p, *pend;
    415  1.1  christos 	char *q, *qend;
    416  1.1  christos 
    417  1.1  christos 	qend = oend;
    418  1.1  christos 	pend = iend;
    419  1.1  christos 	p = *ibuf;
    420  1.1  christos 	q = *obuf;
    421  1.1  christos 	while (p < pend && q < qend) {
    422  1.1  christos 		if (*p == '"') {
    423  1.1  christos 			*q++ = *p++;	/* copy the closing '"' */
    424  1.1  christos 			break;
    425  1.1  christos 		}
    426  1.1  christos 		if (*p == '\\' && p + 1 < pend) { /* quoted-pair */
    427  1.1  christos 			if (p[1] == '"' || p[1] == '\\') {
    428  1.1  christos 				*q++ = *p;
    429  1.1  christos 				if (q >= qend)
    430  1.1  christos 					break;
    431  1.1  christos 			}
    432  1.1  christos 			p++;
    433  1.1  christos 		}
    434  1.1  christos 		*q++ = *p++;
    435  1.1  christos 	}
    436  1.1  christos 	*ibuf = p;
    437  1.1  christos 	*obuf = q;
    438  1.1  christos }
    439  1.1  christos 
    440  1.1  christos /*
    441  1.1  christos  * Decode a domain-literal or no-fold-literal.
    442  1.1  christos  *
    443  1.1  christos  * These cannot contain encoded words.  They can have quoted pairs and
    444  1.1  christos  * are delimited by '[' and ']' making '\\', '[', and ']' special.
    445  1.1  christos  * They have no other structure.  See RFC 2822 sec 3.4.1 and 3.6.4.
    446  1.1  christos  */
    447  1.1  christos static void
    448  1.1  christos decode_domain_literal(char **obuf, char *oend, const char **ibuf, const char *iend)
    449  1.1  christos {
    450  1.1  christos 	const char *p, *pend;
    451  1.1  christos 	char *q, *qend;
    452  1.1  christos 
    453  1.1  christos 	qend = oend;
    454  1.1  christos 	pend = iend;
    455  1.1  christos 	p = *ibuf;
    456  1.1  christos 	q = *obuf;
    457  1.1  christos 	while (p < pend && q < qend) {
    458  1.1  christos 		if (*p == ']') {
    459  1.1  christos 			*q++ = *p++;	/* copy the closing ']' */
    460  1.1  christos 			break;
    461  1.1  christos 		}
    462  1.1  christos 		if (*p == '\\' && p + 1 < pend) { /* quoted-pair */
    463  1.1  christos 			if (p[1] == '[' || p[1] == ']' || p[1] == '\\') {
    464  1.1  christos 				*q++ = *p;
    465  1.1  christos 				if (q >= qend)
    466  1.1  christos 					break;
    467  1.1  christos 			}
    468  1.1  christos 			p++;
    469  1.1  christos 		}
    470  1.1  christos 		*q++ = *p++;
    471  1.1  christos 	}
    472  1.1  christos 	*ibuf = p;
    473  1.1  christos 	*obuf = q;
    474  1.1  christos }
    475  1.1  christos 
    476  1.1  christos /*
    477  1.1  christos  * Specials: see RFC 2822 sec 3.2.1.
    478  1.1  christos  */
    479  1.1  christos static inline int
    480  1.1  christos is_specials(int c)
    481  1.1  christos {
    482  1.1  christos 	static const char specialtab[] = {
    483  1.1  christos 		0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
    484  1.1  christos 		0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
    485  1.1  christos 		0, 0, 1, 0,  0, 0, 0, 0,  1, 1, 0, 0,  1, 0, 1, 0,
    486  1.1  christos 		0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 1, 1,  1, 0, 1, 0,
    487  1.4  christos 
    488  1.1  christos 		1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
    489  1.1  christos 		0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 1,  1, 1, 0, 0,
    490  1.1  christos 		0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
    491  1.1  christos 		0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
    492  1.1  christos 	};
    493  1.4  christos 	return !(c & ~0x7f) ? specialtab[c] : 0;
    494  1.1  christos }
    495  1.1  christos 
    496  1.1  christos /*
    497  1.1  christos  * Decode a structured field.
    498  1.1  christos  *
    499  1.1  christos  * At the top level, structured fields can only contain encoded-words
    500  1.1  christos  * via 'phrases' and 'comments'.  See RFC 2047 sec 5.
    501  1.1  christos  */
    502  1.1  christos static void
    503  1.1  christos mime_decode_sfield(char *linebuf, size_t bufsize, const char *hstring)
    504  1.1  christos {
    505  1.1  christos 	const char *p, *pend, *p0;
    506  1.1  christos 	char *q, *qend;
    507  1.1  christos 	const char *charset;
    508  1.1  christos 	int lastc;
    509  1.1  christos 
    510  1.1  christos 	charset = value(ENAME_MIME_CHARSET);
    511  1.1  christos 
    512  1.1  christos 	p = hstring;
    513  1.1  christos 	q = linebuf;
    514  1.1  christos 	pend = hstring + strlen(hstring);
    515  1.1  christos 	qend = linebuf + bufsize - 1;	/* save room for the NULL terminator */
    516  1.1  christos 	lastc = (unsigned char)' ';
    517  1.1  christos 	p0 = NULL;
    518  1.1  christos 	while (p < pend && q < qend) {
    519  1.1  christos 		const char *p1;
    520  1.1  christos 		char *q1;
    521  1.1  christos 
    522  1.1  christos 		if (*p != '=') {
    523  1.1  christos 			copy_skipped_FWS(&q, qend, &p0, p);
    524  1.1  christos 			if (q >= qend)
    525  1.1  christos 				break;
    526  1.1  christos 		}
    527  1.1  christos 
    528  1.1  christos 		switch (*p) {
    529  1.1  christos 		case '(':	/* start of comment */
    530  1.1  christos 			*q++ = *p++;	/* copy the opening '(' */
    531  1.1  christos 			(void)decode_comment(&q, qend, &p, pend, charset);
    532  1.1  christos 			lastc = (unsigned char)p[-1];
    533  1.1  christos 			break;
    534  1.1  christos 
    535  1.1  christos 		case '"':	/* start of quoted-string or no-fold-quote */
    536  1.1  christos 			*q++ = *p++;	/* copy the opening '"' */
    537  1.1  christos 			decode_quoted_string(&q, qend, &p, pend);
    538  1.1  christos 			lastc = (unsigned char)p[-1];
    539  1.1  christos 			break;
    540  1.1  christos 
    541  1.1  christos 		case '[':	/* start of domain-literal or no-fold-literal */
    542  1.1  christos 			*q++ = *p++;	/* copy the opening '[' */
    543  1.1  christos 			decode_domain_literal(&q, qend, &p, pend);
    544  1.1  christos 			lastc = (unsigned char)p[-1];
    545  1.1  christos 			break;
    546  1.1  christos 
    547  1.1  christos 		case '\\':	/* start of quoted-pair */
    548  1.1  christos 			if (p + 1 < pend) {		/* quoted pair */
    549  1.1  christos 				if (is_specials(p[1])) {
    550  1.1  christos 					*q++ = *p;
    551  1.1  christos 					if (q >= qend)
    552  1.1  christos 						break;
    553  1.1  christos 				}
    554  1.1  christos 				p++;	/* skip the '\\' */
    555  1.1  christos 			}
    556  1.1  christos 			goto copy_char;
    557  1.4  christos 
    558  1.1  christos 		case '=':
    559  1.1  christos 			/*
    560  1.1  christos 			 * At this level encoded words can appear via
    561  1.1  christos 			 * 'phrases' (possibly delimited by ',' as in
    562  1.1  christos 			 * 'keywords').  Thus we handle them as such.
    563  1.1  christos 			 * Hopefully this is sufficient.
    564  1.1  christos 			 */
    565  1.1  christos 			if ((lastc == ',' || is_FWS(lastc)) && p[1] == '?' &&
    566  1.1  christos 			    decode_word((p1 = p, &p1), (q1 = q, &q1), qend, charset) == 0 &&
    567  1.4  christos 			    (*p1 == '\0' || *p1 == ',' || is_FWS(*p1))) {
    568  1.1  christos 				lastc = (unsigned char)*p1;
    569  1.1  christos 				p0 = p1;
    570  1.1  christos 				q = q1;
    571  1.1  christos 				p = skip_FWS(p1);
    572  1.1  christos 				/*
    573  1.1  christos 				 * XXX - this check should be
    574  1.1  christos 				 * unnecessary as *pend should be '\0'
    575  1.1  christos 				 * which will stop skip_FWS()
    576  1.1  christos 				 */
    577  1.1  christos 				if (p > pend)
    578  1.1  christos 					p = pend;
    579  1.1  christos 				break;
    580  1.1  christos 			}
    581  1.1  christos 			else {
    582  1.1  christos 				copy_skipped_FWS(&q, qend, &p0, p);
    583  1.1  christos 				if (q >= qend)
    584  1.1  christos 					break;
    585  1.1  christos 				goto copy_char;
    586  1.1  christos 			}
    587  1.1  christos 
    588  1.1  christos 		case '<':	/* start of angle-addr, msg-id, or path. */
    589  1.1  christos 			/*
    590  1.1  christos 			 * A msg-id cannot contain encoded-pairs or
    591  1.1  christos 			 * encoded-words, but angle-addr and path can.
    592  1.1  christos 			 * Distinguishing between them seems to be
    593  1.1  christos 			 * unnecessary, so let's be loose and just
    594  1.1  christos 			 * decode them as if they were all the same.
    595  1.1  christos 			 */
    596  1.1  christos 		default:
    597  1.1  christos 	copy_char:
    598  1.1  christos 			lastc = (unsigned char)*p;
    599  1.1  christos 			*q++ = *p++;
    600  1.1  christos 			break;
    601  1.1  christos 		}
    602  1.1  christos 	}
    603  1.1  christos 	copy_skipped_FWS(&q, qend, &p0, p);
    604  1.1  christos 	*q = '\0';	/* null terminate the result! */
    605  1.1  christos }
    606  1.1  christos 
    607  1.1  christos /*
    608  1.1  christos  * Returns the correct hfield decoder, or NULL if none.
    609  1.1  christos  * Info extracted from RFC 2822.
    610  1.5  christos  *
    611  1.5  christos  * name - pointer to field name of header line (with colon).
    612  1.1  christos  */
    613  1.1  christos PUBLIC hfield_decoder_t
    614  1.5  christos mime_hfield_decoder(const char *name)
    615  1.1  christos {
    616  1.1  christos 	static const struct field_decoder_tbl_s {
    617  1.1  christos 		const char *field_name;
    618  1.5  christos 		size_t field_len;
    619  1.1  christos 		hfield_decoder_t decoder;
    620  1.1  christos 	} field_decoder_tbl[] = {
    621  1.5  christos #define X(s)	s, sizeof(s) - 1
    622  1.5  christos 		{ X("Received:"),			NULL },
    623  1.5  christos 
    624  1.5  christos 		{ X("Content-Type:"),			NULL },
    625  1.5  christos 		{ X("Content-Disposition:"),		NULL },
    626  1.5  christos 		{ X("Content-Transfer-Encoding:"),	NULL },
    627  1.5  christos 		{ X("Content-Description:"),		mime_decode_sfield },
    628  1.5  christos 		{ X("Content-ID:"),			mime_decode_sfield },
    629  1.5  christos 		{ X("MIME-Version:"),			mime_decode_sfield },
    630  1.5  christos 
    631  1.5  christos 		{ X("Bcc:"),				mime_decode_sfield },
    632  1.5  christos 		{ X("Cc:"),				mime_decode_sfield },
    633  1.5  christos 		{ X("Date:"),				mime_decode_sfield },
    634  1.5  christos 		{ X("From:"),				mime_decode_sfield },
    635  1.5  christos 		{ X("In-Reply-To:"),			mime_decode_sfield },
    636  1.5  christos 		{ X("Keywords:"),			mime_decode_sfield },
    637  1.5  christos 		{ X("Message-ID:"),			mime_decode_sfield },
    638  1.5  christos 		{ X("References:"),			mime_decode_sfield },
    639  1.5  christos 		{ X("Reply-To:"),			mime_decode_sfield },
    640  1.5  christos 		{ X("Return-Path:"),			mime_decode_sfield },
    641  1.5  christos 		{ X("Sender:"),				mime_decode_sfield },
    642  1.5  christos 		{ X("To:"),				mime_decode_sfield },
    643  1.5  christos 		{ X("Subject:"),			mime_decode_usfield },
    644  1.5  christos 		{ X("Comments:"),			mime_decode_usfield },
    645  1.5  christos 		{ X("X-"),				mime_decode_usfield },
    646  1.5  christos 		{ NULL, 0,				mime_decode_usfield },	/* optional-fields */
    647  1.5  christos #undef X
    648  1.1  christos 	};
    649  1.1  christos 	const struct field_decoder_tbl_s *fp;
    650  1.1  christos 
    651  1.1  christos 	/* XXX - this begs for a hash table! */
    652  1.1  christos 	for (fp = field_decoder_tbl; fp->field_name; fp++)
    653  1.5  christos 		if (strncasecmp(name, fp->field_name, fp->field_len) == 0)
    654  1.5  christos 			break;
    655  1.1  christos 	return fp->decoder;
    656  1.1  christos }
    657  1.1  christos 
    658  1.1  christos #endif /* MIME_SUPPORT */
    659