/* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: Michael Zucchi */ #include #include #include "camel-internet-address.h" #include "camel-mime-utils.h" #include "camel-net-utils.h" #define d(x) struct _address { gchar *name; gchar *address; }; G_DEFINE_TYPE (CamelInternetAddress, camel_internet_address, CAMEL_TYPE_ADDRESS) static gint internet_address_decode (CamelAddress *a, const gchar *raw) { struct _camel_header_address *ha, *n; gint count = a->addresses->len; /* Should probably use its own decoder or something */ ha = camel_header_address_decode (raw, NULL); if (ha) { n = ha; while (n) { if (n->type == CAMEL_HEADER_ADDRESS_NAME) { camel_internet_address_add ((CamelInternetAddress *) a, n->name, n->v.addr); } else if (n->type == CAMEL_HEADER_ADDRESS_GROUP) { struct _camel_header_address *g = n->v.members; while (g) { if (g->type == CAMEL_HEADER_ADDRESS_NAME) camel_internet_address_add ((CamelInternetAddress *) a, g->name, g->v.addr); /* otherwise, it's an error, infact */ g = g->next; } } n = n->next; } camel_header_address_list_clear (&ha); } return a->addresses->len - count; } static gchar * internet_address_encode (CamelAddress *a) { gint i; GString *out; gchar *ret; gint len = 6; /* "From: ", assume longer of the address headers */ if (a->addresses->len == 0) return NULL; out = g_string_new (""); for (i = 0; i < a->addresses->len; i++) { struct _address *addr = g_ptr_array_index (a->addresses, i); gchar *enc; if (i != 0) g_string_append (out, ", "); enc = camel_internet_address_encode_address (&len, addr->name, addr->address); g_string_append (out, enc); g_free (enc); } ret = out->str; g_string_free (out, FALSE); return ret; } static gint internet_address_unformat (CamelAddress *a, const gchar *raw) { gchar *buffer, *p, *name, *addr; gint c; gint count = a->addresses->len; if (raw == NULL) return 0; d (printf ("unformatting address: %s\n", raw)); /* we copy, so we can modify as we go */ buffer = g_strdup (raw); /* this can be simpler than decode, since there are much fewer rules */ p = buffer; name = NULL; addr = p; do { c = (guchar) * p++; switch (c) { /* removes quotes, they should only be around the total name anyway */ case '"': p[-1] = ' '; while (*p) if (*p == '"') { *p++ = ' '; break; } else { p++; } break; case '<': if (name == NULL) name = addr; addr = p; addr[-1] = 0; while (*p && *p != '>') p++; if (*p == 0) break; p++; /* falls through */ case ',': p[-1] = 0; /* falls through */ case 0: if (name) name = g_strstrip (name); addr = g_strstrip (addr); if (addr[0]) { d (printf ("found address: '%s' <%s>\n", name, addr)); camel_internet_address_add ((CamelInternetAddress *) a, name, addr); } name = NULL; addr = p; break; } } while (c); g_free (buffer); return a->addresses->len - count; } static gchar * internet_address_format (CamelAddress *a) { gint i; GString *out; gchar *ret; if (a->addresses->len == 0) return NULL; out = g_string_new (""); for (i = 0; i < a->addresses->len; i++) { struct _address *addr = g_ptr_array_index (a->addresses, i); gchar *enc; if (i != 0) g_string_append (out, ", "); enc = camel_internet_address_format_address (addr->name, addr->address); g_string_append (out, enc); g_free (enc); } ret = out->str; g_string_free (out, FALSE); return ret; } static void internet_address_remove (CamelAddress *a, gint index) { struct _address *addr; if (index < 0 || index >= a->addresses->len) return; addr = g_ptr_array_index (a->addresses, index); g_free (addr->name); g_free (addr->address); g_free (addr); g_ptr_array_remove_index (a->addresses, index); } static gint internet_address_cat (CamelAddress *dest, CamelAddress *source) { gint i; g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (source), -1); for (i = 0; i < source->addresses->len; i++) { struct _address *addr = g_ptr_array_index (source->addresses, i); camel_internet_address_add ((CamelInternetAddress *) dest, addr->name, addr->address); } return i; } static void camel_internet_address_class_init (CamelInternetAddressClass *class) { CamelAddressClass *address_class; address_class = CAMEL_ADDRESS_CLASS (class); address_class->decode = internet_address_decode; address_class->encode = internet_address_encode; address_class->unformat = internet_address_unformat; address_class->format = internet_address_format; address_class->remove = internet_address_remove; address_class->cat = internet_address_cat; } static void camel_internet_address_init (CamelInternetAddress *internet_address) { } /** * camel_internet_address_new: * * Create a new #CamelInternetAddress object. * * Returns: a new #CamelInternetAddress object **/ CamelInternetAddress * camel_internet_address_new (void) { return g_object_new (CAMEL_TYPE_INTERNET_ADDRESS, NULL); } /** * camel_internet_address_add: * @addr: a #CamelInternetAddress object * @name: name associated with the new address * @address: routing address associated with the new address * * Add a new internet address to @addr. * * Returns: the index of added entry **/ gint camel_internet_address_add (CamelInternetAddress *addr, const gchar *name, const gchar *address) { struct _address *new; gint index; g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1); new = g_malloc (sizeof (*new)); new->name = g_strdup (name); new->address = g_strdup (address); index = ((CamelAddress *) addr)->addresses->len; g_ptr_array_add (((CamelAddress *) addr)->addresses, new); return index; } /** * camel_internet_address_get: * @addr: a #CamelInternetAddress object * @index: address's array index * @namep: holder for the returned name, or %NULL, if not required. * @addressp: holder for the returned address, or %NULL, if not required. * * Get the address at @index. * * Returns: %TRUE if such an address exists, or %FALSE otherwise **/ gboolean camel_internet_address_get (CamelInternetAddress *addr, gint index, const gchar **namep, const gchar **addressp) { struct _address *a; g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), FALSE); if (index < 0 || index >= ((CamelAddress *) addr)->addresses->len) return FALSE; a = g_ptr_array_index (((CamelAddress *) addr)->addresses, index); if (namep) *namep = a->name; if (addressp) *addressp = a->address; return TRUE; } /** * camel_internet_address_find_name: * @addr: a #CamelInternetAddress object * @name: name to lookup * @addressp: holder for address part, or %NULL, if not required. * * Find address by real name. * * Returns: the index of the address matching the name, or %-1 if no * match was found **/ gint camel_internet_address_find_name (CamelInternetAddress *addr, const gchar *name, const gchar **addressp) { struct _address *a; gint i, len; g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1); len = ((CamelAddress *) addr)->addresses->len; for (i = 0; i < len; i++) { a = g_ptr_array_index (((CamelAddress *) addr)->addresses, i); if (a->name && !strcmp (a->name, name)) { if (addressp) *addressp = a->address; return i; } } return -1; } static gboolean domain_contains_only_ascii (const gchar *address, gint *at_pos) { gint pos; gboolean all_ascii = TRUE; g_return_val_if_fail (address != NULL, TRUE); g_return_val_if_fail (at_pos != NULL, TRUE); *at_pos = -1; for (pos = 0; address[pos]; pos++) { all_ascii = all_ascii && address[pos] > 0; if (*at_pos == -1 && address[pos] == '@') { *at_pos = pos; all_ascii = TRUE; } } /* Do not change anything when there is no domain part of the email address */ return all_ascii || *at_pos == -1; } /** * camel_internet_address_ensure_ascii_domains: * @addr: a #CamelInternetAddress * * Ensures that all email address' domains will be ASCII encoded, * which means that any non-ASCII letters will be properly encoded. * This includes IDN (Internationalized Domain Names). * * Since: 3.16 **/ void camel_internet_address_ensure_ascii_domains (CamelInternetAddress *addr) { struct _address *a; gint i, len; g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr)); len = ((CamelAddress *) addr)->addresses->len; for (i = 0; i < len; i++) { gint at_pos = -1; a = g_ptr_array_index (((CamelAddress *) addr)->addresses, i); if (a->address && !domain_contains_only_ascii (a->address, &at_pos)) { gchar *address, *domain; domain = camel_host_idna_to_ascii (a->address + at_pos + 1); if (at_pos >= 0) { gchar *name = g_strndup (a->address, at_pos); address = g_strconcat (name, "@", domain, NULL); } else { address = domain; domain = NULL; } g_free (domain); g_free (a->address); a->address = address; } } } /** * camel_internet_address_find_address: * @addr: a #CamelInternetAddress object * @address: address to lookup * @namep: holder for the matching name, or %NULL, if not required. * * Find an address by address. * * Returns: the index of the address, or %-1 if not found **/ gint camel_internet_address_find_address (CamelInternetAddress *addr, const gchar *address, const gchar **namep) { struct _address *a; gint i, len; g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1); len = ((CamelAddress *) addr)->addresses->len; for (i = 0; i < len; i++) { a = g_ptr_array_index (((CamelAddress *) addr)->addresses, i); if (!strcmp (a->address, address)) { if (namep) *namep = a->name; return i; } } return -1; } static void cia_encode_addrspec (GString *out, const gchar *addr) { const gchar *at, *p; at = strchr (addr, '@'); if (at == NULL) goto append; p = addr; while (p < at) { gchar c = *p++; /* strictly by rfc, we should split local parts on dots. * however i think 2822 changes this, and not many clients grok it, so * just quote the whole local part if need be */ if (!(camel_mime_is_atom (c) || c == '.')) { g_string_append_c (out, '"'); p = addr; while (p < at) { c = *p++; if (c == '"' || c == '\\') g_string_append_c (out, '\\'); g_string_append_c (out, c); } g_string_append_c (out, '"'); g_string_append (out, p); return; } } append: g_string_append (out, addr); } /** * camel_internet_address_encode_address: * @len: the length of the line the address is being appended to * @name: the unencoded real name associated with the address * @addr: the routing address * * Encode a single address ready for internet usage. Header folding * as per rfc822 is also performed, based on the length *@len. If @len * is %NULL, then no folding will occur. * * Note: The value at *@in will be updated based on any linewrapping done * * Returns: the encoded address **/ gchar * camel_internet_address_encode_address (gint *inlen, const gchar *real, const gchar *addr) { gchar *name; gchar *ret = NULL; gint len = 0; GString *out; g_return_val_if_fail (addr, NULL); name = camel_header_encode_phrase ((const guchar *) real); out = g_string_new (""); if (inlen != NULL) len = *inlen; if (name && name[0]) { if (inlen != NULL && (strlen (name) + len) > CAMEL_FOLD_SIZE) { gchar *folded = camel_header_address_fold (name, len); gchar *last; g_string_append (out, folded); g_free (folded); last = strrchr (out->str, '\n'); if (last) len = last - (out->str + out->len); else len = out->len; } else { g_string_append (out, name); len += strlen (name); } } /* NOTE: Strictly speaking, we could and should split the * internal address up if we need to, on atom or specials * boundaries - however, to aid interoperability with mailers * that will probably not handle this case, we will just move * the whole address to its own line. */ if (inlen != NULL && (strlen (addr) + len) > CAMEL_FOLD_SIZE) { g_string_append (out, "\n\t"); len = 1; } len -= out->len; if (name && name[0]) g_string_append_printf (out, " <"); cia_encode_addrspec (out, addr); if (name && name[0]) g_string_append_printf (out, ">"); len += out->len; if (inlen != NULL) *inlen = len; g_free (name); ret = out->str; g_string_free (out, FALSE); return ret; } /** * camel_internet_address_format_address: * @name: a name, quotes may be stripped from it * @addr: an rfc822 routing address * * Function to format a single address, suitable for display. * * Returns: a nicely formatted string containing the rfc822 address **/ gchar * camel_internet_address_format_address (const gchar *name, const gchar *addr) { gchar *ret = NULL; g_return_val_if_fail (addr, NULL); if (name && name[0]) { const gchar *p = name; gchar *o, c; while ((c = *p++)) { if (c == '\"' || c == ',') { o = ret = g_malloc (strlen (name) + 3 + strlen (addr) + 3 + 1); p = name; *o++ = '\"'; while ((c = *p++)) if (c != '\"') *o++ = c; *o++ = '\"'; sprintf (o, " <%s>", addr); d (printf ("encoded '%s' => '%s'\n", name, ret)); return ret; } } ret = g_strdup_printf ("%s <%s>", name, addr); } else ret = g_strdup (addr); return ret; }