mime_header.c revision 1.9 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