Bug 998191, part 9: Use structured addresses in mime_generate_headers, r=irving
authorJoshua Cranmer <Pidgeot18@gmail.com>
Wed, 07 Jan 2015 15:58:32 -0600
changeset 21604 8b9c93f10dabee7f50dae31559068e6f2bbfc976
parent 21603 81ecc4e6871facb270e6b40431b1d3c15ba636ef
child 21605 920ef6f2f3c2b32c56a6770aba3f51db426d98e6
push id1305
push usermbanner@mozilla.com
push dateMon, 23 Feb 2015 19:48:12 +0000
treeherdercomm-beta@3ae4f13858fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersirving
bugs998191
Bug 998191, part 9: Use structured addresses in mime_generate_headers, r=irving
mailnews/compose/src/nsMsgCompFields.h
mailnews/compose/src/nsMsgCompUtils.cpp
mailnews/compose/test/unit/test_messageHeaders.js
mailnews/mime/src/mimeJSComponents.js
--- a/mailnews/compose/src/nsMsgCompFields.h
+++ b/mailnews/compose/src/nsMsgCompFields.h
@@ -32,16 +32,21 @@ public:
   nsMsgCompFields();
 
   /* this macro defines QueryInterface, AddRef and Release for this class */
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_FORWARD_MSGISTRUCTUREDHEADERS(mStructuredHeaders->)
   NS_FORWARD_MSGIWRITABLESTRUCTUREDHEADERS(mStructuredHeaders->)
   NS_DECL_NSIMSGCOMPFIELDS
 
+  // Allow the C++ utility methods for people who use a concrete class instead
+  // of the interfaces.
+  using msgIStructuredHeaders::GetAddressingHeader;
+  using msgIWritableStructuredHeaders::SetAddressingHeader;
+
   typedef enum MsgHeaderID
   {
     MSG_FROM_HEADER_ID        = 0,
     MSG_REPLY_TO_HEADER_ID,
     MSG_TO_HEADER_ID,
     MSG_CC_HEADER_ID,
     MSG_BCC_HEADER_ID,
     MSG_FCC_HEADER_ID,
--- a/mailnews/compose/src/nsMsgCompUtils.cpp
+++ b/mailnews/compose/src/nsMsgCompUtils.cpp
@@ -24,18 +24,20 @@
 #include "nsComposeStrings.h"
 #include "nsIMsgCompUtils.h"
 #include "nsIMsgMdnGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsMemory.h"
 #include "nsCRTGlue.h"
 #include <ctype.h>
+#include "mozilla/mailnews/Services.h"
 #include "mozilla/Services.h"
 #include "nsIMIMEInfo.h"
+#include "nsIMsgHeaderParser.h"
 
 NS_IMPL_ISUPPORTS(nsMsgCompUtils, nsIMsgCompUtils)
 
 nsMsgCompUtils::nsMsgCompUtils()
 {
 }
 
 nsMsgCompUtils::~nsMsgCompUtils()
@@ -246,137 +248,99 @@ nsMsgStripLine (char * string)
   return string;
 }
 
 //
 // Generate the message headers for the new RFC822 message
 //
 #define UA_PREF_PREFIX "general.useragent."
 
-#define ENCODE_AND_PUSH(name, structured, body, charset, usemime) \
-  { \
-    PUSH_STRING((name)); \
-    convbuf = nsMsgI18NEncodeMimePartIIStr((body), (structured), (charset), strlen(name), (usemime)); \
-    if (convbuf) { \
-      PUSH_STRING (convbuf); \
-      PR_FREEIF(convbuf); \
-    } \
-    else \
-      PUSH_STRING((body)); \
-    PUSH_NEWLINE (); \
-  }
-
 char *
 mime_generate_headers (nsMsgCompFields *fields,
                        const char *charset,
                        nsMsgDeliverMode deliver_mode, nsIPrompt * aPrompt, nsresult *status)
 {
   nsresult rv;
   *status = NS_OK;
 
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
   if (NS_FAILED(rv)) {
     *status = rv;
     return nullptr;
   }
 
-  bool usemime = nsMsgMIMEGetConformToStandard();
   int32_t size = 0;
   char *buffer = nullptr, *buffer_tail = nullptr;
   bool isDraft =
     deliver_mode == nsIMsgSend::nsMsgSaveAsDraft ||
     deliver_mode == nsIMsgSend::nsMsgSaveAsTemplate ||
     deliver_mode == nsIMsgSend::nsMsgQueueForLater ||
     deliver_mode == nsIMsgSend::nsMsgDeliverBackground;
 
-  const char* pFrom;
-  const char* pTo;
-  const char* pCc;
   const char* pNewsGrp;
   const char* pFollow;
   const char* pReference;
-  char *convbuf;
 
   bool hasDisclosedRecipient = false;
 
-  nsAutoCString headerBuf;    // accumulate header strings to get length
-  headerBuf.Truncate();
-
   NS_ASSERTION (fields, "null fields");
   if (!fields) {
     *status = NS_ERROR_NULL_POINTER;
     return nullptr;
   }
-  pFrom = fields->GetFrom();
-  if (pFrom)
-    headerBuf.Append(pFrom);
-  pTo = fields->GetTo();
-  if (pTo)
-    headerBuf.Append(pTo);
-  pCc = fields->GetCc();
-  if (pCc)
-    headerBuf.Append(pCc);
   pNewsGrp = fields->GetNewsgroups(); if (pNewsGrp)     size += 3 * PL_strlen (pNewsGrp);
   pFollow= fields->GetFollowupTo(); if (pFollow)        size += 3 * PL_strlen (pFollow);
   pReference = fields->GetReferences(); if (pReference)   size += 3 * PL_strlen (pReference);
 
-  /* Multiply by 3 here to make enough room for MimePartII conversion */
-  size += 3 * headerBuf.Length();
-
   /* Add a bunch of slop for the static parts of the headers. */
   /* size += 2048; */
   size += 2560;
 
   buffer = (char *) PR_Malloc (size);
   if (!buffer) {
     *status = NS_ERROR_OUT_OF_MEMORY;
     return nullptr; /* NS_ERROR_OUT_OF_MEMORY */
   }
 
   buffer_tail = buffer;
 
+  nsCOMArray<msgIAddressObject> from;
+  fields->GetAddressingHeader("From", from, true);
+
   // Make a new block of headers to store the usable headers in, and copy all
   // headers from the original compose field.
   nsCOMPtr<msgIWritableStructuredHeaders> finalHeaders =
     do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID);
   rv = finalHeaders->AddAllHeaders(fields);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "This shouldn't fail");
 
-  // Don't emit any of these headers, handled by legacy code for now...
-  finalHeaders->DeleteHeader("from");
-  finalHeaders->DeleteHeader("to");
-  finalHeaders->DeleteHeader("cc");
-  finalHeaders->DeleteHeader("bcc");
-
   bool hasMessageId = false;
   if (NS_SUCCEEDED(fields->HasHeader("Message-ID", &hasMessageId)) &&
       hasMessageId)
   {
     /* MDN request header requires to have MessageID header presented
     * in the message in order to
     * coorelate the MDN reports to the original message. Here will be
     * the right place
     */
 
     if (fields->GetReturnReceipt() &&
       (deliver_mode != nsIMsgSend::nsMsgSaveAsDraft &&
       deliver_mode != nsIMsgSend::nsMsgSaveAsTemplate))
     {
-        int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType;
-        fields->GetReceiptHeaderType(&receipt_header_type);
+      int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType;
+      fields->GetReceiptHeaderType(&receipt_header_type);
 
       // nsIMsgMdnGenerator::eDntType = MDN Disposition-Notification-To: ;
       // nsIMsgMdnGenerator::eRrtType = Return-Receipt-To: ;
       // nsIMsgMdnGenerator::eDntRrtType = both MDN DNT and RRT headers .
       if (receipt_header_type != nsIMsgMdnGenerator::eRrtType)
-        ENCODE_AND_PUSH(
-	  "Disposition-Notification-To: ", true, pFrom, charset, usemime);
+        finalHeaders->SetAddressingHeader("Disposition-Notification-To", from);
       if (receipt_header_type != nsIMsgMdnGenerator::eDntType)
-        ENCODE_AND_PUSH(
-	  "Return-Receipt-To: ", true, pFrom, charset, usemime);
+        finalHeaders->SetAddressingHeader("Return-Receipt-To", from);
     }
   }
 
   PRExplodedTime now;
   PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
   int gmtoffset = (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
 
   /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
@@ -391,21 +355,16 @@ mime_generate_headers (nsMsgCompFields *
   buffer_tail += PL_strlen (buffer_tail);
   PR_snprintf(buffer_tail, buffer + size - buffer_tail,
         "%c%02d%02d" CRLF,
         (gmtoffset >= 0 ? '+' : '-'),
         ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
         ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
   buffer_tail += PL_strlen (buffer_tail);
 
-  if (pFrom && *pFrom)
-  {
-    ENCODE_AND_PUSH("From: ", true, pFrom, charset, usemime);
-  }
-
   // X-Mozilla-Draft-Info
   if (isDraft)
   {
     PUSH_STRING(HEADER_X_MOZILLA_DRAFT_INFO);
     PUSH_STRING(": internal/draft; ");
     if (fields->GetAttachVCard())
       PUSH_STRING("vcard=1");
     else
@@ -565,64 +524,68 @@ mime_generate_headers (nsMsgCompFields *
       if(ptr2 != ptr+1)
         PL_strcpy(ptr+1, ptr2);
     }
 
     finalHeaders->SetRawHeader("Followup-To", nsDependentCString(n2), charset);
     PR_Free (duppedFollowup);
   }
 
-  if (pTo && *pTo) {
-    ENCODE_AND_PUSH("To: ", true, pTo, charset, usemime);
-    hasDisclosedRecipient = true;
-  }
-
-  if (pCc && *pCc) {
-    ENCODE_AND_PUSH("CC: ", true, pCc, charset, usemime);
-    hasDisclosedRecipient = true;
-  }
+  nsCOMArray<msgIAddressObject> recipients;
+  finalHeaders->GetAddressingHeader("To", recipients);
+  hasDisclosedRecipient |= !recipients.IsEmpty();
+  finalHeaders->GetAddressingHeader("Cc", recipients);
+  hasDisclosedRecipient |= !recipients.IsEmpty();
 
   // If we don't have disclosed recipient (only Bcc), address the message to
   // undisclosed-recipients to prevent problem with some servers
 
   // If we are saving the message as a draft, don't bother inserting the undisclosed recipients field. We'll take care of that when we
   // really send the message.
   if (!hasDisclosedRecipient && !isDraft) 
   {
     bool bAddUndisclosedRecipients = true;
     prefs->GetBoolPref("mail.compose.add_undisclosed_recipients", &bAddUndisclosedRecipients);
     if (bAddUndisclosedRecipients)
     {
-      const char* pBcc = fields->GetBcc(); //Do not free me!
-      if (pBcc && *pBcc)
+      bool hasBcc = false;
+      fields->HasHeader("Bcc", &hasBcc);
+      if (hasBcc)
       {
         nsCOMPtr<nsIStringBundleService> stringService =
           mozilla::services::GetStringBundleService();
         if (stringService)
         {
           nsCOMPtr<nsIStringBundle> composeStringBundle;
           rv = stringService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(composeStringBundle));
           if (NS_SUCCEEDED(rv))
           {
             nsString undisclosedRecipients;
             rv = composeStringBundle->GetStringFromName(MOZ_UTF16("undisclosedRecipients"),
                                                         getter_Copies(undisclosedRecipients));
             if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty())
             {
-                PUSH_STRING("To: ");
-              PUSH_STRING(NS_LossyConvertUTF16toASCII(undisclosedRecipients).get());
-                PUSH_STRING(":;");
-                PUSH_NEWLINE ();
-              }
+              nsCOMPtr<nsIMsgHeaderParser> headerParser(
+                mozilla::services::GetHeaderParser());
+              nsCOMPtr<msgIAddressObject> group;
+              headerParser->MakeGroupObject(undisclosedRecipients,
+                nullptr, 0, getter_AddRefs(group));
+              recipients.AppendElement(group);
+              finalHeaders->SetAddressingHeader("To", recipients);
+            }
           }
         }
       }
     }
   }
 
+  // We don't want to emit a Bcc header to the output. If we are saving this to
+  // Drafts/Sent, this is readded later in nsMsgSend.cpp.
+  finalHeaders->DeleteHeader("bcc");
+
   // Skip no or empty priority.
   nsAutoCString priority;
   rv = fields->GetRawHeader("X-Priority", priority);
   if (NS_SUCCEEDED(rv) && !priority.IsEmpty())
   {
     nsMsgPriorityValue priorityValue;
 
     NS_MsgGetPriorityFromString(priority.get(), priorityValue);
--- a/mailnews/compose/test/unit/test_messageHeaders.js
+++ b/mailnews/compose/test/unit/test_messageHeaders.js
@@ -491,17 +491,17 @@ function* testSentMessage() {
       "Reply-To": "Charles <charles@tinderbox.invalid>",
       "X-Mozilla-Status": undefined,
       "X-Mozilla-Keys": undefined,
       "X-Mozilla-Draft-Info": undefined,
       "Fcc": undefined
     });
     yield sendMessage({"bcc": "Somebody <test@tinderbox.invalid"}, identity);
     checkMessageHeaders(daemon.post, {
-      "To": "undisclosed-recipients:;"
+      "To": "undisclosed-recipients: ;"
     });
     yield sendMessage({
       "to": "Somebody <test@tinderbox.invalid>",
       "returnReceipt": true,
       "receiptHeaderType": Ci.nsIMsgMdnGenerator.eDntRrtType,
     }, identity);
     checkMessageHeaders(daemon.post, {
       "Disposition-Notification-To": "test@tinderbox.invalid",
--- a/mailnews/mime/src/mimeJSComponents.js
+++ b/mailnews/mime/src/mimeJSComponents.js
@@ -35,16 +35,35 @@ StringEnumerator.prototype = {
     this._next = undefined;
     if (result.done)
       throw Components.results.NS_ERROR_UNEXPECTED;
     return result.value;
   }
 };
 
 /**
+ * If we get XPConnect-wrapped objects for msgIAddressObjects, we will have
+ * properties defined for 'group' that throws off jsmime. This function converts
+ * the addresses into the form that jsmime expects.
+ */
+function fixXpconnectAddresses(addrs) {
+  return addrs.map((addr) => {
+    // This is ideally !addr.group, but that causes a JS strict warning, if
+    // group is not in addr, since that's enabled in all chrome code now.
+    if (!('group' in addr) || addr.group === undefined || addr.group === null) {
+      return MimeAddressParser.prototype.makeMailboxObject(addr.name,
+        addr.email);
+    } else {
+      return MimeAddressParser.prototype.makeGroupObject(addr.name,
+        fixXpconnectAddresses(addr.group));
+    }
+  });
+}
+
+/**
  * This is a base handler for supporting msgIStructuredHeaders, since we have
  * two implementations that need the readable aspects of the interface.
  */
 function MimeStructuredHeaders() {
 }
 MimeStructuredHeaders.prototype = {
   getHeader: function (aHeaderName) {
     let name = aHeaderName.toLowerCase();
@@ -166,17 +185,17 @@ MimeWritableStructuredHeaders.prototype 
     }
   },
 
   setUnstructuredHeader: function (aHeaderName, aValue) {
     this.setHeader(aHeaderName, aValue);
   },
 
   setAddressingHeader: function (aHeaderName, aAddresses, aCount) {
-    this.setHeader(aHeaderName, aAddresses);
+    this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses));
   },
 
   setRawHeader: function (aHeaderName, aValue, aCharset) {
     aValue = jsmime.headerparser.convert8BitHeader(aValue, aCharset);
     try {
       this.setHeader(aHeaderName,
         jsmime.headerparser.parseStructuredHeader(aHeaderName, aValue));
     } catch (e) {
@@ -247,16 +266,17 @@ MimeAddressParser.prototype = {
   },
   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);
     // Don't output any necessary continuations, so make line length as large as
     // possible first.
     let options = {
       softMargin: 900,
       hardMargin: 900,
       useASCII: false // We don't want RFC 2047 encoding here.
     };
     let handler = new HeaderHandler();
@@ -317,17 +337,17 @@ MimeAddressParser.prototype = {
     object.name = aName;
     object.email = aEmail ? aEmail.trim() : aEmail;
     return object;
   },
 
   makeGroupObject: function (aName, aMembers) {
     let object = Object.create(EmailGroup);
     object.name = aName;
-    object.members = aMembers;
+    object.group = aMembers;
     return object;
   },
 
   makeFromDisplayAddress: function (aDisplay, count) {
     // The basic idea is to split on every comma, so long as there is a
     // preceding @.
     let output = [];
     while (aDisplay.length) {