Use _MAX1 not _MAX when maximum is not inclusive
[s-mailx.git] / mime_parse.c
blob6498f79926a9d0ed4aacad115ac538025e2f6909
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 - 2016 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 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 if ((ip->m_mimecontent = mime_type_classify_part(ip)) == MIME_822) {
100 /* TODO (v15) HACK: message/rfc822 is treated special, that this one is
101 * TODO too stupid to apply content-decoding when (falsely) applied */
102 if (ip->m_mime_enc != MIMEE_8B && ip->m_mime_enc != MIMEE_7B) {
103 n_err(_("Pre-v15 %s cannot handle (falsely) encoded message/rfc822\n"
104 " (not 7bit or 8bit)! Interpreting as text/plain!\n"),
105 uagent);
106 ip->m_mimecontent = MIME_TEXT_PLAIN;
110 if (mpf & MIME_PARSE_PARTS) {
111 if (level > 9999) { /* TODO MAGIC */
112 n_err(_("MIME content too deeply nested\n"));
113 goto jleave;
115 switch (ip->m_mimecontent) {
116 case MIME_PKCS7:
117 if (mpf & MIME_PARSE_DECRYPT) {
118 #ifdef HAVE_SSL
119 _mime_parse_pkcs7(zmp, ip, mpf, level);
120 break;
121 #else
122 n_err(_("No SSL support compiled in\n"));
123 goto jleave;
124 #endif
126 /* FALLTHRU */
127 default:
128 break;
129 case MIME_MULTI:
130 case MIME_ALTERNATIVE:
131 case MIME_RELATED: /* TODO /related yet handled like /alternative */
132 case MIME_DIGEST:
133 _mime_parse_multipart(zmp, ip, mpf, level);
134 break;
135 case MIME_822:
136 _mime_parse_rfc822(zmp, ip, mpf, level);
137 break;
141 rv = TRU1;
142 jleave:
143 NYD_LEAVE;
144 return rv;
147 static void
148 _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
149 enum mime_parse_flags mpf, int level)
151 int c, lastc = '\n';
152 size_t cnt;
153 FILE *ibuf;
154 off_t offs;
155 struct mimepart *np;
156 long lines;
157 NYD_ENTER;
159 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
160 goto jleave;
162 cnt = ip->m_size;
163 lines = ip->m_lines;
164 while (cnt && ((c = getc(ibuf)) != EOF)) {
165 --cnt;
166 if (c == '\n') {
167 --lines;
168 if (lastc == '\n')
169 break;
171 lastc = c;
173 offs = ftell(ibuf);
175 np = csalloc(1, sizeof *np);
176 np->m_flag = MNOFROM;
177 np->m_have = HAVE_HEADER | HAVE_BODY;
178 np->m_block = mailx_blockof(offs);
179 np->m_offset = mailx_offsetof(offs);
180 np->m_size = np->m_xsize = cnt;
181 np->m_lines = np->m_xlines = lines;
182 np->m_partstring = ip->m_partstring;
183 np->m_parent = ip;
184 ip->m_multipart = np;
186 if (ok_blook(rfc822_body_from_)) {
187 substdate((struct message*)np);
188 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
191 _mime_parse_part(zmp, np, mpf, level + 1);
192 jleave:
193 NYD_LEAVE;
196 #ifdef HAVE_SSL
197 static void
198 _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
199 enum mime_parse_flags mpf, int level)
201 struct message m, *xmp;
202 struct mimepart *np;
203 char *to, *cc;
204 NYD_ENTER;
206 memcpy(&m, ip, sizeof m);
207 to = hfield1("to", zmp);
208 cc = hfield1("cc", zmp);
210 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
211 np = csalloc(1, sizeof *np);
212 np->m_flag = xmp->m_flag;
213 np->m_have = xmp->m_have;
214 np->m_block = xmp->m_block;
215 np->m_offset = xmp->m_offset;
216 np->m_size = xmp->m_size;
217 np->m_xsize = xmp->m_xsize;
218 np->m_lines = xmp->m_lines;
219 np->m_xlines = xmp->m_xlines;
220 np->m_partstring = ip->m_partstring;
222 if (_mime_parse_part(zmp, np, mpf, level + 1) == OKAY) {
223 np->m_parent = ip;
224 ip->m_multipart = np;
227 NYD_LEAVE;
229 #endif /* HAVE_SSL */
231 static void
232 _mime_parse_multipart(struct message *zmp, struct mimepart *ip,
233 enum mime_parse_flags mpf, int level)
235 /* TODO Instead of the recursive multiple run parse we have today,
236 * TODO the send/MIME layer rewrite must create a "tree" of parts with
237 * TODO a single-pass parse, then address each part directly as
238 * TODO necessary; since boundaries start with -- and the content
239 * TODO rather forms a stack this is pretty cheap indeed! */
240 struct mimepart *np = NULL;
241 char *boundary, *line = NULL;
242 size_t linesize = 0, linelen, cnt, boundlen;
243 FILE *ibuf;
244 off_t offs;
245 int part = 0;
246 long lines = 0;
247 NYD_ENTER;
249 if ((boundary = mime_param_boundary_get(ip->m_ct_type, &linelen)) == NULL)
250 goto jleave;
252 boundlen = linelen;
253 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
254 goto jleave;
256 cnt = ip->m_size;
257 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
258 if (line[0] == '\n')
259 break;
260 offs = ftell(ibuf);
262 __mime_parse_new(ip, &np, offs, NULL);
263 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
264 /* XXX linelen includes LF */
265 if (!((lines > 0 || part == 0) && linelen > boundlen &&
266 !strncmp(line, boundary, boundlen))) {
267 ++lines;
268 continue;
271 /* Subpart boundary? */
272 if (line[boundlen] == '\n') {
273 offs = ftell(ibuf);
274 if (part > 0) {
275 __mime_parse_end(&np, offs - boundlen - 2, lines);
276 __mime_parse_new(ip, &np, offs - boundlen - 2, NULL);
278 __mime_parse_end(&np, offs, 2);
279 __mime_parse_new(ip, &np, offs, &part);
280 lines = 0;
281 continue;
284 /* Final boundary? Be aware of cases where there is no separating
285 * newline in between boundaries, as has been seen in a message with
286 * "Content-Type: multipart/appledouble;" */
287 if (linelen < boundlen + 2)
288 continue;
289 linelen -= boundlen + 2;
290 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
291 (linelen > 0 && line[boundlen + 2] != '\n'))
292 continue;
293 offs = ftell(ibuf);
294 if (part != 0) {
295 __mime_parse_end(&np, offs - boundlen - 4, lines);
296 __mime_parse_new(ip, &np, offs - boundlen - 4, NULL);
298 __mime_parse_end(&np, offs + cnt, 2);
299 break;
301 if (np) {
302 offs = ftell(ibuf);
303 __mime_parse_end(&np, offs, lines);
306 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
307 if (np->m_mimecontent != MIME_DISCARD)
308 _mime_parse_part(zmp, np, mpf, level + 1);
309 free(line);
310 jleave:
311 NYD_LEAVE;
314 static void
315 __mime_parse_new(struct mimepart *ip, struct mimepart **np, off_t offs,
316 int *part)
318 struct mimepart *pp;
319 size_t sz;
320 NYD_ENTER;
322 *np = csalloc(1, sizeof **np);
323 (*np)->m_flag = MNOFROM;
324 (*np)->m_have = HAVE_HEADER | HAVE_BODY;
325 (*np)->m_block = mailx_blockof(offs);
326 (*np)->m_offset = mailx_offsetof(offs);
328 if (part) {
329 ++(*part);
330 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
331 sz += 20;
332 (*np)->m_partstring = salloc(sz);
333 if (ip->m_partstring)
334 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
335 else
336 snprintf((*np)->m_partstring, sz, "%u", *part);
337 } else
338 (*np)->m_mimecontent = MIME_DISCARD;
339 (*np)->m_parent = ip;
341 if (ip->m_multipart) {
342 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
344 pp->m_nextpart = *np;
345 } else
346 ip->m_multipart = *np;
347 NYD_LEAVE;
350 static void
351 __mime_parse_end(struct mimepart **np, off_t xoffs, long lines)
353 off_t offs;
354 NYD_ENTER;
356 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
357 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
358 (*np)->m_lines = (*np)->m_xlines = lines;
359 *np = NULL;
360 NYD_LEAVE;
363 FL struct mimepart *
364 mime_parse_msg(struct message *mp, enum mime_parse_flags mpf)
366 struct mimepart *ip;
367 NYD_ENTER;
369 ip = csalloc(1, sizeof *ip);
370 ip->m_flag = mp->m_flag;
371 ip->m_have = mp->m_have;
372 ip->m_block = mp->m_block;
373 ip->m_offset = mp->m_offset;
374 ip->m_size = mp->m_size;
375 ip->m_xsize = mp->m_xsize;
376 ip->m_lines = mp->m_lines;
377 ip->m_xlines = mp->m_lines;
378 if (!_mime_parse_part(mp, ip, mpf, 0))
379 ip = NULL;
380 NYD_LEAVE;
381 return ip;
384 /* s-it-mode */