# HG changeset patch # User Ian Neal # Date 1623162001 14400 # Parent b7ef29f342d1172b00d027e8427d2036a3c52f11 Bug 1528496 - Back out bug 1023285 (rev 1ecabc4781e3) and fix handling of raw UTF-8 in headers properly| and Introduce EncodedHeaderW() using ParseEncodedHeaderW().r=frg a=frg Combined patch for Bug 1559190. diff --git a/mail/base/content/msgHdrViewOverlay.js b/mail/base/content/msgHdrViewOverlay.js --- a/mail/base/content/msgHdrViewOverlay.js +++ b/mail/base/content/msgHdrViewOverlay.js @@ -1229,51 +1229,44 @@ function OutputMessageIds(headerEntry, h * @param emailAddresses comma separated list of the addresses for this * header field */ function OutputEmailAddresses(headerEntry, emailAddresses) { if (!emailAddresses) return; - var addresses = {}; - var fullNames = {}; - var names = {}; - var numAddresses = 0; - - numAddresses = MailServices.headerParser - .parseHeadersWithArray(emailAddresses, addresses, - names, fullNames); - var index = 0; + // The email addresses are still RFC2047 encoded but libmime has already converted from + // "raw UTF-8" to "wide" (UTF-16) characters. + var addresses = MailServices.headerParser.parseEncodedHeaderW(emailAddresses); + if (headerEntry.useToggle) headerEntry.enclosingBox.resetAddressView(); // make sure we start clean - if (numAddresses == 0 && emailAddresses.includes(":")) { + if (addresses.length == 0 && emailAddresses.includes(":")) { // No addresses and a colon, so an empty group like "undisclosed-recipients: ;". // Add group name so at least something displays. let address = { displayName: emailAddresses }; if (headerEntry.useToggle) headerEntry.enclosingBox.addAddressView(address); else updateEmailAddressNode(headerEntry.enclosingBox.emailAddressNode, address); } - while (index < numAddresses) { + for (let addr of addresses) { // If we want to include short/long toggle views and we have a long view, // always add it. If we aren't including a short/long view OR if we are and // we haven't parsed enough addresses to reach the cutoff valve yet then add // it to the default (short) div. let address = {}; - address.emailAddress = addresses.value[index]; - address.fullAddress = fullNames.value[index]; - address.displayName = names.value[index]; + address.emailAddress = addr.email; + address.fullAddress = addr.toString(); + address.displayName = addr.name; if (headerEntry.useToggle) headerEntry.enclosingBox.addAddressView(address); else updateEmailAddressNode(headerEntry.enclosingBox.emailAddressNode, address); - - index++; } if (headerEntry.useToggle) headerEntry.enclosingBox.buildViews(); } function updateEmailAddressNode(emailAddressNode, address) { diff --git a/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js b/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js --- a/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js +++ b/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js @@ -20,16 +20,22 @@ var tests = [ [{from: "John Doe "}, {sender: "John Doe"}], [{from: "\"Doe, John\" "}, {sender: "Doe, John"}], [{from: "John Doe , Sally Ann "}, {sender: "John Doe"}], [{from: "=?UTF-8?Q?David_H=C3=A5s=C3=A4ther?= "}, {sender: "David Håsäther"}], [{from: "=?UTF-8?Q?H=C3=A5s=C3=A4ther=2C_David?= "}, {sender: "Håsäther, David"}], + [{from: "\"Håsäther, David\" "}, + {sender: "Håsäther, David"}], + [{from: "David Håsäther "}, + {sender: "David Håsäther"}], + [{from: "\xC2\xAB\xCE\xA0\xCE\x9F\xCE\x9B\xCE\x99\xCE\xA4\xCE\x97\xCE\xA3\xC2\xBB"}, + {sender: "«ΠΟΛΙΤΗΣ»"}], [{from: "John Doe \xF5 ", clobberHeaders: { "Content-type" : "text/plain; charset=ISO-8859-1" }}, {sender: "John Doe õ"}], [{from: "John Doe \xF5 ", clobberHeaders: { "Content-type" : "text/plain; charset=ISO-8859-2" }}, {sender: "John Doe ő"}], [{from: "=?UTF-8?Q?H=C3=A5s=C3=A4ther=2C_David?= ", clobberHeaders: { "Content-type" : "text/plain; charset=ISO-8859-2" }}, diff --git a/mailnews/compose/src/nsMsgCompFields.cpp b/mailnews/compose/src/nsMsgCompFields.cpp --- a/mailnews/compose/src/nsMsgCompFields.cpp +++ b/mailnews/compose/src/nsMsgCompFields.cpp @@ -595,17 +595,17 @@ nsMsgCompFields::SplitRecipients(const n char16_t*** aResult) { NS_ENSURE_ARG_POINTER(aLength); NS_ENSURE_ARG_POINTER(aResult); *aLength = 0; *aResult = nullptr; - nsCOMArray header(EncodedHeader(NS_ConvertUTF16toUTF8(aRecipients))); + nsCOMArray header(EncodedHeaderW(aRecipients)); nsTArray results; if (aEmailAddressOnly) ExtractEmails(header, results); else ExtractDisplayAddresses(header, results); uint32_t count = results.Length(); char16_t **result = (char16_t **)NS_Alloc(sizeof(char16_t *) * count); @@ -618,18 +618,17 @@ nsMsgCompFields::SplitRecipients(const n } // This method is called during the sending of message from nsMsgCompose::CheckAndPopulateRecipients() nsresult nsMsgCompFields::SplitRecipientsEx(const nsAString &recipients, nsTArray &aResult) { nsTArray names, addresses; - ExtractAllAddresses(EncodedHeader(NS_ConvertUTF16toUTF8(recipients)), names, - addresses); + ExtractAllAddresses(EncodedHeaderW(recipients), names, addresses); uint32_t numAddresses = names.Length(); for (uint32_t i = 0; i < numAddresses; ++i) { nsMsgRecipient msgRecipient; msgRecipient.mEmail = addresses[i]; msgRecipient.mName = names[i]; aResult.AppendElement(msgRecipient); diff --git a/mailnews/compose/src/nsMsgCompose.cpp b/mailnews/compose/src/nsMsgCompose.cpp --- a/mailnews/compose/src/nsMsgCompose.cpp +++ b/mailnews/compose/src/nsMsgCompose.cpp @@ -2614,25 +2614,23 @@ NS_IMETHODIMP QuotingOutputStreamListene if (endPos > startPos) { const uint32_t mailtoLen = strlen(" toEmailAddresses; - ExtractEmails(EncodedHeader(NS_ConvertUTF16toUTF8(to)), - UTF16ArrayAdapter<>(toEmailAddresses)); + ExtractEmails(EncodedHeaderW(to), UTF16ArrayAdapter<>(toEmailAddresses)); nsTArray ccEmailAddresses; - ExtractEmails(EncodedHeader(NS_ConvertUTF16toUTF8(cc)), - UTF16ArrayAdapter<>(ccEmailAddresses)); + ExtractEmails(EncodedHeaderW(cc), UTF16ArrayAdapter<>(ccEmailAddresses)); nsCOMPtr prefs (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); bool replyToSelfCheckAll = false; prefs->GetBoolPref("mailnews.reply_to_self_check_all_ident", &replyToSelfCheckAll); nsCOMPtr accountManager = diff --git a/mailnews/mime/public/MimeHeaderParser.h b/mailnews/mime/public/MimeHeaderParser.h --- a/mailnews/mime/public/MimeHeaderParser.h +++ b/mailnews/mime/public/MimeHeaderParser.h @@ -25,16 +25,20 @@ nsCOMArray DecodedHea /** * This is used to signal that the input header value needs to be decoded * according to RFC 2047. The charset parameter indicates the charset to assume * that non-ASCII data is in; if the value is null (the default), then the * charset is assumed to be UTF-8. */ nsCOMArray EncodedHeader(const nsACString &aHeader, const char *aCharset = nullptr); +/** + * Same deal, but we're starting with an nsAString. + */ +nsCOMArray EncodedHeaderW(const nsAString &aHeader); namespace detail { void DoConversion(const nsTArray &aUTF16, nsTArray &aUTF8); }; /** * This is a class designed for use as temporaries so that methods can pass * an nsTArray into methods that expect nsTArray for out * parameters (this does not work for in-parameters). diff --git a/mailnews/mime/public/nsIMsgHeaderParser.idl b/mailnews/mime/public/nsIMsgHeaderParser.idl --- a/mailnews/mime/public/nsIMsgHeaderParser.idl +++ b/mailnews/mime/public/nsIMsgHeaderParser.idl @@ -92,16 +92,28 @@ interface nsIMsgHeaderParser : nsISuppor void parseEncodedHeader(in ACString aEncodedHeader, in string aHeaderCharset, [optional] in bool aPreserveGroups, [optional] out unsigned long length, [retval, array, size_is(length)] out msgIAddressObject addresses); /** + * Parse an address-based header that has not yet been 2047-decoded and does not + * contain raw octets but instead wide (UTF-16) characters. + * + * @param aEncodedHeader The RFC 2047-encoded header to parse. + * @return An array corresponding to the header description. + */ + void parseEncodedHeaderW(in AString aEncodedHeader, + [optional] out unsigned long length, + [retval, array, size_is(length)] + out msgIAddressObject addresses); + +/** * Parse an address-based header that has been 2047-decoded. * * The result of this method is an array of objects described in the above * comment. Note that the header is a binary string that will be decoded as if * passed into nsIMimeConverter. * * @param aDecodedHeader The non-RFC 2047-encoded header to parse. * @param aPreserveGroups If false (the default), the result is a flat array @@ -173,19 +185,16 @@ interface nsIMsgHeaderParser : nsISuppor * Return an array of structured mailbox objects for the given display name * string. * * The string is expected to be a comma-separated sequence of strings that * would be produced by msgIAddressObject::toString(). For example, the string * "Bond, James " would produce one address object, * while the string "webmaster@nowhere.invalid, child@nowhere.invalid" would * produce two address objects. - * - * Note that the input string is RFC 2231 and RFC 2047 decoded but no UTF-8 - * decoding takes place. */ void makeFromDisplayAddress(in AString aDisplayAddresses, [optional] out unsigned long count, [retval, array, size_is(count)] out msgIAddressObject addresses); [deprecated] void parseHeadersWithArray(in wstring aLine, [array, size_is(count)] out wstring aEmailAddresses, [array, size_is(count)] out wstring aNames, diff --git a/mailnews/mime/src/MimeHeaderParser.cpp b/mailnews/mime/src/MimeHeaderParser.cpp --- a/mailnews/mime/src/MimeHeaderParser.cpp +++ b/mailnews/mime/src/MimeHeaderParser.cpp @@ -101,16 +101,34 @@ nsCOMArray EncodedHea false, &length, &addresses); MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); if (NS_SUCCEEDED(rv) && length > 0 && addresses) { retval.Adopt(addresses, length); } return retval; } +nsCOMArray EncodedHeaderW(const nsAString &aHeader) +{ + nsCOMArray retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr headerParser(services::GetHeaderParser()); + NS_ENSURE_TRUE(headerParser, retval); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseEncodedHeaderW(aHeader, &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + void ExtractAllAddresses(const nsCOMArray &aHeader, nsTArray &names, nsTArray &emails) { uint32_t count = aHeader.Length(); // Prefill arrays before we start names.SetLength(count); emails.SetLength(count); diff --git a/mailnews/mime/src/mimeJSComponents.js b/mailnews/mime/src/mimeJSComponents.js --- a/mailnews/mime/src/mimeJSComponents.js +++ b/mailnews/mime/src/mimeJSComponents.js @@ -264,16 +264,25 @@ MimeAddressParser.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgHeaderParser]), parseEncodedHeader: function (aHeader, aCharset, aPreserveGroups, count) { aHeader = aHeader || ""; let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_ALL_I18N, aCharset); return fixArray(value, aPreserveGroups, count); }, + parseEncodedHeaderW: function (aHeader, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | + MimeParser.HEADER_OPTION_DECODE_2231 | + MimeParser.HEADER_OPTION_DECODE_2047, + undefined); + return fixArray(value, false, count); + }, parseDecodedHeader: function (aHeader, aPreserveGroups, count) { aHeader = aHeader || ""; let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS); return fixArray(value, aPreserveGroups, count); }, makeMimeHeader: function (addresses, length) { addresses = fixXpconnectAddresses(addresses); @@ -385,23 +394,17 @@ MimeAddressParser.prototype = { return this.makeMailboxObject('', aDisplayName); } }, // What follows is the deprecated API that will be removed shortly. parseHeadersWithArray: function (aHeader, aAddrs, aNames, aFullNames) { let addrs = [], names = [], fullNames = []; - // Parse header, but without HEADER_OPTION_ALLOW_RAW. - let value = MimeParser.parseHeaderField(aHeader || "", - MimeParser.HEADER_ADDRESS | - MimeParser.HEADER_OPTION_DECODE_2231 | - MimeParser.HEADER_OPTION_DECODE_2047, - undefined); - let allAddresses = fixArray(value, false); + let allAddresses = this.parseEncodedHeader(aHeader, undefined, false); // Don't index the dummy empty address. if (aHeader.trim() == "") allAddresses = []; for (let address of allAddresses) { addrs.push(address.email); names.push(address.name || null); fullNames.push(address.toString());