NeoMutt  2025-09-05-7-geaa2bd
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
parse.c
Go to the documentation of this file.
1
35#include "config.h"
36#include <errno.h>
37#include <string.h>
38#include <time.h>
39#include "mutt/lib.h"
40#include "address/lib.h"
41#include "config/lib.h"
42#include "core/lib.h"
43#include "mutt.h"
44#include "parse.h"
45#include "body.h"
46#include "email.h"
47#include "envelope.h"
48#include "from.h"
49#include "globals.h"
50#include "mime.h"
51#include "parameter.h"
52#include "rfc2047.h"
53#include "rfc2231.h"
54#include "url.h"
55#ifdef USE_AUTOCRYPT
56#include "autocrypt/lib.h"
57#endif
58
59/* If the 'Content-Length' is bigger than 1GiB, then it's clearly wrong.
60 * Cap the value to prevent overflow of Body.length */
61#define CONTENT_TOO_BIG (1 << 30)
62
63static void parse_part(FILE *fp, struct Body *b, int *counter);
64static struct Body *rfc822_parse_message(FILE *fp, struct Body *parent, int *counter);
65static struct Body *parse_multipart(FILE *fp, const char *boundary,
66 LOFF_T end_off, bool digest, int *counter);
67
73{
74 if (!header)
75 return;
76
77 for (; (*header != '\0'); header++)
78 {
79 if ((*header < 33) || (*header > 126) || (*header == ':'))
80 *header = '?';
81 }
82}
83
93{
94 if (!header)
95 return;
96
97 for (; (*header != '\0'); header++)
98 {
99 if ((*header == '\n') || (*header == '\r'))
100 *header = ' ';
101 }
102}
103
108void mutt_auto_subscribe(const char *mailto)
109{
110 if (!mailto)
111 return;
112
115
117 return;
118
120
121 struct Envelope *lpenv = mutt_env_new(); /* parsed envelope from the List-Post mailto: URL */
122
123 if (mutt_parse_mailto(lpenv, NULL, mailto) && !TAILQ_EMPTY(&lpenv->to))
124 {
125 const char *mailbox = buf_string(TAILQ_FIRST(&lpenv->to)->mailbox);
126 if (mailbox && !mutt_regexlist_match(&SubscribedLists, mailbox) &&
127 !mutt_regexlist_match(&UnMailLists, mailbox) &&
129 {
130 /* mutt_regexlist_add() detects duplicates, so it is safe to
131 * try to add here without any checks. */
132 mutt_regexlist_add(&MailLists, mailbox, REG_ICASE, NULL);
133 mutt_regexlist_add(&SubscribedLists, mailbox, REG_ICASE, NULL);
134 }
135 }
136
137 mutt_env_free(&lpenv);
138}
139
151static void parse_parameters(struct ParameterList *pl, const char *s, bool allow_value_spaces)
152{
153 struct Parameter *pnew = NULL;
154 const char *p = NULL;
155 size_t i;
156
157 struct Buffer *buf = buf_pool_get();
158 /* allow_value_spaces, especially with autocrypt keydata, can result
159 * in quite large parameter values. avoid frequent reallocs by
160 * pre-sizing */
161 if (allow_value_spaces)
162 buf_alloc(buf, mutt_str_len(s));
163
164 mutt_debug(LL_DEBUG2, "'%s'\n", s);
165
166 const bool assumed = !slist_is_empty(cc_assumed_charset());
167 while (*s)
168 {
169 buf_reset(buf);
170
171 p = strpbrk(s, "=;");
172 if (!p)
173 {
174 mutt_debug(LL_DEBUG1, "malformed parameter: %s\n", s);
175 goto bail;
176 }
177
178 /* if we hit a ; now the parameter has no value, just skip it */
179 if (*p != ';')
180 {
181 i = p - s;
182 /* remove whitespace from the end of the attribute name */
183 while ((i > 0) && mutt_str_is_email_wsp(s[i - 1]))
184 i--;
185
186 /* the check for the missing parameter token is here so that we can skip
187 * over any quoted value that may be present. */
188 if (i == 0)
189 {
190 mutt_debug(LL_DEBUG1, "missing attribute: %s\n", s);
191 pnew = NULL;
192 }
193 else
194 {
195 pnew = mutt_param_new();
196 pnew->attribute = mutt_strn_dup(s, i);
197 }
198
199 do
200 {
201 s = mutt_str_skip_email_wsp(p + 1); /* skip over the =, or space if we loop */
202
203 if (*s == '"')
204 {
205 bool state_ascii = true;
206 s++;
207 for (; *s; s++)
208 {
209 if (assumed)
210 {
211 // As iso-2022-* has a character of '"' with non-ascii state, ignore it
212 if (*s == 0x1b)
213 {
214 if ((s[1] == '(') && ((s[2] == 'B') || (s[2] == 'J')))
215 state_ascii = true;
216 else
217 state_ascii = false;
218 }
219 }
220 if (state_ascii && (*s == '"'))
221 break;
222 if (*s == '\\')
223 {
224 if (s[1])
225 {
226 s++;
227 /* Quote the next character */
228 buf_addch(buf, *s);
229 }
230 }
231 else
232 {
233 buf_addch(buf, *s);
234 }
235 }
236 if (*s)
237 s++; /* skip over the " */
238 }
239 else
240 {
241 for (; *s && *s != ' ' && *s != ';'; s++)
242 buf_addch(buf, *s);
243 }
244
245 p = s;
246 } while (allow_value_spaces && (*s == ' '));
247
248 /* if the attribute token was missing, 'new' will be NULL */
249 if (pnew)
250 {
251 pnew->value = buf_strdup(buf);
252
253 mutt_debug(LL_DEBUG2, "parse_parameter: '%s' = '%s'\n",
254 pnew->attribute ? pnew->attribute : "", pnew->value ? pnew->value : "");
255
256 /* Add this parameter to the list */
257 TAILQ_INSERT_HEAD(pl, pnew, entries);
258 }
259 }
260 else
261 {
262 mutt_debug(LL_DEBUG1, "parameter with no value: %s\n", s);
263 s = p;
264 }
265
266 /* Find the next parameter */
267 if ((*s != ';') && !(s = strchr(s, ';')))
268 break; /* no more parameters */
269
270 do
271 {
272 /* Move past any leading whitespace. the +1 skips over the semicolon */
273 s = mutt_str_skip_email_wsp(s + 1);
274 } while (*s == ';'); /* skip empty parameters */
275 }
276
277bail:
278
280 buf_pool_release(&buf);
281}
282
290static void parse_content_disposition(const char *s, struct Body *b)
291{
292 struct ParameterList pl = TAILQ_HEAD_INITIALIZER(pl);
293
294 if (mutt_istr_startswith(s, "inline"))
296 else if (mutt_istr_startswith(s, "form-data"))
298 else
300
301 /* Check to see if a default filename was given */
302 s = strchr(s, ';');
303 if (s)
304 {
305 s = mutt_str_skip_email_wsp(s + 1);
306 parse_parameters(&pl, s, false);
307 s = mutt_param_get(&pl, "filename");
308 if (s)
310 s = mutt_param_get(&pl, "name");
311 if (s)
313 mutt_param_free(&pl);
314 }
315}
316
322static void parse_references(struct ListHead *head, const char *s)
323{
324 if (!head)
325 return;
326
327 char *m = NULL;
328 for (size_t off = 0; (m = mutt_extract_message_id(s, &off)); s += off)
329 {
330 mutt_list_insert_head(head, m);
331 }
332}
333
339static void parse_content_language(const char *s, struct Body *b)
340{
341 if (!s || !b)
342 return;
343
344 mutt_debug(LL_DEBUG2, "RFC8255 >> Content-Language set to %s\n", s);
346}
347
355bool mutt_matches_ignore(const char *s)
356{
357 return mutt_list_match(s, &Ignore) && !mutt_list_match(s, &UnIgnore);
358}
359
366{
367 if (mutt_istr_equal("text", s))
368 return TYPE_TEXT;
369 if (mutt_istr_equal("multipart", s))
370 return TYPE_MULTIPART;
371 if (mutt_istr_equal("x-sun-attachment", s))
372 return TYPE_MULTIPART;
373 if (mutt_istr_equal("application", s))
374 return TYPE_APPLICATION;
375 if (mutt_istr_equal("message", s))
376 return TYPE_MESSAGE;
377 if (mutt_istr_equal("image", s))
378 return TYPE_IMAGE;
379 if (mutt_istr_equal("audio", s))
380 return TYPE_AUDIO;
381 if (mutt_istr_equal("video", s))
382 return TYPE_VIDEO;
383 if (mutt_istr_equal("model", s))
384 return TYPE_MODEL;
385 if (mutt_istr_equal("*", s))
386 return TYPE_ANY;
387 if (mutt_istr_equal(".*", s))
388 return TYPE_ANY;
389
390 return TYPE_OTHER;
391}
392
400char *mutt_extract_message_id(const char *s, size_t *len)
401{
402 if (!s || (*s == '\0'))
403 return NULL;
404
405 char *decoded = mutt_str_dup(s);
406 rfc2047_decode(&decoded);
407
408 char *res = NULL;
409
410 for (const char *p = decoded, *beg = NULL; *p; p++)
411 {
412 if (*p == '<')
413 {
414 beg = p;
415 continue;
416 }
417
418 if (beg && (*p == '>'))
419 {
420 if (len)
421 *len = p - decoded + 1;
422 res = mutt_strn_dup(beg, (p + 1) - beg);
423 break;
424 }
425 }
426
427 FREE(&decoded);
428 return res;
429}
430
436int mutt_check_encoding(const char *c)
437{
438 if (mutt_istr_startswith(c, "7bit"))
439 return ENC_7BIT;
440 if (mutt_istr_startswith(c, "8bit"))
441 return ENC_8BIT;
442 if (mutt_istr_startswith(c, "binary"))
443 return ENC_BINARY;
444 if (mutt_istr_startswith(c, "quoted-printable"))
446 if (mutt_istr_startswith(c, "base64"))
447 return ENC_BASE64;
448 if (mutt_istr_startswith(c, "x-uuencode"))
449 return ENC_UUENCODED;
450 if (mutt_istr_startswith(c, "uuencode"))
451 return ENC_UUENCODED;
452 return ENC_OTHER;
453}
454
462void mutt_parse_content_type(const char *s, struct Body *b)
463{
464 if (!s || !b)
465 return;
466
467 FREE(&b->subtype);
469
470 /* First extract any existing parameters */
471 char *pc = strchr(s, ';');
472 if (pc)
473 {
474 *pc++ = 0;
475 while (*pc && mutt_isspace(*pc))
476 pc++;
477 parse_parameters(&b->parameter, pc, false);
478
479 /* Some pre-RFC1521 gateways still use the "name=filename" convention,
480 * but if a filename has already been set in the content-disposition,
481 * let that take precedence, and don't set it here */
482 pc = mutt_param_get(&b->parameter, "name");
483 if (pc && !b->filename)
484 b->filename = mutt_str_dup(pc);
485
486 /* this is deep and utter perversion */
487 pc = mutt_param_get(&b->parameter, "conversions");
488 if (pc)
490 }
491
492 /* Now get the subtype */
493 char *subtype = strchr(s, '/');
494 if (subtype)
495 {
496 *subtype++ = '\0';
497 for (pc = subtype; *pc && !mutt_isspace(*pc) && (*pc != ';'); pc++)
498 ; // do nothing
499
500 *pc = '\0';
501 mutt_str_replace(&b->subtype, subtype);
502 }
503
504 /* Finally, get the major type */
506
507 if (mutt_istr_equal("x-sun-attachment", s))
508 mutt_str_replace(&b->subtype, "x-sun-attachment");
509
510 if (b->type == TYPE_OTHER)
511 {
512 mutt_str_replace(&b->xtype, s);
513 }
514
515 if (!b->subtype)
516 {
517 /* Some older non-MIME mailers (i.e., mailtool, elm) have a content-type
518 * field, so we can attempt to convert the type to Body here. */
519 if (b->type == TYPE_TEXT)
520 {
521 b->subtype = mutt_str_dup("plain");
522 }
523 else if (b->type == TYPE_AUDIO)
524 {
525 b->subtype = mutt_str_dup("basic");
526 }
527 else if (b->type == TYPE_MESSAGE)
528 {
529 b->subtype = mutt_str_dup("rfc822");
530 }
531 else if (b->type == TYPE_OTHER)
532 {
533 char buf[128] = { 0 };
534
536 snprintf(buf, sizeof(buf), "x-%s", s);
537 b->subtype = mutt_str_dup(buf);
538 }
539 else
540 {
541 b->subtype = mutt_str_dup("x-unknown");
542 }
543 }
544
545 /* Default character set for text types. */
546 if (b->type == TYPE_TEXT)
547 {
548 pc = mutt_param_get(&b->parameter, "charset");
549 if (pc)
550 {
551 /* Microsoft Outlook seems to think it is necessary to repeat
552 * charset=, strip it off not to confuse ourselves */
553 if (mutt_istrn_equal(pc, "charset=", sizeof("charset=") - 1))
554 mutt_param_set(&b->parameter, "charset", pc + (sizeof("charset=") - 1));
555 }
556 else
557 {
558 mutt_param_set(&b->parameter, "charset",
560 }
561 }
562}
563
564#ifdef USE_AUTOCRYPT
571static struct AutocryptHeader *parse_autocrypt(struct AutocryptHeader *head, const char *s)
572{
573 struct AutocryptHeader *autocrypt = mutt_autocrypthdr_new();
574 autocrypt->next = head;
575
576 struct ParameterList pl = TAILQ_HEAD_INITIALIZER(pl);
577 parse_parameters(&pl, s, true);
578 if (TAILQ_EMPTY(&pl))
579 {
580 autocrypt->invalid = true;
581 goto cleanup;
582 }
583
584 struct Parameter *p = NULL;
585 TAILQ_FOREACH(p, &pl, entries)
586 {
587 if (mutt_istr_equal(p->attribute, "addr"))
588 {
589 if (autocrypt->addr)
590 {
591 autocrypt->invalid = true;
592 goto cleanup;
593 }
594 autocrypt->addr = p->value;
595 p->value = NULL;
596 }
597 else if (mutt_istr_equal(p->attribute, "prefer-encrypt"))
598 {
599 if (mutt_istr_equal(p->value, "mutual"))
600 autocrypt->prefer_encrypt = true;
601 }
602 else if (mutt_istr_equal(p->attribute, "keydata"))
603 {
604 if (autocrypt->keydata)
605 {
606 autocrypt->invalid = true;
607 goto cleanup;
608 }
609 autocrypt->keydata = p->value;
610 p->value = NULL;
611 }
612 else if (p->attribute && (p->attribute[0] != '_'))
613 {
614 autocrypt->invalid = true;
615 goto cleanup;
616 }
617 }
618
619 /* Checking the addr against From, and for multiple valid headers
620 * occurs later, after all the headers are parsed. */
621 if (!autocrypt->addr || !autocrypt->keydata)
622 autocrypt->invalid = true;
623
624cleanup:
625 mutt_param_free(&pl);
626 return autocrypt;
627}
628#endif
629
635static char *rfc2369_first_mailto(const char *body)
636{
637 for (const char *beg = body, *end = NULL; beg; beg = strchr(end, ','))
638 {
639 beg = strchr(beg, '<');
640 if (!beg)
641 {
642 break;
643 }
644 beg++;
645 end = strchr(beg, '>');
646 if (!end)
647 {
648 break;
649 }
650
651 char *mlist = mutt_strn_dup(beg, end - beg);
652 if (url_check_scheme(mlist) == U_MAILTO)
653 {
654 return mlist;
655 }
656 FREE(&mlist);
657 }
658 return NULL;
659}
660
677int mutt_rfc822_parse_line(struct Envelope *env, struct Email *e,
678 const char *name, size_t name_len, const char *body,
679 bool user_hdrs, bool weed, bool do_2047)
680{
681 if (!env || !name)
682 return 0;
683
684 bool matched = false;
685
686 switch (name[0] | 0x20)
687 {
688 case 'a':
689 if ((name_len == 13) && eqi12(name + 1, "pparently-to"))
690 {
691 mutt_addrlist_parse(&env->to, body);
692 matched = true;
693 }
694 else if ((name_len == 15) && eqi14(name + 1, "pparently-from"))
695 {
696 mutt_addrlist_parse(&env->from, body);
697 matched = true;
698 }
699#ifdef USE_AUTOCRYPT
700 else if ((name_len == 9) && eqi8(name + 1, "utocrypt"))
701 {
702 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
703 if (c_autocrypt)
704 {
705 env->autocrypt = parse_autocrypt(env->autocrypt, body);
706 matched = true;
707 }
708 }
709 else if ((name_len == 16) && eqi15(name + 1, "utocrypt-gossip"))
710 {
711 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
712 if (c_autocrypt)
713 {
715 matched = true;
716 }
717 }
718#endif
719 break;
720
721 case 'b':
722 if ((name_len == 3) && eqi2(name + 1, "cc"))
723 {
724 mutt_addrlist_parse(&env->bcc, body);
725 matched = true;
726 }
727 break;
728
729 case 'c':
730 if ((name_len == 2) && eqi1(name + 1, "c"))
731 {
732 mutt_addrlist_parse(&env->cc, body);
733 matched = true;
734 }
735 else
736 {
737 if ((name_len >= 12) && eqi8(name, "content-"))
738 {
739 if ((name_len == 12) && eqi4(name + 8, "type"))
740 {
741 if (e)
743 matched = true;
744 }
745 else if ((name_len == 16) && eqi8(name + 8, "language"))
746 {
747 if (e)
749 matched = true;
750 }
751 else if ((name_len == 25) && eqi17(name + 8, "transfer-encoding"))
752 {
753 if (e)
755 matched = true;
756 }
757 else if ((name_len == 14) && eqi8(name + 6, "t-length"))
758 {
759 if (e)
760 {
761 unsigned long len = 0;
762 e->body->length = mutt_str_atoul(body, &len) ? MIN(len, CONTENT_TOO_BIG) : -1;
763 }
764 matched = true;
765 }
766 else if ((name_len == 19) && eqi11(name + 8, "description"))
767 {
768 if (e)
769 {
772 }
773 matched = true;
774 }
775 else if ((name_len == 19) && eqi11(name + 8, "disposition"))
776 {
777 if (e)
779 matched = true;
780 }
781 }
782 }
783 break;
784
785 case 'd':
786 if ((name_len != 4) || !eqi4(name, "date"))
787 break;
788
789 mutt_str_replace(&env->date, body);
790 if (e)
791 {
792 struct Tz tz = { 0 };
793 // the caller will check e->date_sent for -1
794 e->date_sent = mutt_date_parse_date(body, &tz);
795 if (e->date_sent > 0)
796 {
797 e->zhours = tz.zhours;
798 e->zminutes = tz.zminutes;
799 e->zoccident = tz.zoccident;
800 }
801 }
802 matched = true;
803 break;
804
805 case 'e':
806 if ((name_len == 7) && eqi6(name + 1, "xpires") && e)
807 {
808 const time_t expired = mutt_date_parse_date(body, NULL);
809 if ((expired != -1) && (expired < mutt_date_now()))
810 {
811 e->expired = true;
812 }
813 }
814 break;
815
816 case 'f':
817 if ((name_len == 4) && eqi4(name, "from"))
818 {
819 mutt_addrlist_parse(&env->from, body);
820 matched = true;
821 }
822 else if ((name_len == 11) && eqi10(name + 1, "ollowup-to"))
823 {
824 if (!env->followup_to)
825 {
828 }
829 matched = true;
830 }
831 break;
832
833 case 'i':
834 if ((name_len != 11) || !eqi10(name + 1, "n-reply-to"))
835 break;
836
838 char *body2 = mutt_str_dup(body); // Create a mutable copy
840 parse_references(&env->in_reply_to, body2);
841 FREE(&body2);
842 matched = true;
843 break;
844
845 case 'l':
846 if ((name_len == 5) && eqi4(name + 1, "ines"))
847 {
848 if (e)
849 {
850 unsigned int ui = 0; // we don't want a negative number of lines
851 mutt_str_atoui(body, &ui);
852 e->lines = ui;
853 }
854
855 matched = true;
856 }
857 else if ((name_len == 9) && eqi8(name + 1, "ist-post"))
858 {
859 /* RFC2369 */
860 if (!mutt_strn_equal(mutt_str_skip_whitespace(body), "NO", 2))
861 {
862 char *mailto = rfc2369_first_mailto(body);
863 if (mailto)
864 {
865 FREE(&env->list_post);
866 env->list_post = mailto;
867 const bool c_auto_subscribe = cs_subset_bool(NeoMutt->sub, "auto_subscribe");
868 if (c_auto_subscribe)
870 }
871 }
872 matched = true;
873 }
874 else if ((name_len == 14) && eqi13(name + 1, "ist-subscribe"))
875 {
876 /* RFC2369 */
877 char *mailto = rfc2369_first_mailto(body);
878 if (mailto)
879 {
880 FREE(&env->list_subscribe);
881 env->list_subscribe = mailto;
882 }
883 matched = true;
884 }
885 else if ((name_len == 16) && eqi15(name + 1, "ist-unsubscribe"))
886 {
887 /* RFC2369 */
888 char *mailto = rfc2369_first_mailto(body);
889 if (mailto)
890 {
891 FREE(&env->list_unsubscribe);
892 env->list_unsubscribe = mailto;
893 }
894 matched = true;
895 }
896 break;
897
898 case 'm':
899 if ((name_len == 12) && eqi11(name + 1, "ime-version"))
900 {
901 if (e)
902 e->mime = true;
903 matched = true;
904 }
905 else if ((name_len == 10) && eqi9(name + 1, "essage-id"))
906 {
907 /* We add a new "Message-ID:" when building a message */
908 FREE(&env->message_id);
909 env->message_id = mutt_extract_message_id(body, NULL);
910 matched = true;
911 }
912 else
913 {
914 if ((name_len >= 13) && eqi4(name + 1, "ail-"))
915 {
916 if ((name_len == 13) && eqi8(name + 5, "reply-to"))
917 {
918 /* override the Reply-To: field */
920 mutt_addrlist_parse(&env->reply_to, body);
921 matched = true;
922 }
923 else if ((name_len == 16) && eqi11(name + 5, "followup-to"))
924 {
926 matched = true;
927 }
928 }
929 }
930 break;
931
932 case 'n':
933 if ((name_len == 10) && eqi9(name + 1, "ewsgroups"))
934 {
935 FREE(&env->newsgroups);
938 matched = true;
939 }
940 break;
941
942 case 'o':
943 /* field 'Organization:' saves only for pager! */
944 if ((name_len == 12) && eqi11(name + 1, "rganization"))
945 {
946 if (!env->organization && !mutt_istr_equal(body, "unknown"))
947 env->organization = mutt_str_dup(body);
948 }
949 break;
950
951 case 'r':
952 if ((name_len == 10) && eqi9(name + 1, "eferences"))
953 {
955 parse_references(&env->references, body);
956 matched = true;
957 }
958 else if ((name_len == 8) && eqi8(name, "reply-to"))
959 {
960 mutt_addrlist_parse(&env->reply_to, body);
961 matched = true;
962 }
963 else if ((name_len == 11) && eqi10(name + 1, "eturn-path"))
964 {
965 mutt_addrlist_parse(&env->return_path, body);
966 matched = true;
967 }
968 else if ((name_len == 8) && eqi8(name, "received"))
969 {
970 if (e && (e->received == 0))
971 {
972 char *d = strrchr(body, ';');
973 if (d)
974 {
975 d = mutt_str_skip_email_wsp(d + 1);
976 // the caller will check e->received for -1
977 e->received = mutt_date_parse_date(d, NULL);
978 }
979 }
980 }
981 break;
982
983 case 's':
984 if ((name_len == 7) && eqi6(name + 1, "ubject"))
985 {
986 if (!env->subject)
987 mutt_env_set_subject(env, body);
988 matched = true;
989 }
990 else if ((name_len == 6) && eqi5(name + 1, "ender"))
991 {
992 mutt_addrlist_parse(&env->sender, body);
993 matched = true;
994 }
995 else if ((name_len == 6) && eqi5(name + 1, "tatus"))
996 {
997 if (e)
998 {
999 while (*body)
1000 {
1001 switch (*body)
1002 {
1003 case 'O':
1004 {
1005 e->old = true;
1006 break;
1007 }
1008 case 'R':
1009 e->read = true;
1010 break;
1011 case 'r':
1012 e->replied = true;
1013 break;
1014 }
1015 body++;
1016 }
1017 }
1018 matched = true;
1019 }
1020 else if (e && (name_len == 10) && eqi1(name + 1, "u") &&
1021 (eqi8(name + 2, "persedes") || eqi8(name + 2, "percedes")))
1022 {
1023 FREE(&env->supersedes);
1024 env->supersedes = mutt_str_dup(body);
1025 }
1026 break;
1027
1028 case 't':
1029 if ((name_len == 2) && eqi1(name + 1, "o"))
1030 {
1031 mutt_addrlist_parse(&env->to, body);
1032 matched = true;
1033 }
1034 break;
1035
1036 case 'x':
1037 if ((name_len == 8) && eqi8(name, "x-status"))
1038 {
1039 if (e)
1040 {
1041 while (*body)
1042 {
1043 switch (*body)
1044 {
1045 case 'A':
1046 e->replied = true;
1047 break;
1048 case 'D':
1049 e->deleted = true;
1050 break;
1051 case 'F':
1052 e->flagged = true;
1053 break;
1054 default:
1055 break;
1056 }
1057 body++;
1058 }
1059 }
1060 matched = true;
1061 }
1062 else if ((name_len == 7) && eqi6(name + 1, "-label"))
1063 {
1064 FREE(&env->x_label);
1065 env->x_label = mutt_str_dup(body);
1066 matched = true;
1067 }
1068 else if ((name_len == 12) && eqi11(name + 1, "-comment-to"))
1069 {
1070 if (!env->x_comment_to)
1071 env->x_comment_to = mutt_str_dup(body);
1072 matched = true;
1073 }
1074 else if ((name_len == 4) && eqi4(name, "xref"))
1075 {
1076 if (!env->xref)
1077 env->xref = mutt_str_dup(body);
1078 matched = true;
1079 }
1080 else if ((name_len == 13) && eqi12(name + 1, "-original-to"))
1081 {
1083 matched = true;
1084 }
1085 break;
1086
1087 default:
1088 break;
1089 }
1090
1091 /* Keep track of the user-defined headers */
1092 if (!matched && user_hdrs)
1093 {
1094 const bool c_weed = cs_subset_bool(NeoMutt->sub, "weed");
1095 char *dup = NULL;
1096 mutt_str_asprintf(&dup, "%s: %s", name, body);
1097
1098 if (!weed || !c_weed || !mutt_matches_ignore(dup))
1099 {
1100 struct ListNode *np = mutt_list_insert_tail(&env->userhdrs, dup);
1101 if (do_2047)
1102 {
1103 rfc2047_decode(&np->data);
1104 }
1105 }
1106 else
1107 {
1108 FREE(&dup);
1109 }
1110 }
1111
1112 return matched;
1113}
1114
1124size_t mutt_rfc822_read_line(FILE *fp, struct Buffer *buf)
1125{
1126 if (!fp || !buf)
1127 return 0;
1128
1129 size_t read = 0;
1130 char line[1024] = { 0 }; /* RFC2822 specifies a maximum line length of 998 */
1131
1132 buf_reset(buf);
1133 while (true)
1134 {
1135 if (!fgets(line, sizeof(line), fp))
1136 {
1137 return 0;
1138 }
1139
1140 const size_t linelen = mutt_str_len(line);
1141 if (linelen == 0)
1142 {
1143 break;
1144 }
1145
1146 if (mutt_str_is_email_wsp(line[0]) && buf_is_empty(buf))
1147 {
1148 read = linelen;
1149 break;
1150 }
1151
1152 read += linelen;
1153
1154 size_t off = linelen - 1;
1155 if (line[off] == '\n')
1156 {
1157 /* We did get a full line: remove trailing space */
1158 do
1159 {
1160 line[off] = '\0';
1161 } while (off && mutt_str_is_email_wsp(line[--off]));
1162
1163 /* check to see if the next line is a continuation line */
1164 int ch = fgetc(fp);
1165 if ((ch != ' ') && (ch != '\t'))
1166 {
1167 /* next line is a separate header field or EOH */
1168 ungetc(ch, fp);
1169 buf_addstr(buf, line);
1170 break;
1171 }
1172 read++;
1173
1174 /* eat tabs and spaces from the beginning of the continuation line */
1175 while (((ch = fgetc(fp)) == ' ') || (ch == '\t'))
1176 {
1177 read++;
1178 }
1179
1180 ungetc(ch, fp);
1181 line[off + 1] = ' '; /* string is still terminated because we removed
1182 at least one whitespace char above */
1183 }
1184
1185 buf_addstr(buf, line);
1186 }
1187
1188 return read;
1189}
1190
1204struct Envelope *mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
1205{
1206 if (!fp)
1207 return NULL;
1208
1209 struct Envelope *env = mutt_env_new();
1210 char *p = NULL;
1211 LOFF_T loc = e ? e->offset : ftello(fp);
1212 if (loc < 0)
1213 {
1214 mutt_debug(LL_DEBUG1, "ftello: %s (errno %d)\n", strerror(errno), errno);
1215 loc = 0;
1216 }
1217
1218 struct Buffer *line = buf_pool_get();
1219
1220 if (e)
1221 {
1222 if (!e->body)
1223 {
1224 e->body = mutt_body_new();
1225
1226 /* set the defaults from RFC1521 */
1227 e->body->type = TYPE_TEXT;
1228 e->body->subtype = mutt_str_dup("plain");
1229 e->body->encoding = ENC_7BIT;
1230 e->body->length = -1;
1231
1232 /* RFC2183 says this is arbitrary */
1234 }
1235 }
1236
1237 while (true)
1238 {
1239 LOFF_T line_start_loc = loc;
1240 size_t len = mutt_rfc822_read_line(fp, line);
1241 if (buf_is_empty(line))
1242 {
1243 break;
1244 }
1245 loc += len;
1246 const char *lines = buf_string(line);
1247 p = strpbrk(lines, ": \t");
1248 if (!p || (*p != ':'))
1249 {
1250 char return_path[1024] = { 0 };
1251 time_t t = 0;
1252
1253 /* some bogus MTAs will quote the original "From " line */
1254 if (mutt_str_startswith(lines, ">From "))
1255 {
1256 continue; /* just ignore */
1257 }
1258 else if (is_from(lines, return_path, sizeof(return_path), &t))
1259 {
1260 /* MH sometimes has the From_ line in the middle of the header! */
1261 if (e && (e->received == 0))
1262 e->received = t - mutt_date_local_tz(t);
1263 continue;
1264 }
1265
1266 /* We need to seek back to the start of the body. Note that we
1267 * keep track of loc ourselves, since calling ftello() incurs
1268 * a syscall, which can be expensive to do for every single line */
1269 (void) mutt_file_seek(fp, line_start_loc, SEEK_SET);
1270 break; /* end of header */
1271 }
1272 size_t name_len = p - lines;
1273
1274 char buf[1024] = { 0 };
1275 if (mutt_replacelist_match(&SpamList, buf, sizeof(buf), lines))
1276 {
1277 if (!mutt_regexlist_match(&NoSpamList, lines))
1278 {
1279 /* if spam tag already exists, figure out how to amend it */
1280 if ((!buf_is_empty(&env->spam)) && (*buf != '\0'))
1281 {
1282 /* If `$spam_separator` defined, append with separator */
1283 const char *const c_spam_separator = cs_subset_string(NeoMutt->sub, "spam_separator");
1284 if (c_spam_separator)
1285 {
1286 buf_addstr(&env->spam, c_spam_separator);
1287 buf_addstr(&env->spam, buf);
1288 }
1289 else /* overwrite */
1290 {
1291 buf_reset(&env->spam);
1292 buf_addstr(&env->spam, buf);
1293 }
1294 }
1295 else if (buf_is_empty(&env->spam) && (*buf != '\0'))
1296 {
1297 /* spam tag is new, and match expr is non-empty; copy */
1298 buf_addstr(&env->spam, buf);
1299 }
1300 else if (buf_is_empty(&env->spam))
1301 {
1302 /* match expr is empty; plug in null string if no existing tag */
1303 buf_addstr(&env->spam, "");
1304 }
1305
1306 if (!buf_is_empty(&env->spam))
1307 mutt_debug(LL_DEBUG5, "spam = %s\n", env->spam.data);
1308 }
1309 }
1310
1311 *p = '\0';
1312 p = mutt_str_skip_email_wsp(p + 1);
1313 if (*p == '\0')
1314 continue; /* skip empty header fields */
1315
1316 mutt_rfc822_parse_line(env, e, lines, name_len, p, user_hdrs, weed, true);
1317 }
1318
1319 buf_pool_release(&line);
1320
1321 if (e)
1322 {
1323 e->body->hdr_offset = e->offset;
1324 e->body->offset = ftello(fp);
1325
1327
1328 if (e->received < 0)
1329 {
1330 mutt_debug(LL_DEBUG1, "resetting invalid received time to 0\n");
1331 e->received = 0;
1332 }
1333
1334 /* check for missing or invalid date */
1335 if (e->date_sent <= 0)
1336 {
1337 mutt_debug(LL_DEBUG1, "no date found, using received time from msg separator\n");
1338 e->date_sent = e->received;
1339 }
1340
1341#ifdef USE_AUTOCRYPT
1342 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
1343 if (c_autocrypt)
1344 {
1346 /* No sense in taking up memory after the header is processed */
1348 }
1349#endif
1350 }
1351
1352 return env;
1353}
1354
1361struct Body *mutt_read_mime_header(FILE *fp, bool digest)
1362{
1363 if (!fp)
1364 return NULL;
1365
1366 struct Body *b = mutt_body_new();
1367 struct Envelope *env = mutt_env_new();
1368 char *c = NULL;
1369 struct Buffer *buf = buf_pool_get();
1370 bool matched = false;
1371
1372 b->hdr_offset = ftello(fp);
1373
1374 b->encoding = ENC_7BIT; /* default from RFC1521 */
1375 b->type = digest ? TYPE_MESSAGE : TYPE_TEXT;
1377
1378 while (mutt_rfc822_read_line(fp, buf) != 0)
1379 {
1380 const char *line = buf_string(buf);
1381 /* Find the value of the current header */
1382 c = strchr(line, ':');
1383 if (c)
1384 {
1385 *c = '\0';
1386 c = mutt_str_skip_email_wsp(c + 1);
1387 if (*c == '\0')
1388 {
1389 mutt_debug(LL_DEBUG1, "skipping empty header field: %s\n", line);
1390 continue;
1391 }
1392 }
1393 else
1394 {
1395 mutt_debug(LL_DEBUG1, "bogus MIME header: %s\n", line);
1396 break;
1397 }
1398
1399 size_t plen = mutt_istr_startswith(line, "content-");
1400 if (plen != 0)
1401 {
1402 if (mutt_istr_equal("type", line + plen))
1403 {
1405 }
1406 else if (mutt_istr_equal("language", line + plen))
1407 {
1409 }
1410 else if (mutt_istr_equal("transfer-encoding", line + plen))
1411 {
1413 }
1414 else if (mutt_istr_equal("disposition", line + plen))
1415 {
1417 }
1418 else if (mutt_istr_equal("description", line + plen))
1419 {
1422 }
1423 else if (mutt_istr_equal("id", line + plen))
1424 {
1425 // strip <angle braces> from Content-ID: header
1426 char *id = c;
1427 int cid_len = mutt_str_len(c);
1428 if (cid_len > 2)
1429 {
1430 if (id[0] == '<')
1431 {
1432 id++;
1433 cid_len--;
1434 }
1435 if (id[cid_len - 1] == '>')
1436 id[cid_len - 1] = '\0';
1437 }
1439 }
1440 }
1441 else if ((plen = mutt_istr_startswith(line, "x-sun-")))
1442 {
1443 if (mutt_istr_equal("data-type", line + plen))
1444 {
1446 }
1447 else if (mutt_istr_equal("encoding-info", line + plen))
1448 {
1450 }
1451 else if (mutt_istr_equal("content-lines", line + plen))
1452 {
1453 mutt_param_set(&b->parameter, "content-lines", c);
1454 }
1455 else if (mutt_istr_equal("data-description", line + plen))
1456 {
1459 }
1460 }
1461 else
1462 {
1463 if (mutt_rfc822_parse_line(env, NULL, line, strlen(line), c, false, false, false))
1464 {
1465 matched = true;
1466 }
1467 }
1468 }
1469 b->offset = ftello(fp); /* Mark the start of the real data */
1470 if ((b->type == TYPE_TEXT) && !b->subtype)
1471 b->subtype = mutt_str_dup("plain");
1472 else if ((b->type == TYPE_MESSAGE) && !b->subtype)
1473 b->subtype = mutt_str_dup("rfc822");
1474
1475 buf_pool_release(&buf);
1476
1477 if (matched)
1478 {
1479 b->mime_headers = env;
1481 }
1482 else
1483 {
1484 mutt_env_free(&env);
1485 }
1486
1487 return b;
1488}
1489
1497bool mutt_is_message_type(int type, const char *subtype)
1498{
1499 if (type != TYPE_MESSAGE)
1500 return false;
1501
1502 subtype = NONULL(subtype);
1503 return (mutt_istr_equal(subtype, "rfc822") ||
1504 mutt_istr_equal(subtype, "news") || mutt_istr_equal(subtype, "global"));
1505}
1506
1513static void parse_part(FILE *fp, struct Body *b, int *counter)
1514{
1515 if (!fp || !b)
1516 return;
1517
1518 const char *bound = NULL;
1519 static unsigned short recurse_level = 0;
1520
1521 if (recurse_level >= MUTT_MIME_MAX_DEPTH)
1522 {
1523 mutt_debug(LL_DEBUG1, "recurse level too deep. giving up\n");
1524 return;
1525 }
1526 recurse_level++;
1527
1528 switch (b->type)
1529 {
1530 case TYPE_MULTIPART:
1531 if (mutt_istr_equal(b->subtype, "x-sun-attachment"))
1532 bound = "--------";
1533 else
1534 bound = mutt_param_get(&b->parameter, "boundary");
1535
1536 if (!mutt_file_seek(fp, b->offset, SEEK_SET))
1537 {
1538 goto bail;
1539 }
1540 b->parts = parse_multipart(fp, bound, b->offset + b->length,
1541 mutt_istr_equal("digest", b->subtype), counter);
1542 break;
1543
1544 case TYPE_MESSAGE:
1545 if (!b->subtype)
1546 break;
1547
1548 if (!mutt_file_seek(fp, b->offset, SEEK_SET))
1549 {
1550 goto bail;
1551 }
1552 if (mutt_is_message_type(b->type, b->subtype))
1553 b->parts = rfc822_parse_message(fp, b, counter);
1554 else if (mutt_istr_equal(b->subtype, "external-body"))
1555 b->parts = mutt_read_mime_header(fp, 0);
1556 else
1557 goto bail;
1558 break;
1559
1560 default:
1561 goto bail;
1562 }
1563
1564 /* try to recover from parsing error */
1565 if (!b->parts)
1566 {
1567 b->type = TYPE_TEXT;
1568 mutt_str_replace(&b->subtype, "plain");
1569 }
1570bail:
1571 recurse_level--;
1572}
1573
1584static struct Body *parse_multipart(FILE *fp, const char *boundary,
1585 LOFF_T end_off, bool digest, int *counter)
1586{
1587 if (!fp)
1588 return NULL;
1589
1590 if (!boundary)
1591 {
1592 mutt_error(_("multipart message has no boundary parameter"));
1593 return NULL;
1594 }
1595
1596 char buf[1024] = { 0 };
1597 struct Body *head = NULL, *last = NULL, *new_body = NULL;
1598 bool final = false; /* did we see the ending boundary? */
1599
1600 const size_t blen = mutt_str_len(boundary);
1601 while ((ftello(fp) < end_off) && fgets(buf, sizeof(buf), fp))
1602 {
1603 const size_t len = mutt_str_len(buf);
1604
1605 const size_t crlf = ((len > 1) && (buf[len - 2] == '\r')) ? 1 : 0;
1606
1607 if ((buf[0] == '-') && (buf[1] == '-') && mutt_str_startswith(buf + 2, boundary))
1608 {
1609 if (last)
1610 {
1611 last->length = ftello(fp) - last->offset - len - 1 - crlf;
1612 if (last->parts && (last->parts->length == 0))
1613 last->parts->length = ftello(fp) - last->parts->offset - len - 1 - crlf;
1614 /* if the body is empty, we can end up with a -1 length */
1615 if (last->length < 0)
1616 last->length = 0;
1617 }
1618
1619 if (len > 0)
1620 {
1621 /* Remove any trailing whitespace, up to the length of the boundary */
1622 for (size_t i = len - 1; mutt_isspace(buf[i]) && (i >= (blen + 2)); i--)
1623 buf[i] = '\0';
1624 }
1625
1626 /* Check for the end boundary */
1627 if (mutt_str_equal(buf + blen + 2, "--"))
1628 {
1629 final = true;
1630 break; /* done parsing */
1631 }
1632 else if (buf[2 + blen] == '\0')
1633 {
1634 new_body = mutt_read_mime_header(fp, digest);
1635 if (!new_body)
1636 break;
1637
1638 if (mutt_param_get(&new_body->parameter, "content-lines"))
1639 {
1640 int lines = 0;
1641 mutt_str_atoi(mutt_param_get(&new_body->parameter, "content-lines"), &lines);
1642 for (; lines > 0; lines--)
1643 if ((ftello(fp) >= end_off) || !fgets(buf, sizeof(buf), fp))
1644 break;
1645 }
1646
1647 /* Consistency checking - catch bad attachment end boundaries */
1648 if (new_body->offset > end_off)
1649 {
1650 mutt_body_free(&new_body);
1651 break;
1652 }
1653 if (head)
1654 {
1655 last->next = new_body;
1656 last = new_body;
1657 }
1658 else
1659 {
1660 last = new_body;
1661 head = new_body;
1662 }
1663
1664 /* It seems more intuitive to add the counter increment to
1665 * parse_part(), but we want to stop the case where a multipart
1666 * contains thousands of tiny parts before the memory and data
1667 * structures are allocated. */
1668 if (++(*counter) >= MUTT_MIME_MAX_PARTS)
1669 break;
1670 }
1671 }
1672 }
1673
1674 /* in case of missing end boundary, set the length to something reasonable */
1675 if (last && (last->length == 0) && !final)
1676 last->length = end_off - last->offset;
1677
1678 /* parse recursive MIME parts */
1679 for (last = head; last; last = last->next)
1680 parse_part(fp, last, counter);
1681
1682 return head;
1683}
1684
1694static struct Body *rfc822_parse_message(FILE *fp, struct Body *parent, int *counter)
1695{
1696 if (!fp || !parent)
1697 return NULL;
1698
1699 parent->email = email_new();
1700 parent->email->offset = ftello(fp);
1701 parent->email->env = mutt_rfc822_read_header(fp, parent->email, false, false);
1702 struct Body *msg = parent->email->body;
1703
1704 /* ignore the length given in the content-length since it could be wrong
1705 * and we already have the info to calculate the correct length */
1706 /* if (msg->length == -1) */
1707 msg->length = parent->length - (msg->offset - parent->offset);
1708
1709 /* if body of this message is empty, we can end up with a negative length */
1710 if (msg->length < 0)
1711 msg->length = 0;
1712
1713 parse_part(fp, msg, counter);
1714 return msg;
1715}
1716
1731static bool mailto_header_allowed(const char *s, struct ListHead *h)
1732{
1733 if (!h)
1734 return false;
1735
1736 struct ListNode *np = NULL;
1737 STAILQ_FOREACH(np, h, entries)
1738 {
1739 if ((*np->data == '*') || mutt_istr_equal(s, np->data))
1740 return true;
1741 }
1742 return false;
1743}
1744
1753bool mutt_parse_mailto(struct Envelope *env, char **body, const char *src)
1754{
1755 if (!env || !src)
1756 return false;
1757
1758 struct Url *url = url_parse(src);
1759 if (!url)
1760 return false;
1761
1762 if (url->host)
1763 {
1764 /* this is not a path-only URL */
1765 url_free(&url);
1766 return false;
1767 }
1768
1769 mutt_addrlist_parse(&env->to, url->path);
1770
1771 struct UrlQuery *np;
1772 STAILQ_FOREACH(np, &url->query_strings, entries)
1773 {
1775 const char *tag = np->name;
1776 char *value = np->value;
1777 /* Determine if this header field is on the allowed list. Since NeoMutt
1778 * interprets some header fields specially (such as
1779 * "Attach: ~/.gnupg/secring.gpg"), care must be taken to ensure that
1780 * only safe fields are allowed.
1781 *
1782 * RFC2368, "4. Unsafe headers"
1783 * The user agent interpreting a mailto URL SHOULD choose not to create
1784 * a message if any of the headers are considered dangerous; it may also
1785 * choose to create a message with only a subset of the headers given in
1786 * the URL. */
1788 {
1789 if (mutt_istr_equal(tag, "body"))
1790 {
1791 if (body)
1792 mutt_str_replace(body, value);
1793 }
1794 else
1795 {
1796 char *scratch = NULL;
1797 size_t taglen = mutt_str_len(tag);
1798
1800 mutt_str_asprintf(&scratch, "%s: %s", tag, value);
1801 scratch[taglen] = 0; /* overwrite the colon as mutt_rfc822_parse_line expects */
1802 value = mutt_str_skip_email_wsp(&scratch[taglen + 1]);
1803 mutt_rfc822_parse_line(env, NULL, scratch, taglen, value, true, false, true);
1804 FREE(&scratch);
1805 }
1806 }
1807 }
1808
1809 /* RFC2047 decode after the RFC822 parsing */
1811
1812 url_free(&url);
1813 return true;
1814}
1815
1821void mutt_parse_part(FILE *fp, struct Body *b)
1822{
1823 int counter = 0;
1824
1825 parse_part(fp, b, &counter);
1826}
1827
1836struct Body *mutt_rfc822_parse_message(FILE *fp, struct Body *b)
1837{
1838 int counter = 0;
1839
1840 return rfc822_parse_message(fp, b, &counter);
1841}
1842
1852struct Body *mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
1853{
1854 int counter = 0;
1855
1856 return parse_multipart(fp, boundary, end_off, digest, &counter);
1857}
void mutt_addrlist_clear(struct AddressList *al)
Unlink and free all Address in an AddressList.
Definition: address.c:1460
int mutt_addrlist_parse(struct AddressList *al, const char *s)
Parse a list of email addresses.
Definition: address.c:480
Email Address Handling.
const char * mutt_str_atoul(const char *str, unsigned long *dst)
Convert ASCII string to an unsigned long.
Definition: atoi.c:244
const char * mutt_str_atoui(const char *str, unsigned int *dst)
Convert ASCII string to an unsigned integer.
Definition: atoi.c:218
const char * mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition: atoi.c:192
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:256
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:291
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:226
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:571
void buf_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:337
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:291
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:47
Convenience wrapper for the config headers.
const struct Slist * cc_assumed_charset(void)
Get the cached value of $assumed_charset.
Definition: config_cache.c:101
Convenience wrapper for the core headers.
bool mutt_isspace(int arg)
Wrapper for isspace(3)
Definition: ctype.c:95
void mutt_body_free(struct Body **ptr)
Free a Body.
Definition: body.c:58
struct Body * mutt_body_new(void)
Create a new Body.
Definition: body.c:44
struct Email * email_new(void)
Create a new Email.
Definition: email.c:77
struct ReplaceList SpamList
List of regexes to match subscribed mailing lists.
Definition: globals.c:46
struct RegexList SubscribedLists
List of header patterns to unignore (see)
Definition: globals.c:48
struct HashTable * AutoSubscribeCache
< Hash Table: "mailto:" -> AutoSubscribeCache
Definition: globals.c:36
struct RegexList UnSubscribedLists
Definition: globals.c:54
struct RegexList UnMailLists
List of regexes to exclude false matches in SubscribedLists.
Definition: globals.c:52
struct RegexList MailLists
List of permitted fields in a mailto: url.
Definition: globals.c:40
struct ListHead MailToAllow
List of regexes to identify non-spam emails.
Definition: globals.c:42
struct ListHead Ignore
List of regexes to match mailing lists.
Definition: globals.c:38
struct RegexList NoSpamList
List of regexes and patterns to match spam emails.
Definition: globals.c:44
struct ListHead UnIgnore
List of regexes to exclude false matches in MailLists.
Definition: globals.c:50
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:677
struct Body * mutt_rfc822_parse_message(FILE *fp, struct Body *b)
Parse a Message/RFC822 body.
Definition: parse.c:1836
void mutt_parse_part(FILE *fp, struct Body *b)
Parse a MIME part.
Definition: parse.c:1821
void mutt_parse_content_type(const char *s, struct Body *b)
Parse a content type.
Definition: parse.c:462
void mutt_auto_subscribe(const char *mailto)
Check if user is subscribed to mailing list.
Definition: parse.c:108
size_t mutt_rfc822_read_line(FILE *fp, struct Buffer *buf)
Read a header line from a file.
Definition: parse.c:1124
struct Body * mutt_read_mime_header(FILE *fp, bool digest)
Parse a MIME header.
Definition: parse.c:1361
static struct AutocryptHeader * parse_autocrypt(struct AutocryptHeader *head, const char *s)
Parse an Autocrypt header line.
Definition: parse.c:571
static void parse_references(struct ListHead *head, const char *s)
Parse references from an email header.
Definition: parse.c:322
bool mutt_matches_ignore(const char *s)
Does the string match the ignore list.
Definition: parse.c:355
static char * rfc2369_first_mailto(const char *body)
Extract the first mailto: URL from a RFC2369 list.
Definition: parse.c:635
enum ContentType mutt_check_mime_type(const char *s)
Check a MIME type string.
Definition: parse.c:365
static struct Body * rfc822_parse_message(FILE *fp, struct Body *parent, int *counter)
Parse a Message/RFC822 body.
Definition: parse.c:1694
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1204
static void parse_content_language(const char *s, struct Body *b)
Read the content's language.
Definition: parse.c:339
static bool mailto_header_allowed(const char *s, struct ListHead *h)
Is the string in the list.
Definition: parse.c:1731
bool mutt_parse_mailto(struct Envelope *env, char **body, const char *src)
Parse a mailto:// url.
Definition: parse.c:1753
int mutt_check_encoding(const char *c)
Check the encoding type.
Definition: parse.c:436
static void parse_content_disposition(const char *s, struct Body *b)
Parse a content disposition.
Definition: parse.c:290
struct Body * mutt_parse_multipart(FILE *fp, const char *boundary, LOFF_T end_off, bool digest)
Parse a multipart structure.
Definition: parse.c:1852
void mutt_filter_commandline_header_tag(char *header)
Sanitise characters in a header tag.
Definition: parse.c:72
char * mutt_extract_message_id(const char *s, size_t *len)
Find a message-id.
Definition: parse.c:400
static void parse_part(FILE *fp, struct Body *b, int *counter)
Parse a MIME part.
Definition: parse.c:1513
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:1584
bool mutt_is_message_type(int type, const char *subtype)
Determine if a mime type matches a message or not.
Definition: parse.c:1497
void mutt_filter_commandline_header_value(char *header)
Sanitise characters in a header value.
Definition: parse.c:92
static void parse_parameters(struct ParameterList *pl, const char *s, bool allow_value_spaces)
Parse a list of Parameters.
Definition: parse.c:151
#define CONTENT_TOO_BIG
Definition: parse.c:61
Representation of an email.
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition: envelope.c:126
struct Envelope * mutt_env_new(void)
Create a new Envelope.
Definition: envelope.c:46
void mutt_env_set_subject(struct Envelope *env, const char *subj)
Set both subject and real_subj to subj.
Definition: envelope.c:69
struct AutocryptHeader * mutt_autocrypthdr_new(void)
Create a new AutocryptHeader.
Definition: envelope.c:95
void mutt_autocrypthdr_free(struct AutocryptHeader **ptr)
Free an AutocryptHeader.
Definition: envelope.c:104
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
Expando Parsing.
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:655
bool is_from(const char *s, char *path, size_t pathlen, time_t *tp)
Is a string a 'From' header line?
Definition: from.c:49
Determine who the email is from.
Global variables.
#define mutt_error(...)
Definition: logging2.h:93
#define mutt_debug(LEVEL,...)
Definition: logging2.h:90
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:113
#define MUTT_HASH_STRCASECMP
use strcasecmp() to compare keys
Definition: hash.h:112
struct ListNode * mutt_list_insert_tail(struct ListHead *h, char *s)
Append a string to the end of a List.
Definition: list.c:65
struct ListNode * mutt_list_insert_head(struct ListHead *h, char *s)
Insert a string at the beginning of a List.
Definition: list.c:46
void mutt_list_free(struct ListHead *h)
Free a List AND its strings.
Definition: list.c:123
bool mutt_list_match(const char *s, struct ListHead *h)
Is the string in the list (see notes)
Definition: list.c:194
@ LL_DEBUG5
Log at debug level 5.
Definition: logging2.h:48
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:45
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:44
#define FREE(x)
Definition: memory.h:62
#define MIN(a, b)
Definition: memory.h:37
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:465
int mutt_date_local_tz(time_t t)
Calculate the local timezone in seconds east of UTC.
Definition: date.c:219
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
time_t mutt_date_parse_date(const char *s, struct Tz *tz_out)
Parse a date string in RFC822 format.
Definition: date.c:716
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:140
bool mutt_replacelist_match(struct ReplaceList *rl, char *buf, size_t buflen, const char *str)
Does a string match a pattern?
Definition: regex.c:478
bool mutt_regexlist_match(struct RegexList *rl, const char *str)
Does a string match any Regex in the list?
Definition: regex.c:200
bool slist_is_empty(const struct Slist *list)
Is the slist empty?
Definition: slist.c:140
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition: string.c:381
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:564
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:671
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:254
int mutt_str_asprintf(char **strp, const char *fmt,...)
Definition: string.c:802
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:607
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:659
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:426
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:231
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:550
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:497
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:243
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:454
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:281
Many unsorted constants and some structs.
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition: parameter.c:85
void mutt_param_set(struct ParameterList *pl, const char *attribute, const char *value)
Set a Parameter.
Definition: parameter.c:111
void mutt_param_free(struct ParameterList *pl)
Free a ParameterList.
Definition: parameter.c:62
struct Parameter * mutt_param_new(void)
Create a new Parameter.
Definition: parameter.c:40
Store attributes associated with a MIME part.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:96
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:782
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:390
#define TAILQ_FIRST(head)
Definition: queue.h:780
#define TAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:694
#define TAILQ_EMPTY(head)
Definition: queue.h:778
#define TAILQ_INSERT_HEAD(head, elm, field)
Definition: queue.h:853
void rfc2047_decode_envelope(struct Envelope *env)
Decode the fields of an Envelope.
Definition: rfc2047.c:832
void rfc2047_decode(char **pd)
Decode any RFC2047-encoded header fields.
Definition: rfc2047.c:661
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:36
static bool mutt_str_is_email_wsp(char c)
Is this a whitespace character (for an email header)
Definition: string2.h:103
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:78
char * content_id
Content-Id (RFC2392)
Definition: body.h:58
struct Body * parts
parts of a multipart or message/rfc822
Definition: body.h:73
LOFF_T offset
offset where the actual data begins
Definition: body.h:52
char * xtype
content-type if x-unknown
Definition: body.h:62
struct Envelope * mime_headers
Memory hole protected headers.
Definition: body.h:76
LOFF_T length
length (in bytes) of attachment
Definition: body.h:53
struct ParameterList parameter
Parameters of the content-type.
Definition: body.h:63
struct Email * email
header information for message/rfc822
Definition: body.h:74
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:72
char * subtype
content-type subtype
Definition: body.h:61
unsigned int encoding
content-transfer-encoding, ContentEncoding
Definition: body.h:41
long hdr_offset
Offset in stream where the headers begin.
Definition: body.h:81
char * form_name
Content-Disposition form-data name param.
Definition: body.h:60
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:59
String manipulation buffer.
Definition: buffer.h:36
char * data
Pointer to data.
Definition: buffer.h:37
The envelope/body of an email.
Definition: email.h:39
bool read
Email is read.
Definition: email.h:50
unsigned int zminutes
Minutes away from UTC.
Definition: email.h:57
struct Envelope * env
Envelope information.
Definition: email.h:68
bool mime
Has a MIME-Version header?
Definition: email.h:48
int lines
How many lines in the body of this message?
Definition: email.h:62
struct Body * body
List of MIME parts.
Definition: email.h:69
bool old
Email is seen, but unread.
Definition: email.h:49
bool zoccident
True, if west of UTC, False if east.
Definition: email.h:58
LOFF_T offset
Where in the stream does this message begin?
Definition: email.h:71
bool flagged
Marked important?
Definition: email.h:47
unsigned int zhours
Hours away from UTC.
Definition: email.h:56
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:60
bool replied
Email has been replied to.
Definition: email.h:51
bool expired
Already expired?
Definition: email.h:46
bool deleted
Email is deleted.
Definition: email.h:78
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:61
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
char *const subject
Email's subject.
Definition: envelope.h:70
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-Original-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
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 * 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:37
char * data
String.
Definition: list.h:38
Container for Accounts, Notifications.
Definition: neomutt.h:43
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
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
List of recognised Timezones.
Definition: date.h:50
unsigned char zminutes
Minutes away from UTC.
Definition: date.h:53
bool zoccident
True if west of UTC, False if East.
Definition: date.h:54
unsigned char zhours
Hours away from UTC.
Definition: date.h:52
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