n_shell_quote(): rewrite, tweak user-at-a-glance experience
[s-mailx.git] / mime_parse.c
bloba2385cddc9f429141fe35ebe2e2f147c119c26c3
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 static bool_t _mime_parse_part(struct message *zmp, struct mimepart *ip,
43 enum mime_parse_flags mpf, int level);
45 static void _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
46 enum mime_parse_flags mpf, int level);
48 #ifdef HAVE_SSL
49 static void _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
50 enum mime_parse_flags mpf, int level);
51 #endif
53 static void _mime_parse_multipart(struct message *zmp,
54 struct mimepart *ip, enum mime_parse_flags mpf, int level);
55 static void __mime_parse_new(struct mimepart *ip, struct mimepart **np,
56 off_t offs, int *part);
57 static void __mime_parse_end(struct mimepart **np, off_t xoffs,
58 long lines);
60 static bool_t
61 _mime_parse_part(struct message *zmp, struct mimepart *ip,
62 enum mime_parse_flags mpf, int level)
64 char *cp_b, *cp;
65 bool_t rv = FAL0;
66 NYD_ENTER;
68 ip->m_ct_type = hfield1("content-type", (struct message*)ip);
69 if (ip->m_ct_type != NULL) {
70 ip->m_ct_type_plain = cp_b = savestr(ip->m_ct_type);
71 if ((cp = strchr(cp_b, ';')) != NULL)
72 *cp = '\0';
73 cp = cp_b + strlen(cp_b);
74 while (cp > cp_b && blankchar(cp[-1]))
75 --cp;
76 *cp = '\0';
77 } else if (ip->m_parent != NULL &&
78 ip->m_parent->m_mimecontent == MIME_DIGEST)
79 ip->m_ct_type_plain = "message/rfc822";
80 else
81 ip->m_ct_type_plain = "text/plain";
82 ip->m_ct_type_usr_ovwr = NULL;
84 if (ip->m_ct_type != NULL)
85 ip->m_charset = mime_param_get("charset", ip->m_ct_type);
86 if (ip->m_charset == NULL)
87 ip->m_charset = charset_get_7bit();
89 if ((ip->m_ct_enc = hfield1("content-transfer-encoding",
90 (struct message*)ip)) == NULL)
91 ip->m_ct_enc = mime_enc_from_conversion(CONV_7BIT);
92 ip->m_mime_enc = mime_enc_from_ctehead(ip->m_ct_enc);
94 if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
95 (ip->m_filename = mime_param_get("filename", cp)) == NULL) &&
96 ip->m_ct_type != NULL)
97 ip->m_filename = mime_param_get("name", ip->m_ct_type);
99 ip->m_mimecontent = mime_type_classify_part(ip);
101 if (mpf & MIME_PARSE_PARTS) {
102 if (level > 9999) { /* TODO MAGIC */
103 n_err(_("MIME content too deeply nested\n"));
104 goto jleave;
106 switch (ip->m_mimecontent) {
107 case MIME_PKCS7:
108 if (mpf & MIME_PARSE_DECRYPT) {
109 #ifdef HAVE_SSL
110 _mime_parse_pkcs7(zmp, ip, mpf, level);
111 break;
112 #else
113 n_err(_("No SSL support compiled in\n"));
114 goto jleave;
115 #endif
117 /* FALLTHRU */
118 default:
119 break;
120 case MIME_MULTI:
121 case MIME_ALTERNATIVE:
122 case MIME_RELATED: /* TODO /related yet handled like /alternative */
123 case MIME_DIGEST:
124 _mime_parse_multipart(zmp, ip, mpf, level);
125 break;
126 case MIME_822:
127 _mime_parse_rfc822(zmp, ip, mpf, level);
128 break;
132 rv = TRU1;
133 jleave:
134 NYD_LEAVE;
135 return rv;
138 static void
139 _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
140 enum mime_parse_flags mpf, int level)
142 int c, lastc = '\n';
143 size_t cnt;
144 FILE *ibuf;
145 off_t offs;
146 struct mimepart *np;
147 long lines;
148 NYD_ENTER;
150 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
151 goto jleave;
153 cnt = ip->m_size;
154 lines = ip->m_lines;
155 while (cnt && ((c = getc(ibuf)) != EOF)) {
156 --cnt;
157 if (c == '\n') {
158 --lines;
159 if (lastc == '\n')
160 break;
162 lastc = c;
164 offs = ftell(ibuf);
166 np = csalloc(1, sizeof *np);
167 np->m_flag = MNOFROM;
168 np->m_have = HAVE_HEADER | HAVE_BODY;
169 np->m_block = mailx_blockof(offs);
170 np->m_offset = mailx_offsetof(offs);
171 np->m_size = np->m_xsize = cnt;
172 np->m_lines = np->m_xlines = lines;
173 np->m_partstring = ip->m_partstring;
174 np->m_parent = ip;
175 ip->m_multipart = np;
177 if (ok_blook(rfc822_body_from_)) {
178 substdate((struct message*)np);
179 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
182 _mime_parse_part(zmp, np, mpf, level + 1);
183 jleave:
184 NYD_LEAVE;
187 #ifdef HAVE_SSL
188 static void
189 _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
190 enum mime_parse_flags mpf, int level)
192 struct message m, *xmp;
193 struct mimepart *np;
194 char *to, *cc;
195 NYD_ENTER;
197 memcpy(&m, ip, sizeof m);
198 to = hfield1("to", zmp);
199 cc = hfield1("cc", zmp);
201 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
202 np = csalloc(1, sizeof *np);
203 np->m_flag = xmp->m_flag;
204 np->m_have = xmp->m_have;
205 np->m_block = xmp->m_block;
206 np->m_offset = xmp->m_offset;
207 np->m_size = xmp->m_size;
208 np->m_xsize = xmp->m_xsize;
209 np->m_lines = xmp->m_lines;
210 np->m_xlines = xmp->m_xlines;
211 np->m_partstring = ip->m_partstring;
213 if (_mime_parse_part(zmp, np, mpf, level + 1) == OKAY) {
214 np->m_parent = ip;
215 ip->m_multipart = np;
218 NYD_LEAVE;
220 #endif /* HAVE_SSL */
222 static void
223 _mime_parse_multipart(struct message *zmp, struct mimepart *ip,
224 enum mime_parse_flags mpf, int level)
226 /* TODO Instead of the recursive multiple run parse we have today,
227 * TODO the send/MIME layer rewrite must create a "tree" of parts with
228 * TODO a single-pass parse, then address each part directly as
229 * TODO necessary; since boundaries start with -- and the content
230 * TODO rather forms a stack this is pretty cheap indeed! */
231 struct mimepart *np = NULL;
232 char *boundary, *line = NULL;
233 size_t linesize = 0, linelen, cnt, boundlen;
234 FILE *ibuf;
235 off_t offs;
236 int part = 0;
237 long lines = 0;
238 NYD_ENTER;
240 if ((boundary = mime_param_boundary_get(ip->m_ct_type, &linelen)) == NULL)
241 goto jleave;
243 boundlen = linelen;
244 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
245 goto jleave;
247 cnt = ip->m_size;
248 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
249 if (line[0] == '\n')
250 break;
251 offs = ftell(ibuf);
253 __mime_parse_new(ip, &np, offs, NULL);
254 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
255 /* XXX linelen includes LF */
256 if (!((lines > 0 || part == 0) && linelen > boundlen &&
257 !strncmp(line, boundary, boundlen))) {
258 ++lines;
259 continue;
262 /* Subpart boundary? */
263 if (line[boundlen] == '\n') {
264 offs = ftell(ibuf);
265 if (part > 0) {
266 __mime_parse_end(&np, offs - boundlen - 2, lines);
267 __mime_parse_new(ip, &np, offs - boundlen - 2, NULL);
269 __mime_parse_end(&np, offs, 2);
270 __mime_parse_new(ip, &np, offs, &part);
271 lines = 0;
272 continue;
275 /* Final boundary? Be aware of cases where there is no separating
276 * newline in between boundaries, as has been seen in a message with
277 * "Content-Type: multipart/appledouble;" */
278 if (linelen < boundlen + 2)
279 continue;
280 linelen -= boundlen + 2;
281 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
282 (linelen > 0 && line[boundlen + 2] != '\n'))
283 continue;
284 offs = ftell(ibuf);
285 if (part != 0) {
286 __mime_parse_end(&np, offs - boundlen - 4, lines);
287 __mime_parse_new(ip, &np, offs - boundlen - 4, NULL);
289 __mime_parse_end(&np, offs + cnt, 2);
290 break;
292 if (np) {
293 offs = ftell(ibuf);
294 __mime_parse_end(&np, offs, lines);
297 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
298 if (np->m_mimecontent != MIME_DISCARD)
299 _mime_parse_part(zmp, np, mpf, level + 1);
300 free(line);
301 jleave:
302 NYD_LEAVE;
305 static void
306 __mime_parse_new(struct mimepart *ip, struct mimepart **np, off_t offs,
307 int *part)
309 struct mimepart *pp;
310 size_t sz;
311 NYD_ENTER;
313 *np = csalloc(1, sizeof **np);
314 (*np)->m_flag = MNOFROM;
315 (*np)->m_have = HAVE_HEADER | HAVE_BODY;
316 (*np)->m_block = mailx_blockof(offs);
317 (*np)->m_offset = mailx_offsetof(offs);
319 if (part) {
320 ++(*part);
321 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
322 sz += 20;
323 (*np)->m_partstring = salloc(sz);
324 if (ip->m_partstring)
325 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
326 else
327 snprintf((*np)->m_partstring, sz, "%u", *part);
328 } else
329 (*np)->m_mimecontent = MIME_DISCARD;
330 (*np)->m_parent = ip;
332 if (ip->m_multipart) {
333 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
335 pp->m_nextpart = *np;
336 } else
337 ip->m_multipart = *np;
338 NYD_LEAVE;
341 static void
342 __mime_parse_end(struct mimepart **np, off_t xoffs, long lines)
344 off_t offs;
345 NYD_ENTER;
347 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
348 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
349 (*np)->m_lines = (*np)->m_xlines = lines;
350 *np = NULL;
351 NYD_LEAVE;
354 FL struct mimepart *
355 mime_parse_msg(struct message *mp, enum mime_parse_flags mpf)
357 struct mimepart *ip;
358 NYD_ENTER;
360 ip = csalloc(1, sizeof *ip);
361 ip->m_flag = mp->m_flag;
362 ip->m_have = mp->m_have;
363 ip->m_block = mp->m_block;
364 ip->m_offset = mp->m_offset;
365 ip->m_size = mp->m_size;
366 ip->m_xsize = mp->m_xsize;
367 ip->m_lines = mp->m_lines;
368 ip->m_xlines = mp->m_lines;
369 if (!_mime_parse_part(mp, ip, mpf, 0))
370 ip = NULL;
371 NYD_LEAVE;
372 return ip;
375 /* s-it-mode */