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(&copy);
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