NeoMutt  2022-04-29-145-g9b6a0e
Teaching an old dog new tricks
DOXYGEN
parse.c
Go to the documentation of this file.
1 
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 
67 void mutt_auto_subscribe(const char *mailto)
68 {
69  if (!mailto)
70  return;
71 
72  if (!AutoSubscribeCache)
74 
76  return;
77 
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) &&
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 
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)
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 struct Slist *const c_assumed_charset =
168  cs_subset_slist(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 
239 }
240 
248 static void parse_content_disposition(const char *s, struct Body *ct)
249 {
250  struct ParameterList pl = TAILQ_HEAD_INITIALIZER(pl);
251 
252  if (mutt_istr_startswith(s, "inline"))
253  ct->disposition = DISP_INLINE;
254  else if (mutt_istr_startswith(s, "form-data"))
256  else
257  ct->disposition = DISP_ATTACH;
258 
259  /* Check to see if a default filename was given */
260  s = strchr(s, ';');
261  if (s)
262  {
263  s = mutt_str_skip_email_wsp(s + 1);
264  parse_parameters(&pl, s, false);
265  s = mutt_param_get(&pl, "filename");
266  if (s)
267  mutt_str_replace(&ct->filename, s);
268  s = mutt_param_get(&pl, "name");
269  if (s)
270  mutt_str_replace(&ct->form_name, s);
271  mutt_param_free(&pl);
272  }
273 }
274 
280 static void parse_references(struct ListHead *head, const char *s)
281 {
282  if (!head)
283  return;
284 
285  char *m = NULL;
286  for (size_t off = 0; (m = mutt_extract_message_id(s, &off)); s += off)
287  {
288  mutt_list_insert_head(head, m);
289  }
290 }
291 
297 static void parse_content_language(const char *s, struct Body *ct)
298 {
299  if (!s || !ct)
300  return;
301 
302  mutt_debug(LL_DEBUG2, "RFC8255 >> Content-Language set to %s\n", s);
303  mutt_str_replace(&ct->language, s);
304 }
305 
313 bool mutt_matches_ignore(const char *s)
314 {
315  return mutt_list_match(s, &Ignore) && !mutt_list_match(s, &UnIgnore);
316 }
317 
323 enum ContentType mutt_check_mime_type(const char *s)
324 {
325  if (mutt_istr_equal("text", s))
326  return TYPE_TEXT;
327  if (mutt_istr_equal("multipart", s))
328  return TYPE_MULTIPART;
329 #ifdef SUN_ATTACHMENT
330  if (mutt_istr_equal("x-sun-attachment", s))
331  return TYPE_MULTIPART;
332 #endif
333  if (mutt_istr_equal("application", s))
334  return TYPE_APPLICATION;
335  if (mutt_istr_equal("message", s))
336  return TYPE_MESSAGE;
337  if (mutt_istr_equal("image", s))
338  return TYPE_IMAGE;
339  if (mutt_istr_equal("audio", s))
340  return TYPE_AUDIO;
341  if (mutt_istr_equal("video", s))
342  return TYPE_VIDEO;
343  if (mutt_istr_equal("model", s))
344  return TYPE_MODEL;
345  if (mutt_istr_equal("*", s))
346  return TYPE_ANY;
347  if (mutt_istr_equal(".*", s))
348  return TYPE_ANY;
349 
350  return TYPE_OTHER;
351 }
352 
360 char *mutt_extract_message_id(const char *s, size_t *len)
361 {
362  if (!s || (*s == '\0'))
363  return NULL;
364 
365  char *decoded = mutt_str_dup(s);
366  rfc2047_decode(&decoded);
367 
368  char *res = NULL;
369 
370  for (const char *p = decoded, *beg = NULL; *p; p++)
371  {
372  if (*p == '<')
373  {
374  beg = p;
375  continue;
376  }
377 
378  if (beg && (*p == '>'))
379  {
380  if (len)
381  *len = p - decoded + 1;
382  res = mutt_strn_dup(beg, (p + 1) - beg);
383  break;
384  }
385  }
386 
387  FREE(&decoded);
388  return res;
389 }
390 
396 int mutt_check_encoding(const char *c)
397 {
398  if (mutt_istr_startswith(c, "7bit"))
399  return ENC_7BIT;
400  if (mutt_istr_startswith(c, "8bit"))
401  return ENC_8BIT;
402  if (mutt_istr_startswith(c, "binary"))
403  return ENC_BINARY;
404  if (mutt_istr_startswith(c, "quoted-printable"))
405  return ENC_QUOTED_PRINTABLE;
406  if (mutt_istr_startswith(c, "base64"))
407  return ENC_BASE64;
408  if (mutt_istr_startswith(c, "x-uuencode"))
409  return ENC_UUENCODED;
410 #ifdef SUN_ATTACHMENT
411  if (mutt_istr_startswith(c, "uuencode"))
412  return ENC_UUENCODED;
413 #endif
414  return ENC_OTHER;
415 }
416 
424 void mutt_parse_content_type(const char *s, struct Body *ct)
425 {
426  if (!s || !ct)
427  return;
428 
429  FREE(&ct->subtype);
431 
432  /* First extract any existing parameters */
433  char *pc = strchr(s, ';');
434  if (pc)
435  {
436  *pc++ = 0;
437  while (*pc && IS_SPACE(*pc))
438  pc++;
439  parse_parameters(&ct->parameter, pc, false);
440 
441  /* Some pre-RFC1521 gateways still use the "name=filename" convention,
442  * but if a filename has already been set in the content-disposition,
443  * let that take precedence, and don't set it here */
444  pc = mutt_param_get(&ct->parameter, "name");
445  if (pc && !ct->filename)
446  ct->filename = mutt_str_dup(pc);
447 
448 #ifdef SUN_ATTACHMENT
449  /* this is deep and utter perversion */
450  pc = mutt_param_get(&ct->parameter, "conversions");
451  if (pc)
452  ct->encoding = mutt_check_encoding(pc);
453 #endif
454  }
455 
456  /* Now get the subtype */
457  char *subtype = strchr(s, '/');
458  if (subtype)
459  {
460  *subtype++ = '\0';
461  for (pc = subtype; *pc && !IS_SPACE(*pc) && (*pc != ';'); pc++)
462  ; // do nothing
463 
464  *pc = '\0';
465  mutt_str_replace(&ct->subtype, subtype);
466  }
467 
468  /* Finally, get the major type */
469  ct->type = mutt_check_mime_type(s);
470 
471 #ifdef SUN_ATTACHMENT
472  if (mutt_istr_equal("x-sun-attachment", s))
473  mutt_str_replace(&ct->subtype, "x-sun-attachment");
474 #endif
475 
476  if (ct->type == TYPE_OTHER)
477  {
478  mutt_str_replace(&ct->xtype, s);
479  }
480 
481  if (!ct->subtype)
482  {
483  /* Some older non-MIME mailers (i.e., mailtool, elm) have a content-type
484  * field, so we can attempt to convert the type to Body here. */
485  if (ct->type == TYPE_TEXT)
486  ct->subtype = mutt_str_dup("plain");
487  else if (ct->type == TYPE_AUDIO)
488  ct->subtype = mutt_str_dup("basic");
489  else if (ct->type == TYPE_MESSAGE)
490  ct->subtype = mutt_str_dup("rfc822");
491  else if (ct->type == TYPE_OTHER)
492  {
493  char buf[128];
494 
495  ct->type = TYPE_APPLICATION;
496  snprintf(buf, sizeof(buf), "x-%s", s);
497  ct->subtype = mutt_str_dup(buf);
498  }
499  else
500  ct->subtype = mutt_str_dup("x-unknown");
501  }
502 
503  /* Default character set for text types. */
504  if (ct->type == TYPE_TEXT)
505  {
506  pc = mutt_param_get(&ct->parameter, "charset");
507  if (pc)
508  {
509  /* Microsoft Outlook seems to think it is necessary to repeat
510  * charset=, strip it off not to confuse ourselves */
511  if (mutt_istrn_equal(pc, "charset=", sizeof("charset=") - 1))
512  mutt_param_set(&ct->parameter, "charset", pc + (sizeof("charset=") - 1));
513  }
514  else
515  {
516  mutt_param_set(&ct->parameter, "charset",
517  (const char *) mutt_ch_get_default_charset());
518  }
519  }
520 }
521 
522 #ifdef USE_AUTOCRYPT
529 static struct AutocryptHeader *parse_autocrypt(struct AutocryptHeader *head, const char *s)
530 {
531  struct AutocryptHeader *autocrypt = mutt_autocrypthdr_new();
532  autocrypt->next = head;
533 
534  struct ParameterList pl = TAILQ_HEAD_INITIALIZER(pl);
535  parse_parameters(&pl, s, true);
536  if (TAILQ_EMPTY(&pl))
537  {
538  autocrypt->invalid = true;
539  goto cleanup;
540  }
541 
542  struct Parameter *p = NULL;
543  TAILQ_FOREACH(p, &pl, entries)
544  {
545  if (mutt_istr_equal(p->attribute, "addr"))
546  {
547  if (autocrypt->addr)
548  {
549  autocrypt->invalid = true;
550  goto cleanup;
551  }
552  autocrypt->addr = p->value;
553  p->value = NULL;
554  }
555  else if (mutt_istr_equal(p->attribute, "prefer-encrypt"))
556  {
557  if (mutt_istr_equal(p->value, "mutual"))
558  autocrypt->prefer_encrypt = true;
559  }
560  else if (mutt_istr_equal(p->attribute, "keydata"))
561  {
562  if (autocrypt->keydata)
563  {
564  autocrypt->invalid = true;
565  goto cleanup;
566  }
567  autocrypt->keydata = p->value;
568  p->value = NULL;
569  }
570  else if (p->attribute && (p->attribute[0] != '_'))
571  {
572  autocrypt->invalid = true;
573  goto cleanup;
574  }
575  }
576 
577  /* Checking the addr against From, and for multiple valid headers
578  * occurs later, after all the headers are parsed. */
579  if (!autocrypt->addr || !autocrypt->keydata)
580  autocrypt->invalid = true;
581 
582 cleanup:
583  mutt_param_free(&pl);
584  return autocrypt;
585 }
586 #endif
587 
593 static char *rfc2369_first_mailto(const char *body)
594 {
595  for (const char *beg = body, *end = NULL; beg; beg = strchr(end, ','))
596  {
597  beg = strchr(beg, '<');
598  if (!beg)
599  {
600  break;
601  }
602  beg++;
603  end = strchr(beg, '>');
604  if (!end)
605  {
606  break;
607  }
608 
609  char *mlist = mutt_strn_dup(beg, end - beg);
610  if (url_check_scheme(mlist) == U_MAILTO)
611  {
612  return mlist;
613  }
614  FREE(&mlist);
615  }
616  return NULL;
617 }
618 
634 int mutt_rfc822_parse_line(struct Envelope *env, struct Email *e, const char *name,
635  const char *body, bool user_hdrs, bool weed, bool do_2047)
636 {
637  if (!env || !name)
638  return 0;
639 
640  bool matched = false;
641 
642  switch (tolower(name[0]))
643  {
644  case 'a':
645  if (mutt_istr_equal(name + 1, "pparently-to"))
646  {
647  mutt_addrlist_parse(&env->to, body);
648  matched = true;
649  }
650  else if (mutt_istr_equal(name + 1, "pparently-from"))
651  {
652  mutt_addrlist_parse(&env->from, body);
653  matched = true;
654  }
655 #ifdef USE_AUTOCRYPT
656  else if (mutt_istr_equal(name + 1, "utocrypt"))
657  {
658  const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
659  if (c_autocrypt)
660  {
661  env->autocrypt = parse_autocrypt(env->autocrypt, body);
662  matched = true;
663  }
664  }
665  else if (mutt_istr_equal(name + 1, "utocrypt-gossip"))
666  {
667  const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
668  if (c_autocrypt)
669  {
671  matched = true;
672  }
673  }
674 #endif
675  break;
676 
677  case 'b':
678  if (mutt_istr_equal(name + 1, "cc"))
679  {
680  mutt_addrlist_parse(&env->bcc, body);
681  matched = true;
682  }
683  break;
684 
685  case 'c':
686  if (mutt_istr_equal(name + 1, "c"))
687  {
688  mutt_addrlist_parse(&env->cc, body);
689  matched = true;
690  }
691  else
692  {
693  size_t plen = mutt_istr_startswith(name + 1, "ontent-");
694  if (plen != 0)
695  {
696  if (mutt_istr_equal(name + 1 + plen, "type"))
697  {
698  if (e)
699  mutt_parse_content_type(body, e->body);
700  matched = true;
701  }
702  else if (mutt_istr_equal(name + 1 + plen, "language"))
703  {
704  if (e)
705  parse_content_language(body, e->body);
706  matched = true;
707  }
708  else if (mutt_istr_equal(name + 1 + plen, "transfer-encoding"))
709  {
710  if (e)
711  e->body->encoding = mutt_check_encoding(body);
712  matched = true;
713  }
714  else if (mutt_istr_equal(name + 1 + plen, "length"))
715  {
716  if (e)
717  {
718  unsigned long len = 0;
719  e->body->length = mutt_str_atoul(body, &len) ? MIN(len, CONTENT_TOO_BIG) : -1;
720  }
721  matched = true;
722  }
723  else if (mutt_istr_equal(name + 1 + plen, "description"))
724  {
725  if (e)
726  {
727  mutt_str_replace(&e->body->description, body);
729  }
730  matched = true;
731  }
732  else if (mutt_istr_equal(name + 1 + plen, "disposition"))
733  {
734  if (e)
736  matched = true;
737  }
738  }
739  }
740  break;
741 
742  case 'd':
743  if (!mutt_istr_equal("ate", name + 1))
744  break;
745 
746  mutt_str_replace(&env->date, body);
747  if (e)
748  {
749  struct Tz tz;
750  e->date_sent = mutt_date_parse_date(body, &tz);
751  if (e->date_sent > 0)
752  {
753  e->zhours = tz.zhours;
754  e->zminutes = tz.zminutes;
755  e->zoccident = tz.zoccident;
756  }
757  }
758  matched = true;
759  break;
760 
761  case 'e':
762  if (mutt_istr_equal("xpires", name + 1) && e &&
763  (mutt_date_parse_date(body, NULL) < mutt_date_epoch()))
764  {
765  e->expired = true;
766  }
767  break;
768 
769  case 'f':
770  if (mutt_istr_equal("rom", name + 1))
771  {
772  mutt_addrlist_parse(&env->from, body);
773  matched = true;
774  }
775 #ifdef USE_NNTP
776  else if (mutt_istr_equal(name + 1, "ollowup-to"))
777  {
778  if (!env->followup_to)
779  {
782  }
783  matched = true;
784  }
785 #endif
786  break;
787 
788  case 'i':
789  if (!mutt_istr_equal(name + 1, "n-reply-to"))
790  break;
791 
793  parse_references(&env->in_reply_to, body);
794  matched = true;
795  break;
796 
797  case 'l':
798  if (mutt_istr_equal(name + 1, "ines"))
799  {
800  if (e)
801  {
802  unsigned int ui = 0; // we don't want a negative number of lines
803  mutt_str_atoui(body, &ui);
804  e->lines = ui;
805  }
806 
807  matched = true;
808  }
809  else if (mutt_istr_equal(name + 1, "ist-Post"))
810  {
811  /* RFC2369 */
812  if (!mutt_strn_equal(mutt_str_skip_whitespace(body), "NO", 2))
813  {
814  char *mailto = rfc2369_first_mailto(body);
815  if (mailto)
816  {
817  FREE(&env->list_post);
818  env->list_post = mailto;
819  const bool c_auto_subscribe = cs_subset_bool(NeoMutt->sub, "auto_subscribe");
820  if (c_auto_subscribe)
822  }
823  }
824  matched = true;
825  }
826  else if (mutt_istr_equal(name + 1, "ist-Subscribe"))
827  {
828  /* RFC2369 */
829  char *mailto = rfc2369_first_mailto(body);
830  if (mailto)
831  {
832  FREE(&env->list_subscribe);
833  env->list_subscribe = mailto;
834  }
835  matched = true;
836  }
837  else if (mutt_istr_equal(name + 1, "ist-Unsubscribe"))
838  {
839  /* RFC2369 */
840  char *mailto = rfc2369_first_mailto(body);
841  if (mailto)
842  {
843  FREE(&env->list_unsubscribe);
844  env->list_unsubscribe = mailto;
845  }
846  matched = true;
847  }
848  break;
849 
850  case 'm':
851  if (mutt_istr_equal(name + 1, "ime-version"))
852  {
853  if (e)
854  e->mime = true;
855  matched = true;
856  }
857  else if (mutt_istr_equal(name + 1, "essage-id"))
858  {
859  /* We add a new "Message-ID:" when building a message */
860  FREE(&env->message_id);
861  env->message_id = mutt_extract_message_id(body, NULL);
862  matched = true;
863  }
864  else
865  {
866  size_t plen = mutt_istr_startswith(name + 1, "ail-");
867  if (plen != 0)
868  {
869  if (mutt_istr_equal(name + 1 + plen, "reply-to"))
870  {
871  /* override the Reply-To: field */
873  mutt_addrlist_parse(&env->reply_to, body);
874  matched = true;
875  }
876  else if (mutt_istr_equal(name + 1 + plen, "followup-to"))
877  {
879  matched = true;
880  }
881  }
882  }
883  break;
884 
885 #ifdef USE_NNTP
886  case 'n':
887  if (mutt_istr_equal(name + 1, "ewsgroups"))
888  {
889  FREE(&env->newsgroups);
892  matched = true;
893  }
894  break;
895 #endif
896 
897  case 'o':
898  /* field 'Organization:' saves only for pager! */
899  if (mutt_istr_equal(name + 1, "rganization"))
900  {
901  if (!env->organization && !mutt_istr_equal(body, "unknown"))
902  env->organization = mutt_str_dup(body);
903  }
904  break;
905 
906  case 'r':
907  if (mutt_istr_equal(name + 1, "eferences"))
908  {
909  mutt_list_free(&env->references);
910  parse_references(&env->references, body);
911  matched = true;
912  }
913  else if (mutt_istr_equal(name + 1, "eply-to"))
914  {
915  mutt_addrlist_parse(&env->reply_to, body);
916  matched = true;
917  }
918  else if (mutt_istr_equal(name + 1, "eturn-path"))
919  {
920  mutt_addrlist_parse(&env->return_path, body);
921  matched = true;
922  }
923  else if (mutt_istr_equal(name + 1, "eceived"))
924  {
925  if (e && (e->received == 0))
926  {
927  char *d = strrchr(body, ';');
928  if (d)
929  {
930  d = mutt_str_skip_email_wsp(d + 1);
931  e->received = mutt_date_parse_date(d, NULL);
932  }
933  }
934  }
935  break;
936 
937  case 's':
938  if (mutt_istr_equal(name + 1, "ubject"))
939  {
940  if (!env->subject)
941  env->subject = mutt_str_dup(body);
942  matched = true;
943  }
944  else if (mutt_istr_equal(name + 1, "ender"))
945  {
946  mutt_addrlist_parse(&env->sender, body);
947  matched = true;
948  }
949  else if (mutt_istr_equal(name + 1, "tatus"))
950  {
951  if (e)
952  {
953  while (*body)
954  {
955  switch (*body)
956  {
957  case 'O':
958  {
959  const bool c_mark_old = cs_subset_bool(NeoMutt->sub, "mark_old");
960  e->old = c_mark_old;
961  break;
962  }
963  case 'R':
964  e->read = true;
965  break;
966  case 'r':
967  e->replied = true;
968  break;
969  }
970  body++;
971  }
972  }
973  matched = true;
974  }
975  else if (e && (mutt_istr_equal("upersedes", name + 1) ||
976  mutt_istr_equal("upercedes", name + 1)))
977  {
978  FREE(&env->supersedes);
979  env->supersedes = mutt_str_dup(body);
980  }
981  break;
982 
983  case 't':
984  if (mutt_istr_equal(name + 1, "o"))
985  {
986  mutt_addrlist_parse(&env->to, body);
987  matched = true;
988  }
989  break;
990 
991  case 'x':
992  if (mutt_istr_equal(name + 1, "-status"))
993  {
994  if (e)
995  {
996  while (*body)
997  {
998  switch (*body)
999  {
1000  case 'A':
1001  e->replied = true;
1002  break;
1003  case 'D':
1004  e->deleted = true;
1005  break;
1006  case 'F':
1007  e->flagged = true;
1008  break;
1009  default:
1010  break;
1011  }
1012  body++;
1013  }
1014  }
1015  matched = true;
1016  }
1017  else if (mutt_istr_equal(name + 1, "-label"))
1018  {
1019  FREE(&env->x_label);
1020  env->x_label = mutt_str_dup(body);
1021  matched = true;
1022  }
1023 #ifdef USE_NNTP
1024  else if (mutt_istr_equal(name + 1, "-comment-to"))
1025  {
1026  if (!env->x_comment_to)
1027  env->x_comment_to = mutt_str_dup(body);
1028  matched = true;
1029  }
1030  else if (mutt_istr_equal(name + 1, "ref"))
1031  {
1032  if (!env->xref)
1033  env->xref = mutt_str_dup(body);
1034  matched = true;
1035  }
1036 #endif
1037  else if (mutt_istr_equal(name + 1, "-original-to"))
1038  {
1039  mutt_addrlist_parse(&env->x_original_to, body);
1040  matched = true;
1041  }
1042  break;
1043 
1044  default:
1045  break;
1046  }
1047 
1048  /* Keep track of the user-defined headers */
1049  if (!matched && user_hdrs)
1050  {
1051  const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
1052  char *dup = NULL;
1053  mutt_str_asprintf(&dup, "%s: %s", name, body);
1054 
1055  if (!weed || !c_weed || !mutt_matches_ignore(dup))
1056  {
1057  struct ListNode *np = mutt_list_insert_tail(&env->userhdrs, dup);
1058  if (do_2047)
1059  {
1060  rfc2047_decode(&np->data);
1061  }
1062  }
1063  else
1064  {
1065  FREE(&dup);
1066  }
1067  }
1068 
1069  return matched;
1070 }
1071 
1083 char *mutt_rfc822_read_line(FILE *fp, char *line, size_t *linelen)
1084 {
1085  if (!fp || !line || !linelen)
1086  return NULL;
1087 
1088  char *buf = line;
1089  int ch;
1090  size_t offset = 0;
1091 
1092  while (true)
1093  {
1094  if (!fgets(buf, *linelen - offset, fp) || /* end of file or */
1095  (IS_SPACE(*line) && !offset)) /* end of headers */
1096  {
1097  *line = '\0';
1098  return line;
1099  }
1100 
1101  const size_t len = mutt_str_len(buf);
1102  if (len == 0)
1103  return line;
1104 
1105  buf += len - 1;
1106  if (*buf == '\n')
1107  {
1108  /* we did get a full line. remove trailing space */
1109  while (IS_SPACE(*buf))
1110  {
1111  *buf-- = '\0'; /* we can't come beyond line's beginning because
1112  * it begins with a non-space */
1113  }
1114 
1115  /* check to see if the next line is a continuation line */
1116  ch = fgetc(fp);
1117  if ((ch != ' ') && (ch != '\t'))
1118  {
1119  ungetc(ch, fp);
1120  return line; /* next line is a separate header field or EOH */
1121  }
1122 
1123  /* eat tabs and spaces from the beginning of the continuation line */
1124  while (((ch = fgetc(fp)) == ' ') || (ch == '\t'))
1125  ; // do nothing
1126 
1127  ungetc(ch, fp);
1128  *++buf = ' '; /* string is still terminated because we removed
1129  at least one whitespace char above */
1130  }
1131 
1132  buf++;
1133  offset = buf - line;
1134  if (*linelen < (offset + 256))
1135  {
1136  /* grow the buffer */
1137  *linelen += 256;
1138  mutt_mem_realloc(&line, *linelen);
1139  buf = line + offset;
1140  }
1141  }
1142  /* not reached */
1143 }
1144 
1158 struct Envelope *mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
1159 {
1160  if (!fp)
1161  return NULL;
1162 
1163  struct Envelope *env = mutt_env_new();
1164  char *p = NULL;
1165  LOFF_T loc;
1166  size_t linelen = 1024;
1167  char *line = mutt_mem_malloc(linelen);
1168  char buf[linelen + 1];
1169 
1170  if (e)
1171  {
1172  if (!e->body)
1173  {
1174  e->body = mutt_body_new();
1175 
1176  /* set the defaults from RFC1521 */
1177  e->body->type = TYPE_TEXT;
1178  e->body->subtype = mutt_str_dup("plain");
1179  e->body->encoding = ENC_7BIT;
1180  e->body->length = -1;
1181 
1182  /* RFC2183 says this is arbitrary */
1184  }
1185  }
1186 
1187  while ((loc = ftello(fp)) != -1)
1188  {
1189  line = mutt_rfc822_read_line(fp, line, &linelen);
1190  if (*line == '\0')
1191  break;
1192  p = strpbrk(line, ": \t");
1193  if (!p || (*p != ':'))
1194  {
1195  char return_path[1024];
1196  time_t t = 0;
1197 
1198  /* some bogus MTAs will quote the original "From " line */
1199  if (mutt_str_startswith(line, ">From "))
1200  continue; /* just ignore */
1201  else if (is_from(line, return_path, sizeof(return_path), &t))
1202  {
1203  /* MH sometimes has the From_ line in the middle of the header! */
1204  if (e && (e->received == 0))
1205  e->received = t - mutt_date_local_tz(t);
1206  continue;
1207  }
1208 
1209  (void) mutt_file_seek(fp, loc, SEEK_SET);
1210  break; /* end of header */
1211  }
1212 
1213  *buf = '\0';
1214 
1215  if (mutt_replacelist_match(&SpamList, buf, sizeof(buf), line))
1216  {
1217  if (!mutt_regexlist_match(&NoSpamList, line))
1218  {
1219  /* if spam tag already exists, figure out how to amend it */
1220  if ((!mutt_buffer_is_empty(&env->spam)) && (*buf != '\0'))
1221  {
1222  /* If `$spam_separator` defined, append with separator */
1223  const char *const c_spam_separator = cs_subset_string(NeoMutt->sub, "spam_separator");
1224  if (c_spam_separator)
1225  {
1226  mutt_buffer_addstr(&env->spam, c_spam_separator);
1227  mutt_buffer_addstr(&env->spam, buf);
1228  }
1229  else /* overwrite */
1230  {
1231  mutt_buffer_reset(&env->spam);
1232  mutt_buffer_addstr(&env->spam, buf);
1233  }
1234  }
1235 
1236  /* spam tag is new, and match expr is non-empty; copy */
1237  else if (mutt_buffer_is_empty(&env->spam) && (*buf != '\0'))
1238  {
1239  mutt_buffer_addstr(&env->spam, buf);
1240  }
1241 
1242  /* match expr is empty; plug in null string if no existing tag */
1243  else if (mutt_buffer_is_empty(&env->spam))
1244  {
1245  mutt_buffer_addstr(&env->spam, "");
1246  }
1247 
1248  if (!mutt_buffer_is_empty(&env->spam))
1249  mutt_debug(LL_DEBUG5, "spam = %s\n", env->spam.data);
1250  }
1251  }
1252 
1253  *p = '\0';
1254  p = mutt_str_skip_email_wsp(p + 1);
1255  if (*p == '\0')
1256  continue; /* skip empty header fields */
1257 
1258  mutt_rfc822_parse_line(env, e, line, p, user_hdrs, weed, true);
1259  }
1260 
1261  FREE(&line);
1262 
1263  if (e)
1264  {
1265  e->body->hdr_offset = e->offset;
1266  e->body->offset = ftello(fp);
1267 
1269 
1270  if (env->subject)
1271  {
1272  regmatch_t pmatch[1];
1273 
1274  const struct Regex *c_reply_regex = cs_subset_regex(NeoMutt->sub, "reply_regex");
1275  if (mutt_regex_capture(c_reply_regex, env->subject, 1, pmatch))
1276  {
1277  env->real_subj = env->subject + pmatch[0].rm_eo;
1278  if (env->real_subj[0] == '\0')
1279  env->real_subj = NULL;
1280  }
1281  else
1282  env->real_subj = env->subject;
1283  }
1284 
1285  if (e->received < 0)
1286  {
1287  mutt_debug(LL_DEBUG1, "resetting invalid received time to 0\n");
1288  e->received = 0;
1289  }
1290 
1291  /* check for missing or invalid date */
1292  if (e->date_sent <= 0)
1293  {
1294  mutt_debug(LL_DEBUG1, "no date found, using received time from msg separator\n");
1295  e->date_sent = e->received;
1296  }
1297 
1298 #ifdef USE_AUTOCRYPT
1299  const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
1300  if (c_autocrypt)
1301  {
1303  /* No sense in taking up memory after the header is processed */
1305  }
1306 #endif
1307  }
1308 
1309  return env;
1310 }
1311 
1318 struct Body *mutt_read_mime_header(FILE *fp, bool digest)
1319 {
1320  if (!fp)
1321  return NULL;
1322 
1323  struct Body *p = mutt_body_new();
1324  struct Envelope *env = mutt_env_new();
1325  char *c = NULL;
1326  size_t linelen = 1024;
1327  char *line = mutt_mem_malloc(linelen);
1328  bool matched = false;
1329 
1330  p->hdr_offset = ftello(fp);
1331 
1332  p->encoding = ENC_7BIT; /* default from RFC1521 */
1333  p->type = digest ? TYPE_MESSAGE : TYPE_TEXT;
1334  p->disposition = DISP_INLINE;
1335 
1336  while (*(line = mutt_rfc822_read_line(fp, line, &linelen)) != 0)
1337  {
1338  /* Find the value of the current header */
1339  c = strchr(line, ':');
1340  if (c)
1341  {
1342  *c = '\0';
1343  c = mutt_str_skip_email_wsp(c + 1);
1344  if (*c == '\0')
1345  {
1346  mutt_debug(LL_DEBUG1, "skipping empty header field: %s\n", line);
1347  continue;
1348  }
1349  }
1350  else
1351  {
1352  mutt_debug(LL_DEBUG1, "bogus MIME header: %s\n", line);
1353  break;
1354  }
1355 
1356  size_t plen = mutt_istr_startswith(line, "content-");
1357  if (plen != 0)
1358  {
1359  if (mutt_istr_equal("type", line + plen))
1361  else if (mutt_istr_equal("language", line + plen))
1362  parse_content_language(c, p);
1363  else if (mutt_istr_equal("transfer-encoding", line + plen))
1364  p->encoding = mutt_check_encoding(c);
1365  else if (mutt_istr_equal("disposition", line + plen))
1367  else if (mutt_istr_equal("description", line + plen))
1368  {
1369  mutt_str_replace(&p->description, c);
1371  }
1372  else if (mutt_istr_equal("id", line + plen))
1373  {
1374  // strip <angle braces> from Content-ID: header
1375  char *id = c;
1376  int cid_len = mutt_str_len(c);
1377  if (cid_len > 2)
1378  {
1379  if (id[0] == '<')
1380  {
1381  id++;
1382  cid_len--;
1383  }
1384  if (id[cid_len - 1] == '>')
1385  id[cid_len - 1] = '\0';
1386  }
1387  mutt_param_set(&p->parameter, "content-id", id);
1388  }
1389  }
1390 #ifdef SUN_ATTACHMENT
1391  else if ((plen = mutt_istr_startswith(line, "x-sun-")))
1392  {
1393  if (mutt_istr_equal("data-type", line + plen))
1395  else if (mutt_istr_equal("encoding-info", line + plen))
1396  p->encoding = mutt_check_encoding(c);
1397  else if (mutt_istr_equal("content-lines", line + plen))
1398  mutt_param_set(&p->parameter, "content-lines", c);
1399  else if (mutt_istr_equal("data-description", line + plen))
1400  {
1401  mutt_str_replace(&p->description, c);
1403  }
1404  }
1405 #endif
1406  else
1407  {
1408  if (mutt_rfc822_parse_line(env, NULL, line, c, false, false, false))
1409  {
1410  matched = true;
1411  }
1412  }
1413  }
1414  p->offset = ftello(fp); /* Mark the start of the real data */
1415  if ((p->type == TYPE_TEXT) && !p->subtype)
1416  p->subtype = mutt_str_dup("plain");
1417  else if ((p->type == TYPE_MESSAGE) && !p->subtype)
1418  p->subtype = mutt_str_dup("rfc822");
1419 
1420  FREE(&line);
1421 
1422  if (matched)
1423  {
1424  p->mime_headers = env;
1426  }
1427  else
1428  {
1429  mutt_env_free(&env);
1430  }
1431 
1432  return p;
1433 }
1434 
1442 bool mutt_is_message_type(int type, const char *subtype)
1443 {
1444  if (type != TYPE_MESSAGE)
1445  return false;
1446 
1447  subtype = NONULL(subtype);
1448  return (mutt_istr_equal(subtype, "rfc822") ||
1449  mutt_istr_equal(subtype, "news") || mutt_istr_equal(subtype, "global"));
1450 }
1451 
1458 static void parse_part(FILE *fp, struct Body *b, int *counter)
1459 {
1460  if (!fp || !b)
1461  return;
1462 
1463  const char *bound = NULL;
1464  static unsigned short recurse_level = 0;
1465 
1466  if (recurse_level >= MUTT_MIME_MAX_DEPTH)
1467  {
1468  mutt_debug(LL_DEBUG1, "recurse level too deep. giving up.\n");
1469  return;
1470  }
1471  recurse_level++;
1472 
1473  switch (b->type)
1474  {
1475  case TYPE_MULTIPART:
1476 #ifdef SUN_ATTACHMENT
1477  if (mutt_istr_equal(b->subtype, "x-sun-attachment"))
1478  bound = "--------";
1479  else
1480 #endif
1481  bound = mutt_param_get(&b->parameter, "boundary");
1482 
1483  if (!mutt_file_seek(fp, b->offset, SEEK_SET))
1484  {
1485  goto bail;
1486  }
1487  b->parts = parse_multipart(fp, bound, b->offset + b->length,
1488  mutt_istr_equal("digest", b->subtype), counter);
1489  break;
1490 
1491  case TYPE_MESSAGE:
1492  if (!b->subtype)
1493  break;
1494 
1495  if (!mutt_file_seek(fp, b->offset, SEEK_SET))
1496  {
1497  goto bail;
1498  }
1499  if (mutt_is_message_type(b->type, b->subtype))
1500  b->parts = rfc822_parse_message(fp, b, counter);
1501  else if (mutt_istr_equal(b->subtype, "external-body"))
1502  b->parts = mutt_read_mime_header(fp, 0);
1503  else
1504  goto bail;
1505  break;
1506 
1507  default:
1508  goto bail;
1509  }
1510 
1511  /* try to recover from parsing error */
1512  if (!b->parts)
1513  {
1514  b->type = TYPE_TEXT;
1515  mutt_str_replace(&b->subtype, "plain");
1516  }
1517 bail:
1518  recurse_level--;
1519 }
1520 
1531 static struct Body *parse_multipart(FILE *fp, const char *boundary,
1532  LOFF_T end_off, bool digest, int *counter)
1533 {
1534  if (!fp)
1535  return NULL;
1536 
1537  if (!boundary)
1538  {
1539  mutt_error(_("multipart message has no boundary parameter"));
1540  return NULL;
1541  }
1542 
1543  char buf[1024];
1544  struct Body *head = NULL, *last = NULL, *new_body = NULL;
1545  bool final = false; /* did we see the ending boundary? */
1546 
1547  const size_t blen = mutt_str_len(boundary);
1548  while ((ftello(fp) < end_off) && fgets(buf, sizeof(buf), fp))
1549  {
1550  const size_t len = mutt_str_len(buf);
1551 
1552  const size_t crlf = ((len > 1) && (buf[len - 2] == '\r')) ? 1 : 0;
1553 
1554  if ((buf[0] == '-') && (buf[1] == '-') && mutt_str_startswith(buf + 2, boundary))
1555  {
1556  if (last)
1557  {
1558  last->length = ftello(fp) - last->offset - len - 1 - crlf;
1559  if (last->parts && (last->parts->length == 0))
1560  last->parts->length = ftello(fp) - last->parts->offset - len - 1 - crlf;
1561  /* if the body is empty, we can end up with a -1 length */
1562  if (last->length < 0)
1563  last->length = 0;
1564  }
1565 
1566  if (len > 0)
1567  {
1568  /* Remove any trailing whitespace, up to the length of the boundary */
1569  for (size_t i = len - 1; IS_SPACE(buf[i]) && (i >= (blen + 2)); i--)
1570  buf[i] = '\0';
1571  }
1572 
1573  /* Check for the end boundary */
1574  if (mutt_str_equal(buf + blen + 2, "--"))
1575  {
1576  final = true;
1577  break; /* done parsing */
1578  }
1579  else if (buf[2 + blen] == '\0')
1580  {
1581  new_body = mutt_read_mime_header(fp, digest);
1582 
1583 #ifdef SUN_ATTACHMENT
1584  if (mutt_param_get(&new_body->parameter, "content-lines"))
1585  {
1586  int lines = 0;
1587  mutt_str_atoi(mutt_param_get(&new_body->parameter, "content-lines"), &lines);
1588  for (; lines > 0; lines--)
1589  if ((ftello(fp) >= end_off) || !fgets(buf, sizeof(buf), fp))
1590  break;
1591  }
1592 #endif
1593  /* Consistency checking - catch bad attachment end boundaries */
1594  if (new_body->offset > end_off)
1595  {
1596  mutt_body_free(&new_body);
1597  break;
1598  }
1599  if (head)
1600  {
1601  last->next = new_body;
1602  last = new_body;
1603  }
1604  else
1605  {
1606  last = new_body;
1607  head = new_body;
1608  }
1609 
1610  /* It seems more intuitive to add the counter increment to
1611  * parse_part(), but we want to stop the case where a multipart
1612  * contains thousands of tiny parts before the memory and data
1613  * structures are allocated. */
1614  if (++(*counter) >= MUTT_MIME_MAX_PARTS)
1615  break;
1616  }
1617  }
1618  }
1619 
1620  /* in case of missing end boundary, set the length to something reasonable */
1621  if (last && (last->length == 0) && !final)
1622  last->length = end_off - last->offset;
1623 
1624  /* parse recursive MIME parts */
1625  for (last = head; last; last = last->next)
1626  parse_part(fp, last, counter);
1627 
1628  return head;
1629 }
1630 
1640 static struct Body *rfc822_parse_message(FILE *fp, struct Body *parent, int *counter)
1641 {
1642  if (!fp || !parent)
1643  return NULL;
1644 
1645  parent->email = email_new();
1646  parent->email->offset = ftello(fp);
1647  parent->email->env = mutt_rfc822_read_header(fp, parent->email, false, false);
1648  struct Body *msg = parent->email->body;
1649 
1650  /* ignore the length given in the content-length since it could be wrong
1651  * and we already have the info to calculate the correct length */
1652  /* if (msg->length == -1) */
1653  msg->length = parent->length - (msg->offset - parent->offset);
1654 
1655  /* if body of this message is empty, we can end up with a negative length */
1656  if (msg->length < 0)
1657  msg->length = 0;
1658 
1659  parse_part(fp, msg, counter);
1660  return msg;
1661 }
1662 
1671 bool mutt_parse_mailto(struct Envelope *env, char **body, const char *src)
1672 {
1673  if (!env || !src)
1674  return false;
1675 
1676  struct Url *url = url_parse(src);
1677  if (!url)
1678  return false;
1679 
1680  if (url->host)
1681  {
1682  /* this is not a path-only URL */
1683  url_free(&url);
1684  return false;
1685  }
1686 
1687  mutt_addrlist_parse(&env->to, url->path);
1688 
1689  struct UrlQuery *np;
1690  STAILQ_FOREACH(np, &url->query_strings, entries)
1691  {
1692  const char *tag = np->name;
1693  char *value = np->value;
1694  /* Determine if this header field is on the allowed list. Since NeoMutt
1695  * interprets some header fields specially (such as
1696  * "Attach: ~/.gnupg/secring.gpg"), care must be taken to ensure that
1697  * only safe fields are allowed.
1698  *
1699  * RFC2368, "4. Unsafe headers"
1700  * The user agent interpreting a mailto URL SHOULD choose not to create
1701  * a message if any of the headers are considered dangerous; it may also
1702  * choose to create a message with only a subset of the headers given in
1703  * the URL. */
1704  if (mutt_list_match(tag, &MailToAllow))
1705  {
1706  if (mutt_istr_equal(tag, "body"))
1707  {
1708  if (body)
1709  mutt_str_replace(body, value);
1710  }
1711  else
1712  {
1713  char *scratch = NULL;
1714  size_t taglen = mutt_str_len(tag);
1715 
1716  mutt_str_asprintf(&scratch, "%s: %s", tag, value);
1717  scratch[taglen] = 0; /* overwrite the colon as mutt_rfc822_parse_line expects */
1718  value = mutt_str_skip_email_wsp(&scratch[taglen + 1]);
1719  mutt_rfc822_parse_line(env, NULL, scratch, value, true, false, true);
1720  FREE(&scratch);
1721  }
1722  }
1723  }
1724 
1725  /* RFC2047 decode after the RFC822 parsing */
1727 
1728  url_free(&url);
1729  return true;
1730 }
1731 
1737 void mutt_parse_part(FILE *fp, struct Body *b)
1738 {
1739  int counter = 0;
1740 
1741  parse_part(fp, b, &counter);
1742 }
1743 
1752 struct Body *mutt_rfc822_parse_message(FILE *fp, struct Body *parent)
1753 {
1754  int counter = 0;
1755 
1756  return rfc822_parse_message(fp, parent, &counter);
1757 }
1758 
1768 struct Body *mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
1769 {
1770  int counter = 0;
1771 
1772  return parse_multipart(fp, boundary, end_off, digest, &counter);
1773 }
void mutt_addrlist_clear(struct AddressList *al)
Unlink and free all Address in an AddressList.
Definition: address.c:1470
int mutt_addrlist_parse(struct AddressList *al, const char *s)
Parse a list of email addresses.
Definition: address.c:458
Email Address Handling.
const char * mutt_str_atoul(const char *str, unsigned long *dst)
Convert ASCII string to an unsigned long.
Definition: atoi.c:226
const char * mutt_str_atoui(const char *str, unsigned int *dst)
Convert ASCII string to an unsigned integer.
Definition: atoi.c:202
const char * mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: atoi.c:178
Autocrypt end-to-end encryption.
int mutt_autocrypt_process_autocrypt_header(struct Email *e, struct Envelope *env)
Parse an Autocrypt email header.
Definition: autocrypt.c:265
void mutt_buffer_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:265
bool mutt_buffer_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:250
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:238
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:223
char * mutt_buffer_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:430
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:81
const struct Slist * cs_subset_slist(const struct ConfigSubset *sub, const char *name)
Get a string-list config item by name.
Definition: helpers.c:268
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
const struct Regex * cs_subset_regex(const struct ConfigSubset *sub, const char *name)
Get a regex config item by name.
Definition: helpers.c:243
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
time_t mutt_date_local_tz(time_t t)
Calculate the local timezone in seconds east of UTC.
Definition: date.c:206
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:427
time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
Parse a date string in RFC822 format.
Definition: date.c:456
void mutt_body_free(struct Body **ptr)
Free a Body.
Definition: body.c:57
struct Body * mutt_body_new(void)
Create a new Body.
Definition: body.c:43
struct Email * email_new(void)
Create a new Email.
Definition: email.c:78
Representation of an email.
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition: envelope.c:97
struct Envelope * mutt_env_new(void)
Create a new Envelope.
Definition: envelope.c:43
void mutt_autocrypthdr_free(struct AutocryptHeader **p)
Free an AutocryptHeader.
Definition: envelope.c:75
struct AutocryptHeader * mutt_autocrypthdr_new(void)
Create a new AutocryptHeader.
Definition: envelope.c:66
Representation of an email header (envelope)
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:690
bool is_from(const char *s, char *path, size_t pathlen, time_t *tp)
Is a string a 'From' header line?
Definition: from.c:48
Determine who the email is from.
struct ReplaceList SpamList
List of regexes and patterns to match spam emails.
Definition: globals.c:34
struct RegexList SubscribedLists
List of regexes to match subscribed mailing lists.
Definition: globals.c:43
struct HashTable * AutoSubscribeCache
Hash Table of auto-subscribed mailing lists.
Definition: globals.c:38
struct RegexList UnSubscribedLists
List of regexes to blacklist false matches in SubscribedLists.
Definition: globals.c:40
struct RegexList UnMailLists
List of regexes to blacklist false matches in MailLists.
Definition: globals.c:42
struct RegexList MailLists
List of regexes to match mailing lists.
Definition: globals.c:41
struct ListHead MailToAllow
List of permitted fields in a mailto: url.
Definition: globals.c:37
struct ListHead Ignore
List of header patterns to ignore.
Definition: globals.c:35
struct RegexList NoSpamList
List of regexes to whitelist non-spam emails.
Definition: globals.c:33
struct ListHead UnIgnore
List of header patterns to unignore (see)
Definition: globals.c:36
Email Global Variables.
#define mutt_error(...)
Definition: logging.h:87
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition: hash.c:335
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:362
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:259
#define MUTT_HASH_STRDUP_KEYS
make a copy of the keys
Definition: hash.h:111
#define MUTT_HASH_STRCASECMP
use strcasecmp() to compare keys
Definition: hash.h:110
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition: list.c:45
void mutt_list_free(struct ListHead *h)
Free a List AND its strings.
Definition: list.c:122
struct ListNode * mutt_list_insert_tail(struct ListHead *h, char *s)
Append a string to the end of a List.
Definition: list.c:64
bool mutt_list_match(const char *s, struct ListHead *h)
Is the string in the list (see notes)
Definition: list.c:195
@ LL_DEBUG5
Log at debug level 5.
Definition: logging.h:44
@ LL_DEBUG2
Log at debug level 2.
Definition: logging.h:41
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
#define FREE(x)
Definition: memory.h:43
#define MIN(a, b)
Definition: memory.h:31
Constants and macros for managing MIME encoding.
@ ENC_7BIT
7-bit text
Definition: mime.h:49
@ ENC_UUENCODED
UUEncoded text.
Definition: mime.h:54
@ ENC_OTHER
Encoding unknown.
Definition: mime.h:48
@ ENC_BINARY
Binary.
Definition: mime.h:53
@ ENC_BASE64
Base-64 encoded text.
Definition: mime.h:52
@ ENC_8BIT
8-bit text
Definition: mime.h:50
@ ENC_QUOTED_PRINTABLE
Quoted-printable text.
Definition: mime.h:51
#define MUTT_MIME_MAX_DEPTH
Definition: mime.h:69
ContentType
Content-Type.
Definition: mime.h:30
@ TYPE_AUDIO
Type: 'audio/*'.
Definition: mime.h:32
@ TYPE_IMAGE
Type: 'image/*'.
Definition: mime.h:34
@ TYPE_OTHER
Unknown Content-Type.
Definition: mime.h:31
@ TYPE_MESSAGE
Type: 'message/*'.
Definition: mime.h:35
@ TYPE_MODEL
Type: 'model/*'.
Definition: mime.h:36
@ TYPE_MULTIPART
Type: 'multipart/*'.
Definition: mime.h:37
@ TYPE_APPLICATION
Type: 'application/*'.
Definition: mime.h:33
@ TYPE_TEXT
Type: 'text/*'.
Definition: mime.h:38
@ TYPE_ANY
Type: '*' or '.*'.
Definition: mime.h:40
@ TYPE_VIDEO
Type: 'video/*'.
Definition: mime.h:39
@ DISP_ATTACH
Content is attached.
Definition: mime.h:63
@ DISP_INLINE
Content is inline.
Definition: mime.h:62
@ DISP_FORM_DATA
Content is form-data.
Definition: mime.h:64
#define MUTT_MIME_MAX_PARTS
Definition: mime.h:70
char * mutt_ch_get_default_charset(void)
Get the default character set.
Definition: charset.c:439
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
int mutt_regexlist_add(struct RegexList *rl, const char *str, uint16_t flags, struct Buffer *err)
Compile a regex string and add it to a list.
Definition: regex.c:135
bool mutt_regex_capture(const struct Regex *regex, const char *str, size_t nmatch, regmatch_t matches[])
Match a regex against a string, with provided options.
Definition: regex.c:614
bool mutt_replacelist_match(struct ReplaceList *rl, char *buf, size_t buflen, const char *str)
Does a string match a pattern?
Definition: regex.c:495
bool mutt_regexlist_match(struct RegexList *rl, const char *str)
Does a string match any Regex in the list?
Definition: regex.c:195
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:613
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:796
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition: string.c:428
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1008
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:784
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:473
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:599
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:656
bool mutt_str_is_email_wsp(char c)
Is this a whitespace character (for an email header)
Definition: string.c:672
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:544
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:326
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:239
bool mutt_istrn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings ignoring case (to a maximum), safely.
Definition: string.c:501
Many unsorted constants and some structs.
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition: parameter.c:84
struct Parameter * mutt_param_new(void)
Create a new Parameter.
Definition: parameter.c:39
void mutt_param_set(struct ParameterList *pl, const char *attribute, const char *value)
Set a Parameter.
Definition: parameter.c:110
void mutt_param_free(struct ParameterList *pl)
Free a ParameterList.
Definition: parameter.c:61
Store attributes associated with a MIME part.
static struct AutocryptHeader * parse_autocrypt(struct AutocryptHeader *head, const char *s)
Parse an Autocrypt header line.
Definition: parse.c:529
void mutt_parse_part(FILE *fp, struct Body *b)
Parse a MIME part.
Definition: parse.c:1737
void mutt_auto_subscribe(const char *mailto)
Check if user is subscribed to mailing list.
Definition: parse.c:67
static char * rfc2369_first_mailto(const char *body)
Extract the first mailto: URL from a RFC2369 list.
Definition: parse.c:593
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1158
char * mutt_extract_message_id(const char *s, size_t *len)
Find a message-id.
Definition: parse.c:360
void mutt_parse_content_type(const char *s, struct Body *ct)
Parse a content type.
Definition: parse.c:424
static void parse_references(struct ListHead *head, const char *s)
Parse references from an email header.
Definition: parse.c:280
bool mutt_matches_ignore(const char *s)
Does the string match the ignore list.
Definition: parse.c:313
int mutt_rfc822_parse_line(struct Envelope *env, struct Email *e, const char *name, const char *body, bool user_hdrs, bool weed, bool do_2047)
Parse an email header.
Definition: parse.c:634
enum ContentType mutt_check_mime_type(const char *s)
Check a MIME type string.
Definition: parse.c:323
static struct Body * rfc822_parse_message(FILE *fp, struct Body *parent, int *counter)
Parse a Message/RFC822 body.
Definition: parse.c:1640
char * mutt_rfc822_read_line(FILE *fp, char *line, size_t *linelen)
Read a header line from a file.
Definition: parse.c:1083
static void parse_content_language(const char *s, struct Body *ct)
Read the content's language.
Definition: parse.c:297
bool mutt_parse_mailto(struct Envelope *env, char **body, const char *src)
Parse a mailto:// url.
Definition: parse.c:1671
int mutt_check_encoding(const char *c)
Check the encoding type.
Definition: parse.c:396
static void parse_content_disposition(const char *s, struct Body *ct)
Parse a content disposition.
Definition: parse.c:248
static void parse_part(FILE *fp, struct Body *b, int *counter)
Parse a MIME part.
Definition: parse.c:1458
static struct Body * parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest, int *counter)
Parse a multipart structure.
Definition: parse.c:1531
struct Body * mutt_rfc822_parse_message(FILE *fp, struct Body *parent)
Parse a Message/RFC822 body.
Definition: parse.c:1752
struct Body * mutt_read_mime_header(FILE *fp, bool digest)
Parse a MIME header.
Definition: parse.c:1318
struct Body * mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
Parse a multipart structure.
Definition: parse.c:1768
bool mutt_is_message_type(int type, const char *subtype)
Determine if a mime type matches a message or not.
Definition: parse.c:1442
static void parse_parameters(struct ParameterList *pl, const char *s, bool allow_value_spaces)
Parse a list of Parameters.
Definition: parse.c:110
#define CONTENT_TOO_BIG
Definition: parse.c:56
Miscellaneous email parsing routines.
static size_t plen
Length of cached packet.
Definition: pgppacket.c:39
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:725
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
#define TAILQ_FIRST(head)
Definition: queue.h:723
#define TAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:637
#define TAILQ_EMPTY(head)
Definition: queue.h:721
#define TAILQ_INSERT_HEAD(head, elm, field)
Definition: queue.h:796
void rfc2047_decode_envelope(struct Envelope *env)
Decode the fields of an Envelope.
Definition: rfc2047.c:793
void rfc2047_decode(char **pd)
Decode any RFC2047-encoded header fields.
Definition: rfc2047.c:649
RFC2047 MIME extensions encoding / decoding routines.
void rfc2231_decode_parameters(struct ParameterList *pl)
Decode a Parameter list.
Definition: rfc2231.c:236
RFC2231 MIME Charset routines.
Convenience wrapper for the send headers.
#define NONULL(x)
Definition: string2.h:37
#define IS_SPACE(ch)
Definition: string2.h:38
Parse Autocrypt header info.
Definition: envelope.h:44
bool invalid
Header is invalid.
Definition: envelope.h:48
struct AutocryptHeader * next
Linked list.
Definition: envelope.h:49
char * keydata
PGP Key data.
Definition: envelope.h:46
bool prefer_encrypt
User prefers encryption.
Definition: envelope.h:47
char * addr
Email address.
Definition: envelope.h:45
The body of an email.
Definition: body.h:36
char * language
content-language (RFC8255)
Definition: body.h:77
struct Body * parts
parts of a multipart or message/rfc822
Definition: body.h:72
LOFF_T offset
offset where the actual data begins
Definition: body.h:52
char * xtype
content-type if x-unknown
Definition: body.h:61
struct Envelope * mime_headers
Memory hole protected headers.
Definition: body.h:75
LOFF_T length
length (in bytes) of attachment
Definition: body.h:53
struct ParameterList parameter
Parameters of the content-type.
Definition: body.h:62
struct Email * email
header information for message/rfc822
Definition: body.h:73
char * description
content-description
Definition: body.h:55
unsigned int disposition
content-disposition, ContentDisposition
Definition: body.h:42
struct Body * next
next attachment in the list
Definition: body.h:71
char * subtype
content-type subtype
Definition: body.h:60
unsigned int encoding
content-transfer-encoding, ContentEncoding
Definition: body.h:41
long hdr_offset
Offset in stream where the headers begin.
Definition: body.h:80
char * form_name
Content-Disposition form-data name param.
Definition: body.h:59
unsigned int type
content-type primary type, ContentType
Definition: body.h:40
char * filename
When sending a message, this is the file to which this structure refers.
Definition: body.h:58
String manipulation buffer.
Definition: buffer.h:34
char * data
Pointer to data.
Definition: buffer.h:35
The envelope/body of an email.
Definition: email.h:37
bool read
Email is read.
Definition: email.h:48
unsigned int zminutes
Minutes away from UTC.
Definition: email.h:55
struct Envelope * env
Envelope information.
Definition: email.h:66
bool mime
Has a MIME-Version header?
Definition: email.h:46
int lines
How many lines in the body of this message?
Definition: email.h:60
struct Body * body
List of MIME parts.
Definition: email.h:67
bool old
Email is seen, but unread.
Definition: email.h:47
bool zoccident
True, if west of UTC, False if east.
Definition: email.h:56
LOFF_T offset
Where in the stream does this message begin?
Definition: email.h:69
bool flagged
Marked important?
Definition: email.h:45
unsigned int zhours
Hours away from UTC.
Definition: email.h:54
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:58
bool replied
Email has been replied to.
Definition: email.h:49
bool expired
Already expired?
Definition: email.h:44
bool deleted
Email is deleted.
Definition: email.h:76
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:59
The header of an Email.
Definition: envelope.h:57
struct ListHead userhdrs
user defined headers
Definition: envelope.h:87
char * supersedes
Supersedes header.
Definition: envelope.h:74
char * list_subscribe
This stores a mailto URL, or nothing.
Definition: envelope.h:68
struct AddressList return_path
Return path for the Email.
Definition: envelope.h:58
struct AddressList to
Email's 'To' list.
Definition: envelope.h:60
char * followup_to
List of 'followup-to' fields.
Definition: envelope.h:81
struct AddressList reply_to
Email's 'reply-to'.
Definition: envelope.h:64
char * message_id
Message ID.
Definition: envelope.h:73
char * x_comment_to
List of 'X-comment-to' fields.
Definition: envelope.h:82
struct AddressList x_original_to
Email's 'X-Orig-to'.
Definition: envelope.h:66
struct AutocryptHeader * autocrypt_gossip
Autocrypt Gossip header.
Definition: envelope.h:90
char * newsgroups
List of newsgroups.
Definition: envelope.h:79
struct AddressList mail_followup_to
Email's 'mail-followup-to'.
Definition: envelope.h:65
struct AddressList cc
Email's 'Cc' list.
Definition: envelope.h:61
struct AddressList sender
Email's sender.
Definition: envelope.h:63
struct ListHead references
message references (in reverse order)
Definition: envelope.h:85
struct AutocryptHeader * autocrypt
Autocrypt header.
Definition: envelope.h:89
struct Buffer spam
Spam header.
Definition: envelope.h:84
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:86
char * subject
Email's subject.
Definition: envelope.h:70
struct AddressList bcc
Email's 'Bcc' list.
Definition: envelope.h:62
char * xref
List of cross-references.
Definition: envelope.h:80
char * organization
Organisation header.
Definition: envelope.h:77
char * x_label
X-Label.
Definition: envelope.h:76
char * list_post
This stores a mailto URL, or nothing.
Definition: envelope.h:67
char * date
Sent date.
Definition: envelope.h:75
char * real_subj
Offset of the real subject.
Definition: envelope.h:71
char * list_unsubscribe
This stores a mailto URL, or nothing.
Definition: envelope.h:69
struct AddressList from
Email's 'From' list.
Definition: envelope.h:59
A List node for strings.
Definition: list.h:35
char * data
String.
Definition: list.h:36
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Attribute associated with a MIME part.
Definition: parameter.h:33
char * attribute
Parameter name.
Definition: parameter.h:34
char * value
Parameter value.
Definition: parameter.h:35
Cached regular expression.
Definition: regex3.h:89
String list.
Definition: slist.h:47
List of recognised Timezones.
Definition: date.h:45
unsigned char zminutes
Minutes away from UTC.
Definition: date.h:48
bool zoccident
True if west of UTC, False if East.
Definition: date.h:49
unsigned char zhours
Hours away from UTC.
Definition: date.h:47
Parsed Query String.
Definition: url.h:58
char * name
Query name.
Definition: url.h:59
char * value
Query value.
Definition: url.h:60
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:69
struct UrlQueryList query_strings
List of query strings.
Definition: url.h:76
char * host
Host.
Definition: url.h:73
char * src
Raw URL string.
Definition: url.h:77
char * path
Path.
Definition: url.h:75
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
enum UrlScheme url_check_scheme(const char *str)
Check the protocol of a URL.
Definition: url.c:221
Parse and identify different URL schemes.
@ U_MAILTO
Url is mailto://.
Definition: url.h:45