/* * Copyright notice from original mutt: * Copyright (C) 1996-2000 Michael R. Elkins * * This file is part of mutt-ng, see http://www.muttng.org/. * It's licensed under the GNU General Public License, * please see the file GPL in the top level source directory. */ #if HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "mutt.h" #include "ascii.h" #include "mutt_idna.h" #include "lib/mem.h" #include "lib/intl.h" #include "lib/str.h" #define terminate_string(a, b, c) do { if ((b) < (c)) a[(b)] = 0; else \ a[(c)] = 0; } while (0) #define terminate_buffer(a, b) terminate_string(a, b, sizeof (a) - 1) const char RFC822Specials[] = "@.,:;<>[]\\\"()"; #define is_special(x) strchr(RFC822Specials,x) int RFC822Error = 0; /* these must defined in the same order as the numerated errors given in rfc822.h */ const char *RFC822Errors[] = { "out of memory", "mismatched parenthesis", "mismatched quotes", "bad route in <>", "bad address in <>", "bad address spec" }; void rfc822_dequote_comment (char *s) { char *w = s; for (; *s; s++) { if (*s == '\\') { if (!*++s) break; /* error? */ *w++ = *s; } else if (*s != '\"') { if (w != s) *w = *s; w++; } } *w = 0; } void rfc822_free_address (ADDRESS ** p) { ADDRESS *t; while (*p) { t = *p; *p = (*p)->next; mem_free (&t->personal); mem_free (&t->mailbox); mem_free (&t); } } static const char *parse_comment (const char *s, char *comment, size_t * commentlen, size_t commentmax) { int level = 1; while (*s && level) { if (*s == '(') level++; else if (*s == ')') { if (--level == 0) { s++; break; } } else if (*s == '\\') { if (!*++s) break; } if (*commentlen < commentmax) comment[(*commentlen)++] = *s; s++; } if (level) { RFC822Error = ERR_MISMATCH_PAREN; return NULL; } return s; } static const char *parse_quote (const char *s, char *token, size_t * tokenlen, size_t tokenmax) { if (*tokenlen < tokenmax) token[(*tokenlen)++] = '"'; while (*s) { if (*tokenlen < tokenmax) token[*tokenlen] = *s; if (*s == '"') { (*tokenlen)++; return (s + 1); } if (*s == '\\') { if (!*++s) break; if (*tokenlen < tokenmax) token[*tokenlen] = *s; } (*tokenlen)++; s++; } RFC822Error = ERR_MISMATCH_QUOTE; return NULL; } static const char *next_token (const char *s, char *token, size_t * tokenlen, size_t tokenmax) { if (*s == '(') return (parse_comment (s + 1, token, tokenlen, tokenmax)); if (*s == '"') return (parse_quote (s + 1, token, tokenlen, tokenmax)); if (is_special (*s)) { if (*tokenlen < tokenmax) token[(*tokenlen)++] = *s; return (s + 1); } while (*s) { if (ISSPACE ((unsigned char) *s) || is_special (*s)) break; if (*tokenlen < tokenmax) token[(*tokenlen)++] = *s; s++; } return s; } static const char *parse_mailboxdomain (const char *s, const char *nonspecial, char *mailbox, size_t * mailboxlen, size_t mailboxmax, char *comment, size_t * commentlen, size_t commentmax) { const char *ps; while (*s) { SKIPWS (s); if (strchr (nonspecial, *s) == NULL && is_special (*s)) return s; if (*s == '(') { if (*commentlen && *commentlen < commentmax) comment[(*commentlen)++] = ' '; ps = next_token (s, comment, commentlen, commentmax); } else ps = next_token (s, mailbox, mailboxlen, mailboxmax); if (!ps) return NULL; s = ps; } return s; } static const char *parse_address (const char *s, char *token, size_t * tokenlen, size_t tokenmax, char *comment, size_t * commentlen, size_t commentmax, ADDRESS * addr) { s = parse_mailboxdomain (s, ".\"(\\", token, tokenlen, tokenmax, comment, commentlen, commentmax); if (!s) return NULL; if (*s == '@') { if (*tokenlen < tokenmax) token[(*tokenlen)++] = '@'; s = parse_mailboxdomain (s + 1, ".([]\\", token, tokenlen, tokenmax, comment, commentlen, commentmax); if (!s) return NULL; } terminate_string (token, *tokenlen, tokenmax); addr->mailbox = str_dup (token); if (*commentlen && !addr->personal) { terminate_string (comment, *commentlen, commentmax); addr->personal = str_dup (comment); } return s; } static const char *parse_route_addr (const char *s, char *comment, size_t * commentlen, size_t commentmax, ADDRESS * addr) { char token[STRING]; size_t tokenlen = 0; SKIPWS (s); /* find the end of the route */ if (*s == '@') { while (s && *s == '@') { if (tokenlen < sizeof (token) - 1) token[tokenlen++] = '@'; s = parse_mailboxdomain (s + 1, ",.\\[](", token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax); } if (!s || *s != ':') { RFC822Error = ERR_BAD_ROUTE; return NULL; /* invalid route */ } if (tokenlen < sizeof (token) - 1) token[tokenlen++] = ':'; s++; } if ((s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr)) == NULL) return NULL; if (*s != '>') { RFC822Error = ERR_BAD_ROUTE_ADDR; return NULL; } if (!addr->mailbox) addr->mailbox = str_dup ("@"); s++; return s; } static const char *parse_addr_spec (const char *s, char *comment, size_t * commentlen, size_t commentmax, ADDRESS * addr) { char token[STRING]; size_t tokenlen = 0; s = parse_address (s, token, &tokenlen, sizeof (token) - 1, comment, commentlen, commentmax, addr); if (s && *s && *s != ',' && *s != ';') { RFC822Error = ERR_BAD_ADDR_SPEC; return NULL; } return s; } static void add_addrspec (ADDRESS ** top, ADDRESS ** last, const char *phrase, char *comment, size_t * commentlen, size_t commentmax) { ADDRESS *cur = rfc822_new_address (); if (parse_addr_spec (phrase, comment, commentlen, commentmax, cur) == NULL) { rfc822_free_address (&cur); return; } if (*last) (*last)->next = cur; else *top = cur; *last = cur; } ADDRESS *rfc822_parse_adrlist (ADDRESS * top, const char *s) { int ws_pending; const char *begin, *ps; char comment[STRING], phrase[STRING]; size_t phraselen = 0, commentlen = 0; ADDRESS *cur, *last = NULL; RFC822Error = 0; last = top; while (last && last->next) last = last->next; ws_pending = isspace ((unsigned char) *s); SKIPWS (s); begin = s; while (*s) { if (*s == ',') { if (phraselen) { terminate_buffer (phrase, phraselen); add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); } else if (commentlen && last && !last->personal) { terminate_buffer (comment, commentlen); last->personal = str_dup (comment); } commentlen = 0; phraselen = 0; s++; begin = s; SKIPWS (begin); } else if (*s == '(') { if (commentlen && commentlen < sizeof (comment) - 1) comment[commentlen++] = ' '; if ((ps = next_token (s, comment, &commentlen, sizeof (comment) - 1)) == NULL) { rfc822_free_address (&top); return NULL; } s = ps; } else if (*s == ':') { cur = rfc822_new_address (); terminate_buffer (phrase, phraselen); cur->mailbox = str_dup (phrase); cur->group = 1; if (last) last->next = cur; else top = cur; last = cur; phraselen = 0; commentlen = 0; s++; begin = s; SKIPWS (begin); } else if (*s == ';') { if (phraselen) { terminate_buffer (phrase, phraselen); add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); } else if (commentlen && last && !last->personal) { terminate_buffer (comment, commentlen); last->personal = str_dup (comment); } /* add group terminator */ cur = rfc822_new_address (); if (last) { last->next = cur; last = cur; } phraselen = 0; commentlen = 0; s++; begin = s; SKIPWS (begin); } else if (*s == '<') { terminate_buffer (phrase, phraselen); cur = rfc822_new_address (); if (phraselen) { if (cur->personal) mem_free (&cur->personal); /* if we get something like "Michael R. Elkins" remove the quotes */ rfc822_dequote_comment (phrase); cur->personal = str_dup (phrase); } if ((ps = parse_route_addr (s + 1, comment, &commentlen, sizeof (comment) - 1, cur)) == NULL) { rfc822_free_address (&top); rfc822_free_address (&cur); return NULL; } if (last) last->next = cur; else top = cur; last = cur; phraselen = 0; commentlen = 0; s = ps; } else { if (phraselen && phraselen < sizeof (phrase) - 1 && ws_pending) phrase[phraselen++] = ' '; if ((ps = next_token (s, phrase, &phraselen, sizeof (phrase) - 1)) == NULL) { rfc822_free_address (&top); return NULL; } s = ps; } ws_pending = isspace ((unsigned char) *s); SKIPWS (s); } if (phraselen) { terminate_buffer (phrase, phraselen); terminate_buffer (comment, commentlen); add_addrspec (&top, &last, phrase, comment, &commentlen, sizeof (comment) - 1); } else if (commentlen && last && !last->personal) { terminate_buffer (comment, commentlen); last->personal = str_dup (comment); } return top; } void rfc822_qualify (ADDRESS * addr, const char *host) { char *p; for (; addr; addr = addr->next) if (!addr->group && addr->mailbox && strchr (addr->mailbox, '@') == NULL) { p = mem_malloc (str_len (addr->mailbox) + str_len (host) + 2); sprintf (p, "%s@%s", addr->mailbox, host); /* __SPRINTF_CHECKED__ */ mem_free (&addr->mailbox); addr->mailbox = p; } } void rfc822_cat (char *buf, size_t buflen, const char *value, const char *specials) { if (strpbrk (value, specials)) { char tmp[256], *pc = tmp; size_t tmplen = sizeof (tmp) - 3; *pc++ = '"'; for (; *value && tmplen > 1; value++) { if (*value == '\\' || *value == '"') { *pc++ = '\\'; tmplen--; } *pc++ = *value; tmplen--; } *pc++ = '"'; *pc = 0; strfcpy (buf, tmp, buflen); } else strfcpy (buf, value, buflen); } void rfc822_write_address_single (char *buf, size_t buflen, ADDRESS * addr, int display) { size_t len; char *pbuf = buf; char *pc; if (!addr) return; buflen--; /* save room for the terminal nul */ if (addr->personal) { if (strpbrk (addr->personal, RFC822Specials)) { if (!buflen) goto done; *pbuf++ = '"'; buflen--; for (pc = addr->personal; *pc && buflen > 0; pc++) { if (*pc == '"' || *pc == '\\') { if (!buflen) goto done; *pbuf++ = '\\'; buflen--; } if (!buflen) goto done; *pbuf++ = *pc; buflen--; } if (!buflen) goto done; *pbuf++ = '"'; buflen--; } else { if (!buflen) goto done; strfcpy (pbuf, addr->personal, buflen); len = str_len (pbuf); pbuf += len; buflen -= len; } if (!buflen) goto done; *pbuf++ = ' '; buflen--; } if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) { if (!buflen) goto done; *pbuf++ = '<'; buflen--; } if (addr->mailbox) { if (!buflen) goto done; if (ascii_strcmp (addr->mailbox, "@") && !display) { strfcpy (pbuf, addr->mailbox, buflen); len = str_len (pbuf); } else if (ascii_strcmp (addr->mailbox, "@") && display) { strfcpy (pbuf, mutt_addr_for_display (addr), buflen); len = str_len (pbuf); } else { *pbuf = '\0'; len = 0; } pbuf += len; buflen -= len; if (addr->personal || (addr->mailbox && *addr->mailbox == '@')) { if (!buflen) goto done; *pbuf++ = '>'; buflen--; } if (addr->group) { if (!buflen) goto done; *pbuf++ = ':'; buflen--; if (!buflen) goto done; *pbuf++ = ' '; buflen--; } } else { if (!buflen) goto done; *pbuf++ = ';'; buflen--; } done: /* no need to check for length here since we already save space at the beginning of this routine */ *pbuf = 0; } /* note: it is assumed that `buf' is nul terminated! */ void rfc822_write_address (char *buf, size_t buflen, ADDRESS * addr, int display) { char *pbuf = buf; size_t len = str_len (buf); buflen--; /* save room for the terminal nul */ if (len > 0) { if (len > buflen) return; /* safety check for bogus arguments */ pbuf += len; buflen -= len; if (!buflen) goto done; *pbuf++ = ','; buflen--; if (!buflen) goto done; *pbuf++ = ' '; buflen--; } for (; addr && buflen > 0; addr = addr->next) { /* use buflen+1 here because we already saved space for the trailing nul char, and the subroutine can make use of it */ rfc822_write_address_single (pbuf, buflen + 1, addr, display); /* this should be safe since we always have at least 1 char passed into the above call, which means `pbuf' should always be nul terminated */ len = str_len (pbuf); pbuf += len; buflen -= len; /* if there is another address, and its not a group mailbox name or group terminator, add a comma to separate the addresses */ if (addr->next && addr->next->mailbox && !addr->group) { if (!buflen) goto done; *pbuf++ = ','; buflen--; if (!buflen) goto done; *pbuf++ = ' '; buflen--; } } done: *pbuf = 0; } /* this should be rfc822_cpy_adr */ ADDRESS *rfc822_cpy_adr_real (ADDRESS * addr) { ADDRESS *p = rfc822_new_address (); p->personal = str_dup (addr->personal); p->mailbox = str_dup (addr->mailbox); p->group = addr->group; return p; } /* this should be rfc822_cpy_adrlist */ ADDRESS *rfc822_cpy_adr (ADDRESS * addr) { ADDRESS *top = NULL, *last = NULL; for (; addr; addr = addr->next) { if (last) { last->next = rfc822_cpy_adr_real (addr); last = last->next; } else top = last = rfc822_cpy_adr_real (addr); } return top; } /* append list 'b' to list 'a' and return the last element in the new list */ ADDRESS *rfc822_append (ADDRESS ** a, ADDRESS * b) { ADDRESS *tmp = *a; while (tmp && tmp->next) tmp = tmp->next; if (!b) return tmp; if (tmp) tmp->next = rfc822_cpy_adr (b); else tmp = *a = rfc822_cpy_adr (b); while (tmp && tmp->next) tmp = tmp->next; return tmp; }