NeoMutt  2023-11-03-85-g512e01
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
parse.c
Go to the documentation of this file.
1
30#include "config.h"
31#include <ctype.h>
32#include <errno.h>
33#include <string.h>
34#include <time.h>
35#include "mutt/lib.h"
36#include "address/lib.h"
37#include "config/lib.h"
38#include "core/lib.h"
39#include "mutt.h"
40#include "parse.h"
41#include "body.h"
42#include "email.h"
43#include "envelope.h"
44#include "from.h"
45#include "globals.h"
46#include "mime.h"
47#include "parameter.h"
48#include "rfc2047.h"
49#include "rfc2231.h"
50#include "url.h"
51#ifdef USE_AUTOCRYPT
52#include "autocrypt/lib.h"
53#endif
54
55/* If the 'Content-Length' is bigger than 1GiB, then it's clearly wrong.
56 * Cap the value to prevent overflow of Body.length */
57#define CONTENT_TOO_BIG (1 << 30)
58
59static void parse_part(FILE *fp, struct Body *b, int *counter);
60static struct Body *rfc822_parse_message(FILE *fp, struct Body *parent, int *counter);
61static struct Body *parse_multipart(FILE *fp, const char *boundary,
62 LOFF_T end_off, bool digest, int *counter);
63
68void mutt_auto_subscribe(const char *mailto)
69{
70 if (!mailto)
71 return;
72
75
77 return;
78
80
81 struct Envelope *lpenv = mutt_env_new(); /* parsed envelope from the List-Post mailto: URL */
82
83 if (mutt_parse_mailto(lpenv, NULL, mailto) && !TAILQ_EMPTY(&lpenv->to))
84 {
85 const char *mailbox = buf_string(TAILQ_FIRST(&lpenv->to)->mailbox);
86 if (mailbox && !mutt_regexlist_match(&SubscribedLists, mailbox) &&
89 {
90 /* mutt_regexlist_add() detects duplicates, so it is safe to
91 * try to add here without any checks. */
92 mutt_regexlist_add(&MailLists, mailbox, REG_ICASE, NULL);
93 mutt_regexlist_add(&SubscribedLists, mailbox, REG_ICASE, NULL);
94 }
95 }
96
97 mutt_env_free(&lpenv);
98}
99
111static void parse_parameters(struct ParameterList *pl, const char *s, bool allow_value_spaces)
112{
113 struct Parameter *pnew = NULL;
114 const char *p = NULL;
115 size_t i;
116
117 struct Buffer *buf = buf_pool_get();
118 /* allow_value_spaces, especially with autocrypt keydata, can result
119 * in quite large parameter values. avoid frequent reallocs by
120 * pre-sizing */
121 if (allow_value_spaces)
122 buf_alloc(buf, mutt_str_len(s));
123
124 mutt_debug(LL_DEBUG2, "'%s'\n", s);
125
126 const bool assumed = !slist_is_empty(cc_assumed_charset());
127 while (*s)
128 {
129 buf_reset(buf);
130
131 p = strpbrk(s, "=;");
132 if (!p)
133 {
134 mutt_debug(LL_DEBUG1, "malformed parameter: %s\n", s);
135 goto bail;
136 }
137
138 /* if we hit a ; now the parameter has no value, just skip it */
139 if (*p != ';')
140 {
141 i = p - s;
142 /* remove whitespace from the end of the attribute name */
143 while ((i > 0) && mutt_str_is_email_wsp(s[i - 1]))
144 i--;
145
146 /* the check for the missing parameter token is here so that we can skip
147 * over any quoted value that may be present. */
148 if (i == 0)
149 {
150 mutt_debug(LL_DEBUG1, "missing attribute: %s\n", s);
151 pnew = NULL;
152 }
153 else
154 {
155 pnew = mutt_param_new();
156 pnew->attribute = mutt_strn_dup(s, i);
157 }
158
159 do
160 {
161 s = mutt_str_skip_email_wsp(p + 1); /* skip over the =, or space if we loop */
162
163 if (*s == '"')
164 {
165 bool state_ascii = true;
166 s++;
167 for (; *s; s++)
168 {
169 if (assumed)
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 buf_addch(buf, *s);
189 }
190 }
191 else
192 {
193 buf_addch(buf, *s);
194 }
195 }
196 if (*s)
197 s++; /* skip over the " */
198 }
199 else
200 {
201 for (; *s && *s != ' ' && *s != ';'; s++)
202 buf_addch(buf, *s);
203 }
204
205 p = s;
206 } while (allow_value_spaces && (*s == ' '));
207
208 /* if the attribute token was missing, 'new' will be NULL */
209 if (pnew)
210 {
211 pnew->value = buf_strdup(buf);
212
213 mutt_debug(LL_DEBUG2, "parse_parameter: '%s' = '%s'\n",
214 pnew->attribute ? pnew->attribute : "", pnew->value ? pnew->value : "");
215
216 /* Add this parameter to the list */
217 TAILQ_INSERT_HEAD(pl, pnew, entries);
218 }
219 }
220 else
221 {
222 mutt_debug(LL_DEBUG1, "parameter with no value: %s\n", s);
223 s = p;
224 }
225
226 /* Find the next parameter */
227 if ((*s != ';') && !(s = strchr(s, ';')))
228 break; /* no more parameters */
229
230 do
231 {
232 /* Move past any leading whitespace. the +1 skips over the semicolon */
233 s = mutt_str_skip_email_wsp(s + 1);
234 } while (*s == ';'); /* skip empty parameters */
235 }
236
237bail:
238
240 buf_pool_release(&buf);
241}
242
250static void parse_content_disposition(const char *s, struct Body *b)
251{
252 struct ParameterList pl = TAILQ_HEAD_INITIALIZER(pl);
253
254 if (mutt_istr_startswith(s, "inline"))
256 else if (mutt_istr_startswith(s, "form-data"))
258 else
260
261 /* Check to see if a default filename was given */
262 s = strchr(s, ';');
263 if (s)
264 {
265 s = mutt_str_skip_email_wsp(s + 1);
266 parse_parameters(&pl, s, false);
267 s = mutt_param_get(&pl, "filename");
268 if (s)
270 s = mutt_param_get(&pl, "name");
271 if (s)
273 mutt_param_free(&pl);
274 }
275}
276
282static void parse_references(struct ListHead *head, const char *s)
283{
284 if (!head)
285 return;
286
287 char *m = NULL;
288 for (size_t off = 0; (m = mutt_extract_message_id(s, &off)); s += off)
289 {
290 mutt_list_insert_head(head, m);
291 }
292}
293
299static void parse_content_language(const char *s, struct Body *b)
300{
301 if (!s || !b)
302 return;
303
304 mutt_debug(LL_DEBUG2, "RFC8255 >> Content-Language set to %s\n", s);
306}
307
315bool mutt_matches_ignore(const char *s)
316{
317 return mutt_list_match(s, &Ignore) && !mutt_list_match(s, &UnIgnore);
318}
319
326{
327 if (mutt_istr_equal("text", s))
328 return TYPE_TEXT;
329 if (mutt_istr_equal("multipart", s))
330 return TYPE_MULTIPART;
331 if (mutt_istr_equal("x-sun-attachment", s))
332 return TYPE_MULTIPART;
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
360char *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
396int 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"))
406 if (mutt_istr_startswith(c, "base64"))
407 return ENC_BASE64;
408 if (mutt_istr_startswith(c, "x-uuencode"))
409 return ENC_UUENCODED;
410 if (mutt_istr_startswith(c, "uuencode"))
411 return ENC_UUENCODED;
412 return ENC_OTHER;
413}
414
422void mutt_parse_content_type(const char *s, struct Body *b)
423{
424 if (!s || !b)
425 return;
426
427 FREE(&b->subtype);
429
430 /* First extract any existing parameters */
431 char *pc = strchr(s, ';');
432 if (pc)
433 {
434 *pc++ = 0;
435 while (*pc && isspace(*pc))
436 pc++;
437 parse_parameters(&b->parameter, pc, false);
438
439 /* Some pre-RFC1521 gateways still use the "name=filename" convention,
440 * but if a filename has already been set in the content-disposition,
441 * let that take precedence, and don't set it here */
442 pc = mutt_param_get(&b->parameter, "name");
443 if (pc && !b->filename)
444 b->filename = mutt_str_dup(pc);
445
446 /* this is deep and utter perversion */
447 pc = mutt_param_get(&b->parameter, "conversions");
448 if (pc)
450 }
451
452 /* Now get the subtype */
453 char *subtype = strchr(s, '/');
454 if (subtype)
455 {
456 *subtype++ = '\0';
457 for (pc = subtype; *pc && !isspace(*pc) && (*pc != ';'); pc++)
458 ; // do nothing
459
460 *pc = '\0';
461 mutt_str_replace(&b->subtype, subtype);
462 }
463
464 /* Finally, get the major type */
466
467 if (mutt_istr_equal("x-sun-attachment", s))
468 mutt_str_replace(&b->subtype, "x-sun-attachment");
469
470 if (b->type == TYPE_OTHER)
471 {
472 mutt_str_replace(&b->xtype, s);
473 }
474
475 if (!b->subtype)
476 {
477 /* Some older non-MIME mailers (i.e., mailtool, elm) have a content-type
478 * field, so we can attempt to convert the type to Body here. */
479 if (b->type == TYPE_TEXT)
480 {
481 b->subtype = mutt_str_dup("plain");
482 }
483 else if (b->type == TYPE_AUDIO)
484 {
485 b->subtype = mutt_str_dup("basic");
486 }
487 else if (b->type == TYPE_MESSAGE)
488 {
489 b->subtype = mutt_str_dup("rfc822");
490 }
491 else if (b->type == TYPE_OTHER)
492 {
493 char buf[128] = { 0 };
494
496 snprintf(buf, sizeof(buf), "x-%s", s);
497 b->subtype = mutt_str_dup(buf);
498 }
499 else
500 {
501 b->subtype = mutt_str_dup("x-unknown");
502 }
503 }
504
505 /* Default character set for text types. */
506 if (b->type == TYPE_TEXT)
507 {
508 pc = mutt_param_get(&b->parameter, "charset");
509 if (pc)
510 {
511 /* Microsoft Outlook seems to think it is necessary to repeat
512 * charset=, strip it off not to confuse ourselves */
513 if (mutt_istrn_equal(pc, "charset=", sizeof("charset=") - 1))
514 mutt_param_set(&b->parameter, "charset", pc + (sizeof("charset=") - 1));
515 }
516 else
517 {
518 mutt_param_set(&b->parameter, "charset",
520 }
521 }
522}
523
524#ifdef USE_AUTOCRYPT
531static struct AutocryptHeader *parse_autocrypt(struct AutocryptHeader *head, const char *s)
532{
533 struct AutocryptHeader *autocrypt = mutt_autocrypthdr_new();
534 autocrypt->next = head;
535
536 struct ParameterList pl = TAILQ_HEAD_INITIALIZER(pl);
537 parse_parameters(&pl, s, true);
538 if (TAILQ_EMPTY(&pl))
539 {
540 autocrypt->invalid = true;
541 goto cleanup;
542 }
543
544 struct Parameter *p = NULL;
545 TAILQ_FOREACH(p, &pl, entries)
546 {
547 if (mutt_istr_equal(p->attribute, "addr"))
548 {
549 if (autocrypt->addr)
550 {
551 autocrypt->invalid = true;
552 goto cleanup;
553 }
554 autocrypt->addr = p->value;
555 p->value = NULL;
556 }
557 else if (mutt_istr_equal(p->attribute, "prefer-encrypt"))
558 {
559 if (mutt_istr_equal(p->value, "mutual"))
560 autocrypt->prefer_encrypt = true;
561 }
562 else if (mutt_istr_equal(p->attribute, "keydata"))
563 {
564 if (autocrypt->keydata)
565 {
566 autocrypt->invalid = true;
567 goto cleanup;
568 }
569 autocrypt->keydata = p->value;
570 p->value = NULL;
571 }
572 else if (p->attribute && (p->attribute[0] != '_'))
573 {
574 autocrypt->invalid = true;
575 goto cleanup;
576 }
577 }
578
579 /* Checking the addr against From, and for multiple valid headers
580 * occurs later, after all the headers are parsed. */
581 if (!autocrypt->addr || !autocrypt->keydata)
582 autocrypt->invalid = true;
583
584cleanup:
585 mutt_param_free(&pl);
586 return autocrypt;
587}
588#endif
589
595static char *rfc2369_first_mailto(const char *body)
596{
597 for (const char *beg = body, *end = NULL; beg; beg = strchr(end, ','))
598 {
599 beg = strchr(beg, '<');
600 if (!beg)
601 {
602 break;
603 }
604 beg++;
605 end = strchr(beg, '>');
606 if (!end)
607 {
608 break;
609 }
610
611 char *mlist = mutt_strn_dup(beg, end - beg);
612 if (url_check_scheme(mlist) == U_MAILTO)
613 {
614 return mlist;
615 }
616 FREE(&mlist);
617 }
618 return NULL;
619}
620
637int mutt_rfc822_parse_line(struct Envelope *env, struct Email *e,
638 const char *name, size_t name_len, const char *body,
639 bool user_hdrs, bool weed, bool do_2047)
640{
641 if (!env || !name)
642 return 0;
643
644 bool matched = false;
645
646 switch (name[0] | 0x20)
647 {
648 case 'a':
649 if ((name_len == 13) && eqi12(name + 1, "pparently-to"))
650 {
651 mutt_addrlist_parse(&env->to, body);
652 matched = true;
653 }
654 else if ((name_len == 15) && eqi14(name + 1, "pparently-from"))
655 {
656 mutt_addrlist_parse(&env->from, body);
657 matched = true;
658 }
659#ifdef USE_AUTOCRYPT
660 else if ((name_len == 9) && eqi8(name + 1, "utocrypt"))
661 {
662 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
663 if (c_autocrypt)
664 {
665 env->autocrypt = parse_autocrypt(env->autocrypt, body);
666 matched = true;
667 }
668 }
669 else if ((name_len == 16) && eqi15(name + 1, "utocrypt-gossip"))
670 {
671 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
672 if (c_autocrypt)
673 {
675 matched = true;
676 }
677 }
678#endif
679 break;
680
681 case 'b':
682 if ((name_len == 3) && eqi2(name + 1, "cc"))
683 {
684 mutt_addrlist_parse(&env->bcc, body);
685 matched = true;
686 }
687 break;
688
689 case 'c':
690 if ((name_len == 2) && eqi1(name + 1, "c"))
691 {
692 mutt_addrlist_parse(&env->cc, body);
693 matched = true;
694 }
695 else
696 {
697 if ((name_len >= 12) && eqi8(name, "content-"))
698 {
699 if ((name_len == 12) && eqi4(name + 8, "type"))
700 {
701 if (e)
703 matched = true;
704 }
705 else if ((name_len == 16) && eqi8(name + 8, "language"))
706 {
707 if (e)
709 matched = true;
710 }
711 else if ((name_len == 25) && eqi17(name + 8, "transfer-encoding"))
712 {
713 if (e)
715 matched = true;
716 }
717 else if ((name_len == 14) && eqi8(name + 6, "t-length"))
718 {
719 if (e)
720 {
721 unsigned long len = 0;
722 e->body->length = mutt_str_atoul(body, &len) ? MIN(len, CONTENT_TOO_BIG) : -1;
723 }
724 matched = true;
725 }
726 else if ((name_len == 19) && eqi11(name + 8, "description"))
727 {
728 if (e)
729 {
732 }
733 matched = true;
734 }
735 else if ((name_len == 19) && eqi11(name + 8, "disposition"))
736 {
737 if (e)
739 matched = true;
740 }
741 }
742 }
743 break;
744
745 case 'd':
746 if ((name_len != 4) || !eqi4(name, "date"))
747 break;
748
749 mutt_str_replace(&env->date, body);
750 if (e)
751 {
752 struct Tz tz = { 0 };
753 e->date_sent = mutt_date_parse_date(body, &tz);
754 if (e->date_sent > 0)
755 {
756 e->zhours = tz.zhours;
757 e->zminutes = tz.zminutes;
758 e->zoccident = tz.zoccident;
759 }
760 }
761 matched = true;
762 break;
763
764 case 'e':
765 if ((name_len == 7) && eqi6(name + 1, "xpires") && e &&
766 (mutt_date_parse_date(body, NULL) < mutt_date_now()))
767 {
768 e->expired = true;
769 }
770 break;
771
772 case 'f':
773 if ((name_len == 4) && eqi4(name, "from"))
774 {
775 mutt_addrlist_parse(&env->from, body);
776 matched = true;
777 }
778 else if ((name_len == 11) && eqi10(name + 1, "ollowup-to"))
779 {
780 if (!env->followup_to)
781 {
784 }
785 matched = true;
786 }
787 break;
788
789 case 'i':
790 if ((name_len != 11) || !eqi10(name + 1, "n-reply-to"))
791 break;
792
794 parse_references(&env->in_reply_to, body);
795 matched = true;
796 break;
797
798 case 'l':
799 if ((name_len == 5) && eqi4(name + 1, "ines"))
800 {
801 if (e)
802 {
803 unsigned int ui = 0; // we don't want a negative number of lines
804 mutt_str_atoui(body, &ui);
805 e->lines = ui;
806 }
807
808 matched = true;
809 }
810 else if ((name_len == 9) && eqi8(name + 1, "ist-post"))
811 {
812 /* RFC2369 */
813 if (!mutt_strn_equal(mutt_str_skip_whitespace(body), "NO", 2))
814 {
815 char *mailto = rfc2369_first_mailto(body);
816 if (mailto)
817 {
818 FREE(&env->list_post);
819 env->list_post = mailto;
820 const bool c_auto_subscribe = cs_subset_bool(NeoMutt->sub, "auto_subscribe");
821 if (c_auto_subscribe)
823 }
824 }
825 matched = true;
826 }
827 else if ((name_len == 14) && eqi13(name + 1, "ist-subscribe"))
828 {
829 /* RFC2369 */
830 char *mailto = rfc2369_first_mailto(body);
831 if (mailto)
832 {
833 FREE(&env->list_subscribe);
834 env->list_subscribe = mailto;
835 }
836 matched = true;
837 }
838 else if ((name_len == 16) && eqi15(name + 1, "ist-unsubscribe"))
839 {
840 /* RFC2369 */
841 char *mailto = rfc2369_first_mailto(body);
842 if (mailto)
843 {
844 FREE(&env->list_unsubscribe);
845 env->list_unsubscribe = mailto;
846 }
847 matched = true;
848 }
849 break;
850
851 case 'm':
852 if ((name_len == 12) && eqi11(name + 1, "ime-version"))
853 {
854 if (e)
855 e->mime = true;
856 matched = true;
857 }
858 else if ((name_len == 10) && eqi9(name + 1, "essage-id"))
859 {
860 /* We add a new "Message-ID:" when building a message */
861 FREE(&env->message_id);
862 env->message_id = mutt_extract_message_id(body, NULL);
863 matched = true;
864 }
865 else
866 {
867 if ((name_len >= 13) && eqi4(name + 1, "ail-"))
868 {
869 if ((name_len == 13) && eqi8(name + 5, "reply-to"))
870 {
871 /* override the Reply-To: field */
873 mutt_addrlist_parse(&env->reply_to, body);
874 matched = true;
875 }
876 else if ((name_len == 16) && eqi11(name + 5, "followup-to"))
877 {
879 matched = true;
880 }
881 }
882 }
883 break;
884
885 case 'n':
886 if ((name_len == 10) && eqi9(name + 1, "ewsgroups"))
887 {
888 FREE(&env->newsgroups);
891 matched = true;
892 }
893 break;
894
895 case 'o':
896 /* field 'Organization:' saves only for pager! */
897 if ((name_len == 12) && eqi11(name + 1, "rganization"))
898 {
899 if (!env->organization && !mutt_istr_equal(body, "unknown"))
900 env->organization = mutt_str_dup(body);
901 }
902 break;
903
904 case 'r':
905 if ((name_len == 10) && eqi9(name + 1, "eferences"))
906 {
908 parse_references(&env->references, body);
909 matched = true;
910 }
911 else if ((name_len == 8) && eqi8(name, "reply-to"))
912 {
913 mutt_addrlist_parse(&env->reply_to, body);
914 matched = true;
915 }
916 else if ((name_len == 11) && eqi10(name + 1, "eturn-path"))
917 {
918 mutt_addrlist_parse(&env->return_path, body);
919 matched = true;
920 }
921 else if ((name_len == 8) && eqi8(name, "received"))
922 {
923 if (e && (e->received == 0))
924 {
925 char *d = strrchr(body, ';');
926 if (d)
927 {
928 d = mutt_str_skip_email_wsp(d + 1);
929 e->received = mutt_date_parse_date(d, NULL);
930 }
931 }
932 }
933 break;
934
935 case 's':
936 if ((name_len == 7) && eqi6(name + 1, "ubject"))
937 {
938 if (!env->subject)
939 env->subject = mutt_str_dup(body);
940 matched = true;
941 }
942 else if ((name_len == 6) && eqi5(name + 1, "ender"))
943 {
944 mutt_addrlist_parse(&env->sender, body);
945 matched = true;
946 }
947 else if ((name_len == 6) && eqi5(name + 1, "tatus"))
948 {
949 if (e)
950 {
951 while (*body)
952 {
953 switch (*body)
954 {
955 case 'O':
956 {
957 e->old = true;
958 break;
959 }
960 case 'R':
961 e->read = true;
962 break;
963 case 'r':
964 e->replied = true;
965 break;
966 }
967 body++;
968 }
969 }
970 matched = true;
971 }
972 else if (e && (name_len == 10) && eqi1(name + 1, "u") &&
973 (eqi8(name + 2, "persedes") || eqi8(name + 2, "percedes")))
974 {
975 FREE(&env->supersedes);
976 env->supersedes = mutt_str_dup(body);
977 }
978 break;
979
980 case 't':
981 if ((name_len == 2) && eqi1(name + 1, "o"))
982 {
983 mutt_addrlist_parse(&env->to, body);
984 matched = true;
985 }
986 break;
987
988 case 'x':
989 if ((name_len == 8) && eqi8(name, "x-status"))
990 {
991 if (e)
992 {
993 while (*body)
994 {
995 switch (*body)
996 {
997 case 'A':
998 e->replied = true;
999 break;
1000 case 'D':
1001 e->deleted = true;
1002 break;
1003 case 'F':
1004 e->flagged = true;
1005 break;
1006 default:
1007 break;
1008 }
1009 body++;
1010 }
1011 }
1012 matched = true;
1013 }
1014 else if ((name_len == 7) && eqi6(name + 1, "-label"))
1015 {
1016 FREE(&env->x_label);
1017 env->x_label = mutt_str_dup(body);
1018 matched = true;
1019 }
1020 else if ((name_len == 12) && eqi11(name + 1, "-comment-to"))
1021 {
1022 if (!env->x_comment_to)
1023 env->x_comment_to = mutt_str_dup(body);
1024 matched = true;
1025 }
1026 else if ((name_len == 4) && eqi4(name, "xref"))
1027 {
1028 if (!env->xref)
1029 env->xref = mutt_str_dup(body);
1030 matched = true;
1031 }
1032 else if ((name_len == 13) && eqi12(name + 1, "-original-to"))
1033 {
1035 matched = true;
1036 }
1037 break;
1038
1039 default:
1040 break;
1041 }
1042
1043 /* Keep track of the user-defined headers */
1044 if (!matched && user_hdrs)
1045 {
1046 const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
1047 char *dup = NULL;
1048 mutt_str_asprintf(&dup, "%s: %s", name, body);
1049
1050 if (!weed || !c_weed || !mutt_matches_ignore(dup))
1051 {
1052 struct ListNode *np = mutt_list_insert_tail(&env->userhdrs, dup);
1053 if (do_2047)
1054 {
1055 rfc2047_decode(&np->data);
1056 }
1057 }
1058 else
1059 {
1060 FREE(&dup);
1061 }
1062 }
1063
1064 return matched;
1065}
1066
1076size_t mutt_rfc822_read_line(FILE *fp, struct Buffer *buf)
1077{
1078 if (!fp || !buf)
1079 return 0;
1080
1081 size_t read = 0;
1082 char line[1024] = { 0 }; /* RFC2822 specifies a maximum line length of 998 */
1083
1084 buf_reset(buf);
1085 while (true)
1086 {
1087 if (!fgets(line, sizeof(line), fp))
1088 {
1089 return 0;
1090 }
1091
1092 const size_t linelen = mutt_str_len(line);
1093 if (linelen == 0)
1094 {
1095 break;
1096 }
1097
1098 if (isspace(line[0]) && buf_is_empty(buf))
1099 {
1100 read = linelen;
1101 break;
1102 }
1103
1104 read += linelen;
1105
1106 size_t off = linelen - 1;
1107 if (line[off] == '\n')
1108 {
1109 /* We did get a full line: remove trailing space */
1110 do
1111 {
1112 line[off] = '\0';
1113 } while (off && isspace(line[--off]));
1114
1115 /* check to see if the next line is a continuation line */
1116 int ch = fgetc(fp);
1117 if ((ch != ' ') && (ch != '\t'))
1118 {
1119 /* next line is a separate header field or EOH */
1120 ungetc(ch, fp);
1121 buf_addstr(buf, line);
1122 break;
1123 }
1124 read++;
1125
1126 /* eat tabs and spaces from the beginning of the continuation line */
1127 while (((ch = fgetc(fp)) == ' ') || (ch == '\t'))
1128 {
1129 read++;
1130 }
1131
1132 ungetc(ch, fp);
1133 line[off + 1] = ' '; /* string is still terminated because we removed
1134 at least one whitespace char above */
1135 }
1136
1137 buf_addstr(buf, line);
1138 }
1139
1140 return read;
1141}
1142
1156struct Envelope *mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
1157{
1158 if (!fp)
1159 return NULL;
1160
1161 struct Envelope *env = mutt_env_new();
1162 char *p = NULL;
1163 LOFF_T loc = e ? e->offset : ftello(fp);
1164 if (loc < 0)
1165 {
1166 mutt_debug(LL_DEBUG1, "ftello: %s (errno %d)\n", strerror(errno), errno);
1167 loc = 0;
1168 }
1169
1170 struct Buffer *line = buf_pool_get();
1171
1172 if (e)
1173 {
1174 if (!e->body)
1175 {
1176 e->body = mutt_body_new();
1177
1178 /* set the defaults from RFC1521 */
1179 e->body->type = TYPE_TEXT;
1180 e->body->subtype = mutt_str_dup("plain");
1181 e->body->encoding = ENC_7BIT;
1182 e->body->length = -1;
1183
1184 /* RFC2183 says this is arbitrary */
1186 }
1187 }
1188
1189 while (true)
1190 {
1191 LOFF_T line_start_loc = loc;
1192 size_t len = mutt_rfc822_read_line(fp, line);
1193 if (buf_len(line) == 0)
1194 {
1195 break;
1196 }
1197 loc += len;
1198 const char *lines = buf_string(line);
1199 p = strpbrk(lines, ": \t");
1200 if (!p || (*p != ':'))
1201 {
1202 char return_path[1024] = { 0 };
1203 time_t t = 0;
1204
1205 /* some bogus MTAs will quote the original "From " line */
1206 if (mutt_str_startswith(lines, ">From "))
1207 {
1208 continue; /* just ignore */
1209 }
1210 else if (is_from(lines, return_path, sizeof(return_path), &t))
1211 {
1212 /* MH sometimes has the From_ line in the middle of the header! */
1213 if (e && (e->received == 0))
1214 e->received = t - mutt_date_local_tz(t);
1215 continue;
1216 }
1217
1218 /* We need to seek back to the start of the body. Note that we
1219 * keep track of loc ourselves, since calling ftello() incurs
1220 * a syscall, which can be expensive to do for every single line */
1221 (void) mutt_file_seek(fp, line_start_loc, SEEK_SET);
1222 break; /* end of header */
1223 }
1224 size_t name_len = p - lines;
1225
1226 char buf[1024] = { 0 };
1227 if (mutt_replacelist_match(&SpamList, buf, sizeof(buf), lines))
1228 {
1229 if (!mutt_regexlist_match(&NoSpamList, lines))
1230 {
1231 /* if spam tag already exists, figure out how to amend it */
1232 if ((!buf_is_empty(&env->spam)) && (*buf != '\0'))
1233 {
1234 /* If `$spam_separator` defined, append with separator */
1235 const char *const c_spam_separator = cs_subset_string(NeoMutt->sub, "spam_separator");
1236 if (c_spam_separator)
1237 {
1238 buf_addstr(&env->spam, c_spam_separator);
1239 buf_addstr(&env->spam, buf);
1240 }
1241 else /* overwrite */
1242 {
1243 buf_reset(&env->spam);
1244 buf_addstr(&env->spam, buf);
1245 }
1246 }
1247 else if (buf_is_empty(&env->spam) && (*buf != '\0'))
1248 {
1249 /* spam tag is new, and match expr is non-empty; copy */
1250 buf_addstr(&env->spam, buf);
1251 }
1252 else if (buf_is_empty(&env->spam))
1253 {
1254 /* match expr is empty; plug in null string if no existing tag */
1255 buf_addstr(&env->spam, "");
1256 }
1257
1258 if (!buf_is_empty(&env->spam))
1259 mutt_debug(LL_DEBUG5, "spam = %s\n", env->spam.data);
1260 }
1261 }
1262
1263 *p = '\0';
1264 p = mutt_str_skip_email_wsp(p + 1);
1265 if (*p == '\0')
1266 continue; /* skip empty header fields */
1267
1268 mutt_rfc822_parse_line(env, e, lines, name_len, p, user_hdrs, weed, true);
1269 }
1270
1271 buf_pool_release(&line);
1272
1273 if (e)
1274 {
1275 e->body->hdr_offset = e->offset;
1276 e->body->offset = ftello(fp);
1277
1279
1280 if (env->subject)
1281 {
1282 regmatch_t pmatch[1];
1283
1284 const struct Regex *c_reply_regex = cs_subset_regex(NeoMutt->sub, "reply_regex");
1285 if (mutt_regex_capture(c_reply_regex, env->subject, 1, pmatch))
1286 {
1287 env->real_subj = env->subject + pmatch[0].rm_eo;
1288 if (env->real_subj[0] == '\0')
1289 env->real_subj = NULL;
1290 }
1291 else
1292 {
1293 env->real_subj = env->subject;
1294 }
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, "no date found, using received time from msg separator\n");
1307 e->date_sent = e->received;
1308 }
1309
1310#ifdef USE_AUTOCRYPT
1311 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
1312 if (c_autocrypt)
1313 {
1315 /* No sense in taking up memory after the header is processed */
1317 }
1318#endif
1319 }
1320
1321 return env;
1322}
1323
1330struct Body *mutt_read_mime_header(FILE *fp, bool digest)
1331{
1332 if (!fp)
1333 return NULL;
1334
1335 struct Body *b = mutt_body_new();
1336 struct Envelope *env = mutt_env_new();
1337 char *c = NULL;
1338 struct Buffer *buf = buf_pool_get();
1339 bool matched = false;
1340
1341 b->hdr_offset = ftello(fp);
1342
1343 b->encoding = ENC_7BIT; /* default from RFC1521 */
1344 b->type = digest ? TYPE_MESSAGE : TYPE_TEXT;
1346
1347 while (mutt_rfc822_read_line(fp, buf) != 0)
1348 {
1349 const char *line = buf_string(buf);
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 {
1374 }
1375 else if (mutt_istr_equal("language", line + plen))
1376 {
1378 }
1379 else if (mutt_istr_equal("transfer-encoding", line + plen))
1380 {
1382 }
1383 else if (mutt_istr_equal("disposition", line + plen))
1384 {
1386 }
1387 else if (mutt_istr_equal("description", line + plen))
1388 {
1391 }
1392 else if (mutt_istr_equal("id", line + plen))
1393 {
1394 // strip <angle braces> from Content-ID: header
1395 char *id = c;
1396 int cid_len = mutt_str_len(c);
1397 if (cid_len > 2)
1398 {
1399 if (id[0] == '<')
1400 {
1401 id++;
1402 cid_len--;
1403 }
1404 if (id[cid_len - 1] == '>')
1405 id[cid_len - 1] = '\0';
1406 }
1407 mutt_param_set(&b->parameter, "content-id", id);
1408 }
1409 }
1410 else if ((plen = mutt_istr_startswith(line, "x-sun-")))
1411 {
1412 if (mutt_istr_equal("data-type", line + plen))
1413 {
1415 }
1416 else if (mutt_istr_equal("encoding-info", line + plen))
1417 {
1419 }
1420 else if (mutt_istr_equal("content-lines", line + plen))
1421 {
1422 mutt_param_set(&b->parameter, "content-lines", c);
1423 }
1424 else if (mutt_istr_equal("data-description", line + plen))
1425 {
1428 }
1429 }
1430 else
1431 {
1432 if (mutt_rfc822_parse_line(env, NULL, line, strlen(line), c, false, false, false))
1433 {
1434 matched = true;
1435 }
1436 }
1437 }
1438 b->offset = ftello(fp); /* Mark the start of the real data */
1439 if ((b->type == TYPE_TEXT) && !b->subtype)
1440 b->subtype = mutt_str_dup("plain");
1441 else if ((b->type == TYPE_MESSAGE) && !b->subtype)
1442 b->subtype = mutt_str_dup("rfc822");
1443
1444 buf_pool_release(&buf);
1445
1446 if (matched)
1447 {
1448 b->mime_headers = env;
1450 }
1451 else
1452 {
1453 mutt_env_free(&env);
1454 }
1455
1456 return b;
1457}
1458
1466bool mutt_is_message_type(int type, const char *subtype)
1467{
1468 if (type != TYPE_MESSAGE)
1469 return false;
1470
1471 subtype = NONULL(subtype);
1472 return (mutt_istr_equal(subtype, "rfc822") ||
1473 mutt_istr_equal(subtype, "news") || mutt_istr_equal(subtype, "global"));
1474}
1475
1482static void parse_part(FILE *fp, struct Body *b, int *counter)
1483{
1484 if (!fp || !b)
1485 return;
1486
1487 const char *bound = NULL;
1488 static unsigned short recurse_level = 0;
1489
1490 if (recurse_level >= MUTT_MIME_MAX_DEPTH)
1491 {
1492 mutt_debug(LL_DEBUG1, "recurse level too deep. giving up.\n");
1493 return;
1494 }
1495 recurse_level++;
1496
1497 switch (b->type)
1498 {
1499 case TYPE_MULTIPART:
1500 if (mutt_istr_equal(b->subtype, "x-sun-attachment"))
1501 bound = "--------";
1502 else
1503 bound = mutt_param_get(&b->parameter, "boundary");
1504
1505 if (!mutt_file_seek(fp, b->offset, SEEK_SET))
1506 {
1507 goto bail;
1508 }
1509 b->parts = parse_multipart(fp, bound, b->offset + b->length,
1510 mutt_istr_equal("digest", b->subtype), counter);
1511 break;
1512
1513 case TYPE_MESSAGE:
1514 if (!b->subtype)
1515 break;
1516
1517 if (!mutt_file_seek(fp, b->offset, SEEK_SET))
1518 {
1519 goto bail;
1520 }
1521 if (mutt_is_message_type(b->type, b->subtype))
1522 b->parts = rfc822_parse_message(fp, b, counter);
1523 else if (mutt_istr_equal(b->subtype, "external-body"))
1524 b->parts = mutt_read_mime_header(fp, 0);
1525 else
1526 goto bail;
1527 break;
1528
1529 default:
1530 goto bail;
1531 }
1532
1533 /* try to recover from parsing error */
1534 if (!b->parts)
1535 {
1536 b->type = TYPE_TEXT;
1537 mutt_str_replace(&b->subtype, "plain");
1538 }
1539bail:
1540 recurse_level--;
1541}
1542
1553static struct Body *parse_multipart(FILE *fp, const char *boundary,
1554 LOFF_T end_off, bool digest, int *counter)
1555{
1556 if (!fp)
1557 return NULL;
1558
1559 if (!boundary)
1560 {
1561 mutt_error(_("multipart message has no boundary parameter"));
1562 return NULL;
1563 }
1564
1565 char buf[1024] = { 0 };
1566 struct Body *head = NULL, *last = NULL, *new_body = NULL;
1567 bool final = false; /* did we see the ending boundary? */
1568
1569 const size_t blen = mutt_str_len(boundary);
1570 while ((ftello(fp) < end_off) && fgets(buf, sizeof(buf), fp))
1571 {
1572 const size_t len = mutt_str_len(buf);
1573
1574 const size_t crlf = ((len > 1) && (buf[len - 2] == '\r')) ? 1 : 0;
1575
1576 if ((buf[0] == '-') && (buf[1] == '-') && mutt_str_startswith(buf + 2, boundary))
1577 {
1578 if (last)
1579 {
1580 last->length = ftello(fp) - last->offset - len - 1 - crlf;
1581 if (last->parts && (last->parts->length == 0))
1582 last->parts->length = ftello(fp) - last->parts->offset - len - 1 - crlf;
1583 /* if the body is empty, we can end up with a -1 length */
1584 if (last->length < 0)
1585 last->length = 0;
1586 }
1587
1588 if (len > 0)
1589 {
1590 /* Remove any trailing whitespace, up to the length of the boundary */
1591 for (size_t i = len - 1; isspace(buf[i]) && (i >= (blen + 2)); i--)
1592 buf[i] = '\0';
1593 }
1594
1595 /* Check for the end boundary */
1596 if (mutt_str_equal(buf + blen + 2, "--"))
1597 {
1598 final = true;
1599 break; /* done parsing */
1600 }
1601 else if (buf[2 + blen] == '\0')
1602 {
1603 new_body = mutt_read_mime_header(fp, digest);
1604 if (!new_body)
1605 break;
1606
1607 if (mutt_param_get(&new_body->parameter, "content-lines"))
1608 {
1609 int lines = 0;
1610 mutt_str_atoi(mutt_param_get(&new_body->parameter, "content-lines"), &lines);
1611 for (; lines > 0; lines--)
1612 if ((ftello(fp) >= end_off) || !fgets(buf, sizeof(buf), fp))
1613 break;
1614 }
1615
1616 /* Consistency checking - catch bad attachment end boundaries */
1617 if (new_body->offset > end_off)
1618 {
1619 mutt_body_free(&new_body);
1620 break;
1621 }
1622 if (head)
1623 {
1624 last->next = new_body;
1625 last = new_body;
1626 }
1627 else
1628 {
1629 last = new_body;
1630 head = new_body;
1631 }
1632
1633 /* It seems more intuitive to add the counter increment to
1634 * parse_part(), but we want to stop the case where a multipart
1635 * contains thousands of tiny parts before the memory and data
1636 * structures are allocated. */
1637 if (++(*counter) >= MUTT_MIME_MAX_PARTS)
1638 break;
1639 }
1640 }
1641 }
1642
1643 /* in case of missing end boundary, set the length to something reasonable */
1644 if (last && (last->length == 0) && !final)
1645 last->length = end_off - last->offset;
1646
1647 /* parse recursive MIME parts */
1648 for (last = head; last; last = last->next)
1649 parse_part(fp, last, counter);
1650
1651 return head;
1652}
1653
1663static struct Body *rfc822_parse_message(FILE *fp, struct Body *parent, int *counter)
1664{
1665 if (!fp || !parent)
1666 return NULL;
1667
1668 parent->email = email_new();
1669 parent->email->offset = ftello(fp);
1670 parent->email->env = mutt_rfc822_read_header(fp, parent->email, false, false);
1671 struct Body *msg = parent->email->body;
1672
1673 /* ignore the length given in the content-length since it could be wrong
1674 * and we already have the info to calculate the correct length */
1675 /* if (msg->length == -1) */
1676 msg->length = parent->length - (msg->offset - parent->offset);
1677
1678 /* if body of this message is empty, we can end up with a negative length */
1679 if (msg->length < 0)
1680 msg->length = 0;
1681
1682 parse_part(fp, msg, counter);
1683 return msg;
1684}
1685
1694bool mutt_parse_mailto(struct Envelope *env, char **body, const char *src)
1695{
1696 if (!env || !src)
1697 return false;
1698
1699 struct Url *url = url_parse(src);
1700 if (!url)
1701 return false;
1702
1703 if (url->host)
1704 {
1705 /* this is not a path-only URL */
1706 url_free(&url);
1707 return false;
1708 }
1709
1710 mutt_addrlist_parse(&env->to, url->path);
1711
1712 struct UrlQuery *np;
1713 STAILQ_FOREACH(np, &url->query_strings, entries)
1714 {
1715 const char *tag = np->name;
1716 char *value = np->value;
1717 /* Determine if this header field is on the allowed list. Since NeoMutt
1718 * interprets some header fields specially (such as
1719 * "Attach: ~/.gnupg/secring.gpg"), care must be taken to ensure that
1720 * only safe fields are allowed.
1721 *
1722 * RFC2368, "4. Unsafe headers"
1723 * The user agent interpreting a mailto URL SHOULD choose not to create
1724 * a message if any of the headers are considered dangerous; it may also
1725 * choose to create a message with only a subset of the headers given in
1726 * the URL. */
1727 if (mutt_list_match(tag, &MailToAllow))
1728 {
1729 if (mutt_istr_equal(tag, "body"))
1730 {
1731 if (body)
1732 mutt_str_replace(body, value);
1733 }
1734 else
1735 {
1736 char *scratch = NULL;
1737 size_t taglen = mutt_str_len(tag);
1738
1739 mutt_str_asprintf(&scratch, "%s: %s", tag, value);
1740 scratch[taglen] = 0; /* overwrite the colon as mutt_rfc822_parse_line expects */
1741 value = mutt_str_skip_email_wsp(&scratch[taglen + 1]);
1742 mutt_rfc822_parse_line(env, NULL, scratch, taglen, value, true, false, true);
1743 FREE(&scratch);
1744 }
1745 }
1746 }
1747
1748 /* RFC2047 decode after the RFC822 parsing */
1750
1751 url_free(&url);
1752 return true;
1753}
1754
1760void mutt_parse_part(FILE *fp, struct Body *b)
1761{
1762 int counter = 0;
1763
1764 parse_part(fp, b, &counter);
1765}
1766
1775struct Body *mutt_rfc822_parse_message(FILE *fp, struct Body *b)
1776{
1777 int counter = 0;
1778
1779 return rfc822_parse_message(fp, b, &counter);
1780}
1781
1791struct Body *mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
1792{
1793 int counter = 0;
1794
1795 return parse_multipart(fp, boundary, end_off, digest, &counter);
1796}
void mutt_addrlist_clear(struct AddressList *al)
Unlink and free all Address in an AddressList.
Definition: address.c:1461
int mutt_addrlist_parse(struct AddressList *al, const char *s)
Parse a list of email addresses.
Definition: address.c:478
Email Address Handling.
const char * mutt_str_atoul(const char *str, unsigned long *dst)
Convert ASCII string to an unsigned long.
Definition: atoi.c:239
const char * mutt_str_atoui(const char *str, unsigned int *dst)
Convert ASCII string to an unsigned integer.
Definition: atoi.c:213
const char * mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: atoi.c:187
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:255
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition: buffer.c:466
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:88
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:303
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:253
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:238
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:542
void buf_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:349
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:93
const struct Regex * cs_subset_regex(const struct ConfigSubset *sub, const char *name)
Get a regex config item by name.
Definition: helpers.c:218
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:292
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
Convenience wrapper for the config headers.
const struct Slist * cc_assumed_charset(void)
Get the cached value of $assumed_charset.
Definition: config_cache.c:100
Convenience wrapper for the core headers.
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
struct ReplaceList SpamList
List of regexes to match subscribed mailing lists.
Definition: globals.c:45
struct RegexList SubscribedLists
List of header patterns to unignore (see)
Definition: globals.c:47
struct HashTable * AutoSubscribeCache
< Hash Table: "mailto:" -> AutoSubscribeCache
Definition: globals.c:35
struct RegexList UnSubscribedLists
Definition: globals.c:53
struct RegexList UnMailLists
List of regexes to exclude false matches in SubscribedLists.
Definition: globals.c:51
struct RegexList MailLists
List of permitted fields in a mailto: url.
Definition: globals.c:39
struct ListHead MailToAllow
List of regexes to identify non-spam emails.
Definition: globals.c:41
struct ListHead Ignore
List of regexes to match mailing lists.
Definition: globals.c:37
struct RegexList NoSpamList
List of regexes and patterns to match spam emails.
Definition: globals.c:43
struct ListHead UnIgnore
List of regexes to exclude false matches in MailLists.
Definition: globals.c:49
int mutt_rfc822_parse_line(struct Envelope *env, struct Email *e, const char *name, size_t name_len, const char *body, bool user_hdrs, bool weed, bool do_2047)
Parse an email header.
Definition: parse.c:637
struct Body * mutt_rfc822_parse_message(FILE *fp, struct Body *b)
Parse a Message/RFC822 body.
Definition: parse.c:1775
void mutt_parse_part(FILE *fp, struct Body *b)
Parse a MIME part.
Definition: parse.c:1760
void mutt_parse_content_type(const char *s, struct Body *b)
Parse a content type.
Definition: parse.c:422
void mutt_auto_subscribe(const char *mailto)
Check if user is subscribed to mailing list.
Definition: parse.c:68
size_t mutt_rfc822_read_line(FILE *fp, struct Buffer *buf)
Read a header line from a file.
Definition: parse.c:1076
struct Body * mutt_read_mime_header(FILE *fp, bool digest)
Parse a MIME header.
Definition: parse.c:1330
static struct AutocryptHeader * parse_autocrypt(struct AutocryptHeader *head, const char *s)
Parse an Autocrypt header line.
Definition: parse.c:531
static void parse_references(struct ListHead *head, const char *s)
Parse references from an email header.
Definition: parse.c:282
bool mutt_matches_ignore(const char *s)
Does the string match the ignore list.
Definition: parse.c:315
static char * rfc2369_first_mailto(const char *body)
Extract the first mailto: URL from a RFC2369 list.
Definition: parse.c:595
enum ContentType mutt_check_mime_type(const char *s)
Check a MIME type string.
Definition: parse.c:325
static struct Body * rfc822_parse_message(FILE *fp, struct Body *parent, int *counter)
Parse a Message/RFC822 body.
Definition: parse.c:1663
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1156
static void parse_content_language(const char *s, struct Body *b)
Read the content's language.
Definition: parse.c:299
bool mutt_parse_mailto(struct Envelope *env, char **body, const char *src)
Parse a mailto:// url.
Definition: parse.c:1694
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 *b)
Parse a content disposition.
Definition: parse.c:250
struct Body * mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
Parse a multipart structure.
Definition: parse.c:1791
char * mutt_extract_message_id(const char *s, size_t *len)
Find a message-id.
Definition: parse.c:360
static void parse_part(FILE *fp, struct Body *b, int *counter)
Parse a MIME part.
Definition: parse.c:1482
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:1553
bool mutt_is_message_type(int type, const char *subtype)
Determine if a mime type matches a message or not.
Definition: parse.c:1466
static void parse_parameters(struct ParameterList *pl, const char *s, bool allow_value_spaces)
Parse a list of Parameters.
Definition: parse.c:111
#define CONTENT_TOO_BIG
Definition: parse.c:57
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
struct AutocryptHeader * mutt_autocrypthdr_new(void)
Create a new AutocryptHeader.
Definition: envelope.c:66
void mutt_autocrypthdr_free(struct AutocryptHeader **ptr)
Free an AutocryptHeader.
Definition: envelope.c:75
Representation of an email header (envelope)
static bool eqi17(const char *a, const char b[17])
eqi17 - Compare two 17-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:205
static bool eqi9(const char *a, const char b[9])
eqi9 - Compare two 9-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:157
static bool eqi10(const char *a, const char b[10])
eqi10 - Compare two 10-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:163
static bool eqi8(const char *a, const char b[8])
Compare two 8-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons.
Definition: eqi.h:122
static bool eqi11(const char *a, const char b[11])
eqi11 - Compare two 11-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:169
static bool eqi6(const char *a, const char b[6])
eqi6 - Compare two 6-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:149
static bool eqi14(const char *a, const char b[14])
eqi14 - Compare two 14-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:187
static bool eqi13(const char *a, const char b[13])
eqi13 - Compare two 13-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:181
static bool eqi4(const char *a, const char b[4])
Compare two 4-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons.
Definition: eqi.h:104
static bool eqi5(const char *a, const char b[5])
eqi5 - Compare two 5-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:143
static bool eqi12(const char *a, const char b[12])
eqi12 - Compare two 12-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:175
static bool eqi15(const char *a, const char b[15])
eqi15 - Compare two 15-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons
Definition: eqi.h:193
static bool eqi1(const char *a, const char b[1])
Compare two 1-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons.
Definition: eqi.h:76
static bool eqi2(const char *a, const char b[2])
Compare two 2-byte strings, ignoring case - See: Case-insensitive fixed-chunk comparisons.
Definition: eqi.h:88
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:733
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.
Global variables.
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
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:112
#define MUTT_HASH_STRCASECMP
use strcasecmp() to compare keys
Definition: hash.h:111
struct ListNode * mutt_list_insert_tail(struct ListHead *h, char *s)
Append a string to the end of a List.
Definition: list.c:64
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
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: logging2.h:47
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
#define FREE(x)
Definition: memory.h:45
#define MIN(a, b)
Definition: memory.h:32
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
const char * mutt_ch_get_default_charset(const struct Slist *const assumed_charset)
Get the default character set.
Definition: charset.c:460
int mutt_date_local_tz(time_t t)
Calculate the local timezone in seconds east of UTC.
Definition: date.c:209
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:446
time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
Parse a date string in RFC822 format.
Definition: date.c:686
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:136
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:619
bool mutt_replacelist_match(struct ReplaceList *rl, char *buf, size_t buflen, const char *str)
Does a string match a pattern?
Definition: regex.c:500
bool mutt_regexlist_match(struct RegexList *rl, const char *str)
Does a string match any Regex in the list?
Definition: regex.c:196
bool slist_is_empty(const struct Slist *list)
Is the slist empty?
Definition: slist.c:178
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition: string.c:452
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:637
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:810
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:1022
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:680
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
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:497
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:228
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:623
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:568
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:240
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:525
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:327
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
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
struct Parameter * mutt_param_new(void)
Create a new Parameter.
Definition: parameter.c:39
Store attributes associated with a MIME part.
Miscellaneous email parsing routines.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
#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:828
void rfc2047_decode(char **pd)
Decode any RFC2047-encoded header fields.
Definition: rfc2047.c:659
RFC2047 MIME extensions encoding / decoding routines.
void rfc2231_decode_parameters(struct ParameterList *pl)
Decode a Parameter list.
Definition: rfc2231.c:240
RFC2231 MIME Charset routines.
Convenience wrapper for the send headers.
#define NONULL(x)
Definition: string2.h:37
static bool mutt_str_is_email_wsp(char c)
Is this a whitespace character (for an email header)
Definition: string2.h:111
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:85
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:80
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:81
struct AddressList x_original_to
Email's 'X-Orig-to'.
Definition: envelope.h:66
struct AutocryptHeader * autocrypt_gossip
Autocrypt Gossip header.
Definition: envelope.h:88
char * newsgroups
List of newsgroups.
Definition: envelope.h:78
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:83
struct AutocryptHeader * autocrypt
Autocrypt header.
Definition: envelope.h:87
struct Buffer spam
Spam header.
Definition: envelope.h:82
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:84
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:79
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:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
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
List of recognised Timezones.
Definition: date.h:47
unsigned char zminutes
Minutes away from UTC.
Definition: date.h:50
bool zoccident
True if west of UTC, False if East.
Definition: date.h:51
unsigned char zhours
Hours away from UTC.
Definition: date.h:49
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:238
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:225
Parse and identify different URL schemes.
@ U_MAILTO
Url is mailto://.
Definition: url.h:45