collect(): ~[FfMmUu]: should default to the "dot" (Andrew Gee)
[s-mailx.git] / mime-parse.c
blob4787c008b08d5c12f7e9c88048db76ba1d137482
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 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE mime_parse
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Fetch plain */
43 static char * _mime_parse_ct_plain_from_ct(char const *cth);
45 static bool_t _mime_parse_part(struct message *zmp, struct mimepart *ip,
46 enum mime_parse_flags mpf, int level);
48 static void _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
49 enum mime_parse_flags mpf, int level);
51 #ifdef HAVE_SSL
52 static void _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
53 enum mime_parse_flags mpf, int level);
54 #endif
56 static bool_t _mime_parse_multipart(struct message *zmp,
57 struct mimepart *ip, enum mime_parse_flags mpf, int level);
58 static void __mime_parse_new(struct mimepart *ip, struct mimepart **np,
59 off_t offs, int *part);
60 static void __mime_parse_end(struct mimepart **np, off_t xoffs,
61 long lines);
63 static char *
64 _mime_parse_ct_plain_from_ct(char const *cth)
66 char *rv_b, *rv;
67 NYD2_ENTER;
69 rv_b = savestr(cth);
71 if ((rv = strchr(rv_b, ';')) != NULL)
72 *rv = '\0';
74 rv = rv_b + strlen(rv_b);
75 while (rv > rv_b && blankchar(rv[-1]))
76 --rv;
77 *rv = '\0';
78 NYD2_LEAVE;
79 return rv_b;
82 static bool_t
83 _mime_parse_part(struct message *zmp, struct mimepart *ip,
84 enum mime_parse_flags mpf, int level)
86 char *cp;
87 bool_t rv = FAL0;
88 NYD_ENTER;
90 ip->m_ct_type = hfield1("content-type", (struct message*)ip);
91 if (ip->m_ct_type != NULL)
92 ip->m_ct_type_plain = _mime_parse_ct_plain_from_ct(ip->m_ct_type);
93 else if (ip->m_parent != NULL && ip->m_parent->m_mimecontent == MIME_DIGEST)
94 ip->m_ct_type_plain = "message/rfc822";
95 else
96 ip->m_ct_type_plain = "text/plain";
97 ip->m_ct_type_usr_ovwr = NULL;
99 if(ip->m_ct_type != NULL &&
100 (ip->m_charset = cp = mime_param_get("charset", ip->m_ct_type)
101 ) != NULL)
102 ip->m_charset = n_iconv_normalize_name(cp);
104 if (ip->m_charset == NULL)
105 ip->m_charset = ok_vlook(charset_7bit);
106 else
107 ip->m_charset = n_charsetalias_expand(ip->m_charset);
109 if ((ip->m_ct_enc = hfield1("content-transfer-encoding",
110 (struct message*)ip)) == NULL)
111 ip->m_ct_enc = mime_enc_from_conversion(CONV_7BIT);
112 ip->m_mime_enc = mime_enc_from_ctehead(ip->m_ct_enc);
114 if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
115 (ip->m_filename = mime_param_get("filename", cp)) == NULL) &&
116 ip->m_ct_type != NULL)
117 ip->m_filename = mime_param_get("name", ip->m_ct_type);
119 if ((cp = hfield1("content-description", (struct message*)ip)) != NULL)
120 ip->m_content_description = cp;
122 if ((ip->m_mimecontent = n_mimetype_classify_part(ip,
123 ((mpf & MIME_PARSE_FOR_USER_CONTEXT) != 0))) == MIME_822) {
124 /* TODO (v15) HACK: message/rfc822 is treated special, that this one is
125 * TODO too stupid to apply content-decoding when (falsely) applied */
126 if (ip->m_mime_enc != MIMEE_8B && ip->m_mime_enc != MIMEE_7B) {
127 n_err(_("Pre-v15 %s cannot handle (falsely) encoded message/rfc822\n"
128 " (not 7bit or 8bit)! Interpreting as text/plain!\n"),
129 n_uagent);
130 ip->m_mimecontent = MIME_TEXT_PLAIN;
134 assert(ip->m_external_body_url == NULL);
135 if(!asccasecmp(ip->m_ct_type_plain, "message/external-body") &&
136 (cp = mime_param_get("access-type", ip->m_ct_type)) != NULL &&
137 !asccasecmp(cp, "URL"))
138 ip->m_external_body_url = mime_param_get("url", ip->m_ct_type);
140 if (mpf & MIME_PARSE_PARTS) {
141 if (level > 9999) { /* TODO MAGIC */
142 n_err(_("MIME content too deeply nested\n"));
143 goto jleave;
145 switch (ip->m_mimecontent) {
146 case MIME_PKCS7:
147 if (mpf & MIME_PARSE_DECRYPT) {
148 #ifdef HAVE_SSL
149 _mime_parse_pkcs7(zmp, ip, mpf, level);
150 if (ip->m_content_info & CI_ENCRYPTED_OK)
151 ip->m_content_info |= CI_EXPANDED;
152 break;
153 #else
154 n_err(_("No SSL / S/MIME support compiled in\n"));
155 goto jleave;
156 #endif
158 break;
159 default:
160 break;
161 case MIME_ALTERNATIVE:
162 case MIME_RELATED: /* TODO /related yet handled like /alternative */
163 case MIME_DIGEST:
164 case MIME_SIGNED:
165 case MIME_ENCRYPTED:
166 case MIME_MULTI:
167 if (!_mime_parse_multipart(zmp, ip, mpf, level))
168 goto jleave;
169 break;
170 case MIME_822:
171 _mime_parse_rfc822(zmp, ip, mpf, level);
172 break;
176 rv = TRU1;
177 jleave:
178 NYD_LEAVE;
179 return rv;
182 static void
183 _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
184 enum mime_parse_flags mpf, int level)
186 int c, lastc = '\n';
187 size_t cnt;
188 FILE *ibuf;
189 off_t offs;
190 struct mimepart *np;
191 long lines;
192 NYD_ENTER;
194 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
195 goto jleave;
197 cnt = ip->m_size;
198 lines = ip->m_lines;
199 while (cnt && ((c = getc(ibuf)) != EOF)) {
200 --cnt;
201 if (c == '\n') {
202 --lines;
203 if (lastc == '\n')
204 break;
206 lastc = c;
208 offs = ftell(ibuf);
210 np = csalloc(1, sizeof *np);
211 np->m_flag = MNOFROM;
212 np->m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
213 np->m_block = mailx_blockof(offs);
214 np->m_offset = mailx_offsetof(offs);
215 np->m_size = np->m_xsize = cnt;
216 np->m_lines = np->m_xlines = lines;
217 np->m_partstring = ip->m_partstring;
218 np->m_parent = ip;
219 ip->m_multipart = np;
221 if (!(mpf & MIME_PARSE_SHALLOW) && ok_blook(rfc822_body_from_)) {
222 substdate((struct message*)np);
223 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
226 _mime_parse_part(zmp, np, mpf, level + 1);
227 jleave:
228 NYD_LEAVE;
231 #ifdef HAVE_SSL
232 static void
233 _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
234 enum mime_parse_flags mpf, int level)
236 struct message m, *xmp;
237 struct mimepart *np;
238 char *to, *cc;
239 NYD_ENTER;
241 memcpy(&m, ip, sizeof m);
242 to = hfield1("to", zmp);
243 cc = hfield1("cc", zmp);
245 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
246 np = csalloc(1, sizeof *np);
247 np->m_flag = xmp->m_flag;
248 np->m_content_info = xmp->m_content_info | CI_ENCRYPTED | CI_ENCRYPTED_OK;
249 np->m_block = xmp->m_block;
250 np->m_offset = xmp->m_offset;
251 np->m_size = xmp->m_size;
252 np->m_xsize = xmp->m_xsize;
253 np->m_lines = xmp->m_lines;
254 np->m_xlines = xmp->m_xlines;
256 /* TODO using part "1" for decrypted content is a hack */
257 if ((np->m_partstring = ip->m_partstring) == NULL)
258 ip->m_partstring = np->m_partstring = n_UNCONST(n_1);
260 if (_mime_parse_part(zmp, np, mpf, level + 1) == OKAY) {
261 ip->m_content_info |= CI_ENCRYPTED | CI_ENCRYPTED_OK;
262 np->m_parent = ip;
263 ip->m_multipart = np;
265 } else
266 ip->m_content_info |= CI_ENCRYPTED | CI_ENCRYPTED_BAD;
267 NYD_LEAVE;
269 #endif /* HAVE_SSL */
271 static bool_t
272 _mime_parse_multipart(struct message *zmp, struct mimepart *ip,
273 enum mime_parse_flags mpf, int level)
275 struct mimepart *np = NULL;
276 char *boundary, *line = NULL;
277 size_t linesize = 0, linelen, cnt, boundlen;
278 FILE *ibuf;
279 off_t offs;
280 int part = 0;
281 long lines = 0;
282 NYD_ENTER;
284 if ((boundary = mime_param_boundary_get(ip->m_ct_type, &linelen)) == NULL)
285 goto jleave;
287 boundlen = linelen;
288 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
289 goto jleave;
291 cnt = ip->m_size;
292 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
293 if (line[0] == '\n')
294 break;
295 offs = ftell(ibuf);
297 /* TODO using part "1" for decrypted content is a hack */
298 if (ip->m_partstring == NULL)
299 ip->m_partstring = n_UNCONST("1");
300 __mime_parse_new(ip, &np, offs, NULL);
302 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
303 /* XXX linelen includes LF */
304 if (!((lines > 0 || part == 0) && linelen > boundlen &&
305 !strncmp(line, boundary, boundlen))) {
306 ++lines;
307 continue;
310 /* Subpart boundary? */
311 if (line[boundlen] == '\n') {
312 offs = ftell(ibuf);
313 if (part > 0) {
314 __mime_parse_end(&np, offs - boundlen - 2, lines);
315 __mime_parse_new(ip, &np, offs - boundlen - 2, NULL);
317 __mime_parse_end(&np, offs, 2);
318 __mime_parse_new(ip, &np, offs, &part);
319 lines = 0;
320 continue;
323 /* Final boundary? Be aware of cases where there is no separating
324 * newline in between boundaries, as has been seen in a message with
325 * "Content-Type: multipart/appledouble;" */
326 if (linelen < boundlen + 2)
327 continue;
328 linelen -= boundlen + 2;
329 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
330 (linelen > 0 && line[boundlen + 2] != '\n'))
331 continue;
332 offs = ftell(ibuf);
333 if (part != 0) {
334 __mime_parse_end(&np, offs - boundlen - 4, lines);
335 __mime_parse_new(ip, &np, offs - boundlen - 4, NULL);
337 __mime_parse_end(&np, offs + cnt, 2);
338 break;
340 if (np) {
341 offs = ftell(ibuf);
342 __mime_parse_end(&np, offs, lines);
345 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
346 if (np->m_mimecontent != MIME_DISCARD)
347 _mime_parse_part(zmp, np, mpf, level + 1);
349 jleave:
350 if (line != NULL)
351 free(line);
352 NYD_LEAVE;
353 return TRU1;
356 static void
357 __mime_parse_new(struct mimepart *ip, struct mimepart **np, off_t offs,
358 int *part)
360 struct mimepart *pp;
361 size_t sz;
362 NYD_ENTER;
364 *np = csalloc(1, sizeof **np);
365 (*np)->m_flag = MNOFROM;
366 (*np)->m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
367 (*np)->m_block = mailx_blockof(offs);
368 (*np)->m_offset = mailx_offsetof(offs);
370 if (part) {
371 ++(*part);
372 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
373 sz += 20;
374 (*np)->m_partstring = salloc(sz);
375 if (ip->m_partstring)
376 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
377 else
378 snprintf((*np)->m_partstring, sz, "%u", *part);
379 } else
380 (*np)->m_mimecontent = MIME_DISCARD;
381 (*np)->m_parent = ip;
383 if (ip->m_multipart) {
384 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
386 pp->m_nextpart = *np;
387 } else
388 ip->m_multipart = *np;
389 NYD_LEAVE;
392 static void
393 __mime_parse_end(struct mimepart **np, off_t xoffs, long lines)
395 off_t offs;
396 NYD_ENTER;
398 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
399 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
400 (*np)->m_lines = (*np)->m_xlines = lines;
401 *np = NULL;
402 NYD_LEAVE;
405 FL struct mimepart *
406 mime_parse_msg(struct message *mp, enum mime_parse_flags mpf)
408 struct mimepart *ip;
409 NYD_ENTER;
411 ip = csalloc(1, sizeof *ip);
412 ip->m_flag = mp->m_flag;
413 ip->m_content_info = mp->m_content_info;
414 ip->m_block = mp->m_block;
415 ip->m_offset = mp->m_offset;
416 ip->m_size = mp->m_size;
417 ip->m_xsize = mp->m_xsize;
418 ip->m_lines = mp->m_lines;
419 ip->m_xlines = mp->m_lines;
420 if (!_mime_parse_part(mp, ip, mpf, 0))
421 ip = NULL;
422 NYD_LEAVE;
423 return ip;
426 /* s-it-mode */