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