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