1 /**
2 * @file
3 * Representation of an email address
4 *
5 * @authors
6 * Copyright (C) 1996-2000,2011-2013 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2017 Richard Russon <rich@flatcap.org>
8 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
9 *
10 * @copyright
11 * This program is free software: you can redistribute it and/or modify it under
12 * the terms of the GNU General Public License as published by the Free Software
13 * Foundation, either version 2 of the License, or (at your option) any later
14 * version.
15 *
16 * This program is distributed in the hope that it will be useful, but WITHOUT
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19 * details.
20 *
21 * You should have received a copy of the GNU General Public License along with
22 * this program. If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 /**
26 * @page addr_address Email address
27 *
28 * Representation of an email address
29 */
30
31 #include "config.h"
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include "mutt/lib.h"
36 #include "address.h"
37 #include "idna2.h"
38
39 /**
40 * AddressSpecials - Characters with special meaning for email addresses
41 */
42 const char AddressSpecials[] = "@.,:;<>[]\\\"()";
43
44 /**
45 * is_special - Is this character special to an email address?
46 * @param ch Character
47 */
48 #define is_special(ch) strchr(AddressSpecials, ch)
49
50 /**
51 * AddressError - An out-of-band error code
52 *
53 * Many of the Address functions set this variable on error.
54 * Its values are defined in #AddressError.
55 * Text for the errors can be looked up using #AddressErrors.
56 */
57 int AddressError = 0;
58
59 /**
60 * AddressErrors - Messages for the error codes in #AddressError
61 *
62 * These must defined in the same order as enum AddressError.
63 */
64 const char *const AddressErrors[] = {
65 "out of memory", "mismatched parentheses", "mismatched quotes",
66 "bad route in <>", "bad address in <>", "bad address spec",
67 };
68
69 /**
70 * parse_comment - Extract a comment (parenthesised string)
71 * @param[in] s String, just after the opening parenthesis
72 * @param[out] comment Buffer to store parenthesised string
73 * @param[out] commentlen Length of parenthesised string
74 * @param[in] commentmax Length of buffer
75 * @retval ptr First character after parenthesised string
76 * @retval NULL Error
77 */
parse_comment(const char * s,char * comment,size_t * commentlen,size_t commentmax)78 static const char *parse_comment(const char *s, char *comment, size_t *commentlen, size_t commentmax)
79 {
80 int level = 1;
81
82 while (*s && level)
83 {
84 if (*s == '(')
85 level++;
86 else if (*s == ')')
87 {
88 if (--level == 0)
89 {
90 s++;
91 break;
92 }
93 }
94 else if (*s == '\\')
95 {
96 if (!*++s)
97 break;
98 }
99 if (*commentlen < commentmax)
100 comment[(*commentlen)++] = *s;
101 s++;
102 }
103 if (level != 0)
104 {
105 AddressError = ADDR_ERR_MISMATCH_PAREN;
106 return NULL;
107 }
108 return s;
109 }
110
111 /**
112 * parse_quote - Extract a quoted string
113 * @param[in] s String, just after the opening quote mark
114 * @param[out] token Buffer to store quoted string
115 * @param[out] tokenlen Length of quoted string
116 * @param[in] tokenmax Length of buffer
117 * @retval ptr First character after quoted string
118 * @retval NULL Error
119 */
parse_quote(const char * s,char * token,size_t * tokenlen,size_t tokenmax)120 static const char *parse_quote(const char *s, char *token, size_t *tokenlen, size_t tokenmax)
121 {
122 while (*s)
123 {
124 if (*tokenlen < tokenmax)
125 token[*tokenlen] = *s;
126 if (*s == '\\')
127 {
128 if (!*++s)
129 break;
130
131 if (*tokenlen < tokenmax)
132 token[*tokenlen] = *s;
133 }
134 else if (*s == '"')
135 return s + 1;
136 (*tokenlen)++;
137 s++;
138 }
139 AddressError = ADDR_ERR_MISMATCH_QUOTE;
140 return NULL;
141 }
142
143 /**
144 * next_token - Find the next word, skipping quoted and parenthesised text
145 * @param[in] s String to search
146 * @param[out] token Buffer for the token
147 * @param[out] tokenlen Length of the next token
148 * @param[in] tokenmax Length of the buffer
149 * @retval ptr First character after the next token
150 */
next_token(const char * s,char * token,size_t * tokenlen,size_t tokenmax)151 static const char *next_token(const char *s, char *token, size_t *tokenlen, size_t tokenmax)
152 {
153 if (*s == '(')
154 return parse_comment(s + 1, token, tokenlen, tokenmax);
155 if (*s == '"')
156 return parse_quote(s + 1, token, tokenlen, tokenmax);
157 if (*s && is_special(*s))
158 {
159 if (*tokenlen < tokenmax)
160 token[(*tokenlen)++] = *s;
161 return s + 1;
162 }
163 while (*s)
164 {
165 if (mutt_str_is_email_wsp(*s) || is_special(*s))
166 break;
167 if (*tokenlen < tokenmax)
168 token[(*tokenlen)++] = *s;
169 s++;
170 }
171 return s;
172 }
173
174 /**
175 * parse_mailboxdomain - Extract part of an email address (and a comment)
176 * @param[in] s String to parse
177 * @param[in] nonspecial Specific characters that are valid
178 * @param[out] mailbox Buffer for email address
179 * @param[out] mailboxlen Length of saved email address
180 * @param[in] mailboxmax Length of mailbox buffer
181 * @param[out] comment Buffer for comment
182 * @param[out] commentlen Length of saved comment
183 * @param[in] commentmax Length of comment buffer
184 * @retval ptr First character after the email address part
185 *
186 * This will be called twice to parse an email address, first for the mailbox
187 * name, then for the domain name. Each part can also have a comment in `()`.
188 * The comment can be at the start or end of the mailbox or domain.
189 *
190 * Examples:
191 * - "john.doe@example.com"
192 * - "john.doe(comment)@example.com"
193 * - "john.doe@example.com(comment)"
194 *
195 * The first call will return "john.doe" with optional comment, "comment".
196 * The second call will return "example.com" with optional comment, "comment".
197 */
parse_mailboxdomain(const char * s,const char * nonspecial,char * mailbox,size_t * mailboxlen,size_t mailboxmax,char * comment,size_t * commentlen,size_t commentmax)198 static const char *parse_mailboxdomain(const char *s, const char *nonspecial,
199 char *mailbox, size_t *mailboxlen,
200 size_t mailboxmax, char *comment,
201 size_t *commentlen, size_t commentmax)
202 {
203 const char *ps = NULL;
204
205 while (*s)
206 {
207 s = mutt_str_skip_email_wsp(s);
208 if ((*s == '\0'))
209 return s;
210
211 if (!strchr(nonspecial, *s) && is_special(*s))
212 return s;
213
214 if (*s == '(')
215 {
216 if (*commentlen && (*commentlen < commentmax))
217 comment[(*commentlen)++] = ' ';
218 ps = next_token(s, comment, commentlen, commentmax);
219 }
220 else
221 ps = next_token(s, mailbox, mailboxlen, mailboxmax);
222 if (!ps)
223 return NULL;
224 s = ps;
225 }
226
227 return s;
228 }
229
230 /**
231 * parse_address - Extract an email address
232 * @param[in] s String, just after the opening `<`
233 * @param[out] token Buffer for the email address
234 * @param[out] tokenlen Length of the email address
235 * @param[in] tokenmax Length of the email address buffer
236 * @param[out] comment Buffer for any comments
237 * @param[out] commentlen Length of any comments
238 * @param[in] commentmax Length of the comment buffer
239 * @param[in] addr Address to store the results
240 * @retval ptr The closing `>` of the email address
241 * @retval NULL Error
242 */
parse_address(const char * s,char * token,size_t * tokenlen,size_t tokenmax,char * comment,size_t * commentlen,size_t commentmax,struct Address * addr)243 static const char *parse_address(const char *s, char *token, size_t *tokenlen,
244 size_t tokenmax, char *comment, size_t *commentlen,
245 size_t commentmax, struct Address *addr)
246 {
247 s = parse_mailboxdomain(s, ".\"(\\", token, tokenlen, tokenmax, comment,
248 commentlen, commentmax);
249 if (!s)
250 return NULL;
251
252 if (*s == '@')
253 {
254 if (*tokenlen < tokenmax)
255 token[(*tokenlen)++] = '@';
256 s = parse_mailboxdomain(s + 1, ".([]\\", token, tokenlen, tokenmax, comment,
257 commentlen, commentmax);
258 if (!s)
259 return NULL;
260 }
261
262 terminate_string(token, *tokenlen, tokenmax);
263 addr->mailbox = mutt_str_dup(token);
264
265 if (*commentlen && !addr->personal)
266 {
267 terminate_string(comment, *commentlen, commentmax);
268 addr->personal = mutt_str_dup(comment);
269 }
270
271 return s;
272 }
273
274 /**
275 * parse_route_addr - Parse an email addresses
276 * @param[in] s String, just after the opening `<`
277 * @param[out] comment Buffer for any comments
278 * @param[out] commentlen Length of any comments
279 * @param[in] commentmax Length of the comments buffer
280 * @param[in] addr Address to store the details
281 * @retval ptr First character after the email address
282 */
parse_route_addr(const char * s,char * comment,size_t * commentlen,size_t commentmax,struct Address * addr)283 static const char *parse_route_addr(const char *s, char *comment, size_t *commentlen,
284 size_t commentmax, struct Address *addr)
285 {
286 char token[1024];
287 size_t tokenlen = 0;
288
289 s = mutt_str_skip_email_wsp(s);
290
291 /* find the end of the route */
292 if (*s == '@')
293 {
294 while (s && (*s == '@'))
295 {
296 if (tokenlen < (sizeof(token) - 1))
297 token[tokenlen++] = '@';
298 s = parse_mailboxdomain(s + 1, ",.\\[](", token, &tokenlen,
299 sizeof(token) - 1, comment, commentlen, commentmax);
300 }
301 if (!s || (*s != ':'))
302 {
303 AddressError = ADDR_ERR_BAD_ROUTE;
304 return NULL; /* invalid route */
305 }
306
307 if (tokenlen < (sizeof(token) - 1))
308 token[tokenlen++] = ':';
309 s++;
310 }
311
312 s = parse_address(s, token, &tokenlen, sizeof(token) - 1, comment, commentlen,
313 commentmax, addr);
314 if (!s)
315 return NULL;
316
317 if (*s != '>')
318 {
319 AddressError = ADDR_ERR_BAD_ROUTE_ADDR;
320 return NULL;
321 }
322
323 if (!addr->mailbox)
324 addr->mailbox = mutt_str_dup("@");
325
326 s++;
327 return s;
328 }
329
330 /**
331 * parse_addr_spec - Parse an email address
332 * @param[in] s String to parse
333 * @param[out] comment Buffer for any comments
334 * @param[out] commentlen Length of any comments
335 * @param[in] commentmax Length of the comments buffer
336 * @param[in] addr Address to fill in
337 * @retval ptr First character after the email address
338 */
parse_addr_spec(const char * s,char * comment,size_t * commentlen,size_t commentmax,struct Address * addr)339 static const char *parse_addr_spec(const char *s, char *comment, size_t *commentlen,
340 size_t commentmax, struct Address *addr)
341 {
342 char token[1024];
343 size_t tokenlen = 0;
344
345 s = parse_address(s, token, &tokenlen, sizeof(token) - 1, comment, commentlen,
346 commentmax, addr);
347 if (s && *s && (*s != ',') && (*s != ';'))
348 {
349 AddressError = ADDR_ERR_BAD_ADDR_SPEC;
350 return NULL;
351 }
352 return s;
353 }
354
355 /**
356 * add_addrspec - Parse an email address and add an Address to a list
357 * @param[out] al Address list
358 * @param[in] phrase String to parse
359 * @param[out] comment Buffer for any comments
360 * @param[out] commentlen Length of any comments
361 * @param[in] commentmax Length of the comments buffer
362 * @retval true An address was successfully parsed and added
363 */
add_addrspec(struct AddressList * al,const char * phrase,char * comment,size_t * commentlen,size_t commentmax)364 static bool add_addrspec(struct AddressList *al, const char *phrase,
365 char *comment, size_t *commentlen, size_t commentmax)
366 {
367 struct Address *cur = mutt_addr_new();
368
369 if (!parse_addr_spec(phrase, comment, commentlen, commentmax, cur))
370 {
371 mutt_addr_free(&cur);
372 return false;
373 }
374
375 mutt_addrlist_append(al, cur);
376 return true;
377 }
378
379 /**
380 * mutt_addr_new - Create a new Address
381 * @retval ptr Newly allocated Address
382 *
383 * Free the result with mutt_addr_free()
384 */
mutt_addr_new(void)385 struct Address *mutt_addr_new(void)
386 {
387 return mutt_mem_calloc(1, sizeof(struct Address));
388 }
389
390 /**
391 * mutt_addr_create - Create and populate a new Address
392 * @param[in] personal The personal name for the Address (can be NULL)
393 * @param[in] mailbox The mailbox for the Address (can be NULL)
394 * @retval ptr Newly allocated Address
395 * @note The personal and mailbox values, if not NULL, are going to be copied
396 * into the newly allocated Address.
397 */
mutt_addr_create(const char * personal,const char * mailbox)398 struct Address *mutt_addr_create(const char *personal, const char *mailbox)
399 {
400 struct Address *a = mutt_addr_new();
401 a->personal = mutt_str_dup(personal);
402 a->mailbox = mutt_str_dup(mailbox);
403 return a;
404 }
405
406 /**
407 * mutt_addrlist_remove - Remove an Address from a list
408 * @param[in,out] al AddressList
409 * @param[in] mailbox Email address to match
410 * @retval 0 Success
411 * @retval -1 Error, or email not found
412 */
mutt_addrlist_remove(struct AddressList * al,const char * mailbox)413 int mutt_addrlist_remove(struct AddressList *al, const char *mailbox)
414 {
415 if (!al)
416 return -1;
417
418 if (!mailbox)
419 return 0;
420
421 int rc = -1;
422 struct Address *a = NULL, *tmp = NULL;
423 TAILQ_FOREACH_SAFE(a, al, entries, tmp)
424 {
425 if (mutt_istr_equal(mailbox, a->mailbox))
426 {
427 TAILQ_REMOVE(al, a, entries);
428 mutt_addr_free(&a);
429 rc = 0;
430 }
431 }
432
433 return rc;
434 }
435
436 /**
437 * mutt_addr_free - Free a single Address
438 * @param[out] ptr Address to free
439 */
mutt_addr_free(struct Address ** ptr)440 void mutt_addr_free(struct Address **ptr)
441 {
442 if (!ptr || !*ptr)
443 return;
444
445 struct Address *a = *ptr;
446
447 FREE(&a->personal);
448 FREE(&a->mailbox);
449 FREE(ptr);
450 }
451
452 /**
453 * mutt_addrlist_parse - Parse a list of email addresses
454 * @param al AddressList to append addresses
455 * @param s String to parse
456 * @retval num Number of parsed addresses
457 */
mutt_addrlist_parse(struct AddressList * al,const char * s)458 int mutt_addrlist_parse(struct AddressList *al, const char *s)
459 {
460 if (!s)
461 return 0;
462
463 int parsed = 0;
464 char comment[1024], phrase[1024];
465 size_t phraselen = 0, commentlen = 0;
466 AddressError = 0;
467
468 bool ws_pending = mutt_str_is_email_wsp(*s);
469
470 s = mutt_str_skip_email_wsp(s);
471 while (*s)
472 {
473 switch (*s)
474 {
475 case ';':
476 case ',':
477 if (phraselen != 0)
478 {
479 terminate_buffer(phrase, phraselen);
480 if (add_addrspec(al, phrase, comment, &commentlen, sizeof(comment) - 1))
481 {
482 parsed++;
483 }
484 }
485 else if (commentlen != 0)
486 {
487 struct Address *last = TAILQ_LAST(al, AddressList);
488 if (last && !last->personal && last->mailbox)
489 {
490 terminate_buffer(comment, commentlen);
491 last->personal = mutt_str_dup(comment);
492 }
493 }
494
495 if (*s == ';')
496 {
497 /* add group terminator */
498 mutt_addrlist_append(al, mutt_addr_new());
499 }
500
501 phraselen = 0;
502 commentlen = 0;
503 s++;
504 break;
505
506 case '(':
507 if ((commentlen != 0) && (commentlen < (sizeof(comment) - 1)))
508 comment[commentlen++] = ' ';
509 s = next_token(s, comment, &commentlen, sizeof(comment) - 1);
510 if (!s)
511 {
512 mutt_addrlist_clear(al);
513 return 0;
514 }
515 break;
516
517 case '"':
518 if ((phraselen != 0) && (phraselen < (sizeof(phrase) - 1)))
519 phrase[phraselen++] = ' ';
520 s = parse_quote(s + 1, phrase, &phraselen, sizeof(phrase) - 1);
521 if (!s)
522 {
523 mutt_addrlist_clear(al);
524 return 0;
525 }
526 break;
527
528 case ':':
529 {
530 struct Address *a = mutt_addr_new();
531 terminate_buffer(phrase, phraselen);
532 a->mailbox = mutt_str_dup(phrase);
533 a->group = true;
534 mutt_addrlist_append(al, a);
535 phraselen = 0;
536 commentlen = 0;
537 s++;
538 break;
539 }
540
541 case '<':
542 {
543 struct Address *a = mutt_addr_new();
544 terminate_buffer(phrase, phraselen);
545 a->personal = mutt_str_dup(phrase);
546 s = parse_route_addr(s + 1, comment, &commentlen, sizeof(comment) - 1, a);
547 if (!s)
548 {
549 mutt_addrlist_clear(al);
550 mutt_addr_free(&a);
551 return 0;
552 }
553 mutt_addrlist_append(al, a);
554 phraselen = 0;
555 commentlen = 0;
556 parsed++;
557 break;
558 }
559
560 default:
561 if ((phraselen != 0) && (phraselen < (sizeof(phrase) - 1)) && ws_pending)
562 phrase[phraselen++] = ' ';
563 if (*s == '\\')
564 {
565 s++;
566 if (*s && (phraselen < (sizeof(phrase) - 1)))
567 {
568 phrase[phraselen++] = *s;
569 s++;
570 }
571 }
572 s = next_token(s, phrase, &phraselen, sizeof(phrase) - 1);
573 if (!s)
574 {
575 mutt_addrlist_clear(al);
576 return 0;
577 }
578 break;
579 } // switch (*s)
580
581 ws_pending = mutt_str_is_email_wsp(*s);
582 s = mutt_str_skip_email_wsp(s);
583 } // while (*s)
584
585 if (phraselen != 0)
586 {
587 terminate_buffer(phrase, phraselen);
588 terminate_buffer(comment, commentlen);
589 if (add_addrspec(al, phrase, comment, &commentlen, sizeof(comment) - 1))
590 {
591 parsed++;
592 }
593 }
594 else if (commentlen != 0)
595 {
596 struct Address *last = TAILQ_LAST(al, AddressList);
597 if (last && !last->personal && last->mailbox)
598 {
599 terminate_buffer(comment, commentlen);
600 last->personal = mutt_str_dup(comment);
601 }
602 }
603
604 return parsed;
605 }
606
607 /**
608 * mutt_addrlist_parse2 - Parse a list of email addresses
609 * @param al Add to this List of Addresses
610 * @param s String to parse
611 * @retval num Number of parsed addresses
612 *
613 * Simple email addresses (without any personal name or grouping) can be
614 * separated by whitespace or commas.
615 */
mutt_addrlist_parse2(struct AddressList * al,const char * s)616 int mutt_addrlist_parse2(struct AddressList *al, const char *s)
617 {
618 if (!s || (*s == '\0'))
619 return 0;
620
621 int parsed = 0;
622
623 /* check for a simple whitespace separated list of addresses */
624 if (!strpbrk(s, "\"<>():;,\\"))
625 {
626 char *copy = mutt_str_dup(s);
627 char *r = copy;
628 while ((r = strtok(r, " \t")))
629 {
630 parsed += mutt_addrlist_parse(al, r);
631 r = NULL;
632 }
633 FREE(©);
634 }
635 else
636 parsed = mutt_addrlist_parse(al, s);
637
638 return parsed;
639 }
640
641 /**
642 * mutt_addrlist_qualify - Expand local names in an Address list using a hostname
643 * @param al Address list
644 * @param host Hostname
645 *
646 * Any addresses containing a bare name will be expanded using the hostname.
647 * e.g. "john", "example.com" -> 'john@example.com'. This function has no
648 * effect if host is NULL or the empty string.
649 */
mutt_addrlist_qualify(struct AddressList * al,const char * host)650 void mutt_addrlist_qualify(struct AddressList *al, const char *host)
651 {
652 if (!al || !host || (*host == '\0'))
653 return;
654
655 struct Address *a = NULL;
656 TAILQ_FOREACH(a, al, entries)
657 {
658 if (!a->group && a->mailbox && !strchr(a->mailbox, '@'))
659 {
660 char *p = mutt_mem_malloc(mutt_str_len(a->mailbox) + mutt_str_len(host) + 2);
661 sprintf(p, "%s@%s", a->mailbox, host);
662 FREE(&a->mailbox);
663 a->mailbox = p;
664 }
665 }
666 }
667
668 /**
669 * mutt_addr_cat - Copy a string and wrap it in quotes if it contains special characters
670 * @param buf Buffer for the result
671 * @param buflen Length of the result buffer
672 * @param value String to copy
673 * @param specials Characters to lookup
674 *
675 * This function copies the string in the "value" parameter in the buffer
676 * pointed to by "buf" parameter. If the input string contains any of the
677 * characters specified in the "specials" parameters, the output string is
678 * wrapped in double quoted. Additionally, any backslashes or quotes inside the
679 * input string are backslash-escaped.
680 */
mutt_addr_cat(char * buf,size_t buflen,const char * value,const char * specials)681 void mutt_addr_cat(char *buf, size_t buflen, const char *value, const char *specials)
682 {
683 if (!buf || !value || !specials)
684 return;
685
686 if (strpbrk(value, specials))
687 {
688 char tmp[256];
689 char *pc = tmp;
690 size_t tmplen = sizeof(tmp) - 3;
691
692 *pc++ = '"';
693 for (; *value && (tmplen > 1); value++)
694 {
695 if ((*value == '\\') || (*value == '"'))
696 {
697 *pc++ = '\\';
698 tmplen--;
699 }
700 *pc++ = *value;
701 tmplen--;
702 }
703 *pc++ = '"';
704 *pc = '\0';
705 mutt_str_copy(buf, tmp, buflen);
706 }
707 else
708 mutt_str_copy(buf, value, buflen);
709 }
710
711 /**
712 * mutt_addr_copy - Copy the real address
713 * @param addr Address to copy
714 * @retval ptr New Address
715 */
mutt_addr_copy(const struct Address * addr)716 struct Address *mutt_addr_copy(const struct Address *addr)
717 {
718 if (!addr)
719 return NULL;
720
721 struct Address *p = mutt_addr_new();
722
723 p->personal = mutt_str_dup(addr->personal);
724 p->mailbox = mutt_str_dup(addr->mailbox);
725 p->group = addr->group;
726 p->is_intl = addr->is_intl;
727 p->intl_checked = addr->intl_checked;
728 return p;
729 }
730
731 /**
732 * mutt_addrlist_copy - Copy a list of addresses into another list
733 * @param dst Destination Address list
734 * @param src Source Address list
735 * @param prune Skip groups if there are more addresses
736 */
mutt_addrlist_copy(struct AddressList * dst,const struct AddressList * src,bool prune)737 void mutt_addrlist_copy(struct AddressList *dst, const struct AddressList *src, bool prune)
738 {
739 if (!dst || !src)
740 return;
741
742 struct Address *a = NULL;
743 TAILQ_FOREACH(a, src, entries)
744 {
745 struct Address *next = TAILQ_NEXT(a, entries);
746 if (prune && a->group && (!next || !next->mailbox))
747 {
748 /* ignore this element of the list */
749 }
750 else
751 {
752 mutt_addrlist_append(dst, mutt_addr_copy(a));
753 }
754 }
755 }
756
757 /**
758 * mutt_addr_valid_msgid - Is this a valid Message ID?
759 * @param msgid Message ID
760 * @retval true It is a valid message ID
761 *
762 * Incomplete. Only used to thwart the APOP MD5 attack
763 */
mutt_addr_valid_msgid(const char * msgid)764 bool mutt_addr_valid_msgid(const char *msgid)
765 {
766 /* msg-id = "<" addr-spec ">"
767 * addr-spec = local-part "@" domain
768 * local-part = word *("." word)
769 * word = atom / quoted-string
770 * atom = 1*<any CHAR except specials, SPACE and CTLs>
771 * CHAR = ( 0.-127. )
772 * specials = "(" / ")" / "<" / ">" / "@"
773 * / "," / ";" / ":" / "\" / <">
774 * / "." / "[" / "]"
775 * SPACE = ( 32. )
776 * CTLS = ( 0.-31., 127.)
777 * quoted-string = <"> *(qtext/quoted-pair) <">
778 * qtext = <any CHAR except <">, "\" and CR>
779 * CR = ( 13. )
780 * quoted-pair = "\" CHAR
781 * domain = sub-domain *("." sub-domain)
782 * sub-domain = domain-ref / domain-literal
783 * domain-ref = atom
784 * domain-literal = "[" *(dtext / quoted-pair) "]"
785 */
786
787 if (!msgid || (*msgid == '\0'))
788 return false;
789
790 size_t l = mutt_str_len(msgid);
791 if (l < 5) /* <atom@atom> */
792 return false;
793 if ((msgid[0] != '<') || (msgid[l - 1] != '>'))
794 return false;
795 if (!(strrchr(msgid, '@')))
796 return false;
797
798 /* TODO: complete parser */
799 for (size_t i = 0; i < l; i++)
800 if ((unsigned char) msgid[i] > 127)
801 return false;
802
803 return true;
804 }
805
806 /**
807 * mutt_addrlist_equal - Compare two Address lists for equality
808 * @param ala First Address
809 * @param alb Second Address
810 * @retval true Address lists are strictly identical
811 */
mutt_addrlist_equal(const struct AddressList * ala,const struct AddressList * alb)812 bool mutt_addrlist_equal(const struct AddressList *ala, const struct AddressList *alb)
813 {
814 if (!ala || !alb)
815 {
816 return !(ala || alb);
817 }
818
819 struct Address *ana = TAILQ_FIRST(ala);
820 struct Address *anb = TAILQ_FIRST(alb);
821
822 while (ana && anb)
823 {
824 if (!mutt_str_equal(ana->mailbox, anb->mailbox) ||
825 !mutt_str_equal(ana->personal, anb->personal))
826 {
827 break;
828 }
829
830 ana = TAILQ_NEXT(ana, entries);
831 anb = TAILQ_NEXT(anb, entries);
832 }
833
834 return !(ana || anb);
835 }
836
837 /**
838 * mutt_addrlist_count_recips - Count the number of Addresses with valid recipients
839 * @param al Address list
840 * @retval num Number of valid Addresses
841 *
842 * An Address has a recipient if the mailbox is set and is not a group
843 */
mutt_addrlist_count_recips(const struct AddressList * al)844 int mutt_addrlist_count_recips(const struct AddressList *al)
845 {
846 if (!al)
847 return 0;
848
849 int c = 0;
850 struct Address *a = NULL;
851 TAILQ_FOREACH(a, al, entries)
852 {
853 c += (a->mailbox && !a->group);
854 }
855 return c;
856 }
857
858 /**
859 * mutt_addr_cmp - Compare two e-mail addresses
860 * @param a Address 1
861 * @param b Address 2
862 * @retval true They are equivalent
863 */
mutt_addr_cmp(const struct Address * a,const struct Address * b)864 bool mutt_addr_cmp(const struct Address *a, const struct Address *b)
865 {
866 if (!a || !b)
867 return false;
868 if (!a->mailbox || !b->mailbox)
869 return false;
870 if (!mutt_istr_equal(a->mailbox, b->mailbox))
871 return false;
872 return true;
873 }
874
875 /**
876 * mutt_addrlist_search - Search for an e-mail address in a list
877 * @param haystack Address List
878 * @param needle Address containing the search email
879 * @retval true The Address is in the list
880 */
mutt_addrlist_search(const struct AddressList * haystack,const struct Address * needle)881 bool mutt_addrlist_search(const struct AddressList *haystack, const struct Address *needle)
882 {
883 if (!needle || !haystack)
884 return false;
885
886 struct Address *a = NULL;
887 TAILQ_FOREACH(a, haystack, entries)
888 {
889 if (mutt_addr_cmp(needle, a))
890 return true;
891 }
892 return false;
893 }
894
895 /**
896 * addr_is_intl - Does the Address have IDN components
897 * @param a Address to check
898 * @retval true Address contains IDN components
899 */
addr_is_intl(const struct Address * a)900 static bool addr_is_intl(const struct Address *a)
901 {
902 if (!a)
903 return false;
904 return a->intl_checked && a->is_intl;
905 }
906
907 /**
908 * addr_is_local - Does the Address have NO IDN components
909 * @param a Address to check
910 * @retval true Address contains NO IDN components
911 */
addr_is_local(const struct Address * a)912 static bool addr_is_local(const struct Address *a)
913 {
914 if (!a)
915 return false;
916 return a->intl_checked && !a->is_intl;
917 }
918
919 /**
920 * addr_mbox_to_udomain - Split a mailbox name into user and domain
921 * @param[in] mbox Mailbox name to split
922 * @param[out] user User
923 * @param[out] domain Domain
924 * @retval 0 Success
925 * @retval -1 Error
926 *
927 * @warning The caller must free user and domain
928 */
addr_mbox_to_udomain(const char * mbox,char ** user,char ** domain)929 static int addr_mbox_to_udomain(const char *mbox, char **user, char **domain)
930 {
931 if (!mbox || !user || !domain)
932 return -1;
933
934 char *ptr = strchr(mbox, '@');
935
936 /* Fail if '@' is missing, at the start, or at the end */
937 if (!ptr || (ptr == mbox) || (ptr[1] == '\0'))
938 return -1;
939
940 *user = mutt_strn_dup(mbox, ptr - mbox);
941 *domain = mutt_str_dup(ptr + 1);
942
943 return 0;
944 }
945
946 /**
947 * addr_set_intl - Mark an Address as having IDN components
948 * @param a Address to modify
949 * @param intl_mailbox Email address with IDN components
950 */
addr_set_intl(struct Address * a,char * intl_mailbox)951 static void addr_set_intl(struct Address *a, char *intl_mailbox)
952 {
953 if (!a)
954 return;
955
956 FREE(&a->mailbox);
957 a->mailbox = intl_mailbox;
958 a->intl_checked = true;
959 a->is_intl = true;
960 }
961
962 /**
963 * addr_set_local - Mark an Address as having NO IDN components
964 * @param a Address
965 * @param local_mailbox Email address with NO IDN components
966 */
addr_set_local(struct Address * a,char * local_mailbox)967 static void addr_set_local(struct Address *a, char *local_mailbox)
968 {
969 if (!a)
970 return;
971
972 FREE(&a->mailbox);
973 a->mailbox = local_mailbox;
974 a->intl_checked = true;
975 a->is_intl = false;
976 }
977
978 /**
979 * mutt_addr_for_display - Convert an Address for display purposes
980 * @param a Address to convert
981 * @retval ptr Address to display
982 *
983 * @warning This function may return a static pointer. It must not be freed by
984 * the caller. Later calls may overwrite the returned pointer.
985 */
mutt_addr_for_display(const struct Address * a)986 const char *mutt_addr_for_display(const struct Address *a)
987 {
988 if (!a)
989 return NULL;
990
991 char *user = NULL, *domain = NULL;
992 static char *buf = NULL;
993
994 if (!a->mailbox || addr_is_local(a))
995 return a->mailbox;
996
997 if (addr_mbox_to_udomain(a->mailbox, &user, &domain) == -1)
998 return a->mailbox;
999
1000 char *local_mailbox = mutt_idna_intl_to_local(user, domain, MI_MAY_BE_IRREVERSIBLE);
1001
1002 FREE(&user);
1003 FREE(&domain);
1004
1005 if (!local_mailbox)
1006 return a->mailbox;
1007
1008 mutt_str_replace(&buf, local_mailbox);
1009 FREE(&local_mailbox);
1010
1011 return buf;
1012 }
1013
1014 /**
1015 * mutt_addr_write - Write a single Address to a buffer
1016 * @param buf Buffer for the Address
1017 * @param buflen Length of the buffer
1018 * @param addr Address to display
1019 * @param display This address will be displayed to the user
1020 * @retval num Length of the string written to buf
1021 *
1022 * If 'display' is set, then it doesn't matter if the transformation isn't
1023 * reversible.
1024 */
mutt_addr_write(char * buf,size_t buflen,struct Address * addr,bool display)1025 size_t mutt_addr_write(char *buf, size_t buflen, struct Address *addr, bool display)
1026 {
1027 if (!buf || (buflen == 0) || !addr)
1028 return 0;
1029
1030 if (!addr->personal && !addr->mailbox)
1031 return 0;
1032
1033 size_t len;
1034 char *pbuf = buf;
1035 char *pc = NULL;
1036
1037 buflen--; /* save room for the terminal nul */
1038
1039 if (addr->personal)
1040 {
1041 if (strpbrk(addr->personal, AddressSpecials))
1042 {
1043 if (buflen == 0)
1044 goto done;
1045 *pbuf++ = '"';
1046 buflen--;
1047 for (pc = addr->personal; *pc && (buflen > 0); pc++)
1048 {
1049 if ((*pc == '"') || (*pc == '\\'))
1050 {
1051 *pbuf++ = '\\';
1052 buflen--;
1053 }
1054 if (buflen == 0)
1055 goto done;
1056 *pbuf++ = *pc;
1057 buflen--;
1058 }
1059 if (buflen == 0)
1060 goto done;
1061 *pbuf++ = '"';
1062 buflen--;
1063 }
1064 else
1065 {
1066 if (buflen == 0)
1067 goto done;
1068 len = mutt_str_copy(pbuf, addr->personal, buflen + 1);
1069 pbuf += len;
1070 buflen -= len;
1071 }
1072
1073 if (buflen == 0)
1074 goto done;
1075 *pbuf++ = ' ';
1076 buflen--;
1077 }
1078
1079 if (addr->personal || (addr->mailbox && (*addr->mailbox == '@')))
1080 {
1081 if (buflen == 0)
1082 goto done;
1083 *pbuf++ = '<';
1084 buflen--;
1085 }
1086
1087 if (addr->mailbox)
1088 {
1089 if (buflen == 0)
1090 goto done;
1091 if (mutt_str_equal(addr->mailbox, "@"))
1092 {
1093 *pbuf = '\0';
1094 }
1095 else
1096 {
1097 const char *a = display ? mutt_addr_for_display(addr) : addr->mailbox;
1098 len = mutt_str_copy(pbuf, a, buflen + 1);
1099 pbuf += len;
1100 buflen -= len;
1101 }
1102
1103 if (addr->personal || (addr->mailbox && (*addr->mailbox == '@')))
1104 {
1105 if (buflen == 0)
1106 goto done;
1107 *pbuf++ = '>';
1108 buflen--;
1109 }
1110
1111 if (addr->group)
1112 {
1113 if (buflen == 0)
1114 goto done;
1115 *pbuf++ = ':';
1116 buflen--;
1117 if (buflen == 0)
1118 goto done;
1119 *pbuf++ = ' ';
1120 buflen--;
1121 }
1122 }
1123 else
1124 {
1125 if (buflen == 0)
1126 goto done;
1127 *pbuf++ = ';';
1128 }
1129 done:
1130 /* no need to check for length here since we already save space at the
1131 * beginning of this routine */
1132 *pbuf = '\0';
1133
1134 return pbuf - buf;
1135 }
1136
1137 /**
1138 * mutt_addrlist_write - Write an Address to a buffer
1139 * @param al AddressList to display
1140 * @param buf Buffer for the Address
1141 * @param buflen Length of the buffer
1142 * @param display This address will be displayed to the user
1143 * @retval num Length of the string written to buf
1144 *
1145 * If 'display' is set, then it doesn't matter if the transformation isn't
1146 * reversible.
1147 *
1148 * @note It is assumed that `buf` is nul terminated!
1149 */
mutt_addrlist_write(const struct AddressList * al,char * buf,size_t buflen,bool display)1150 size_t mutt_addrlist_write(const struct AddressList *al, char *buf, size_t buflen, bool display)
1151 {
1152 if (!buf || (buflen == 0) || !al)
1153 return 0;
1154
1155 size_t len = mutt_str_len(buf);
1156 if (len >= buflen)
1157 {
1158 return 0;
1159 }
1160
1161 char *pbuf = buf + len;
1162 buflen -= len;
1163
1164 struct Address *a = NULL;
1165 TAILQ_FOREACH(a, al, entries)
1166 {
1167 if (len > 0)
1168 {
1169 if (buflen > 1)
1170 {
1171 *pbuf++ = ',';
1172 buflen--;
1173 }
1174 if (buflen > 1)
1175 {
1176 *pbuf++ = ' ';
1177 buflen--;
1178 }
1179 }
1180
1181 if (buflen == 1)
1182 {
1183 break;
1184 }
1185
1186 len = mutt_addr_write(pbuf, buflen, a, display);
1187 pbuf += len;
1188 buflen -= len;
1189 }
1190
1191 *pbuf = '\0';
1192 return pbuf - buf;
1193 }
1194
1195 /**
1196 * mutt_addrlist_write_list - Write Addresses to a List
1197 * @param al AddressList to write
1198 * @param list List for the Addresses
1199 * @retval num Number of addresses written
1200 */
mutt_addrlist_write_list(const struct AddressList * al,struct ListHead * list)1201 size_t mutt_addrlist_write_list(const struct AddressList *al, struct ListHead *list)
1202 {
1203 if (!al || !list)
1204 return 0;
1205
1206 char addr[256];
1207 size_t count = 0;
1208 struct Address *a = NULL;
1209 TAILQ_FOREACH(a, al, entries)
1210 {
1211 if (mutt_addr_write(addr, sizeof(addr), a, true) != 0)
1212 {
1213 mutt_list_insert_tail(list, strdup(addr));
1214 count++;
1215 }
1216 }
1217
1218 return count;
1219 }
1220
1221 /**
1222 * mutt_addrlist_write_file - Wrapper for mutt_write_address()
1223 * @param al Address list
1224 * @param fp File to write to
1225 * @param start_col Starting column in the output line
1226 * @param display True if these addresses will be displayed to the user
1227 *
1228 * So we can handle very large recipient lists without needing a huge temporary
1229 * buffer in memory
1230 */
mutt_addrlist_write_file(const struct AddressList * al,FILE * fp,int start_col,bool display)1231 void mutt_addrlist_write_file(const struct AddressList *al, FILE *fp, int start_col, bool display)
1232 {
1233 char buf[1024];
1234 int count = 0;
1235 int linelen = start_col;
1236
1237 struct Address *a = NULL;
1238 TAILQ_FOREACH(a, al, entries)
1239 {
1240 buf[0] = '\0';
1241 size_t len = mutt_addr_write(buf, sizeof(buf), a, display);
1242 if (len == 0)
1243 continue;
1244 if (count && (linelen + len > 74))
1245 {
1246 fputs("\n\t", fp);
1247 linelen = len + 8; /* tab is usually about 8 spaces... */
1248 }
1249 else
1250 {
1251 if (count && a->mailbox)
1252 {
1253 fputc(' ', fp);
1254 linelen++;
1255 }
1256 linelen += len;
1257 }
1258 fputs(buf, fp);
1259 struct Address *next = TAILQ_NEXT(a, entries);
1260 if (!a->group && next && next->mailbox)
1261 {
1262 linelen++;
1263 fputc(',', fp);
1264 }
1265 count++;
1266 }
1267 fputc('\n', fp);
1268 }
1269
1270 /**
1271 * mutt_addr_to_intl - Convert an Address to Punycode
1272 * @param a Address to convert
1273 * @retval true Success
1274 * @retval false Otherwise
1275 */
mutt_addr_to_intl(struct Address * a)1276 bool mutt_addr_to_intl(struct Address *a)
1277 {
1278 if (!a || !a->mailbox || addr_is_intl(a))
1279 return true;
1280
1281 char *user = NULL;
1282 char *domain = NULL;
1283 if (addr_mbox_to_udomain(a->mailbox, &user, &domain) == -1)
1284 return true;
1285
1286 char *intl_mailbox = mutt_idna_local_to_intl(user, domain);
1287
1288 FREE(&user);
1289 FREE(&domain);
1290
1291 if (!intl_mailbox)
1292 return false;
1293
1294 addr_set_intl(a, intl_mailbox);
1295 return true;
1296 }
1297
1298 /**
1299 * mutt_addrlist_to_intl - Convert an Address list to Punycode
1300 * @param[in] al Address list to modify
1301 * @param[out] err Pointer for failed addresses
1302 * @retval 0 Success, all addresses converted
1303 * @retval -1 Error, err will be set to the failed address
1304 */
mutt_addrlist_to_intl(struct AddressList * al,char ** err)1305 int mutt_addrlist_to_intl(struct AddressList *al, char **err)
1306 {
1307 if (!al)
1308 return 0;
1309
1310 int rc = 0;
1311
1312 if (err)
1313 *err = NULL;
1314
1315 struct Address *a = NULL;
1316 TAILQ_FOREACH(a, al, entries)
1317 {
1318 if (!a->mailbox || addr_is_intl(a))
1319 continue;
1320
1321 char *user = NULL;
1322 char *domain = NULL;
1323 if (addr_mbox_to_udomain(a->mailbox, &user, &domain) == -1)
1324 continue;
1325
1326 char *intl_mailbox = mutt_idna_local_to_intl(user, domain);
1327
1328 FREE(&user);
1329 FREE(&domain);
1330
1331 if (!intl_mailbox)
1332 {
1333 rc = -1;
1334 if (err && !*err)
1335 *err = mutt_str_dup(a->mailbox);
1336 continue;
1337 }
1338
1339 addr_set_intl(a, intl_mailbox);
1340 }
1341
1342 return rc;
1343 }
1344
1345 /**
1346 * mutt_addr_to_local - Convert an Address from Punycode
1347 * @param a Address to convert
1348 * @retval true Success
1349 * @retval false Otherwise
1350 */
mutt_addr_to_local(struct Address * a)1351 bool mutt_addr_to_local(struct Address *a)
1352 {
1353 if (!a->mailbox)
1354 {
1355 return false;
1356 }
1357
1358 if (addr_is_local(a))
1359 {
1360 return true;
1361 }
1362
1363 char *user = NULL;
1364 char *domain = NULL;
1365 if (addr_mbox_to_udomain(a->mailbox, &user, &domain) == -1)
1366 {
1367 return false;
1368 }
1369
1370 char *local_mailbox = mutt_idna_intl_to_local(user, domain, MI_NO_FLAGS);
1371 FREE(&user);
1372 FREE(&domain);
1373
1374 if (!local_mailbox)
1375 {
1376 return false;
1377 }
1378
1379 addr_set_local(a, local_mailbox);
1380 return true;
1381 }
1382
1383 /**
1384 * mutt_addrlist_to_local - Convert an Address list from Punycode
1385 * @param al Address list to modify
1386 * @retval 0 Always
1387 */
mutt_addrlist_to_local(struct AddressList * al)1388 int mutt_addrlist_to_local(struct AddressList *al)
1389 {
1390 if (!al)
1391 return 0;
1392
1393 struct Address *a = NULL;
1394 TAILQ_FOREACH(a, al, entries)
1395 {
1396 mutt_addr_to_local(a);
1397 }
1398 return 0;
1399 }
1400
1401 /**
1402 * mutt_addrlist_dedupe - Remove duplicate addresses
1403 * @param al Address list to de-dupe
1404 *
1405 * Given a list of addresses, return a list of unique addresses
1406 */
mutt_addrlist_dedupe(struct AddressList * al)1407 void mutt_addrlist_dedupe(struct AddressList *al)
1408 {
1409 if (!al)
1410 return;
1411
1412 struct Address *a = NULL;
1413 struct Address *tmp = NULL;
1414 TAILQ_FOREACH_SAFE(a, al, entries, tmp)
1415 {
1416 if (a->mailbox)
1417 {
1418 struct Address *a2 = TAILQ_NEXT(a, entries);
1419
1420 if (a2)
1421 {
1422 struct Address *tmp2 = NULL;
1423 TAILQ_FOREACH_FROM_SAFE(a2, al, entries, tmp2)
1424 {
1425 if (a2->mailbox && mutt_istr_equal(a->mailbox, a2->mailbox))
1426 {
1427 mutt_debug(LL_DEBUG2, "Removing %s\n", a2->mailbox);
1428 TAILQ_REMOVE(al, a2, entries);
1429 mutt_addr_free(&a2);
1430 }
1431 }
1432 }
1433 }
1434 }
1435 }
1436
1437 /**
1438 * mutt_addrlist_remove_xrefs - Remove cross-references
1439 * @param a Reference AddressList
1440 * @param b AddressLis to trim
1441 *
1442 * Remove addresses from "b" which are contained in "a"
1443 */
mutt_addrlist_remove_xrefs(const struct AddressList * a,struct AddressList * b)1444 void mutt_addrlist_remove_xrefs(const struct AddressList *a, struct AddressList *b)
1445 {
1446 if (!a || !b)
1447 return;
1448
1449 struct Address *aa = NULL, *ab = NULL, *tmp = NULL;
1450
1451 TAILQ_FOREACH_SAFE(ab, b, entries, tmp)
1452 {
1453 TAILQ_FOREACH(aa, a, entries)
1454 {
1455 if (mutt_addr_cmp(aa, ab))
1456 {
1457 TAILQ_REMOVE(b, ab, entries);
1458 mutt_addr_free(&ab);
1459 break;
1460 }
1461 }
1462 }
1463 }
1464
1465 /**
1466 * mutt_addrlist_clear - Unlink and free all Address in an AddressList
1467 * @param al AddressList
1468 *
1469 * @note After this call, the AddressList is reinitialized and ready for reuse.
1470 */
mutt_addrlist_clear(struct AddressList * al)1471 void mutt_addrlist_clear(struct AddressList *al)
1472 {
1473 if (!al)
1474 return;
1475
1476 struct Address *a = TAILQ_FIRST(al), *next = NULL;
1477 while (a)
1478 {
1479 next = TAILQ_NEXT(a, entries);
1480 mutt_addr_free(&a);
1481 a = next;
1482 }
1483 TAILQ_INIT(al);
1484 }
1485
1486 /**
1487 * mutt_addrlist_append - Append an Address to an AddressList
1488 * @param al AddressList
1489 * @param a Address
1490 */
mutt_addrlist_append(struct AddressList * al,struct Address * a)1491 void mutt_addrlist_append(struct AddressList *al, struct Address *a)
1492 {
1493 if (al && a)
1494 TAILQ_INSERT_TAIL(al, a, entries);
1495 }
1496
1497 /**
1498 * mutt_addrlist_prepend - Prepend an Address to an AddressList
1499 * @param al AddressList
1500 * @param a Address
1501 */
mutt_addrlist_prepend(struct AddressList * al,struct Address * a)1502 void mutt_addrlist_prepend(struct AddressList *al, struct Address *a)
1503 {
1504 if (al && a)
1505 TAILQ_INSERT_HEAD(al, a, entries);
1506 }
1507
1508 /**
1509 * mutt_addr_uses_unicode - Does this address use Unicode character
1510 * @param str Address string to check
1511 * @retval true The string uses 8-bit characters
1512 */
mutt_addr_uses_unicode(const char * str)1513 bool mutt_addr_uses_unicode(const char *str)
1514 {
1515 if (!str)
1516 return false;
1517
1518 while (*str)
1519 {
1520 if ((unsigned char) *str & (1 << 7))
1521 return true;
1522 str++;
1523 }
1524
1525 return false;
1526 }
1527
1528 /**
1529 * mutt_addrlist_uses_unicode - Do any of a list of addresses use Unicode characters
1530 * @param al Address list to check
1531 * @retval true Any use 8-bit characters
1532 */
mutt_addrlist_uses_unicode(const struct AddressList * al)1533 bool mutt_addrlist_uses_unicode(const struct AddressList *al)
1534 {
1535 if (!al)
1536 {
1537 return false;
1538 }
1539
1540 struct Address *a = NULL;
1541 TAILQ_FOREACH(a, al, entries)
1542 {
1543 if (a->mailbox && !a->group && mutt_addr_uses_unicode(a->mailbox))
1544 return true;
1545 }
1546 return false;
1547 }
1548