diff --git a/lib/libmime/addr.c b/lib/libmime/addr.c new file mode 100644 index 000000000000..614470b00126 --- /dev/null +++ b/lib/libmime/addr.c @@ -0,0 +1,1607 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "NPL"); you may not use this file except in + * compliance with the NPL. You may obtain a copy of the NPL at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the NPL is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL + * for the specific language governing rights and limitations under the + * NPL. + * + * The Initial Developer of this code under the NPL is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All Rights + * Reserved. + */ + +/* addrutil.cpp --- parsing RFC822 addresses. + */ + +#include "xp.h" + +#undef FREEIF +#define FREEIF(obj) do { if (obj) { XP_FREE (obj); obj = 0; }} while (0) + +extern int MK_OUT_OF_MEMORY; + +static int msg_quote_phrase_or_addr (char *address, int32 length, + XP_Bool addr_p); +static int msg_parse_rfc822_addresses (const char *line, + char **names, + char **addresses, + XP_Bool quote_names_p, + XP_Bool quote_addrs_p, + XP_Bool first_only_p); + + +/* Given a string which contains a list of RFC822 addresses, parses it into + their component names and mailboxes. + + The returned value is the number of addresses, or a negative error code; + the names and addresses are returned into the provided pointers as + consecutive null-terminated strings. It is up to the caller to free them. + Note that some of the strings may be zero-length. + + Either of the provided pointers may be NULL if the caller is not interested + in those components. + + quote_names_p and quote_addrs_p control whether the returned strings should + be quoted as RFC822 entities, or returned in a more human-presentable (but + not necessarily parsable) form. + + If first_only_p is true, then only the first element of the list is + returned; we don't bother parsing the rest. + */ +int +msg_parse_rfc822_addresses (const char *line, + char **names, + char **addresses, + XP_Bool quote_names_p, + XP_Bool quote_addrs_p, + XP_Bool first_only_p) +{ + uint32 addr_count = 0; + uint32 line_length; + const char *line_end; + const char *this_start; + char *name_buf = 0, *name_out, *name_start; + char *addr_buf = 0, *addr_out, *addr_start; + XP_ASSERT (line); + if (! line) + return -1; + if (names) + *names = 0; + if (addresses) + *addresses = 0; + line_length = XP_STRLEN (line); + if (line_length == 0) + return 0; + + name_buf = (char *) XP_ALLOC (line_length * 2 + 10); + if (! name_buf) + return MK_OUT_OF_MEMORY; + + addr_buf = (char *) XP_ALLOC (line_length * 2 + 10); + if (! addr_buf) + { + FREEIF (name_buf); + return MK_OUT_OF_MEMORY; + } + + line_end = line; + addr_out = addr_buf; + name_out = name_buf; + name_start = name_buf; + addr_start = addr_buf; + this_start = line; + + /* Skip over extra whitespace or commas before addresses. */ + while (*line_end && + (XP_IS_SPACE (*line_end) || *line_end == ',')) + line_end++; + + while (*line_end) + { + uint32 paren_depth = 0; + const char *oparen = 0; + const char *mailbox_start = 0; + const char *mailbox_end = 0; + + while (*line_end && + !(*line_end == ',' && + paren_depth <= 0 && /* comma is ok inside () */ + (!mailbox_start || mailbox_end))) /* comma is ok inside <> */ + { + if (*line_end == '\\') + { + line_end++; + if (!*line_end) /* otherwise, we walk off end of line, right? */ + break; + } + else if (*line_end == '"') + { + int leave_quotes = 0; + + line_end++; /* remove open " */ + + /* handle '"John.Van Doe"@space.com' case */ + if (paren_depth == 0 && !mailbox_start) + { + char *end_quote = strchr(line_end, '"'); + char *mailbox = end_quote ? strchr(end_quote, '<') : (char *)NULL, + *comma = end_quote ? strchr(end_quote, ',') : (char *)NULL; + if (!mailbox || (comma && comma < mailbox)) + { + + leave_quotes = 1; /* no mailbox for this address */ + *addr_out++ = '"'; + } + } + + while (*line_end) + { + if (*line_end == '\\') + { + if ( paren_depth == 0 + && (*(line_end+1) == '\\' || *(line_end+1) == '"')) + *addr_out++ = *line_end++; + else + line_end++; + } + else if (*line_end == '"') + break; + + if (paren_depth == 0) + *addr_out++ = *line_end; + + line_end++; + } + if (*line_end) line_end++; /* remove close " */ + if (leave_quotes) *addr_out++ = '"'; + continue; + } + + if (*line_end == '(') + { + if (paren_depth == 0) + oparen = line_end; + paren_depth++; + } + else if (*line_end == '<' && paren_depth == 0) + { + mailbox_start = line_end; + } + else if (*line_end == '>' && mailbox_start && paren_depth == 0) + { + mailbox_end = line_end; + } + else if (*line_end == ')' && paren_depth > 0) + { + paren_depth--; + if (paren_depth == 0) + { + const char *s = oparen + 1; + /* Copy the characters inside the parens onto the + "name" buffer. */ + + /* Push out some whitespace before the paren, if + there is non-whitespace there already. */ + if (name_out > name_start && + !XP_IS_SPACE (name_out [-1])) + *name_out++ = ' '; + + /* Skip leading whitespace. */ + while (XP_IS_SPACE (*s) && s < line_end) + s++; + + while (s < line_end) + { + if (*s == '\"') + { + /* Strip out " within () unless backslashed */ + s++; + continue; + } + + if (*s == '\\') /* remove one \ */ + s++; + + if (XP_IS_SPACE (*s) && + name_out > name_start && + XP_IS_SPACE (name_out[-1])) + /* collapse consecutive whitespace */; + else + *name_out++ = *s; + + s++; + } + oparen = 0; + } + } + else + { + /* If we're not inside parens or a , tack this + on to the end of the addr_buf. */ + if (paren_depth == 0 && + (!mailbox_start || mailbox_end)) + { + /* Eat whitespace at the beginning of the line, + and eat consecutive whitespace within the line. */ + if (XP_IS_SPACE (*line_end) && + (addr_out == addr_start || + XP_IS_SPACE (addr_out[-1]))) + /* skip it */; + else + *addr_out++ = *line_end; + } + } + + line_end++; + } + + /* Now we have extracted a single address from the comma-separated + list of addresses. The characters have been divided among the + various buffers: the parts inside parens have been placed in the + name_buf, and everything else has been placed in the addr_buf. + Quoted strings and backslashed characters have been `expanded.' + + If there was a spec in it, we have remembered where it was. + Copy that on to the addr_buf, replacing what was there, and copy the + characters not inside <> onto the name_buf, replacing what is there + now (which was just the parenthesized parts.) (And we need to do the + quote and backslash hacking again, since we're coming from the + original source.) + + Otherwise, we're already done - the addr_buf and name_buf contain + the right data already (de-quoted.) + */ + if (mailbox_end) + { + const char *s; + XP_ASSERT (*mailbox_start == '<'); + XP_ASSERT (*mailbox_end == '>'); + + /* First, copy the name. + */ + name_out = name_start; + s = this_start; + /* Skip leading whitespace. */ + while (XP_IS_SPACE (*s) && s < mailbox_start) + s++; + /* Copy up to (not including) the < */ + while (s < mailbox_start) + { + if (*s == '\"') + { + s++; + continue; + } + if (*s == '\\') + { + if (s + 1 < mailbox_start && (*(s+1) == '\\' || *(s+1) == '\"')) + *name_out++ = *s++; + else + s++; + } + if (XP_IS_SPACE (*s) && + name_out > name_start && + XP_IS_SPACE (name_out[-1])) + /* collapse consecutive whitespace */; + else + *name_out++ = *s; + s++; + } + /* Trim trailing whitespace. */ + while (name_out > name_start && XP_IS_SPACE (name_out[-1])) + name_out--; + /* Push out one space. */ + *name_out++ = ' '; + s = mailbox_end+1; + /* Skip whitespace after > */ + while (XP_IS_SPACE (*s) && s < line_end) + s++; + /* Copy from just after > to the end. */ + while (s < line_end) + { + if (*s == '\"') + { + s++; + continue; + } + if (*s == '\\') + { + if (s + 1 < line_end && (*(s+1) == '\\' || *(s+1) == '\"')) + *name_out++ = *s++; + else + s++; + } + if (XP_IS_SPACE (*s) && + name_out > name_start && + XP_IS_SPACE (name_out[-1])) + /* collapse consecutive whitespace */; + else + *name_out++ = *s; + s++; + } + /* Trim trailing whitespace. */ + while (name_out > name_start && XP_IS_SPACE (name_out[-1])) + name_out--; + /* null-terminate. */ + *name_out++ = 0; + + /* Now, copy the address. + */ + mailbox_start++; + addr_out = addr_start; + s = mailbox_start; + /* Skip leading whitespace. */ + while (XP_IS_SPACE (*s) && s < mailbox_end) + s++; + /* Copy up to (not including) the > */ + while (s < mailbox_end) + { + if (*s == '\"') + { + s++; + continue; + } + if (*s == '\\') + { + if (s + 1 < mailbox_end && (*(s+1) == '\\' || *(s+1) == '\"')) + *addr_out++ = *s++; + else + s++; + } + *addr_out++ = *s++; + } + /* Trim trailing whitespace. */ + while (addr_out > addr_start && XP_IS_SPACE (addr_out[-1])) + addr_out--; + /* null-terminate. */ + *addr_out++ = 0; + } + else /* No component of form. */ + { + /* Trim trailing whitespace. */ + while (addr_out > addr_start && XP_IS_SPACE (addr_out[-1])) + addr_out--; + /* null-terminate. */ + *addr_out++ = 0; + + /* Trim trailing whitespace. */ + while (name_out > name_start && XP_IS_SPACE (name_out[-1])) + name_out--; + /* null-terminate. */ + *name_out++ = 0; + + /* Attempt to deal with the simple error case of a missing comma. + We can only really deal with this in the non-<> case. + If there is no name, and if the address doesn't contain + double-quotes, but the address does contain whitespace, + then assume that the whitespace is an address delimiter. + */ + if (!name_start || !*name_start) + { + char *s; + char *space = 0; + for (s = addr_start; s < addr_out; s++) + if (*s == '\\') + s++; + else if (!space && XP_IS_SPACE (*s)) + space = s; + else if (*s == '"') + { + space = 0; + break; + } + if (space) + { + for (s = space; s < addr_out; s++) + if (*s == '\\') + s++; + else if (XP_IS_SPACE (*s)) + { + *s = 0; + *name_out++ = 0; + addr_count++; + } + } + } + } + + /* Now re-quote the names and addresses if necessary. + */ + if (quote_names_p && names) + { + int L = name_out - name_start - 1; + L = msg_quote_phrase_or_addr (name_start, L, FALSE); + name_out = name_start + L + 1; + } + + if (quote_addrs_p && addresses) + { + int L = addr_out - addr_start - 1; + L = msg_quote_phrase_or_addr (addr_start, L, TRUE); + addr_out = addr_start + L + 1; + } + + addr_count++; + + if (first_only_p) + /* If we only want the first address, we can stop now. */ + break; + + if (*line_end) + line_end++; + + /* Skip over extra whitespace or commas between addresses. */ + while (*line_end && + (XP_IS_SPACE (*line_end) || *line_end == ',')) + line_end++; + + this_start = line_end; + name_start = name_out; + addr_start = addr_out; + } + + /* Make one more pass through and convert all whitespace characters + to SPC. We could do that in the first pass, but this is simpler. */ + { + char *s; + for (s = name_buf; s < name_out; s++) + if (XP_IS_SPACE (*s) && *s != ' ') + *s = ' '; + for (s = addr_buf; s < addr_out; s++) + if (XP_IS_SPACE (*s) && *s != ' ') + *s = ' '; + } + + /* #### Should we bother realloc'ing them smaller? */ + + if (names) + *names = name_buf; + else + XP_FREE (name_buf); + + if (addresses) + *addresses = addr_buf; + else + XP_FREE (addr_buf); + + return addr_count; +} + + +int +MSG_ParseRFC822Addresses (const char *line, + char **names, + char **addresses) +{ + return msg_parse_rfc822_addresses(line, names, addresses, TRUE, TRUE, FALSE); +} + + + +/* Given a single mailbox, this quotes the characters in it which need + to be quoted; it writes into `address' and returns a new length. + `address' is assumed to be long enough; worst case, its size will + be (N*2)+2. + */ +static int +msg_quote_phrase_or_addr (char *address, int32 length, XP_Bool addr_p) +{ + int quotable_count = 0, in_quote = 0; + int unquotable_count = 0; + int32 i, new_length; + char *in, *out; + XP_Bool atsign = FALSE; + XP_Bool user_quote = FALSE; + + /* If the entire address is quoted, fall out now. */ + if (address[0] == '"' && address[length - 1] == '"') + return length; + + for (i = 0, in = address; i < length; i++, in++) + { + if (*in == 0) + return length; /* #### horrible kludge... */ + + else if (addr_p && *in == '@' && !atsign && !in_quote) + { + /* Exactly one unquoted at-sign is allowed in an address. */ + atsign = TRUE; + + /* If address is of the form '"userid"@somewhere.com' don't quote + * the quotes around 'userid'. Also reset the quotable count, since + * any quotables we've seen are already inside quotes. + */ + if (address[0] == '"' && in > address + 2 && *(in - 1) == '"' && *(in - 2) != '\\') + unquotable_count -= 2, quotable_count = 0, user_quote = TRUE; + } + + else if (*in == '\\') + { + if (i + 1 < length && (*(in + 1) == '\\' || *(in + 1) == '"')) + /* If the next character is a backslash or quote, this backslash */ + /* is an escape backslash; ignore it and the next character. */ + i++, in++; + else + /* If the name contains backslashes or quotes, they must be escaped. */ + unquotable_count++; + } + + else if (*in == '"') + /* If the name contains quotes, they must be escaped. */ + unquotable_count++, in_quote = !in_quote; + + else if ( *in >= 127 || *in < 0 + || *in == '[' || *in == ']' || *in == '(' || *in == ')' + || *in == '<' || *in == '>' || *in == '@' || *in == ',' + || *in == ';' || *in == '$') + /* If the name contains control chars or RFC822 specials, it needs to + * be enclosed in quotes. Double-quotes and backslashes will be dealt + * with seperately. + * + * The ":" character is explicitly not in this list, though RFC822 says + * it should be quoted, because that has been seen to break VMS + * systems. (Rather, it has been seen that there are Unix SMTP servers + * which accept RCPT TO: but not RCPT TO:<"host::user"> or + * RCPT TO:, which is the syntax that VMS/DECNET hosts + * use. + * + * For future reference: it is also claimed that some VMS SMTP servers + * allow \ quoting but not "" quoting; and that sendmail uses self- + * contradcitory quoting conventions that violate both RFCs 821 and + * 822, so any address quoting on a sendmail system will lose badly. + */ + quotable_count++; + + else if (addr_p && *in == ' ') + /* Naked spaces are allowed in names, but not addresses. */ + quotable_count++; + + else if ( !addr_p + && (*in == '.' || *in == '!' || *in == '$' || *in == '%')) + /* Naked dots are allowed in addresses, but not in names. + * The other characters (!$%) are technically allowed in names, but + * are surely going to cause someone trouble, so we quote them anyway. + */ + quotable_count++; + } + + if (quotable_count == 0 && unquotable_count == 0) + return length; + + /* Add 2 to the length for the quotes, plus one for each character + * which will need a backslash as well. + */ + new_length = length + unquotable_count + 2; + + /* Now walk through the string backwards (so that we can use the same + * block.) First put on the terminating quote, then push out each + * character, backslashing as necessary. Then a final quote. + * Uh, except, put the second quote just before the last @ if there + * is one. + */ + out = address + new_length - 1; + in = address + length - 1; + if (!atsign || (user_quote && quotable_count > 0)) + *out-- = '"'; + while (out > address) + { + XP_ASSERT(in >= address); + + if (*in == '@' && user_quote && quotable_count > 0) + *out-- = '"'; + + *out-- = *in; + + if (*in == '@' && atsign && !user_quote) + { + *out-- = '"'; + atsign = FALSE; + } + else if (*in == '\\' || *in == '"') + { + if ( user_quote && *in == '"' + && ( in == address + || ( in < address + length - 1 && in > address + && *(in + 1) == '@' && *(in - 1) != '\\'))) + /* Do nothing */; + else if (in > address && *(in - 1) == '\\') + *out-- = *--in; + else + { + XP_ASSERT(out > address); + *out-- = '\\'; + } + } + in--; + } + XP_ASSERT(in == address - 1 || (user_quote && in == address)); + XP_ASSERT(out == address); + *out = '"'; + address[new_length] = 0; + return new_length; +} + +/* Given a name or address that might have been quoted + it will take out the escape and double quotes + The caller is responsible for freeing the resulting + string. + */ +int +MSG_UnquotePhraseOrAddr (char *line, char** lineout) +{ + int outlen = 0; + char *lineptr = NULL; + char *tmpLine = NULL; + char *outptr = NULL; + int result = 0; + + (*lineout) = NULL; + if (line) { + /* if the first character isnt a double quote + then there is nothing to do */ + if (*line != '"') + { + (*lineout) = XP_STRDUP (line); + if (!lineout) + return -1; + else + return 0; + } + + /* dont count the first character that is the double quote */ + lineptr = line; + lineptr++; + /* count up how many characters we are going to output */ + while (*lineptr) { + /* if the character is an '\' then + output the escaped character */ + if (*lineptr == '\\') + lineptr++; + outlen++; + lineptr++; + } + tmpLine = (char *) XP_ALLOC (outlen + 1); + if (!tmpLine) + return -1; + XP_MEMSET(tmpLine, 0, outlen); + /* dont output the first double quote */ + line++; + lineptr = line; + outptr = (tmpLine); + while ((*lineptr) != '\0') { + /* if the character is an '\' then + output the character that was escaped */ + /* if it was part of the quote then don't + output it */ + if (*lineptr == '\\' || *lineptr == '"') { + lineptr++; + } + *outptr = *lineptr; + if (*lineptr != '\0') { + outptr++; + lineptr++; + } + } + *outptr = '\0'; + if (tmpLine) + (*lineout) = XP_STRDUP (tmpLine); + else + result = -1; + XP_FREEIF (tmpLine); + } + return result; +} + + +/* Given a string which contains a list of RFC822 addresses, returns a + comma-seperated list of just the `mailbox' portions. + */ +char * +MSG_ExtractRFC822AddressMailboxes (const char *line) +{ + char *addrs = 0; + char *result, *s, *out; + uint32 i, size = 0; + int status = MSG_ParseRFC822Addresses (line, 0, &addrs); + if (status <= 0) + return 0; + + s = addrs; + for (i = 0; (int) i < status; i++) + { + uint32 j = XP_STRLEN (s); + s += j + 1; + size += j + 2; + } + + result = (char*)XP_ALLOC (size + 1); + if (! result) + { + XP_FREE (addrs); + return 0; + } + out = result; + s = addrs; + for (i = 0; (int) i < status; i++) + { + uint32 j = XP_STRLEN (s); + XP_MEMCPY (out, s, j); + out += j; + if ((int) (i+1) < status) + { + *out++ = ','; + *out++ = ' '; + } + s += j + 1; + } + *out = 0; + + XP_FREE (addrs); + return result; +} + + +/* Given a string which contains a list of RFC822 addresses, returns a + comma-seperated list of just the `user name' portions. If any of + the addresses doesn't have a name, then the mailbox is used instead. + + The names are *unquoted* and therefore cannot be re-parsed in any way. + They are, however, nice and human-readable. + */ +char * +MSG_ExtractRFC822AddressNames (const char *line) +{ + char *names = 0; + char *addrs = 0; + char *result, *s1, *s2, *out; + uint32 i, size = 0; + int status = msg_parse_rfc822_addresses(line, &names, &addrs, FALSE, FALSE, + FALSE); + if (status <= 0) + return 0; + + s1 = names; + s2 = addrs; + for (i = 0; (int) i < status; i++) + { + uint32 j1 = XP_STRLEN (s1); + uint32 j2 = XP_STRLEN (s2); + s1 += j1 + 1; + s2 += j2 + 1; + size += (j1 ? j1 : j2) + 2; + } + + result = (char*)XP_ALLOC (size + 1); + if (! result) + { + XP_FREE (names); + XP_FREE (addrs); + return 0; + } + out = result; + s1 = names; + s2 = addrs; + for (i = 0; (int) i < status; i++) + { + uint32 j1 = XP_STRLEN (s1); + uint32 j2 = XP_STRLEN (s2); + + if (j1) + { + XP_MEMCPY (out, s1, j1); + out += j1; + } + else + { + XP_MEMCPY (out, s2, j2); + out += j2; + } + + if ((int) (i+1) < status) + { + *out++ = ','; + *out++ = ' '; + } + s1 += j1 + 1; + s2 += j2 + 1; + } + *out = 0; + + XP_FREE (names); + XP_FREE (addrs); + return result; +} + +/* Like MSG_ExtractRFC822AddressNames(), but only returns the first name + in the list, if there is more than one. + */ +char * +MSG_ExtractRFC822AddressName (const char *line) +{ + char *name = 0; + char *addr = 0; + int status = msg_parse_rfc822_addresses(line, &name, &addr, FALSE, FALSE, + TRUE); + if (status <= 0) + return 0; + /* This can happen if there is an address like "From: foo bar" which + we parse as two addresses (that's a syntax error.) In that case, + we'll return just the first one (the rest is after the NULL.) + XP_ASSERT(status == 1); + */ + if (name && *name) + { + FREEIF(addr); + return name; + } + else + { + FREEIF(name); + return addr; + } +} + + +static char * +msg_format_rfc822_addresses (const char *names, const char *addrs, + int count, XP_Bool wrap_lines_p) +{ + char *result, *out; + const char *s1, *s2; + uint32 i, size = 0; + uint32 column = 10; + + if (count <= 0) + return 0; + + s1 = names; + s2 = addrs; + for (i = 0; (int) i < count; i++) + { + uint32 j1 = XP_STRLEN (s1); + uint32 j2 = XP_STRLEN (s2); + s1 += j1 + 1; + s2 += j2 + 1; + size += j1 + j2 + 10; + } + + result = (char *) XP_ALLOC (size + 1); + if (! result) return 0; + + out = result; + s1 = names; + s2 = addrs; + + for (i = 0; (int) i < count; i++) + { + char *o; + uint32 j1 = XP_STRLEN (s1); + uint32 j2 = XP_STRLEN (s2); + + if (wrap_lines_p && i > 0 && + (column + j1 + j2 + 3 + + (((int) (i+1) < count) ? 2 : 0) + > 76)) + { + if (out > result && out[-1] == ' ') + out--; + *out++ = CR; + *out++ = LF; + *out++ = '\t'; + column = 8; + } + + o = out; + + if (j1) + { + XP_MEMCPY (out, s1, j1); + out += j1; + *out++ = ' '; + *out++ = '<'; + } + XP_MEMCPY (out, s2, j2); + out += j2; + if (j1) + *out++ = '>'; + + if ((int) (i+1) < count) + { + *out++ = ','; + *out++ = ' '; + } + s1 += j1 + 1; + s2 += j2 + 1; + + column += (out - o); + } + *out = 0; + return result; +} + +/* Given a string which contains a list of RFC822 addresses, returns a new + string with the same data, but inserts missing commas, parses and reformats + it, and wraps long lines with newline-tab. + */ +char * +MSG_ReformatRFC822Addresses (const char *line) +{ + char *names = 0; + char *addrs = 0; + char *result; + int status = MSG_ParseRFC822Addresses (line, &names, &addrs); + if (status <= 0) + return 0; + result = msg_format_rfc822_addresses (names, addrs, status, TRUE); + XP_FREE (names); + XP_FREE (addrs); + return result; +} + +/* Returns a copy of ADDRS which may have had some addresses removed. + Addresses are removed if they are already in either ADDRS or OTHER_ADDRS. + (If OTHER_ADDRS contain addresses which are not in ADDRS, they are not + added. That argument is for passing in addresses that were already + mentioned in other header fields.) + + Addresses are considered to be the same if they contain the same mailbox + part (case-insensitive.) Real names and other comments are not compared. + + removeAliasesToMe allows the address parser to use the preference which + contains regular expressions which also mean 'me' for the purpose of + stripping the user's email address(es) out of addrs + */ +char * +MSG_RemoveDuplicateAddresses (const char *addrs, + const char *other_addrs, + XP_Bool removeAliasesToMe) +{ + /* This is probably way more complicated than it should be... */ + char *s1 = 0, *s2 = 0; + char *output = 0, *out = 0; + char *result = 0; + int count1 = 0, count2 = 0, count3 = 0; + int size1 = 0, size2 = 0, size3 = 0; + char *names1 = 0, *names2 = 0; + char *addrs1 = 0, *addrs2 = 0; + char **a_array1 = 0, **a_array2 = 0, **a_array3 = 0; + char **n_array1 = 0, **n_array3 = 0; + int i, j; + + if (!addrs) return 0; + + count1 = MSG_ParseRFC822Addresses (addrs, &names1, &addrs1); + if (count1 < 0) goto FAIL; + if (count1 == 0) + { + result = XP_STRDUP(""); + goto FAIL; + } + if (other_addrs) + count2 = MSG_ParseRFC822Addresses (other_addrs, &names2, &addrs2); + if (count2 < 0) goto FAIL; + + s1 = names1; + s2 = addrs1; + for (i = 0; i < count1; i++) + { + uint32 j1 = XP_STRLEN (s1); + uint32 j2 = XP_STRLEN (s2); + s1 += j1 + 1; + s2 += j2 + 1; + size1 += j1 + j2 + 10; + } + + s1 = names2; + s2 = addrs2; + for (i = 0; i < count2; i++) + { + uint32 j1 = XP_STRLEN (s1); + uint32 j2 = XP_STRLEN (s2); + s1 += j1 + 1; + s2 += j2 + 1; + size2 += j1 + j2 + 10; + } + + a_array1 = (char **) XP_ALLOC (count1 * sizeof(char *)); + if (!a_array1) goto FAIL; + n_array1 = (char **) XP_ALLOC (count1 * sizeof(char *)); + if (!n_array1) goto FAIL; + + if (count2 > 0) + { + a_array2 = (char **) XP_ALLOC (count2 * sizeof(char *)); + if (!a_array2) goto FAIL; + /* don't need an n_array2 */ + } + + a_array3 = (char **) XP_ALLOC (count1 * sizeof(char *)); + if (!a_array3) goto FAIL; + n_array3 = (char **) XP_ALLOC (count1 * sizeof(char *)); + if (!n_array3) goto FAIL; + + + /* fill in the input arrays */ + s1 = names1; + s2 = addrs1; + for (i = 0; i < count1; i++) + { + n_array1[i] = s1; + a_array1[i] = s2; + s1 += XP_STRLEN (s1) + 1; + s2 += XP_STRLEN (s2) + 1; + } + + s2 = addrs2; + for (i = 0; i < count2; i++) + { + a_array2[i] = s2; + s2 += XP_STRLEN (s2) + 1; + } + + /* Iterate over all addrs in the "1" arrays. + If those addrs are not present in "3" or "2", add them to "3". + */ + for (i = 0; i < count1; i++) /* iterate over all addrs */ + { + XP_Bool found = FALSE; + for (j = 0; j < count2; j++) + if (!strcasecomp (a_array1[i], a_array2[j])) + { + found = TRUE; + break; + } + + if (!found) + for (j = 0; j < count3; j++) + if (!strcasecomp (a_array1[i], a_array3[j])) + { + found = TRUE; + break; + } + + if (!found && removeAliasesToMe) + { +#ifndef MOZILLA_30 + found = MSG_Prefs::IsEmailAddressAnAliasForMe (a_array1[i]); + if (found) + break; +#endif /* MOZILLA_30 */ + } + + if (!found) + { + n_array3[count3] = n_array1[i]; + a_array3[count3] = a_array1[i]; + size3 += (XP_STRLEN(n_array3[count3]) + XP_STRLEN(a_array3[count3]) + + 10); + count3++; + XP_ASSERT (count3 <= count1); + if (count3 > count1) break; + } + } + + output = (char *) XP_ALLOC (size3 + 1); + if (!output) goto FAIL; + + *output = 0; + out = output; + s2 = output; + for (i = 0; i < count3; i++) + { + XP_STRCPY (out, a_array3[i]); + out += XP_STRLEN (out); + *out++ = 0; + } + s1 = out; + for (i = 0; i < count3; i++) + { + XP_STRCPY (out, n_array3[i]); + out += XP_STRLEN (out); + *out++ = 0; + } + result = msg_format_rfc822_addresses (s1, s2, count3, FALSE); + + FAIL: + FREEIF (a_array1); + FREEIF (a_array2); + FREEIF (a_array3); + FREEIF (n_array1); + FREEIF (n_array3); + FREEIF (names1); + FREEIF (names2); + FREEIF (addrs1); + FREEIF (addrs2); + FREEIF (output); + return result; +} + + +/* Given an e-mail address and a person's name, cons them together into a + single string of the form "name
", doing all the necessary quoting. + A new string is returned, which you must free when you're done with it. + */ +char * +MSG_MakeFullAddress (const char* name, const char* addr) +{ + int nl = name ? XP_STRLEN (name) : 0; + int al = addr ? XP_STRLEN (addr) : 0; + char *buf, *s; + int L; + if (al == 0) + return 0; + buf = (char *) XP_ALLOC ((nl * 2) + (al * 2) + 20); + if (!buf) + return 0; + if (nl > 0) + { + XP_STRCPY (buf, name); + L = msg_quote_phrase_or_addr (buf, nl, FALSE); + s = buf + L; + *s++ = ' '; + *s++ = '<'; + } + else + { + s = buf; + } + + XP_STRCPY (s, addr); + L = msg_quote_phrase_or_addr (s, al, TRUE); + s += L; + if (nl > 0) + *s++ = '>'; + *s = 0; + L = (s - buf) + 1; + buf = (char *) XP_REALLOC (buf, L); + return buf; +} + +#if 0 +main (int argc, char **argv) +{ + fprintf (stderr, "%s\n", + MSG_RemoveDuplicateAddresses (argv[1], argv[2], FALSE)); +} +#endif + + +#if 0 +main (int argc, char **argv) +{ + fprintf (stderr, "%s\n", MSG_MakeFullAddress (argv[1], argv[2])); +} +#endif + + +#if 0 +/* Test cases for the above routines. + */ +static void +test1 (const char *line, XP_Bool np, XP_Bool ap, + uint32 expect_count, const char *expect_n, const char *expect_a) +{ + char *names = 0, *addrs = 0; + int result; + if (! np) expect_n = 0; + if (! ap) expect_a = 0; + result = MSG_ParseRFC822Addresses (line, + (np ? &names : 0), + (ap ? &addrs : 0)); + if (result <= 0) + printf (" #### error %d\n", result); + else + { + uint32 i; + char *n = names, *a = addrs; + if (expect_count != result) + printf (" #### wrong number of results (%d instead of %d)\n", + (int) result, (int) expect_count); + for (i = 0; i < result; i++) + { + if (((!!n) != (!!expect_n)) || + (n && XP_STRCMP (n, expect_n))) + { + printf (" ####### name got: %s\n" + " #### name wanted: %s\n", + (n ? n : ""), + (expect_n ? expect_n : "")); + } + if (((!!a) != (!!expect_a)) || + (a && XP_STRCMP (a, expect_a))) + { + printf (" ####### addr got: %s\n" + " #### addr wanted: %s\n", + (a ? a : ""), + (expect_a ? expect_a : "")); + } + if (n) n += XP_STRLEN (n) + 1; + if (a) a += XP_STRLEN (a) + 1; + if (expect_n) expect_n += XP_STRLEN (expect_n) + 1; + if (expect_a) expect_a += XP_STRLEN (expect_a) + 1; + } + } + FREEIF (names); + FREEIF (addrs); +} + +static void +test (const char *line, uint32 expect_n, + const char *expect_names, const char *expect_addrs, + const char *expect_all_names, const char *expect_all_addrs, + const char *canonical) +{ + char *s; + printf ("testing %s\n", line); + test1 (line, TRUE, TRUE, expect_n, expect_names, expect_addrs); + test1 (line, TRUE, FALSE, expect_n, expect_names, expect_addrs); + test1 (line, FALSE, TRUE, expect_n, expect_names, expect_addrs); + test1 (line, FALSE, FALSE, expect_n, expect_names, expect_addrs); + + s = MSG_ExtractRFC822AddressMailboxes (line); + if (!s || XP_STRCMP (s, expect_all_addrs)) + printf (" #### expected addrs: %s\n" + " ######### got addrs: %s\n", + expect_all_addrs, (s ? s : "")); + FREEIF (s); + + s = MSG_ExtractRFC822AddressNames (line); + if (!s || XP_STRCMP (s, expect_all_names)) + printf (" #### expected names: %s\n" + " ######### got names: %s\n", + expect_all_names, (s ? s : "")); + FREEIF (s); + + s = MSG_ReformatRFC822Addresses (line); + if (!s || XP_STRCMP (s, canonical)) + printf (" #### expected canonical: %s\n" + " ######### got canonical: %s\n", + canonical, (s ? s : "")); + FREEIF (s); +} + + +void +main () +{ + test ("spanky", + 1, "", "spanky", + "spanky", "spanky", + "spanky"); + + test ("", + 1, "", "spanky", + "spanky", "spanky", + "spanky"); + + test ("< spanky> ", + 1, "", "spanky", + "spanky", "spanky", + "spanky"); + + test ("Simple Case ", + 1, + "Simple Case", "simple1", + "Simple Case", "simple1", + "Simple Case "); + + test (" Simple Case < simple1 > ", + 1, + "Simple Case", "simple1", + "Simple Case", "simple1", + "Simple Case "); + + test ("simple2 (Simple Case)", + 1, + "Simple Case", "simple2", + "Simple Case", "simple2", + "Simple Case "); + + test ("simple3 (Slightly) (Trickier)", + 1, + "Slightly Trickier", "simple3", + "Slightly Trickier", "simple3", + "Slightly Trickier "); + + test ("(Slightly) simple3 (Trickier)", + 1, + "Slightly Trickier", "simple3", + "Slightly Trickier", "simple3", + "Slightly Trickier "); + + test ("( Slightly ) simple3 ( Trickier ) ", + 1, + "Slightly Trickier", "simple3", + "Slightly Trickier", "simple3", + "Slightly Trickier "); + + test ("(Even) more (Trickier\\, I say)", + 1, + "\"(Even) more (Trickier, I say)\"", "trickier", + "(Even) more (Trickier, I say)", "trickier", + "\"(Even) more (Trickier, I say)\" "); + + test ("\"this, is\" <\"some loser\"@address> (foo)", + 1, + "\"this, is (foo)\"", "\"some loser\"@address", + "this, is (foo)", "\"some loser\"@address", + "\"this, is (foo)\" <\"some loser\"@address>"); + + test ("foo, bar", + 2, + "" "\000" "", + "foo" "\000" "bar", + "foo, bar", "foo, bar", + "foo, bar"); + + test (", ", + 2, + "" "\000" "", + "foo" "\000" "bar", + "foo, bar", "foo, bar", + "foo, bar"); + + test ("< foo > , < bar > ", + 2, + "" "\000" "", + "foo" "\000" "bar", + "foo, bar", "foo, bar", + "foo, bar"); + + test ("< foo > , , , ,,,,, , < bar > ,", + 2, + "" "\000" "", + "foo" "\000" "bar", + "foo, bar", "foo, bar", + "foo, bar"); + + test ("\"this, is\" <\"some loser\"@address> (foo), ", + 2, + "\"this, is (foo)\"" "\000" "", + "\"some loser\"@address" "\000" "bar", + "this, is (foo), bar", + "\"some loser\"@address, bar", + "\"this, is (foo)\" <\"some loser\"@address>, bar"); + + test ("\"this, is\" (foo), bar", + 2, + "\"this, is (foo)\"" "\000" "", + "\"some loser\"@address" "\000" "bar", + "this, is (foo), bar", + "\"some loser\"@address, bar", + "\"this, is (foo)\" <\"some loser\"@address>, bar"); + + test ("(I'm a (total) loser) \"space address\"", + 1, + "\"I'm a (total) loser\"", "\"space address\"", + "I'm a (total) loser", "\"space address\"", + "\"I'm a (total) loser\" <\"space address\">"); + + test ("(I'm a (total) loser) \"space address\"@host", + 1, + "\"I'm a (total) loser\"", "\"space address\"@host", + "I'm a (total) loser", "\"space address\"@host", + "\"I'm a (total) loser\" <\"space address\"@host>"); + + test ("It\\'s \"me\"
, I'm a (total) loser <\"space address\">", + 2, + "It's me" "\000" "\"I'm a (total) loser\"", + "address" "\000" "\"space address\"", + "It's me, I'm a (total) loser", + "address, \"space address\"", + "It's me
, \"I'm a (total) loser\" <\"space address\">"); + + test("It\\'s \"me\"
, I'm a (total) loser <\"space address\"@host>", + 2, + "It's me" "\000" "\"I'm a (total) loser\"", + "address" "\000" "\"space address\"@host", + "It's me, I'm a (total) loser", + "address, \"space address\"@host", + "It's me
, \"I'm a (total) loser\" <\"space address\"@host>"); + + test ("(It\\'s \"me\") address, (I'm a (total) loser) \"space address\"", + 2, + "It's me" "\000" "\"I'm a (total) loser\"", + "address" "\000" "\"space address\"", + "It's me, I'm a (total) loser", + "address, \"space address\"", + "It's me
, \"I'm a (total) loser\" <\"space address\">"); + + test ("(It\\'s \"me\") address, (I'm a (total) loser) \"space \\\"address\"", + 2, + "It's me" "\000" "\"I'm a (total) loser\"", + "address" "\000" "\"space \\\"address\"", + "It's me, I'm a (total) loser", + "address, \"space \\\"address\"", + "It's me
, \"I'm a (total) loser\" <\"space \\\"address\">"); + + test ("(It\\'s \"me\") address, (I'm a (total) loser) \"space @address\"@host", + 2, + "It's me" "\000" "\"I'm a (total) loser\"", + "address" "\000" "\"space @address\"@host", + "It's me, I'm a (total) loser", + "address, \"space @address\"@host", + "It's me
, \"I'm a (total) loser\" <\"space @address\"@host>"); + + test ("Probably Bogus ", + 1, + "Probably Bogus", + "\"some@loser\"@somewhere", + "Probably Bogus", + "\"some@loser\"@somewhere", + "Probably Bogus <\"some@loser\"@somewhere>"); + + test ("Probably Bogus <\"some$loser,666\"@somewhere>", + 1, + "Probably Bogus", + "\"some$loser,666\"@somewhere", + "Probably Bogus", + "\"some$loser,666\"@somewhere", + "Probably Bogus <\"some$loser,666\"@somewhere>"); + + test ("Probably Bogus <\"some$loser,666\"@somewhere>", + 1, + "Probably Bogus", + "\"some$loser,666\"@somewhere", + "Probably Bogus", + "\"some$loser,666\"@somewhere", + "Probably Bogus <\"some$loser,666\"@somewhere>"); + + test ("\"Probably Bogus, Jr.\" <\"some$loser,666\"@somewhere>", + 1, + "\"Probably Bogus, Jr.\"", + "\"some$loser,666\"@somewhere", + "Probably Bogus, Jr.", + "\"some$loser,666\"@somewhere", + "\"Probably Bogus, Jr.\" <\"some$loser,666\"@somewhere>"); + + test ("Probably Bogus\\, Jr. <\"some$loser,666\"@somewhere>", + 1, + "\"Probably Bogus, Jr.\"", + "\"some$loser,666\"@somewhere", + "Probably Bogus, Jr.", + "\"some$loser,666\"@somewhere", + "\"Probably Bogus, Jr.\" <\"some$loser,666\"@somewhere>"); + + test ("This isn't legal ", + 1, + "This isn't legal", "\"some$loser,666\"@somewhere", + "This isn't legal", "\"some$loser,666\"@somewhere", + "This isn't legal <\"some$loser,666\"@somewhere>"); + + test ("This isn't legal!! ", + 1, + "\"This isn't legal!!\"", "\"some$loser,666\"@somewhere", + "This isn't legal!!", "\"some$loser,666\"@somewhere", + "\"This isn't legal!!\" <\"some$loser,666\"@somewhere>"); + + test ("addr1, addr2, \n\taddr3", + 3, + "" "\000" "" "\000" "", + "addr1" "\000" "addr2" "\000" "addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3"); + + test ("addr1 addr2 addr3", + 3, + "" "\000" "" "\000" "", + "addr1" "\000" "addr2" "\000" "addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3"); + + test (" addr1 addr2 addr3 ,,,,,, ", + 3, + "" "\000" "" "\000" "", + "addr1" "\000" "addr2" "\000" "addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3"); + + test ("addr1, addr2 \n\t addr3", + 3, + "" "\000" "" "\000" "", + "addr1" "\000" "addr2" "\000" "addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3", + "addr1, addr2, addr3"); + + test ("addr1, addr2, addr3 addr4, , (and) addr6 (yeah)", + 6, + "" "\000" "" "\000" "" "\000" "" "\000" "" + "\000" "and yeah", + "addr1" "\000" "addr2" "\000" "addr3" "\000" "addr4" "\000" "addr5" + "\000" "addr6", + "addr1, addr2, addr3, addr4, addr5, and yeah", + "addr1, addr2, addr3, addr4, addr5, addr6", + "addr1, addr2, addr3, addr4, addr5, and yeah "); + + test ("addr1 (and some (nested) parens), addr2 , addr2"); + + test ("addr1))) ((()))()()()()()()())), addr2 addr3, addr4 (foo, bar)", + 4, + "\"(())\"" "\000" "" "\000" "" "\000" "\"foo, bar\"", + "\"addr1))) ))\"" "\000" "addr2" "\000" "addr3" "\000" "addr4", + "(()), addr2, addr3, foo, bar", + "\"addr1))) ))\", addr2, addr3, addr4", + "\"(())\" <\"addr1))) ))\">, addr2, addr3, \"foo, bar\" "); + + test ("avec le quoted quotes <\"a \\\" quote\">", + 1, + "avec le quoted quotes", "\"a \\\" quote\"", + "avec le quoted quotes", "\"a \\\" quote\"", + "avec le quoted quotes <\"a \\\" quote\">"); + + test ("avec le quoted quotes <\"a \\\" quote\"@host>", + 1, + "avec le quoted quotes", "\"a \\\" quote\"@host", + "avec le quoted quotes", "\"a \\\" quote\"@host", + "avec le quoted quotes <\"a \\\" quote\"@host>"); + + /* bang paths work, right? */ + test ("nelsonb ", + 1, + "nelsonb", "abit.com!nelsonb@netscape.com", + "nelsonb", "abit.com!nelsonb@netscape.com", + "nelsonb "); + +# if 0 /* these tests don't pass, but should. */ + + /* a perverse example from RFC822: */ + test ("Muhammed.(I am the greatest) Ali @(the)Vegas.WBA", + 1, + "I am the greatest", "Muhammed.Ali@Vegas.WBA", + "I am the greatest", "Muhammed.Ali@Vegas.WBA", + "I am the greatest "); + + /* Oops, this should work but doesn't. */ + test ("nelsonb <@abit.com.tw:nelsonb@netscape.com>", + 1, + "nelsonb", "@abit.com.tw:nelsonb@netscape.com", + "nelsonb", "@abit.com.tw:nelsonb@netscape.com", + "nelsonb <@abit.com.tw:nelsonb@netscape.com>"); + + test ("(Sat43Jan@cyberpromo.com) ((#$&'*+-/=?^_`|~)) ((\\)))", + 1, + "(Sat43Jan@cyberpromo.com)", "\"Ronald.F.Guilmette#$&'*+-/=?^_`|~\"@monkeys.com", + "(Sat43Jan@cyberpromo.com)", "\"Ronald.F.Guilmette#$&'*+-/=?^_`|~\"@monkeys.com", + "(Sat43Jan@cyberpromo.com) <\"Ronald.F.Guilmette#$&'*+-/=?^_`|~\"@monkeys.com>"); + + /* Intentionally fail this one */ + test("my . name @ my . host . com", + 1, + "", "my.name@my.host.com", + "", "my.name@my.host.com", + ""); + + /* but this one should work */ + test("loser < my . name @ my . host . com > ", + 1, + "loser", "my.name@my.host.com", + "loser", "my.name@my.host.com", + "loser "); + + test("my(@).(@)name(<)@(>)my(:).(;)host(((\\)))).(@)com", + 1, + "@", "my.name@my.host.com", + "@", "my.name@my.host.com", + "\"@\" "); + +# endif /* 0 */ + + exit (0); +} +#endif /* 0 */