make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / mime-parse.c
blob64cb4271aae0dae96e6156bd99cc650b3e60f6ae
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Parse a message into a tree of struct mimepart objects.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-3-Clause
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE mime_parse
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 /* Fetch plain */
44 static char * _mime_parse_ct_plain_from_ct(char const *cth);
46 static bool_t _mime_parse_part(struct message *zmp, struct mimepart *ip,
47 enum mime_parse_flags mpf, int level);
49 static void _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
50 enum mime_parse_flags mpf, int level);
52 #ifdef HAVE_TLS
53 static void _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
54 enum mime_parse_flags mpf, int level);
55 #endif
57 static bool_t _mime_parse_multipart(struct message *zmp,
58 struct mimepart *ip, enum mime_parse_flags mpf, int level);
59 static void __mime_parse_new(struct mimepart *ip, struct mimepart **np,
60 off_t offs, int *part);
61 static void __mime_parse_end(struct mimepart **np, off_t xoffs,
62 long lines);
64 static char *
65 _mime_parse_ct_plain_from_ct(char const *cth)
67 char *rv_b, *rv;
68 NYD2_ENTER;
70 rv_b = savestr(cth);
72 if ((rv = strchr(rv_b, ';')) != NULL)
73 *rv = '\0';
75 rv = rv_b + strlen(rv_b);
76 while (rv > rv_b && blankchar(rv[-1]))
77 --rv;
78 *rv = '\0';
79 NYD2_LEAVE;
80 return rv_b;
83 static bool_t
84 _mime_parse_part(struct message *zmp, struct mimepart *ip,
85 enum mime_parse_flags mpf, int level)
87 char *cp;
88 bool_t rv = FAL0;
89 NYD_ENTER;
91 ip->m_ct_type = hfield1("content-type", (struct message*)ip);
92 if (ip->m_ct_type != NULL)
93 ip->m_ct_type_plain = _mime_parse_ct_plain_from_ct(ip->m_ct_type);
94 else if (ip->m_parent != NULL && ip->m_parent->m_mimecontent == MIME_DIGEST)
95 ip->m_ct_type_plain = "message/rfc822";
96 else
97 ip->m_ct_type_plain = "text/plain";
98 ip->m_ct_type_usr_ovwr = NULL;
100 if(ip->m_ct_type != NULL &&
101 (ip->m_charset = cp = mime_param_get("charset", ip->m_ct_type)
102 ) != NULL)
103 ip->m_charset = n_iconv_normalize_name(cp);
105 if (ip->m_charset == NULL)
106 ip->m_charset = ok_vlook(charset_7bit);
107 else
108 ip->m_charset = n_charsetalias_expand(ip->m_charset);
110 if ((ip->m_ct_enc = hfield1("content-transfer-encoding",
111 (struct message*)ip)) == NULL)
112 ip->m_ct_enc = mime_enc_from_conversion(CONV_7BIT);
113 ip->m_mime_enc = mime_enc_from_ctehead(ip->m_ct_enc);
115 if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
116 (ip->m_filename = mime_param_get("filename", cp)) == NULL) &&
117 ip->m_ct_type != NULL)
118 ip->m_filename = mime_param_get("name", ip->m_ct_type);
120 if ((cp = hfield1("content-description", (struct message*)ip)) != NULL)
121 ip->m_content_description = cp;
123 if ((ip->m_mimecontent = n_mimetype_classify_part(ip,
124 ((mpf & MIME_PARSE_FOR_USER_CONTEXT) != 0))) == MIME_822) {
125 /* TODO (v15) HACK: message/rfc822 is treated special, that this one is
126 * TODO too stupid to apply content-decoding when (falsely) applied */
127 if (ip->m_mime_enc != MIMEE_8B && ip->m_mime_enc != MIMEE_7B) {
128 n_err(_("Pre-v15 %s cannot handle (falsely) encoded message/rfc822\n"
129 " (not 7bit or 8bit)! Interpreting as text/plain!\n"),
130 n_uagent);
131 ip->m_mimecontent = MIME_TEXT_PLAIN;
135 assert(ip->m_external_body_url == NULL);
136 if(!asccasecmp(ip->m_ct_type_plain, "message/external-body") &&
137 (cp = mime_param_get("access-type", ip->m_ct_type)) != NULL &&
138 !asccasecmp(cp, "URL"))
139 ip->m_external_body_url = mime_param_get("url", ip->m_ct_type);
141 if (mpf & MIME_PARSE_PARTS) {
142 if (level > 9999) { /* TODO MAGIC */
143 n_err(_("MIME content too deeply nested\n"));
144 goto jleave;
146 switch (ip->m_mimecontent) {
147 case MIME_PKCS7:
148 if (mpf & MIME_PARSE_DECRYPT) {
149 #ifdef HAVE_TLS
150 _mime_parse_pkcs7(zmp, ip, mpf, level);
151 if (ip->m_content_info & CI_ENCRYPTED_OK)
152 ip->m_content_info |= CI_EXPANDED;
153 break;
154 #else
155 n_err(_("No SSL / S/MIME support compiled in\n"));
156 goto jleave;
157 #endif
159 break;
160 default:
161 break;
162 case MIME_ALTERNATIVE:
163 case MIME_RELATED: /* TODO /related yet handled like /alternative */
164 case MIME_DIGEST:
165 case MIME_SIGNED:
166 case MIME_ENCRYPTED:
167 case MIME_MULTI:
168 if (!_mime_parse_multipart(zmp, ip, mpf, level))
169 goto jleave;
170 break;
171 case MIME_822:
172 _mime_parse_rfc822(zmp, ip, mpf, level);
173 break;
177 rv = TRU1;
178 jleave:
179 NYD_LEAVE;
180 return rv;
183 static void
184 _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
185 enum mime_parse_flags mpf, int level)
187 int c, lastc = '\n';
188 size_t cnt;
189 FILE *ibuf;
190 off_t offs;
191 struct mimepart *np;
192 long lines;
193 NYD_ENTER;
195 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
196 goto jleave;
198 cnt = ip->m_size;
199 lines = ip->m_lines;
200 while (cnt && ((c = getc(ibuf)) != EOF)) {
201 --cnt;
202 if (c == '\n') {
203 --lines;
204 if (lastc == '\n')
205 break;
207 lastc = c;
209 offs = ftell(ibuf);
211 np = n_autorec_calloc(1, sizeof *np);
212 np->m_flag = MNOFROM;
213 np->m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
214 np->m_block = mailx_blockof(offs);
215 np->m_offset = mailx_offsetof(offs);
216 np->m_size = np->m_xsize = cnt;
217 np->m_lines = np->m_xlines = lines;
218 np->m_partstring = ip->m_partstring;
219 np->m_parent = ip;
220 ip->m_multipart = np;
222 if (!(mpf & MIME_PARSE_SHALLOW) && ok_blook(rfc822_body_from_)) {
223 substdate((struct message*)np);
224 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
227 _mime_parse_part(zmp, np, mpf, level + 1);
228 jleave:
229 NYD_LEAVE;
232 #ifdef HAVE_TLS
233 static void
234 _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
235 enum mime_parse_flags mpf, int level)
237 struct message m, *xmp;
238 struct mimepart *np;
239 char *to, *cc;
240 NYD_ENTER;
242 memcpy(&m, ip, sizeof m);
243 to = hfield1("to", zmp);
244 cc = hfield1("cc", zmp);
246 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
247 np = n_autorec_calloc(1, sizeof *np);
248 np->m_flag = xmp->m_flag;
249 np->m_content_info = xmp->m_content_info | CI_ENCRYPTED | CI_ENCRYPTED_OK;
250 np->m_block = xmp->m_block;
251 np->m_offset = xmp->m_offset;
252 np->m_size = xmp->m_size;
253 np->m_xsize = xmp->m_xsize;
254 np->m_lines = xmp->m_lines;
255 np->m_xlines = xmp->m_xlines;
257 /* TODO using part "1" for decrypted content is a hack */
258 if ((np->m_partstring = ip->m_partstring) == NULL)
259 ip->m_partstring = np->m_partstring = n_UNCONST(n_1);
261 if (_mime_parse_part(zmp, np, mpf, level + 1) == OKAY) {
262 ip->m_content_info |= CI_ENCRYPTED | CI_ENCRYPTED_OK;
263 np->m_parent = ip;
264 ip->m_multipart = np;
266 } else
267 ip->m_content_info |= CI_ENCRYPTED | CI_ENCRYPTED_BAD;
268 NYD_LEAVE;
270 #endif /* HAVE_TLS */
272 static bool_t
273 _mime_parse_multipart(struct message *zmp, struct mimepart *ip,
274 enum mime_parse_flags mpf, int level)
276 struct mimepart *np = NULL;
277 char *boundary, *line = NULL;
278 size_t linesize = 0, linelen, cnt, boundlen;
279 FILE *ibuf;
280 off_t offs;
281 int part = 0;
282 long lines = 0;
283 NYD_ENTER;
285 if ((boundary = mime_param_boundary_get(ip->m_ct_type, &linelen)) == NULL)
286 goto jleave;
288 boundlen = linelen;
289 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
290 goto jleave;
292 cnt = ip->m_size;
293 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
294 if (line[0] == '\n')
295 break;
296 offs = ftell(ibuf);
298 /* TODO using part "1" for decrypted content is a hack */
299 if (ip->m_partstring == NULL)
300 ip->m_partstring = n_UNCONST("1");
301 __mime_parse_new(ip, &np, offs, NULL);
303 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
304 /* XXX linelen includes LF */
305 if (!((lines > 0 || part == 0) && linelen > boundlen &&
306 !strncmp(line, boundary, boundlen))) {
307 ++lines;
308 continue;
311 /* Subpart boundary? */
312 if (line[boundlen] == '\n') {
313 offs = ftell(ibuf);
314 if (part > 0) {
315 __mime_parse_end(&np, offs - boundlen - 2, lines);
316 __mime_parse_new(ip, &np, offs - boundlen - 2, NULL);
318 __mime_parse_end(&np, offs, 2);
319 __mime_parse_new(ip, &np, offs, &part);
320 lines = 0;
321 continue;
324 /* Final boundary? Be aware of cases where there is no separating
325 * newline in between boundaries, as has been seen in a message with
326 * "Content-Type: multipart/appledouble;" */
327 if (linelen < boundlen + 2)
328 continue;
329 linelen -= boundlen + 2;
330 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
331 (linelen > 0 && line[boundlen + 2] != '\n'))
332 continue;
333 offs = ftell(ibuf);
334 if (part != 0) {
335 __mime_parse_end(&np, offs - boundlen - 4, lines);
336 __mime_parse_new(ip, &np, offs - boundlen - 4, NULL);
338 __mime_parse_end(&np, offs + cnt, 2);
339 break;
341 if (np) {
342 offs = ftell(ibuf);
343 __mime_parse_end(&np, offs, lines);
346 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
347 if (np->m_mimecontent != MIME_DISCARD)
348 _mime_parse_part(zmp, np, mpf, level + 1);
350 jleave:
351 if (line != NULL)
352 n_free(line);
353 NYD_LEAVE;
354 return TRU1;
357 static void
358 __mime_parse_new(struct mimepart *ip, struct mimepart **np, off_t offs,
359 int *part)
361 struct mimepart *pp;
362 size_t sz;
363 NYD_ENTER;
365 *np = n_autorec_calloc(1, sizeof **np);
366 (*np)->m_flag = MNOFROM;
367 (*np)->m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
368 (*np)->m_block = mailx_blockof(offs);
369 (*np)->m_offset = mailx_offsetof(offs);
371 if (part) {
372 ++(*part);
373 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
374 sz += 20;
375 (*np)->m_partstring = n_autorec_alloc(sz);
376 if (ip->m_partstring)
377 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
378 else
379 snprintf((*np)->m_partstring, sz, "%u", *part);
380 } else
381 (*np)->m_mimecontent = MIME_DISCARD;
382 (*np)->m_parent = ip;
384 if (ip->m_multipart) {
385 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
387 pp->m_nextpart = *np;
388 } else
389 ip->m_multipart = *np;
390 NYD_LEAVE;
393 static void
394 __mime_parse_end(struct mimepart **np, off_t xoffs, long lines)
396 off_t offs;
397 NYD_ENTER;
399 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
400 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
401 (*np)->m_lines = (*np)->m_xlines = lines;
402 *np = NULL;
403 NYD_LEAVE;
406 FL struct mimepart *
407 mime_parse_msg(struct message *mp, enum mime_parse_flags mpf)
409 struct mimepart *ip;
410 NYD_ENTER;
412 ip = n_autorec_calloc(1, sizeof *ip);
413 ip->m_flag = mp->m_flag;
414 ip->m_content_info = mp->m_content_info;
415 ip->m_block = mp->m_block;
416 ip->m_offset = mp->m_offset;
417 ip->m_size = mp->m_size;
418 ip->m_xsize = mp->m_xsize;
419 ip->m_lines = mp->m_lines;
420 ip->m_xlines = mp->m_lines;
421 if (!_mime_parse_part(mp, ip, mpf, 0))
422 ip = NULL;
423 NYD_LEAVE;
424 return ip;
427 /* s-it-mode */