1 /**
2 * @file
3 * Miscellaneous email parsing routines
4 *
5 * @authors
6 * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
7 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
8 *
9 * @copyright
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 2 of the License, or (at your option) any later
13 * version.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 * details.
19 *
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /**
25 * @page email_parse Email parsing code
26 *
27 * Miscellaneous email parsing routines
28 */
29
30 #include "config.h"
31 #include <ctype.h>
32 #include <string.h>
33 #include <time.h>
34 #include "mutt/lib.h"
35 #include "address/lib.h"
36 #include "config/lib.h"
37 #include "core/lib.h"
38 #include "mutt.h"
39 #include "parse.h"
40 #include "body.h"
41 #include "email.h"
42 #include "envelope.h"
43 #include "from.h"
44 #include "globals.h"
45 #include "mime.h"
46 #include "parameter.h"
47 #include "rfc2047.h"
48 #include "rfc2231.h"
49 #include "url.h"
50 #ifdef USE_AUTOCRYPT
51 #include "autocrypt/lib.h"
52 #endif
53
54 /* If the 'Content-Length' is bigger than 1GiB, then it's clearly wrong.
55 * Cap the value to prevent overflow of Body.length */
56 #define CONTENT_TOO_BIG (1 << 30)
57
58 static void parse_part(FILE *fp, struct Body *b, int *counter);
59 static struct Body *rfc822_parse_message(FILE *fp, struct Body *parent, int *counter);
60 static struct Body *parse_multipart(FILE *fp, const char *boundary,
61 LOFF_T end_off, bool digest, int *counter);
62
63 /**
64 * mutt_auto_subscribe - Check if user is subscribed to mailing list
65 * @param mailto URL of mailing list subscribe
66 */
mutt_auto_subscribe(const char * mailto)67 void mutt_auto_subscribe(const char *mailto)
68 {
69 if (!mailto)
70 return;
71
72 if (!AutoSubscribeCache)
73 AutoSubscribeCache = mutt_hash_new(200, MUTT_HASH_STRCASECMP | MUTT_HASH_STRDUP_KEYS);
74
75 if (mutt_hash_find(AutoSubscribeCache, mailto))
76 return;
77
78 mutt_hash_insert(AutoSubscribeCache, mailto, AutoSubscribeCache);
79
80 struct Envelope *lpenv = mutt_env_new(); /* parsed envelope from the List-Post mailto: URL */
81
82 if (mutt_parse_mailto(lpenv, NULL, mailto) && !TAILQ_EMPTY(&lpenv->to))
83 {
84 const char *mailbox = TAILQ_FIRST(&lpenv->to)->mailbox;
85 if (mailbox && !mutt_regexlist_match(&SubscribedLists, mailbox) &&
86 !mutt_regexlist_match(&UnMailLists, mailbox) &&
87 !mutt_regexlist_match(&UnSubscribedLists, mailbox))
88 {
89 /* mutt_regexlist_add() detects duplicates, so it is safe to
90 * try to add here without any checks. */
91 mutt_regexlist_add(&MailLists, mailbox, REG_ICASE, NULL);
92 mutt_regexlist_add(&SubscribedLists, mailbox, REG_ICASE, NULL);
93 }
94 }
95
96 mutt_env_free(&lpenv);
97 }
98
99 /**
100 * parse_parameters - Parse a list of Parameters
101 * @param pl Parameter list for the results
102 * @param s String to parse
103 * @param allow_value_spaces Allow values with spaces
104 *
105 * Autocrypt defines an irregular parameter format that doesn't follow the
106 * rfc. It splits keydata across multiple lines without parameter continuations.
107 * The allow_value_spaces parameter allows parsing those values which
108 * are split by spaces when unfolded.
109 */
parse_parameters(struct ParameterList * pl,const char * s,bool allow_value_spaces)110 static void parse_parameters(struct ParameterList *pl, const char *s, bool allow_value_spaces)
111 {
112 struct Parameter *pnew = NULL;
113 const char *p = NULL;
114 size_t i;
115
116 struct Buffer *buf = mutt_buffer_pool_get();
117 /* allow_value_spaces, especially with autocrypt keydata, can result
118 * in quite large parameter values. avoid frequent reallocs by
119 * pre-sizing */
120 if (allow_value_spaces)
121 mutt_buffer_alloc(buf, mutt_str_len(s));
122
123 mutt_debug(LL_DEBUG2, "'%s'\n", s);
124
125 while (*s)
126 {
127 mutt_buffer_reset(buf);
128
129 p = strpbrk(s, "=;");
130 if (!p)
131 {
132 mutt_debug(LL_DEBUG1, "malformed parameter: %s\n", s);
133 goto bail;
134 }
135
136 /* if we hit a ; now the parameter has no value, just skip it */
137 if (*p != ';')
138 {
139 i = p - s;
140 /* remove whitespace from the end of the attribute name */
141 while ((i > 0) && mutt_str_is_email_wsp(s[i - 1]))
142 i--;
143
144 /* the check for the missing parameter token is here so that we can skip
145 * over any quoted value that may be present. */
146 if (i == 0)
147 {
148 mutt_debug(LL_DEBUG1, "missing attribute: %s\n", s);
149 pnew = NULL;
150 }
151 else
152 {
153 pnew = mutt_param_new();
154 pnew->attribute = mutt_strn_dup(s, i);
155 }
156
157 do
158 {
159 s = mutt_str_skip_email_wsp(p + 1); /* skip over the =, or space if we loop */
160
161 if (*s == '"')
162 {
163 bool state_ascii = true;
164 s++;
165 for (; *s; s++)
166 {
167 const char *const c_assumed_charset =
168 cs_subset_string(NeoMutt->sub, "assumed_charset");
169 if (c_assumed_charset)
170 {
171 // As iso-2022-* has a character of '"' with non-ascii state, ignore it
172 if (*s == 0x1b)
173 {
174 if ((s[1] == '(') && ((s[2] == 'B') || (s[2] == 'J')))
175 state_ascii = true;
176 else
177 state_ascii = false;
178 }
179 }
180 if (state_ascii && (*s == '"'))
181 break;
182 if (*s == '\\')
183 {
184 if (s[1])
185 {
186 s++;
187 /* Quote the next character */
188 mutt_buffer_addch(buf, *s);
189 }
190 }
191 else
192 mutt_buffer_addch(buf, *s);
193 }
194 if (*s)
195 s++; /* skip over the " */
196 }
197 else
198 {
199 for (; *s && *s != ' ' && *s != ';'; s++)
200 mutt_buffer_addch(buf, *s);
201 }
202
203 p = s;
204 } while (allow_value_spaces && (*s == ' '));
205
206 /* if the attribute token was missing, 'new' will be NULL */
207 if (pnew)
208 {
209 pnew->value = mutt_buffer_strdup(buf);
210
211 mutt_debug(LL_DEBUG2, "parse_parameter: '%s' = '%s'\n",
212 pnew->attribute ? pnew->attribute : "", pnew->value ? pnew->value : "");
213
214 /* Add this parameter to the list */
215 TAILQ_INSERT_HEAD(pl, pnew, entries);
216 }
217 }
218 else
219 {
220 mutt_debug(LL_DEBUG1, "parameter with no value: %s\n", s);
221 s = p;
222 }
223
224 /* Find the next parameter */
225 if ((*s != ';') && !(s = strchr(s, ';')))
226 break; /* no more parameters */
227
228 do
229 {
230 /* Move past any leading whitespace. the +1 skips over the semicolon */
231 s = mutt_str_skip_email_wsp(s + 1);
232 } while (*s == ';'); /* skip empty parameters */
233 }
234
235 bail:
236
237 rfc2231_decode_parameters(pl);
238 mutt_buffer_pool_release(&buf);
239 }
240
241 /**
242 * parse_content_disposition - Parse a content disposition
243 * @param s String to parse
244 * @param ct Body to save the result
245 *
246 * e.g. parse a string "inline" and set #DISP_INLINE.
247 */
parse_content_disposition(const char * s,struct Body * ct)248 static void parse_content_disposition(const char *s, struct Body *ct)
249 {
250 struct ParameterList pl;
251 TAILQ_INIT(&pl);
252
253 if (mutt_istr_startswith(s, "inline"))
254 ct->disposition = DISP_INLINE;
255 else if (mutt_istr_startswith(s, "form-data"))
256 ct->disposition = DISP_FORM_DATA;
257 else
258 ct->disposition = DISP_ATTACH;
259
260 /* Check to see if a default filename was given */
261 s = strchr(s, ';');
262 if (s)
263 {
264 s = mutt_str_skip_email_wsp(s + 1);
265 parse_parameters(&pl, s, false);
266 s = mutt_param_get(&pl, "filename");
267 if (s)
268 mutt_str_replace(&ct->filename, s);
269 s = mutt_param_get(&pl, "name");
270 if (s)
271 ct->form_name = mutt_str_dup(s);
272 mutt_param_free(&pl);
273 }
274 }
275
276 /**
277 * parse_references - Parse references from an email header
278 * @param head List to receive the references
279 * @param s String to parse
280 */
parse_references(struct ListHead * head,const char * s)281 static void parse_references(struct ListHead *head, const char *s)
282 {
283 if (!head)
284 return;
285
286 char *m = NULL;
287 for (size_t off = 0; (m = mutt_extract_message_id(s, &off)); s += off)
288 {
289 mutt_list_insert_head(head, m);
290 }
291 }
292
293 /**
294 * parse_content_language - Read the content's language
295 * @param s Language string
296 * @param ct Body of the email
297 */
parse_content_language(const char * s,struct Body * ct)298 static void parse_content_language(const char *s, struct Body *ct)
299 {
300 if (!s || !ct)
301 return;
302
303 mutt_debug(LL_DEBUG2, "RFC8255 >> Content-Language set to %s\n", s);
304 ct->language = mutt_str_dup(s);
305 }
306
307 /**
308 * mutt_matches_ignore - Does the string match the ignore list
309 * @param s String to check
310 * @retval true String matches
311 *
312 * Checks Ignore and UnIgnore using mutt_list_match
313 */
mutt_matches_ignore(const char * s)314 bool mutt_matches_ignore(const char *s)
315 {
316 return mutt_list_match(s, &Ignore) && !mutt_list_match(s, &UnIgnore);
317 }
318
319 /**
320 * mutt_check_mime_type - Check a MIME type string
321 * @param s String to check
322 * @retval num MIME type, e.g. #TYPE_TEXT
323 */
mutt_check_mime_type(const char * s)324 enum ContentType mutt_check_mime_type(const char *s)
325 {
326 if (mutt_istr_equal("text", s))
327 return TYPE_TEXT;
328 if (mutt_istr_equal("multipart", s))
329 return TYPE_MULTIPART;
330 #ifdef SUN_ATTACHMENT
331 if (mutt_istr_equal("x-sun-attachment", s))
332 return TYPE_MULTIPART;
333 #endif
334 if (mutt_istr_equal("application", s))
335 return TYPE_APPLICATION;
336 if (mutt_istr_equal("message", s))
337 return TYPE_MESSAGE;
338 if (mutt_istr_equal("image", s))
339 return TYPE_IMAGE;
340 if (mutt_istr_equal("audio", s))
341 return TYPE_AUDIO;
342 if (mutt_istr_equal("video", s))
343 return TYPE_VIDEO;
344 if (mutt_istr_equal("model", s))
345 return TYPE_MODEL;
346 if (mutt_istr_equal("*", s))
347 return TYPE_ANY;
348 if (mutt_istr_equal(".*", s))
349 return TYPE_ANY;
350
351 return TYPE_OTHER;
352 }
353
354 /**
355 * mutt_extract_message_id - Find a message-id
356 * @param[in] s String to parse
357 * @param[out] len Number of bytes of s parsed
358 * @retval ptr Message id found
359 * @retval NULL No more message ids
360 */
mutt_extract_message_id(const char * s,size_t * len)361 char *mutt_extract_message_id(const char *s, size_t *len)
362 {
363 if (!s || (*s == '\0'))
364 return NULL;
365
366 char *decoded = mutt_str_dup(s);
367 rfc2047_decode(&decoded);
368
369 char *res = NULL;
370
371 for (const char *p = decoded, *beg = NULL; *p; p++)
372 {
373 if (*p == '<')
374 {
375 beg = p;
376 continue;
377 }
378
379 if (beg && (*p == '>'))
380 {
381 if (len)
382 *len = p - decoded + 1;
383 res = mutt_strn_dup(beg, (p + 1) - beg);
384 break;
385 }
386 }
387
388 FREE(&decoded);
389 return res;
390 }
391
392 /**
393 * mutt_check_encoding - Check the encoding type
394 * @param c String to check
395 * @retval num Encoding type, e.g. #ENC_QUOTED_PRINTABLE
396 */
mutt_check_encoding(const char * c)397 int mutt_check_encoding(const char *c)
398 {
399 if (mutt_istr_startswith(c, "7bit"))
400 return ENC_7BIT;
401 if (mutt_istr_startswith(c, "8bit"))
402 return ENC_8BIT;
403 if (mutt_istr_startswith(c, "binary"))
404 return ENC_BINARY;
405 if (mutt_istr_startswith(c, "quoted-printable"))
406 return ENC_QUOTED_PRINTABLE;
407 if (mutt_istr_startswith(c, "base64"))
408 return ENC_BASE64;
409 if (mutt_istr_startswith(c, "x-uuencode"))
410 return ENC_UUENCODED;
411 #ifdef SUN_ATTACHMENT
412 if (mutt_istr_startswith(c, "uuencode"))
413 return ENC_UUENCODED;
414 #endif
415 return ENC_OTHER;
416 }
417
418 /**
419 * mutt_parse_content_type - Parse a content type
420 * @param s String to parse
421 * @param ct Body to save the result
422 *
423 * e.g. parse a string "inline" and set #DISP_INLINE.
424 */
mutt_parse_content_type(const char * s,struct Body * ct)425 void mutt_parse_content_type(const char *s, struct Body *ct)
426 {
427 if (!s || !ct)
428 return;
429
430 FREE(&ct->subtype);
431 mutt_param_free(&ct->parameter);
432
433 /* First extract any existing parameters */
434 char *pc = strchr(s, ';');
435 if (pc)
436 {
437 *pc++ = 0;
438 while (*pc && IS_SPACE(*pc))
439 pc++;
440 parse_parameters(&ct->parameter, pc, false);
441
442 /* Some pre-RFC1521 gateways still use the "name=filename" convention,
443 * but if a filename has already been set in the content-disposition,
444 * let that take precedence, and don't set it here */
445 pc = mutt_param_get(&ct->parameter, "name");
446 if (pc && !ct->filename)
447 ct->filename = mutt_str_dup(pc);
448
449 #ifdef SUN_ATTACHMENT
450 /* this is deep and utter perversion */
451 pc = mutt_param_get(&ct->parameter, "conversions");
452 if (pc)
453 ct->encoding = mutt_check_encoding(pc);
454 #endif
455 }
456
457 /* Now get the subtype */
458 char *subtype = strchr(s, '/');
459 if (subtype)
460 {
461 *subtype++ = '\0';
462 for (pc = subtype; *pc && !IS_SPACE(*pc) && (*pc != ';'); pc++)
463 ; // do nothing
464
465 *pc = '\0';
466 ct->subtype = mutt_str_dup(subtype);
467 }
468
469 /* Finally, get the major type */
470 ct->type = mutt_check_mime_type(s);
471
472 #ifdef SUN_ATTACHMENT
473 if (mutt_istr_equal("x-sun-attachment", s))
474 ct->subtype = mutt_str_dup("x-sun-attachment");
475 #endif
476
477 if (ct->type == TYPE_OTHER)
478 {
479 mutt_str_replace(&ct->xtype, s);
480 }
481
482 if (!ct->subtype)
483 {
484 /* Some older non-MIME mailers (i.e., mailtool, elm) have a content-type
485 * field, so we can attempt to convert the type to Body here. */
486 if (ct->type == TYPE_TEXT)
487 ct->subtype = mutt_str_dup("plain");
488 else if (ct->type == TYPE_AUDIO)
489 ct->subtype = mutt_str_dup("basic");
490 else if (ct->type == TYPE_MESSAGE)
491 ct->subtype = mutt_str_dup("rfc822");
492 else if (ct->type == TYPE_OTHER)
493 {
494 char buf[128];
495
496 ct->type = TYPE_APPLICATION;
497 snprintf(buf, sizeof(buf), "x-%s", s);
498 ct->subtype = mutt_str_dup(buf);
499 }
500 else
501 ct->subtype = mutt_str_dup("x-unknown");
502 }
503
504 /* Default character set for text types. */
505 if (ct->type == TYPE_TEXT)
506 {
507 pc = mutt_param_get(&ct->parameter, "charset");
508 if (pc)
509 {
510 /* Microsoft Outlook seems to think it is necessary to repeat
511 * charset=, strip it off not to confuse ourselves */
512 if (mutt_istrn_equal(pc, "charset=", sizeof("charset=") - 1))
513 mutt_param_set(&ct->parameter, "charset", pc + (sizeof("charset=") - 1));
514 }
515 else
516 {
517 const char *const c_assumed_charset =
518 cs_subset_string(NeoMutt->sub, "assumed_charset");
519 mutt_param_set(&ct->parameter, "charset",
520 (c_assumed_charset) ? (const char *) mutt_ch_get_default_charset() :
521 "us-ascii");
522 }
523 }
524 }
525
526 #ifdef USE_AUTOCRYPT
527 /**
528 * parse_autocrypt - Parse an Autocrypt header line
529 * @param head Autocrypt header to insert before
530 * @param s Header string to parse
531 * @retval ptr New AutocryptHeader inserted before head
532 */
parse_autocrypt(struct AutocryptHeader * head,const char * s)533 static struct AutocryptHeader *parse_autocrypt(struct AutocryptHeader *head, const char *s)
534 {
535 struct AutocryptHeader *autocrypt = mutt_autocrypthdr_new();
536 autocrypt->next = head;
537
538 struct ParameterList pl = TAILQ_HEAD_INITIALIZER(pl);
539 parse_parameters(&pl, s, true);
540 if (TAILQ_EMPTY(&pl))
541 {
542 autocrypt->invalid = true;
543 goto cleanup;
544 }
545
546 struct Parameter *p = NULL;
547 TAILQ_FOREACH(p, &pl, entries)
548 {
549 if (mutt_istr_equal(p->attribute, "addr"))
550 {
551 if (autocrypt->addr)
552 {
553 autocrypt->invalid = true;
554 goto cleanup;
555 }
556 autocrypt->addr = p->value;
557 p->value = NULL;
558 }
559 else if (mutt_istr_equal(p->attribute, "prefer-encrypt"))
560 {
561 if (mutt_istr_equal(p->value, "mutual"))
562 autocrypt->prefer_encrypt = true;
563 }
564 else if (mutt_istr_equal(p->attribute, "keydata"))
565 {
566 if (autocrypt->keydata)
567 {
568 autocrypt->invalid = true;
569 goto cleanup;
570 }
571 autocrypt->keydata = p->value;
572 p->value = NULL;
573 }
574 else if (p->attribute && (p->attribute[0] != '_'))
575 {
576 autocrypt->invalid = true;
577 goto cleanup;
578 }
579 }
580
581 /* Checking the addr against From, and for multiple valid headers
582 * occurs later, after all the headers are parsed. */
583 if (!autocrypt->addr || !autocrypt->keydata)
584 autocrypt->invalid = true;
585
586 cleanup:
587 mutt_param_free(&pl);
588 return autocrypt;
589 }
590 #endif
591
592 /**
593 * rfc2369_first_mailto - Extract the first mailto: URL from a RFC2369 list
594 * @param body Body of the header
595 * @return First mailto: URL found, or NULL if none was found
596 */
rfc2369_first_mailto(const char * body)597 static char *rfc2369_first_mailto(const char *body)
598 {
599 for (const char *beg = body, *end = NULL; beg; beg = strchr(end, ','))
600 {
601 beg = strchr(beg, '<');
602 if (!beg)
603 {
604 break;
605 }
606 beg++;
607 end = strchr(beg, '>');
608 if (!end)
609 {
610 break;
611 }
612
613 char *mlist = mutt_strn_dup(beg, end - beg);
614 if (url_check_scheme(mlist) == U_MAILTO)
615 {
616 return mlist;
617 }
618 FREE(&mlist);
619 }
620 return NULL;
621 }
622
623 /**
624 * mutt_rfc822_parse_line - Parse an email header
625 * @param env Envelope of the email
626 * @param e Email
627 * @param name Header field name, e.g. 'to'
628 * @param body Header field body, e.g. 'john@example.com'
629 * @param user_hdrs If true, save into the Envelope's userhdrs
630 * @param weed If true, perform header weeding (filtering)
631 * @param do_2047 If true, perform RFC2047 decoding of the field
632 * @retval 1 The field is recognised
633 * @retval 0 The field is not recognised
634 *
635 * Process a line from an email header. Each line that is recognised is parsed
636 * and the information put in the Envelope or Header.
637 */
mutt_rfc822_parse_line(struct Envelope * env,struct Email * e,const char * name,const char * body,bool user_hdrs,bool weed,bool do_2047)638 int mutt_rfc822_parse_line(struct Envelope *env, struct Email *e, const char *name,
639 const char *body, bool user_hdrs, bool weed, bool do_2047)
640 {
641 if (!env || !name)
642 return 0;
643
644 bool matched = false;
645
646 switch (tolower(name[0]))
647 {
648 case 'a':
649 if (mutt_istr_equal(name + 1, "pparently-to"))
650 {
651 mutt_addrlist_parse(&env->to, body);
652 matched = true;
653 }
654 else if (mutt_istr_equal(name + 1, "pparently-from"))
655 {
656 mutt_addrlist_parse(&env->from, body);
657 matched = true;
658 }
659 break;
660
661 case 'b':
662 if (mutt_istr_equal(name + 1, "cc"))
663 {
664 mutt_addrlist_parse(&env->bcc, body);
665 matched = true;
666 }
667 break;
668
669 case 'c':
670 if (mutt_istr_equal(name + 1, "c"))
671 {
672 mutt_addrlist_parse(&env->cc, body);
673 matched = true;
674 }
675 else
676 {
677 size_t plen = mutt_istr_startswith(name + 1, "ontent-");
678 if (plen != 0)
679 {
680 if (mutt_istr_equal(name + 1 + plen, "type"))
681 {
682 if (e)
683 mutt_parse_content_type(body, e->body);
684 matched = true;
685 }
686 else if (mutt_istr_equal(name + 1 + plen, "language"))
687 {
688 if (e)
689 parse_content_language(body, e->body);
690 matched = true;
691 }
692 else if (mutt_istr_equal(name + 1 + plen, "transfer-encoding"))
693 {
694 if (e)
695 e->body->encoding = mutt_check_encoding(body);
696 matched = true;
697 }
698 else if (mutt_istr_equal(name + 1 + plen, "length"))
699 {
700 if (e)
701 {
702 int rc = mutt_str_atol(body, (long *) &e->body->length);
703 if ((rc < 0) || (e->body->length < 0))
704 e->body->length = -1;
705 if (e->body->length > CONTENT_TOO_BIG)
706 e->body->length = CONTENT_TOO_BIG;
707 }
708 matched = true;
709 }
710 else if (mutt_istr_equal(name + 1 + plen, "description"))
711 {
712 if (e)
713 {
714 mutt_str_replace(&e->body->description, body);
715 rfc2047_decode(&e->body->description);
716 }
717 matched = true;
718 }
719 else if (mutt_istr_equal(name + 1 + plen, "disposition"))
720 {
721 if (e)
722 parse_content_disposition(body, e->body);
723 matched = true;
724 }
725 }
726 }
727 break;
728
729 case 'd':
730 if (!mutt_istr_equal("ate", name + 1))
731 break;
732
733 mutt_str_replace(&env->date, body);
734 if (e)
735 {
736 struct Tz tz;
737 e->date_sent = mutt_date_parse_date(body, &tz);
738 if (e->date_sent > 0)
739 {
740 e->zhours = tz.zhours;
741 e->zminutes = tz.zminutes;
742 e->zoccident = tz.zoccident;
743 }
744 }
745 matched = true;
746 break;
747
748 case 'e':
749 if (mutt_istr_equal("xpires", name + 1) && e &&
750 (mutt_date_parse_date(body, NULL) < mutt_date_epoch()))
751 {
752 e->expired = true;
753 }
754 break;
755
756 case 'f':
757 if (mutt_istr_equal("rom", name + 1))
758 {
759 mutt_addrlist_parse(&env->from, body);
760 matched = true;
761 }
762 #ifdef USE_NNTP
763 else if (mutt_istr_equal(name + 1, "ollowup-to"))
764 {
765 if (!env->followup_to)
766 {
767 env->followup_to = mutt_str_dup(mutt_str_skip_whitespace(body));
768 mutt_str_remove_trailing_ws(env->followup_to);
769 }
770 matched = true;
771 }
772 #endif
773 break;
774
775 case 'i':
776 if (!mutt_istr_equal(name + 1, "n-reply-to"))
777 break;
778
779 mutt_list_free(&env->in_reply_to);
780 parse_references(&env->in_reply_to, body);
781 matched = true;
782 break;
783
784 case 'l':
785 if (mutt_istr_equal(name + 1, "ines"))
786 {
787 if (e)
788 {
789 /* HACK - neomutt has, for a very short time, produced negative
790 * Lines header values. Ignore them. */
791 if ((mutt_str_atoi(body, &e->lines) < 0) || (e->lines < 0))
792 e->lines = 0;
793 }
794
795 matched = true;
796 }
797 else if (mutt_istr_equal(name + 1, "ist-Post"))
798 {
799 /* RFC2369 */
800 if (!mutt_strn_equal(mutt_str_skip_whitespace(body), "NO", 2))
801 {
802 char *mailto = rfc2369_first_mailto(body);
803 if (mailto)
804 {
805 FREE(&env->list_post);
806 env->list_post = mailto;
807 const bool c_auto_subscribe =
808 cs_subset_bool(NeoMutt->sub, "auto_subscribe");
809 if (c_auto_subscribe)
810 mutt_auto_subscribe(env->list_post);
811 }
812 }
813 matched = true;
814 }
815 else if (mutt_istr_equal(name + 1, "ist-Subscribe"))
816 {
817 /* RFC2369 */
818 char *mailto = rfc2369_first_mailto(body);
819 if (mailto)
820 {
821 FREE(&env->list_subscribe);
822 env->list_subscribe = mailto;
823 }
824 matched = true;
825 }
826 else if (mutt_istr_equal(name + 1, "ist-Unsubscribe"))
827 {
828 /* RFC2369 */
829 char *mailto = rfc2369_first_mailto(body);
830 if (mailto)
831 {
832 FREE(&env->list_unsubscribe);
833 env->list_unsubscribe = mailto;
834 }
835 matched = true;
836 }
837 break;
838
839 case 'm':
840 if (mutt_istr_equal(name + 1, "ime-version"))
841 {
842 if (e)
843 e->mime = true;
844 matched = true;
845 }
846 else if (mutt_istr_equal(name + 1, "essage-id"))
847 {
848 /* We add a new "Message-ID:" when building a message */
849 FREE(&env->message_id);
850 env->message_id = mutt_extract_message_id(body, NULL);
851 matched = true;
852 }
853 else
854 {
855 size_t plen = mutt_istr_startswith(name + 1, "ail-");
856 if (plen != 0)
857 {
858 if (mutt_istr_equal(name + 1 + plen, "reply-to"))
859 {
860 /* override the Reply-To: field */
861 mutt_addrlist_clear(&env->reply_to);
862 mutt_addrlist_parse(&env->reply_to, body);
863 matched = true;
864 }
865 else if (mutt_istr_equal(name + 1 + plen, "followup-to"))
866 {
867 mutt_addrlist_parse(&env->mail_followup_to, body);
868 matched = true;
869 }
870 }
871 }
872 break;
873
874 #ifdef USE_NNTP
875 case 'n':
876 if (mutt_istr_equal(name + 1, "ewsgroups"))
877 {
878 FREE(&env->newsgroups);
879 env->newsgroups = mutt_str_dup(mutt_str_skip_whitespace(body));
880 mutt_str_remove_trailing_ws(env->newsgroups);
881 matched = true;
882 }
883 break;
884 #endif
885
886 case 'o':
887 /* field 'Organization:' saves only for pager! */
888 if (mutt_istr_equal(name + 1, "rganization"))
889 {
890 if (!env->organization && !mutt_istr_equal(body, "unknown"))
891 env->organization = mutt_str_dup(body);
892 }
893 break;
894
895 case 'r':
896 if (mutt_istr_equal(name + 1, "eferences"))
897 {
898 mutt_list_free(&env->references);
899 parse_references(&env->references, body);
900 matched = true;
901 }
902 else if (mutt_istr_equal(name + 1, "eply-to"))
903 {
904 mutt_addrlist_parse(&env->reply_to, body);
905 matched = true;
906 }
907 else if (mutt_istr_equal(name + 1, "eturn-path"))
908 {
909 mutt_addrlist_parse(&env->return_path, body);
910 matched = true;
911 }
912 else if (mutt_istr_equal(name + 1, "eceived"))
913 {
914 if (e && !e->received)
915 {
916 char *d = strrchr(body, ';');
917 if (d)
918 {
919 d = mutt_str_skip_email_wsp(d + 1);
920 e->received = mutt_date_parse_date(d, NULL);
921 }
922 }
923 }
924 break;
925
926 case 's':
927 if (mutt_istr_equal(name + 1, "ubject"))
928 {
929 if (!env->subject)
930 env->subject = mutt_str_dup(body);
931 matched = true;
932 }
933 else if (mutt_istr_equal(name + 1, "ender"))
934 {
935 mutt_addrlist_parse(&env->sender, body);
936 matched = true;
937 }
938 else if (mutt_istr_equal(name + 1, "tatus"))
939 {
940 if (e)
941 {
942 while (*body)
943 {
944 switch (*body)
945 {
946 case 'O':
947 {
948 const bool c_mark_old =
949 cs_subset_bool(NeoMutt->sub, "mark_old");
950 e->old = c_mark_old;
951 break;
952 }
953 case 'R':
954 e->read = true;
955 break;
956 case 'r':
957 e->replied = true;
958 break;
959 }
960 body++;
961 }
962 }
963 matched = true;
964 }
965 else if (e && (mutt_istr_equal("upersedes", name + 1) ||
966 mutt_istr_equal("upercedes", name + 1)))
967 {
968 FREE(&env->supersedes);
969 env->supersedes = mutt_str_dup(body);
970 }
971 break;
972
973 case 't':
974 if (mutt_istr_equal(name + 1, "o"))
975 {
976 mutt_addrlist_parse(&env->to, body);
977 matched = true;
978 }
979 #ifdef USE_AUTOCRYPT
980 else if (mutt_istr_equal(name + 1, "utocrypt"))
981 {
982 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
983 if (c_autocrypt)
984 {
985 env->autocrypt = parse_autocrypt(env->autocrypt, body);
986 matched = true;
987 }
988 }
989 else if (mutt_istr_equal(name + 1, "utocrypt-gossip"))
990 {
991 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
992 if (c_autocrypt)
993 {
994 env->autocrypt_gossip = parse_autocrypt(env->autocrypt_gossip, body);
995 matched = true;
996 }
997 }
998 #endif
999 break;
1000
1001 case 'x':
1002 if (mutt_istr_equal(name + 1, "-status"))
1003 {
1004 if (e)
1005 {
1006 while (*body)
1007 {
1008 switch (*body)
1009 {
1010 case 'A':
1011 e->replied = true;
1012 break;
1013 case 'D':
1014 e->deleted = true;
1015 break;
1016 case 'F':
1017 e->flagged = true;
1018 break;
1019 default:
1020 break;
1021 }
1022 body++;
1023 }
1024 }
1025 matched = true;
1026 }
1027 else if (mutt_istr_equal(name + 1, "-label"))
1028 {
1029 FREE(&env->x_label);
1030 env->x_label = mutt_str_dup(body);
1031 matched = true;
1032 }
1033 #ifdef USE_NNTP
1034 else if (mutt_istr_equal(name + 1, "-comment-to"))
1035 {
1036 if (!env->x_comment_to)
1037 env->x_comment_to = mutt_str_dup(body);
1038 matched = true;
1039 }
1040 else if (mutt_istr_equal(name + 1, "ref"))
1041 {
1042 if (!env->xref)
1043 env->xref = mutt_str_dup(body);
1044 matched = true;
1045 }
1046 #endif
1047 else if (mutt_istr_equal(name + 1, "-original-to"))
1048 {
1049 mutt_addrlist_parse(&env->x_original_to, body);
1050 matched = true;
1051 }
1052 break;
1053
1054 default:
1055 break;
1056 }
1057
1058 /* Keep track of the user-defined headers */
1059 if (!matched && user_hdrs)
1060 {
1061 const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
1062 char *dup = NULL;
1063 mutt_str_asprintf(&dup, "%s: %s", name, body);
1064
1065 if (!weed || !c_weed || !mutt_matches_ignore(dup))
1066 {
1067 struct ListNode *np = mutt_list_insert_tail(&env->userhdrs, dup);
1068 if (do_2047)
1069 {
1070 rfc2047_decode(&np->data);
1071 }
1072 }
1073 else
1074 {
1075 FREE(&dup);
1076 }
1077 }
1078
1079 return matched;
1080 }
1081
1082 /**
1083 * mutt_rfc822_read_line - Read a header line from a file
1084 * @param fp File to read from
1085 * @param line Buffer to store the result
1086 * @param linelen Length of buffer
1087 * @retval ptr Line read from file
1088 *
1089 * Reads an arbitrarily long header field, and looks ahead for continuation
1090 * lines. "line" must point to a dynamically allocated string; it is
1091 * increased if more space is required to fit the whole line.
1092 */
mutt_rfc822_read_line(FILE * fp,char * line,size_t * linelen)1093 char *mutt_rfc822_read_line(FILE *fp, char *line, size_t *linelen)
1094 {
1095 if (!fp || !line || !linelen)
1096 return NULL;
1097
1098 char *buf = line;
1099 int ch;
1100 size_t offset = 0;
1101
1102 while (true)
1103 {
1104 if (!fgets(buf, *linelen - offset, fp) || /* end of file or */
1105 (IS_SPACE(*line) && !offset)) /* end of headers */
1106 {
1107 *line = '\0';
1108 return line;
1109 }
1110
1111 const size_t len = mutt_str_len(buf);
1112 if (len == 0)
1113 return line;
1114
1115 buf += len - 1;
1116 if (*buf == '\n')
1117 {
1118 /* we did get a full line. remove trailing space */
1119 while (IS_SPACE(*buf))
1120 {
1121 *buf-- = '\0'; /* we can't come beyond line's beginning because
1122 * it begins with a non-space */
1123 }
1124
1125 /* check to see if the next line is a continuation line */
1126 ch = fgetc(fp);
1127 if ((ch != ' ') && (ch != '\t'))
1128 {
1129 ungetc(ch, fp);
1130 return line; /* next line is a separate header field or EOH */
1131 }
1132
1133 /* eat tabs and spaces from the beginning of the continuation line */
1134 while (((ch = fgetc(fp)) == ' ') || (ch == '\t'))
1135 ; // do nothing
1136
1137 ungetc(ch, fp);
1138 *++buf = ' '; /* string is still terminated because we removed
1139 at least one whitespace char above */
1140 }
1141
1142 buf++;
1143 offset = buf - line;
1144 if (*linelen < (offset + 256))
1145 {
1146 /* grow the buffer */
1147 *linelen += 256;
1148 mutt_mem_realloc(&line, *linelen);
1149 buf = line + offset;
1150 }
1151 }
1152 /* not reached */
1153 }
1154
1155 /**
1156 * mutt_rfc822_read_header - Parses an RFC822 header
1157 * @param fp Stream to read from
1158 * @param e Current Email (optional)
1159 * @param user_hdrs If set, store user headers
1160 * Used for recall-message and postpone modes
1161 * @param weed If this parameter is set and the user has activated the
1162 * $weed option, honor the header weed list for user headers.
1163 * Used for recall-message
1164 * @retval ptr Newly allocated envelope structure
1165 *
1166 * Caller should free the Envelope using mutt_env_free().
1167 */
mutt_rfc822_read_header(FILE * fp,struct Email * e,bool user_hdrs,bool weed)1168 struct Envelope *mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
1169 {
1170 if (!fp)
1171 return NULL;
1172
1173 struct Envelope *env = mutt_env_new();
1174 char *p = NULL;
1175 LOFF_T loc;
1176 size_t linelen = 1024;
1177 char *line = mutt_mem_malloc(linelen);
1178 char buf[linelen + 1];
1179
1180 if (e)
1181 {
1182 if (!e->body)
1183 {
1184 e->body = mutt_body_new();
1185
1186 /* set the defaults from RFC1521 */
1187 e->body->type = TYPE_TEXT;
1188 e->body->subtype = mutt_str_dup("plain");
1189 e->body->encoding = ENC_7BIT;
1190 e->body->length = -1;
1191
1192 /* RFC2183 says this is arbitrary */
1193 e->body->disposition = DISP_INLINE;
1194 }
1195 }
1196
1197 while ((loc = ftello(fp)) != -1)
1198 {
1199 line = mutt_rfc822_read_line(fp, line, &linelen);
1200 if (*line == '\0')
1201 break;
1202 p = strpbrk(line, ": \t");
1203 if (!p || (*p != ':'))
1204 {
1205 char return_path[1024];
1206 time_t t;
1207
1208 /* some bogus MTAs will quote the original "From " line */
1209 if (mutt_str_startswith(line, ">From "))
1210 continue; /* just ignore */
1211 else if (is_from(line, return_path, sizeof(return_path), &t))
1212 {
1213 /* MH sometimes has the From_ line in the middle of the header! */
1214 if (e && !e->received)
1215 e->received = t - mutt_date_local_tz(t);
1216 continue;
1217 }
1218
1219 (void) fseeko(fp, loc, SEEK_SET);
1220 break; /* end of header */
1221 }
1222
1223 *buf = '\0';
1224
1225 if (mutt_replacelist_match(&SpamList, buf, sizeof(buf), line))
1226 {
1227 if (!mutt_regexlist_match(&NoSpamList, line))
1228 {
1229 /* if spam tag already exists, figure out how to amend it */
1230 if ((!mutt_buffer_is_empty(&env->spam)) && (*buf != '\0'))
1231 {
1232 /* If `$spam_separator` defined, append with separator */
1233 const char *const c_spam_separator =
1234 cs_subset_string(NeoMutt->sub, "spam_separator");
1235 if (c_spam_separator)
1236 {
1237 mutt_buffer_addstr(&env->spam, c_spam_separator);
1238 mutt_buffer_addstr(&env->spam, buf);
1239 }
1240 else /* overwrite */
1241 {
1242 mutt_buffer_reset(&env->spam);
1243 mutt_buffer_addstr(&env->spam, buf);
1244 }
1245 }
1246
1247 /* spam tag is new, and match expr is non-empty; copy */
1248 else if (mutt_buffer_is_empty(&env->spam) && (*buf != '\0'))
1249 {
1250 mutt_buffer_addstr(&env->spam, buf);
1251 }
1252
1253 /* match expr is empty; plug in null string if no existing tag */
1254 else if (mutt_buffer_is_empty(&env->spam))
1255 {
1256 mutt_buffer_addstr(&env->spam, "");
1257 }
1258
1259 if (!mutt_buffer_is_empty(&env->spam))
1260 mutt_debug(LL_DEBUG5, "spam = %s\n", env->spam.data);
1261 }
1262 }
1263
1264 *p = '\0';
1265 p = mutt_str_skip_email_wsp(p + 1);
1266 if (*p == '\0')
1267 continue; /* skip empty header fields */
1268
1269 mutt_rfc822_parse_line(env, e, line, p, user_hdrs, weed, true);
1270 }
1271
1272 FREE(&line);
1273
1274 if (e)
1275 {
1276 e->body->hdr_offset = e->offset;
1277 e->body->offset = ftello(fp);
1278
1279 rfc2047_decode_envelope(env);
1280
1281 if (env->subject)
1282 {
1283 regmatch_t pmatch[1];
1284
1285 const struct Regex *c_reply_regex =
1286 cs_subset_regex(NeoMutt->sub, "reply_regex");
1287 if (mutt_regex_capture(c_reply_regex, env->subject, 1, pmatch))
1288 {
1289 env->real_subj = env->subject + pmatch[0].rm_eo;
1290 if (env->real_subj[0] == '\0')
1291 env->real_subj = NULL;
1292 }
1293 else
1294 env->real_subj = env->subject;
1295 }
1296
1297 if (e->received < 0)
1298 {
1299 mutt_debug(LL_DEBUG1, "resetting invalid received time to 0\n");
1300 e->received = 0;
1301 }
1302
1303 /* check for missing or invalid date */
1304 if (e->date_sent <= 0)
1305 {
1306 mutt_debug(LL_DEBUG1,
1307 "no date found, using received time from msg separator\n");
1308 e->date_sent = e->received;
1309 }
1310
1311 #ifdef USE_AUTOCRYPT
1312 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
1313 if (c_autocrypt)
1314 {
1315 mutt_autocrypt_process_autocrypt_header(e, env);
1316 /* No sense in taking up memory after the header is processed */
1317 mutt_autocrypthdr_free(&env->autocrypt);
1318 }
1319 #endif
1320 }
1321
1322 return env;
1323 }
1324
1325 /**
1326 * mutt_read_mime_header - Parse a MIME header
1327 * @param fp stream to read from
1328 * @param digest true if reading subparts of a multipart/digest
1329 * @retval ptr New Body containing parsed structure
1330 */
mutt_read_mime_header(FILE * fp,bool digest)1331 struct Body *mutt_read_mime_header(FILE *fp, bool digest)
1332 {
1333 if (!fp)
1334 return NULL;
1335
1336 struct Body *p = mutt_body_new();
1337 struct Envelope *env = mutt_env_new();
1338 char *c = NULL;
1339 size_t linelen = 1024;
1340 char *line = mutt_mem_malloc(linelen);
1341
1342 p->hdr_offset = ftello(fp);
1343
1344 p->encoding = ENC_7BIT; /* default from RFC1521 */
1345 p->type = digest ? TYPE_MESSAGE : TYPE_TEXT;
1346 p->disposition = DISP_INLINE;
1347
1348 while (*(line = mutt_rfc822_read_line(fp, line, &linelen)) != 0)
1349 {
1350 /* Find the value of the current header */
1351 c = strchr(line, ':');
1352 if (c)
1353 {
1354 *c = '\0';
1355 c = mutt_str_skip_email_wsp(c + 1);
1356 if (*c == '\0')
1357 {
1358 mutt_debug(LL_DEBUG1, "skipping empty header field: %s\n", line);
1359 continue;
1360 }
1361 }
1362 else
1363 {
1364 mutt_debug(LL_DEBUG1, "bogus MIME header: %s\n", line);
1365 break;
1366 }
1367
1368 size_t plen = mutt_istr_startswith(line, "content-");
1369 if (plen != 0)
1370 {
1371 if (mutt_istr_equal("type", line + plen))
1372 mutt_parse_content_type(c, p);
1373 else if (mutt_istr_equal("language", line + plen))
1374 parse_content_language(c, p);
1375 else if (mutt_istr_equal("transfer-encoding", line + plen))
1376 p->encoding = mutt_check_encoding(c);
1377 else if (mutt_istr_equal("disposition", line + plen))
1378 parse_content_disposition(c, p);
1379 else if (mutt_istr_equal("description", line + plen))
1380 {
1381 mutt_str_replace(&p->description, c);
1382 rfc2047_decode(&p->description);
1383 }
1384 }
1385 #ifdef SUN_ATTACHMENT
1386 else if ((plen = mutt_istr_startswith(line, "x-sun-")))
1387 {
1388 if (mutt_istr_equal("data-type", line + plen))
1389 mutt_parse_content_type(c, p);
1390 else if (mutt_istr_equal("encoding-info", line + plen))
1391 p->encoding = mutt_check_encoding(c);
1392 else if (mutt_istr_equal("content-lines", line + plen))
1393 mutt_param_set(&p->parameter, "content-lines", c);
1394 else if (mutt_istr_equal("data-description", line + plen))
1395 {
1396 mutt_str_replace(&p->description, c);
1397 rfc2047_decode(&p->description);
1398 }
1399 }
1400 #endif
1401 else
1402 {
1403 if (mutt_rfc822_parse_line(env, NULL, line, c, false, false, false))
1404 p->mime_headers = env;
1405 }
1406 }
1407 p->offset = ftello(fp); /* Mark the start of the real data */
1408 if ((p->type == TYPE_TEXT) && !p->subtype)
1409 p->subtype = mutt_str_dup("plain");
1410 else if ((p->type == TYPE_MESSAGE) && !p->subtype)
1411 p->subtype = mutt_str_dup("rfc822");
1412
1413 FREE(&line);
1414
1415 if (p->mime_headers)
1416 rfc2047_decode_envelope(p->mime_headers);
1417 else
1418 mutt_env_free(&env);
1419
1420 return p;
1421 }
1422
1423 /**
1424 * mutt_is_message_type - Determine if a mime type matches a message or not
1425 * @param type Message type enum value
1426 * @param subtype Message subtype
1427 * @retval true Type is message/news or message/rfc822
1428 * @retval false Otherwise
1429 */
mutt_is_message_type(int type,const char * subtype)1430 bool mutt_is_message_type(int type, const char *subtype)
1431 {
1432 if (type != TYPE_MESSAGE)
1433 return false;
1434
1435 subtype = NONULL(subtype);
1436 return (mutt_istr_equal(subtype, "rfc822") ||
1437 mutt_istr_equal(subtype, "news") || mutt_istr_equal(subtype, "global"));
1438 }
1439
1440 /**
1441 * parse_part - Parse a MIME part
1442 * @param fp File to read from
1443 * @param b Body to store the results in
1444 * @param counter Number of parts processed so far
1445 */
parse_part(FILE * fp,struct Body * b,int * counter)1446 static void parse_part(FILE *fp, struct Body *b, int *counter)
1447 {
1448 if (!fp || !b)
1449 return;
1450
1451 const char *bound = NULL;
1452 static unsigned short recurse_level = 0;
1453
1454 if (recurse_level >= MUTT_MIME_MAX_DEPTH)
1455 {
1456 mutt_debug(LL_DEBUG1, "recurse level too deep. giving up.\n");
1457 return;
1458 }
1459 recurse_level++;
1460
1461 switch (b->type)
1462 {
1463 case TYPE_MULTIPART:
1464 #ifdef SUN_ATTACHMENT
1465 if (mutt_istr_equal(b->subtype, "x-sun-attachment"))
1466 bound = "--------";
1467 else
1468 #endif
1469 bound = mutt_param_get(&b->parameter, "boundary");
1470
1471 fseeko(fp, b->offset, SEEK_SET);
1472 b->parts = parse_multipart(fp, bound, b->offset + b->length,
1473 mutt_istr_equal("digest", b->subtype), counter);
1474 break;
1475
1476 case TYPE_MESSAGE:
1477 if (!b->subtype)
1478 break;
1479
1480 fseeko(fp, b->offset, SEEK_SET);
1481 if (mutt_is_message_type(b->type, b->subtype))
1482 b->parts = rfc822_parse_message(fp, b, counter);
1483 else if (mutt_istr_equal(b->subtype, "external-body"))
1484 b->parts = mutt_read_mime_header(fp, 0);
1485 else
1486 goto bail;
1487 break;
1488
1489 default:
1490 goto bail;
1491 }
1492
1493 /* try to recover from parsing error */
1494 if (!b->parts)
1495 {
1496 b->type = TYPE_TEXT;
1497 mutt_str_replace(&b->subtype, "plain");
1498 }
1499 bail:
1500 recurse_level--;
1501 }
1502
1503 /**
1504 * parse_multipart - Parse a multipart structure
1505 * @param fp Stream to read from
1506 * @param boundary Body separator
1507 * @param end_off Length of the multipart body (used when the final
1508 * boundary is missing to avoid reading too far)
1509 * @param digest true if reading a multipart/digest
1510 * @param counter Number of parts processed so far
1511 * @retval ptr New Body containing parsed structure
1512 */
parse_multipart(FILE * fp,const char * boundary,LOFF_T end_off,bool digest,int * counter)1513 static struct Body *parse_multipart(FILE *fp, const char *boundary,
1514 LOFF_T end_off, bool digest, int *counter)
1515 {
1516 if (!fp)
1517 return NULL;
1518
1519 if (!boundary)
1520 {
1521 mutt_error(_("multipart message has no boundary parameter"));
1522 return NULL;
1523 }
1524
1525 char buf[1024];
1526 struct Body *head = NULL, *last = NULL, *new_body = NULL;
1527 bool final = false; /* did we see the ending boundary? */
1528
1529 const size_t blen = mutt_str_len(boundary);
1530 while ((ftello(fp) < end_off) && fgets(buf, sizeof(buf), fp))
1531 {
1532 const size_t len = mutt_str_len(buf);
1533
1534 const size_t crlf = ((len > 1) && (buf[len - 2] == '\r')) ? 1 : 0;
1535
1536 if ((buf[0] == '-') && (buf[1] == '-') && mutt_str_startswith(buf + 2, boundary))
1537 {
1538 if (last)
1539 {
1540 last->length = ftello(fp) - last->offset - len - 1 - crlf;
1541 if (last->parts && (last->parts->length == 0))
1542 last->parts->length = ftello(fp) - last->parts->offset - len - 1 - crlf;
1543 /* if the body is empty, we can end up with a -1 length */
1544 if (last->length < 0)
1545 last->length = 0;
1546 }
1547
1548 if (len > 0)
1549 {
1550 /* Remove any trailing whitespace, up to the length of the boundary */
1551 for (size_t i = len - 1; IS_SPACE(buf[i]) && (i >= (blen + 2)); i--)
1552 buf[i] = '\0';
1553 }
1554
1555 /* Check for the end boundary */
1556 if (mutt_str_equal(buf + blen + 2, "--"))
1557 {
1558 final = true;
1559 break; /* done parsing */
1560 }
1561 else if (buf[2 + blen] == '\0')
1562 {
1563 new_body = mutt_read_mime_header(fp, digest);
1564
1565 #ifdef SUN_ATTACHMENT
1566 if (mutt_param_get(&new_body->parameter, "content-lines"))
1567 {
1568 int lines = 0;
1569 if (mutt_str_atoi(
1570 mutt_param_get(&new_body->parameter, "content-lines"), &lines) < 0)
1571 {
1572 lines = 0;
1573 }
1574 for (; lines > 0; lines--)
1575 if ((ftello(fp) >= end_off) || !fgets(buf, sizeof(buf), fp))
1576 break;
1577 }
1578 #endif
1579 /* Consistency checking - catch bad attachment end boundaries */
1580 if (new_body->offset > end_off)
1581 {
1582 mutt_body_free(&new_body);
1583 break;
1584 }
1585 if (head)
1586 {
1587 last->next = new_body;
1588 last = new_body;
1589 }
1590 else
1591 {
1592 last = new_body;
1593 head = new_body;
1594 }
1595
1596 /* It seems more intuitive to add the counter increment to
1597 * parse_part(), but we want to stop the case where a multipart
1598 * contains thousands of tiny parts before the memory and data
1599 * structures are allocated. */
1600 if (++(*counter) >= MUTT_MIME_MAX_PARTS)
1601 break;
1602 }
1603 }
1604 }
1605
1606 /* in case of missing end boundary, set the length to something reasonable */
1607 if (last && (last->length == 0) && !final)
1608 last->length = end_off - last->offset;
1609
1610 /* parse recursive MIME parts */
1611 for (last = head; last; last = last->next)
1612 parse_part(fp, last, counter);
1613
1614 return head;
1615 }
1616
1617 /**
1618 * rfc822_parse_message - Parse a Message/RFC822 body
1619 * @param fp Stream to read from
1620 * @param parent Info about the message/rfc822 body part
1621 * @param counter Number of parts processed so far
1622 * @retval ptr New Body containing parsed message
1623 *
1624 * @note This assumes that 'parent->length' has been set!
1625 */
rfc822_parse_message(FILE * fp,struct Body * parent,int * counter)1626 static struct Body *rfc822_parse_message(FILE *fp, struct Body *parent, int *counter)
1627 {
1628 if (!fp || !parent)
1629 return NULL;
1630
1631 parent->email = email_new();
1632 parent->email->offset = ftello(fp);
1633 parent->email->env = mutt_rfc822_read_header(fp, parent->email, false, false);
1634 struct Body *msg = parent->email->body;
1635
1636 /* ignore the length given in the content-length since it could be wrong
1637 * and we already have the info to calculate the correct length */
1638 /* if (msg->length == -1) */
1639 msg->length = parent->length - (msg->offset - parent->offset);
1640
1641 /* if body of this message is empty, we can end up with a negative length */
1642 if (msg->length < 0)
1643 msg->length = 0;
1644
1645 parse_part(fp, msg, counter);
1646 return msg;
1647 }
1648
1649 /**
1650 * mutt_parse_mailto - Parse a mailto:// url
1651 * @param[in] e Envelope to fill
1652 * @param[out] body Body to
1653 * @param[in] src String to parse
1654 * @retval true Success
1655 * @retval false Error
1656 */
mutt_parse_mailto(struct Envelope * e,char ** body,const char * src)1657 bool mutt_parse_mailto(struct Envelope *e, char **body, const char *src)
1658 {
1659 if (!e || !src)
1660 return false;
1661
1662 struct Url *url = url_parse(src);
1663 if (!url)
1664 return false;
1665
1666 if (url->host)
1667 {
1668 /* this is not a path-only URL */
1669 url_free(&url);
1670 return false;
1671 }
1672
1673 mutt_addrlist_parse(&e->to, url->path);
1674
1675 struct UrlQuery *np;
1676 STAILQ_FOREACH(np, &url->query_strings, entries)
1677 {
1678 const char *tag = np->name;
1679 char *value = np->value;
1680 /* Determine if this header field is on the allowed list. Since NeoMutt
1681 * interprets some header fields specially (such as
1682 * "Attach: ~/.gnupg/secring.gpg"), care must be taken to ensure that
1683 * only safe fields are allowed.
1684 *
1685 * RFC2368, "4. Unsafe headers"
1686 * The user agent interpreting a mailto URL SHOULD choose not to create
1687 * a message if any of the headers are considered dangerous; it may also
1688 * choose to create a message with only a subset of the headers given in
1689 * the URL. */
1690 if (mutt_list_match(tag, &MailToAllow))
1691 {
1692 if (mutt_istr_equal(tag, "body"))
1693 {
1694 if (body)
1695 mutt_str_replace(body, value);
1696 }
1697 else
1698 {
1699 char *scratch = NULL;
1700 size_t taglen = mutt_str_len(tag);
1701
1702 mutt_str_asprintf(&scratch, "%s: %s", tag, value);
1703 scratch[taglen] = 0; /* overwrite the colon as mutt_rfc822_parse_line expects */
1704 value = mutt_str_skip_email_wsp(&scratch[taglen + 1]);
1705 mutt_rfc822_parse_line(e, NULL, scratch, value, true, false, true);
1706 FREE(&scratch);
1707 }
1708 }
1709 }
1710
1711 /* RFC2047 decode after the RFC822 parsing */
1712 rfc2047_decode_envelope(e);
1713
1714 url_free(&url);
1715 return true;
1716 }
1717
1718 /**
1719 * mutt_parse_part - Parse a MIME part
1720 * @param fp File to read from
1721 * @param b Body to store the results in
1722 */
mutt_parse_part(FILE * fp,struct Body * b)1723 void mutt_parse_part(FILE *fp, struct Body *b)
1724 {
1725 int counter = 0;
1726
1727 parse_part(fp, b, &counter);
1728 }
1729
1730 /**
1731 * mutt_rfc822_parse_message - Parse a Message/RFC822 body
1732 * @param fp Stream to read from
1733 * @param parent Info about the message/rfc822 body part
1734 * @retval ptr New Body containing parsed message
1735 *
1736 * @note This assumes that 'parent->length' has been set!
1737 */
mutt_rfc822_parse_message(FILE * fp,struct Body * parent)1738 struct Body *mutt_rfc822_parse_message(FILE *fp, struct Body *parent)
1739 {
1740 int counter = 0;
1741
1742 return rfc822_parse_message(fp, parent, &counter);
1743 }
1744
1745 /**
1746 * mutt_parse_multipart - Parse a multipart structure
1747 * @param fp Stream to read from
1748 * @param boundary Body separator
1749 * @param end_off Length of the multipart body (used when the final
1750 * boundary is missing to avoid reading too far)
1751 * @param digest true if reading a multipart/digest
1752 * @retval ptr New Body containing parsed structure
1753 */
mutt_parse_multipart(FILE * fp,const char * boundary,LOFF_T end_off,bool digest)1754 struct Body *mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
1755 {
1756 int counter = 0;
1757
1758 return parse_multipart(fp, boundary, end_off, digest, &counter);
1759 }
1760