/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* GMime * Copyright (C) 2000-2007 Jeffrey Stedfast * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "internet-address.h" #include "gmime-table-private.h" #include "gmime-parse-utils.h" #include "gmime-iconv-utils.h" #include "gmime-utils.h" #ifdef ENABLE_WARNINGS #define w(x) x #else #define w(x) #endif /* ENABLE_WARNINGS */ #define d(x) /** * internet_address_new: * * Creates a new #InternetAddress object * * Returns a new #InternetAddress object. **/ InternetAddress * internet_address_new () { InternetAddress *ia; ia = g_new (InternetAddress, 1); ia->type = INTERNET_ADDRESS_NONE; ia->refcount = 1; ia->name = NULL; ia->value.addr = NULL; return ia; } /** * internet_address_destroy: * @ia: internet address * * Destroy the #InternetAddress object pointed to by @ia. **/ static void internet_address_destroy (InternetAddress *ia) { if (ia) { g_free (ia->name); if (ia->type == INTERNET_ADDRESS_GROUP) { internet_address_list_destroy (ia->value.members); } else { g_free (ia->value.addr); } g_free (ia); } } /** * internet_address_ref: * @ia: internet address * * Ref's the internet address. **/ void internet_address_ref (InternetAddress *ia) { ia->refcount++; } /** * internet_address_unref: * @ia: internet address * * Unref's the internet address. **/ void internet_address_unref (InternetAddress *ia) { if (ia->refcount <= 1) { internet_address_destroy (ia); } else { ia->refcount--; } } /** * internet_address_new_name: * @name: person's name * @addr: person's address * * Creates a new #InternetAddress object with name @name and address * @addr. * * Returns a new #InternetAddress object. **/ InternetAddress * internet_address_new_name (const char *name, const char *addr) { InternetAddress *ia; g_return_val_if_fail (addr != NULL, NULL); ia = internet_address_new (); ia->type = INTERNET_ADDRESS_NAME; if (name) { ia->name = g_mime_utils_header_decode_phrase (name); g_mime_utils_unquote_string (ia->name); } ia->value.addr = g_strdup (addr); return ia; } /** * internet_address_new_group: * @name: group name * * Creates a new #InternetAddress object with group name @name. * * Returns a new #InternetAddress object. **/ InternetAddress * internet_address_new_group (const char *name) { InternetAddress *ia; ia = internet_address_new (); ia->type = INTERNET_ADDRESS_GROUP; if (name) { ia->name = g_mime_utils_header_decode_phrase (name); g_mime_utils_unquote_string (ia->name); } return ia; } /** * internet_address_set_name: * @ia: internet address * @name: group or contact's name * * Set the name of the internet address. **/ void internet_address_set_name (InternetAddress *ia, const char *name) { g_return_if_fail (ia != NULL); g_free (ia->name); if (name) { ia->name = g_mime_utils_header_decode_phrase (name); g_mime_utils_unquote_string (ia->name); } else ia->name = NULL; } /** * internet_address_set_addr: * @ia: internet address * @addr: contact's email address * * Set the internet address's address. **/ void internet_address_set_addr (InternetAddress *ia, const char *addr) { g_return_if_fail (ia != NULL); g_return_if_fail (ia->type != INTERNET_ADDRESS_GROUP); ia->type = INTERNET_ADDRESS_NAME; g_free (ia->value.addr); ia->value.addr = g_strdup (addr); } /** * internet_address_set_group: * @ia: internet address * @group: a list of internet addresses * * Set the members of the internet address group. **/ void internet_address_set_group (InternetAddress *ia, InternetAddressList *group) { g_return_if_fail (ia != NULL); g_return_if_fail (ia->type != INTERNET_ADDRESS_NAME); ia->type = INTERNET_ADDRESS_GROUP; internet_address_list_destroy (ia->value.members); ia->value.members = group; } /** * internet_address_add_member: * @ia: internet address * @member: group member's internet address * * Add a contact to the internet address group. **/ void internet_address_add_member (InternetAddress *ia, InternetAddress *member) { g_return_if_fail (ia != NULL); g_return_if_fail (ia->type != INTERNET_ADDRESS_NAME); g_return_if_fail (member != NULL); ia->type = INTERNET_ADDRESS_GROUP; ia->value.members = internet_address_list_append (ia->value.members, member); } /** * internet_address_get_type: * @ia: internet address * * Returns the type of the internet address. **/ InternetAddressType internet_address_get_type (InternetAddress *ia) { g_return_val_if_fail (ia != NULL, INTERNET_ADDRESS_NONE); return ia->type; } /** * internet_address_get_name: * @ia: internet address * * Returns the name of the internet address. **/ const char * internet_address_get_name (InternetAddress *ia) { g_return_val_if_fail (ia != NULL, NULL); return ia->name; } /** * internet_address_get_addr: * @ia: internet address * * Returns the address of the internet address. **/ const char * internet_address_get_addr (InternetAddress *ia) { g_return_val_if_fail (ia != NULL, NULL); g_return_val_if_fail (ia->type != INTERNET_ADDRESS_GROUP, NULL); return ia->value.addr; } /** * internet_address_get_members: * @ia: internet address * * Returns the list of internet addresses. **/ const InternetAddressList * internet_address_get_members (InternetAddress *ia) { g_return_val_if_fail (ia != NULL, NULL); g_return_val_if_fail (ia->type != INTERNET_ADDRESS_NAME, NULL); return ia->value.members; } /** * internet_address_list_prepend: * @list: a list of internet addresses * @ia: internet address to prepend * * Prepends the internet address to the list of internet addresses * pointed to by @list. * * Returns the resultant list. **/ InternetAddressList * internet_address_list_prepend (InternetAddressList *list, InternetAddress *ia) { InternetAddressList *node; g_return_val_if_fail (ia != NULL, NULL); internet_address_ref (ia); node = g_new (InternetAddressList, 1); node->next = list; node->address = ia; return node; } /** * internet_address_list_append: * @list: a list of internet addresses * @ia: internet address to append * * Appends the internet address to the list of internet addresses * pointed to by @list. * * Returns the resultant list. **/ InternetAddressList * internet_address_list_append (InternetAddressList *list, InternetAddress *ia) { InternetAddressList *node, *n; g_return_val_if_fail (ia != NULL, NULL); internet_address_ref (ia); node = g_new (InternetAddressList, 1); node->next = NULL; node->address = ia; if (list == NULL) return node; n = list; while (n->next) n = n->next; n->next = node; return list; } /** * internet_address_list_concat: * @a: first list * @b: second list * * Concatenates a copy of list @b onto the end of list @a. * * Returns the resulting list. **/ InternetAddressList * internet_address_list_concat (InternetAddressList *a, InternetAddressList *b) { InternetAddressList *node, *tail, *n; if (b == NULL) return a; /* find the end of list a */ if (a != NULL) { tail = a; while (tail->next) tail = tail->next; } else { tail = (InternetAddressList *) &a; } /* concat a copy of list b to list a */ node = b; while (node) { internet_address_ref (node->address); n = g_new (InternetAddressList, 1); n->next = NULL; n->address = node->address; tail->next = n; tail = n; node = node->next; } return a; } /** * internet_address_list_next: * @list: list of internet addresses * * Advances to the next address node in the #InternetAddessList. * * Returns the next address node in the #InternetAddessList. **/ InternetAddressList * internet_address_list_next (const InternetAddressList *list) { return list ? list->next : NULL; } /** * internet_address_list_get_address: * @list: list of internet addresses * * Gets the #InternetAddress currently pointed to in @list. * * Returns the #InternetAddress currently pointed to in @list. **/ InternetAddress * internet_address_list_get_address (const InternetAddressList *list) { return list ? list->address : NULL; } /** * internet_address_list_length: * @list: list of internet addresses * * Calculates the length of the list of addresses. * * Returns the number of internet addresses in @list. **/ int internet_address_list_length (const InternetAddressList *list) { const InternetAddressList *node; int len = 0; node = list; while (node) { node = node->next; len++; } return len; } /** * internet_address_list_destroy: * @list: address list * * Destroys the list of internet addresses. **/ void internet_address_list_destroy (InternetAddressList *list) { InternetAddressList *node, *next; node = list; while (node) { next = node->next; internet_address_unref (node->address); g_free (node); node = next; } } static char * encoded_name (const char *raw, gboolean rfc2047_encode) { char *name; g_return_val_if_fail (raw != NULL, NULL); if (rfc2047_encode) { name = g_mime_utils_header_encode_phrase (raw); } else { name = g_mime_utils_quote_string (raw); } return name; } static void internet_address_list_to_string_internal (const InternetAddressList *list, gboolean encode, GString *string) { while (list) { char *addr; addr = internet_address_to_string (list->address, encode); if (addr) { g_string_append (string, addr); g_free (addr); if (list->next) g_string_append (string, ", "); } list = list->next; } } /** * internet_address_to_string: * @ia: Internet Address object * @encode: TRUE if the address should be rfc2047 encoded * * Allocates a string containing the contents of the #InternetAddress * object. * * Returns the #InternetAddress object as an allocated string in * rfc822 format. **/ char * internet_address_to_string (const InternetAddress *ia, gboolean encode) { char *name, *str = NULL; if (ia->type == INTERNET_ADDRESS_NAME) { if (ia->name && *ia->name) { name = encoded_name (ia->name, encode); str = g_strdup_printf ("%s <%s>", name, ia->value.addr); g_free (name); } else { str = g_strdup (ia->value.addr); } } else if (ia->type == INTERNET_ADDRESS_GROUP) { InternetAddressList *members; GString *string; name = encoded_name (ia->name, encode); string = g_string_new (name); g_string_append (string, ": "); g_free (name); members = ia->value.members; internet_address_list_to_string_internal (members, encode, string); g_string_append (string, ";"); str = string->str; g_string_free (string, FALSE); } return str; } /** * internet_address_list_to_string: * @list: list of internet addresses * @encode: %TRUE if the address should be rfc2047 encoded * * Allocates a string buffer containing the rfc822 formatted addresses * in @list. * * Returns a string containing the list of addresses in rfc822 format. **/ char * internet_address_list_to_string (const InternetAddressList *list, gboolean encode) { GString *string; char *str; string = g_string_new (""); internet_address_list_to_string_internal (list, encode, string); str = string->str; g_string_free (string, FALSE); return str; } static InternetAddress * decode_mailbox (const char **in) { InternetAddress *mailbox = NULL; const char *inptr; gboolean bracket = FALSE; GString *name = NULL; GString *addr; char *pre; addr = g_string_new (""); decode_lwsp (in); inptr = *in; pre = decode_word (&inptr); decode_lwsp (&inptr); if (*inptr && !strchr (",.@", *inptr)) { gboolean retried = FALSE; /* this mailbox has a name part, so get the name */ name = g_string_new (""); while (pre) { retried = FALSE; g_string_append (name, pre); g_free (pre); retry: pre = decode_word (&inptr); if (pre) g_string_append_c (name, ' '); } decode_lwsp (&inptr); if (*inptr == '<') { inptr++; bracket = TRUE; pre = decode_word (&inptr); } else if (!retried && *inptr) { w(g_warning ("Unexpected char '%c' in address: %s: attempting recovery.", *inptr, *in)); /* chew up this bad char and then attempt 1 more pass at parsing */ g_string_append_c (name, *inptr++); retried = TRUE; goto retry; } else { g_string_free (name, TRUE); g_string_free (addr, TRUE); *in = inptr; return NULL; } } if (pre) { g_string_append (addr, pre); } else { w(g_warning ("No local part for email address: %s", *in)); if (name) g_string_free (name, TRUE); g_string_free (addr, TRUE); /* comma will be eaten by our caller */ if (*inptr != ',') *in = inptr + 1; else *in = inptr; return NULL; } /* get the rest of the local-part */ decode_lwsp (&inptr); while (*inptr == '.' && pre) { inptr++; g_free (pre); if ((pre = decode_word (&inptr))) { g_string_append_c (addr, '.'); g_string_append (addr, pre); } decode_lwsp (&inptr); } g_free (pre); /* we should be at the '@' now... */ if (*inptr == '@') { char *domain; inptr++; if ((domain = decode_domain (&inptr))) { g_string_append_c (addr, '@'); g_string_append (addr, domain); g_free (domain); } } else { w(g_warning ("No domain in email address: %s", *in)); } if (bracket) { decode_lwsp (&inptr); if (*inptr == '>') inptr++; else w(g_warning ("Missing closing '>' bracket for email address: %s", *in)); } if (!name || !name->len) { /* look for a trailing comment to use as the name? */ char *comment; if (name) { g_string_free (name, TRUE); name = NULL; } comment = (char *) inptr; decode_lwsp (&inptr); if (inptr > comment) { if ((comment = memchr (comment, '(', inptr - comment))) { const char *cend; /* find the end of the comment */ cend = inptr - 1; while (cend > comment && is_lwsp (*cend)) cend--; if (*cend == ')') cend--; comment = g_strndup (comment + 1, cend - comment); g_strstrip (comment); name = g_string_new (comment); g_free (comment); } } } *in = inptr; if (addr->len) { if (name && !g_utf8_validate (name->str, -1, NULL)) { /* A (broken) mailer has sent us an unencoded * 8bit value (and it doesn't seem to be valid * UTF-8 either). Attempt to save it by * assuming it's in the user's locale and * converting to UTF-8 */ char *buf; if ((buf = g_mime_iconv_locale_to_utf8 (name->str))) { g_string_truncate (name, 0); g_string_append (name, buf); g_free (buf); } else { d(g_warning ("Failed to convert \"%s\" to UTF-8: %s", name->str, g_strerror (errno))); } } mailbox = internet_address_new_name (name ? name->str : NULL, addr->str); } g_string_free (addr, TRUE); if (name) g_string_free (name, TRUE); return mailbox; } static InternetAddress * decode_address (const char **in) { InternetAddress *addr = NULL; const char *inptr, *start; GString *name; char *pre; decode_lwsp (in); start = inptr = *in; /* pre-scan */ name = g_string_new (""); pre = decode_word (&inptr); while (pre) { g_string_append (name, pre); g_free (pre); pre = decode_word (&inptr); if (pre) g_string_append_c (name, ' '); } decode_lwsp (&inptr); if (*inptr == ':') { /* this is a group */ InternetAddressList *group = NULL, *tail; tail = (InternetAddressList *) &group; inptr++; addr = internet_address_new_group (name->str); decode_lwsp (&inptr); while (*inptr && *inptr != ';') { InternetAddress *member; if ((member = decode_mailbox (&inptr))) { tail->next = g_new (InternetAddressList, 1); tail = tail->next; tail->next = NULL; tail->address = member; } decode_lwsp (&inptr); while (*inptr == ',') { inptr++; decode_lwsp (&inptr); if ((member = decode_mailbox (&inptr))) { tail->next = g_new (InternetAddressList, 1); tail = tail->next; tail->next = NULL; tail->address = member; } decode_lwsp (&inptr); } } if (*inptr == ';') inptr++; else w(g_warning ("Invalid group spec, missing closing ';': %.*s", inptr - start, start)); internet_address_set_group (addr, group); *in = inptr; } else { /* this is a mailbox */ addr = decode_mailbox (in); } g_string_free (name, TRUE); return addr; } /** * internet_address_parse_string: * @string: a string containing internet addresses * * Construct a list of internet addresses from the given string. * * Returns a linked list of internet addresses. *Must* be free'd by * the caller. **/ InternetAddressList * internet_address_parse_string (const char *string) { InternetAddressList *node, *tail, *addrlist = NULL; const char *inptr; inptr = string; tail = (InternetAddressList *) &addrlist; while (inptr && *inptr) { InternetAddress *addr; const char *start; start = inptr; addr = decode_address (&inptr); if (addr) { node = g_new (InternetAddressList, 1); node->next = NULL; node->address = addr; tail->next = node; tail = node; } else { w(g_warning ("Invalid or incomplete address: %.*s", inptr - start, start)); } decode_lwsp (&inptr); if (*inptr == ',') inptr++; else if (*inptr) { w(g_warning ("Parse error at '%s': expected ','", inptr)); /* try skipping to the next address */ if ((inptr = strchr (inptr, ','))) inptr++; } } return addrlist; }