summaryrefslogtreecommitdiffstats
path: root/mailnews/import/outlook/src/nsOutlookCompose.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/import/outlook/src/nsOutlookCompose.cpp')
-rw-r--r--mailnews/import/outlook/src/nsOutlookCompose.cpp814
1 files changed, 814 insertions, 0 deletions
diff --git a/mailnews/import/outlook/src/nsOutlookCompose.cpp b/mailnews/import/outlook/src/nsOutlookCompose.cpp
new file mode 100644
index 000000000..96c0824aa
--- /dev/null
+++ b/mailnews/import/outlook/src/nsOutlookCompose.cpp
@@ -0,0 +1,814 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "prthread.h"
+#include "nsStringGlue.h"
+#include "nsMsgUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIIOService.h"
+#include "nsIURI.h"
+#include "nsMsgI18N.h"
+#include "nsINetUtil.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsMsgAttachmentData.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgCompCID.h"
+#include "nsIArray.h"
+#include "nsIMsgCompose.h"
+#include "nsIMsgCompFields.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgSend.h"
+#include "nsIMutableArray.h"
+#include "nsImportEmbeddedImageData.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsOutlookCompose.h"
+
+#include "OutlookDebugLog.h"
+
+#include "nsMimeTypes.h"
+#include "nsMsgUtils.h"
+
+#include "nsAutoPtr.h"
+
+#include "nsMsgMessageFlags.h"
+#include "nsMsgLocalFolderHdrs.h"
+
+
+static NS_DEFINE_CID(kMsgSendCID, NS_MSGSEND_CID);
+static NS_DEFINE_CID(kMsgCompFieldsCID, NS_MSGCOMPFIELDS_CID);
+
+// We need to do some calculations to set these numbers to something reasonable!
+// Unless of course, CreateAndSendMessage will NEVER EVER leave us in the lurch
+#define kHungCount 100000
+#define kHungAbortCount 1000
+
+#ifdef IMPORT_DEBUG
+static const char *p_test_headers =
+"Received: from netppl.invalid (IDENT:monitor@get.freebsd.because.microsoftsucks.invalid [209.3.31.115])\n\
+ by mail4.sirius.invalid (8.9.1/8.9.1) with SMTP id PAA27232;\n\
+ Mon, 17 May 1999 15:27:43 -0700 (PDT)\n\
+Message-ID: <ikGD3jRTsKklU.Ggm2HmE2A1Jsqd0p@netppl.invalid>\n\
+From: \"adsales@qualityservice.invalid\" <adsales@qualityservice.invalid>\n\
+Subject: Re: Your College Diploma (36822)\n\
+Date: Mon, 17 May 1999 15:09:29 -0400 (EDT)\n\
+MIME-Version: 1.0\n\
+Content-Type: TEXT/PLAIN; charset=\"US-ASCII\"\n\
+Content-Transfer-Encoding: 7bit\n\
+X-UIDL: 19990517.152941\n\
+Status: RO";
+
+static const char *p_test_body =
+"Hello world?\n\
+";
+#else
+#define p_test_headers nullptr
+#define p_test_body nullptr
+#endif
+
+#define kWhitespace "\b\t\r\n "
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+// A replacement for SimpleBufferTonyRCopiedTwice round-robin buffer and ReadFileState classes
+class CCompositionFile {
+public:
+ // fifoBuffer is used for memory allocation optimization
+ // convertCRs controls if we want to convert standalone CRs to CRLFs
+ CCompositionFile(nsIFile* aFile, void* fifoBuffer, uint32_t fifoBufferSize, bool convertCRs=false);
+
+ operator bool() const { return m_fileSize && m_pInputStream; }
+
+ // Reads up to and including the term sequence, or entire file if term isn't found
+ // termSize may be used to include NULLs in the terminator sequences.
+ // termSize value of -1 means "zero-terminated string" -> size is calculated with strlen
+ nsresult ToString(nsCString& dest, const char* term=0, int termSize=-1);
+ nsresult ToStream(nsIOutputStream *dest, const char* term=0, int termSize=-1);
+ char LastChar() { return m_lastChar; }
+private:
+ nsCOMPtr<nsIFile> m_pFile;
+ nsCOMPtr<nsIInputStream> m_pInputStream;
+ int64_t m_fileSize;
+ int64_t m_fileReadPos;
+ char* m_fifoBuffer;
+ uint32_t m_fifoBufferSize;
+ char* m_fifoBufferReadPos; // next character to read
+ char* m_fifoBufferWrittenPos; // if we have read less than buffer size then this will show it
+ bool m_convertCRs;
+ char m_lastChar;
+
+ nsresult EnsureHasDataInBuffer();
+ template <class _OutFn> nsresult ToDest(_OutFn dest, const char* term, int termSize);
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+// First off, a listener
+class OutlookSendListener : public nsIMsgSendListener
+{
+public:
+ OutlookSendListener() {
+ m_done = false;
+ m_location = nullptr;
+ }
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* void OnStartSending (in string aMsgID, in uint32_t aMsgSize); */
+ NS_IMETHOD OnStartSending(const char *aMsgID, uint32_t aMsgSize) {return NS_OK;}
+
+ /* void OnProgress (in string aMsgID, in uint32_t aProgress, in uint32_t aProgressMax); */
+ NS_IMETHOD OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) {return NS_OK;}
+
+ /* void OnStatus (in string aMsgID, in wstring aMsg); */
+ NS_IMETHOD OnStatus(const char *aMsgID, const char16_t *aMsg) {return NS_OK;}
+
+ /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg, in nsIFile returnFile); */
+ NS_IMETHOD OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg,
+ nsIFile *returnFile) {
+ m_done = true;
+ NS_IF_ADDREF(m_location = returnFile);
+ return NS_OK;
+ }
+
+ /* void OnSendNotPerformed */
+ NS_IMETHOD OnSendNotPerformed(const char *aMsgID, nsresult aStatus) {return NS_OK;}
+
+ /* void OnGetDraftFolderURI (); */
+ NS_IMETHOD OnGetDraftFolderURI(const char *aFolderURI) {return NS_OK;}
+
+ static nsresult CreateSendListener(nsIMsgSendListener **ppListener);
+ void Reset() { m_done = false; NS_IF_RELEASE(m_location);}
+
+public:
+ virtual ~OutlookSendListener() { NS_IF_RELEASE(m_location); }
+
+ bool m_done;
+ nsIFile * m_location;
+};
+
+NS_IMPL_ISUPPORTS(OutlookSendListener, nsIMsgSendListener)
+
+nsresult OutlookSendListener::CreateSendListener(nsIMsgSendListener **ppListener)
+{
+ NS_PRECONDITION(ppListener != nullptr, "null ptr");
+ NS_ENSURE_ARG_POINTER(ppListener);
+
+ *ppListener = new OutlookSendListener();
+ if (! *ppListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*ppListener);
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+
+#define hackBeginA "begin"
+#define hackBeginW u"begin"
+#define hackEndA "\015\012end"
+#define hackEndW u"\015\012end"
+#define hackCRLFA "crlf"
+#define hackCRLFW u"crlf"
+#define hackAmpersandA "amp"
+#define hackAmpersandW u"amp"
+
+nsOutlookCompose::nsOutlookCompose()
+{
+ m_pListener = nullptr;
+ m_pMsgFields = nullptr;
+
+ m_optimizationBuffer = new char[FILE_IO_BUFFER_SIZE];
+}
+
+nsOutlookCompose::~nsOutlookCompose()
+{
+ NS_IF_RELEASE(m_pListener);
+ NS_IF_RELEASE(m_pMsgFields);
+ if (m_pIdentity) {
+ nsresult rv = m_pIdentity->ClearAllValues();
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to clear values");
+ if (NS_FAILED(rv))
+ return;
+ }
+ delete[] m_optimizationBuffer;
+}
+
+nsIMsgIdentity * nsOutlookCompose::m_pIdentity = nullptr;
+
+nsresult nsOutlookCompose::CreateIdentity(void)
+{
+ if (m_pIdentity)
+ return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accMgr->CreateIdentity(&m_pIdentity);
+ nsString name;
+ name.AssignLiteral("Import Identity");
+ if (m_pIdentity) {
+ m_pIdentity->SetFullName(name);
+ m_pIdentity->SetEmail(NS_LITERAL_CSTRING("import@service.invalid"));
+ }
+ return rv;
+}
+
+void nsOutlookCompose::ReleaseIdentity()
+{
+ NS_IF_RELEASE(m_pIdentity);
+}
+
+nsresult nsOutlookCompose::CreateComponents(void)
+{
+ nsresult rv = NS_OK;
+
+ NS_IF_RELEASE(m_pMsgFields);
+ if (!m_pListener && NS_SUCCEEDED(rv))
+ rv = OutlookSendListener::CreateSendListener(&m_pListener);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = CallCreateInstance(kMsgCompFieldsCID, &m_pMsgFields);
+ if (NS_SUCCEEDED(rv) && m_pMsgFields) {
+ // IMPORT_LOG0("nsOutlookCompose - CreateComponents succeeded\n");
+ m_pMsgFields->SetForcePlainText(false);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsOutlookCompose::ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage &msg, nsIFile **pMsg)
+{
+ nsresult rv = CreateComponents();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CreateIdentity();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // IMPORT_LOG0("Outlook Compose created necessary components\n");
+
+ CMapiMessageHeaders* headers = msg.GetHeaders();
+
+ nsString unival;
+ headers->UnfoldValue(CMapiMessageHeaders::hdrFrom, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetFrom(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrTo, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetTo(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrSubject, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetSubject(unival);
+ m_pMsgFields->SetCharacterSet(msg.GetBodyCharset());
+ headers->UnfoldValue(CMapiMessageHeaders::hdrCc, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetCc(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrReplyTo, unival, msg.GetBodyCharset());
+ m_pMsgFields->SetReplyTo(unival);
+ m_pMsgFields->SetMessageId(headers->Value(CMapiMessageHeaders::hdrMessageID));
+
+ // We only use those headers that may need to be processed by Thunderbird
+ // to create a good rfc822 document, or need to be encoded (like To and Cc).
+ // These will replace the originals on import. All the other headers
+ // will be copied to the destination unaltered in CopyComposedMessage().
+
+ nsCOMPtr<nsIArray> pAttach;
+ msg.GetAttachments(getter_AddRefs(pAttach));
+
+ nsString bodyW;
+ // Bug 593907
+ if (GenerateHackSequence(msg.GetBody(), msg.GetBodyLen()))
+ HackBody(msg.GetBody(), msg.GetBodyLen(), bodyW);
+ else
+ bodyW = msg.GetBody();
+ // End Bug 593907
+
+ nsCOMPtr<nsIMutableArray> embeddedObjects;
+
+ if (msg.BodyIsHtml()) {
+ for (unsigned int i = 0; i <msg.EmbeddedAttachmentsCount(); i++) {
+ nsIURI *uri;
+ const char* cid;
+ const char* name;
+ if (msg.GetEmbeddedAttachmentInfo(i, &uri, &cid, &name)) {
+ if (!embeddedObjects) {
+ embeddedObjects = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr<nsIMsgEmbeddedImageData> imageData =
+ new nsImportEmbeddedImageData(uri, nsDependentCString(cid),
+ nsDependentCString(name));
+ embeddedObjects->AppendElement(imageData, false);
+ }
+ }
+ }
+
+ nsCString bodyA;
+ nsMsgI18NConvertFromUnicode(msg.GetBodyCharset(), bodyW, bodyA);
+
+ nsCOMPtr<nsIImportService> impService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = impService->CreateRFC822Message(
+ m_pIdentity, // dummy identity
+ m_pMsgFields, // message fields
+ msg.BodyIsHtml() ? "text/html" : "text/plain",
+ bodyA, // body pointer
+ mode == nsIMsgSend::nsMsgSaveAsDraft,
+ pAttach, // local attachments
+ embeddedObjects,
+ m_pListener); // listener
+
+ OutlookSendListener *pListen = (OutlookSendListener *)m_pListener;
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** Error, CreateAndSendMessage FAILED: 0x%lx\n", rv);
+ }
+ else {
+ // wait for the listener to get done!
+ int32_t abortCnt = 0;
+ int32_t cnt = 0;
+ int32_t sleepCnt = 1;
+ while (!pListen->m_done && (abortCnt < kHungAbortCount)) {
+ PR_Sleep(sleepCnt);
+ cnt++;
+ if (cnt > kHungCount) {
+ abortCnt++;
+ sleepCnt *= 2;
+ cnt = 0;
+ }
+ }
+
+ if (abortCnt >= kHungAbortCount) {
+ IMPORT_LOG0("**** Create and send message hung\n");
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ if (pListen->m_location) {
+ pListen->m_location->Clone(pMsg);
+ rv = NS_OK;
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ IMPORT_LOG0("*** Error, Outlook compose unsuccessful\n");
+ }
+
+ pListen->Reset();
+ return rv;
+}
+
+nsresult nsOutlookCompose::CopyComposedMessage(nsIFile *pSrc,
+ nsIOutputStream *pDst,
+ CMapiMessage& origMsg)
+{
+ // I'm unsure if we really need the convertCRs feature here.
+ // The headers in the file are generated by TB, the body was generated by rtf reader that always used CRLF,
+ // and the attachments were processed by TB either... However, I let it stay as it was in the original code.
+ CCompositionFile f(pSrc, m_optimizationBuffer, FILE_IO_BUFFER_SIZE, true);
+ if (!f) {
+ IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // The "From ..." separates the messages. Without it, TB cannot see the messages in the mailbox file.
+ // Thus, the lines that look like "From ..." in the message must be escaped (see EscapeFromSpaceLine())
+ int fromLineLen;
+ const char* fromLine = origMsg.GetFromLine(fromLineLen);
+ uint32_t written;
+ nsresult rv = pDst->Write(fromLine, fromLineLen, &written);
+
+ // Bug 219269
+ // Write out the x-mozilla-status headers.
+ char statusLine[50];
+ uint32_t msgFlags = 0;
+ if (origMsg.IsRead())
+ msgFlags |= nsMsgMessageFlags::Read;
+ if (!origMsg.FullMessageDownloaded())
+ msgFlags |= nsMsgMessageFlags::Partial;
+ if (origMsg.IsForvarded())
+ msgFlags |= nsMsgMessageFlags::Forwarded;
+ if (origMsg.IsReplied())
+ msgFlags |= nsMsgMessageFlags::Replied;
+ if (origMsg.HasAttach())
+ msgFlags |= nsMsgMessageFlags::Attachment;
+ _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
+ rv = pDst->Write(statusLine, strlen(statusLine), &written);
+ _snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
+ rv = pDst->Write(statusLine, strlen(statusLine), &written);
+ // End Bug 219269
+
+ // well, isn't this a hoot!
+ // Read the headers from the new message, get the ones we like
+ // and write out only the headers we want from the new message,
+ // along with all of the other headers from the "old" message!
+
+ nsCString newHeadersStr;
+ rv = f.ToString(newHeadersStr, MSG_LINEBREAK MSG_LINEBREAK); // Read all the headers
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateHeaders(*origMsg.GetHeaders(), newHeadersStr.get());
+ rv = origMsg.GetHeaders()->ToStream(pDst);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bug 593907
+ if (!m_hackedPostfix.IsEmpty()) {
+ nsCString hackedPartEnd;
+ LossyCopyUTF16toASCII(m_hackedPostfix, hackedPartEnd);
+ hackedPartEnd.Insert(hackEndA, 0);
+ nsCString body;
+ rv = f.ToString(body, hackedPartEnd.get(), hackedPartEnd.Length());
+ UnhackBody(body);
+ EscapeFromSpaceLine(pDst, const_cast<char*>(body.get()), body.get()+body.Length());
+ }
+ // End Bug 593907
+
+ // I use the terminating sequence here to avoid a possible situation when a "From " line
+ // gets split over two sequential reads and thus will not be escaped.
+ // This is done by reading up to CRLF (one line every time), though it may be slower
+
+ // Here I revert the changes that were made when the multipart/related message
+ // was composed in nsMsgSend::ProcessMultipartRelated() - the Content-Ids of
+ // attachments were replaced with new ones.
+ nsCString line;
+ while (NS_SUCCEEDED(f.ToString(line, MSG_LINEBREAK))) {
+ EscapeFromSpaceLine(pDst, const_cast<char*>(line.get()), line.get()+line.Length());
+ }
+
+ if (f.LastChar() != nsCRT::LF) {
+ rv = pDst->Write(MSG_LINEBREAK, 2, &written);
+ if (written != 2)
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult nsOutlookCompose::ProcessMessage(nsMsgDeliverMode mode,
+ CMapiMessage &msg,
+ nsIOutputStream *pDst)
+{
+ nsCOMPtr<nsIFile> compositionFile;
+ nsresult rv = ComposeTheMessage(mode, msg, getter_AddRefs(compositionFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CopyComposedMessage(compositionFile, pDst, msg);
+ compositionFile->Remove(false);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error copying composed message to destination mailbox\n");
+ }
+ return rv;
+}
+
+void nsOutlookCompose::UpdateHeader(CMapiMessageHeaders& oldHeaders,
+ const CMapiMessageHeaders& newHeaders,
+ CMapiMessageHeaders::SpecialHeader header,
+ bool addIfAbsent)
+{
+ const char* oldVal = oldHeaders.Value(header);
+ if (!addIfAbsent && !oldVal)
+ return;
+ const char* newVal = newHeaders.Value(header);
+ if (!newVal)
+ return;
+ // Bug 145150 - Turn "Content-Type: application/ms-tnef" into "Content-Type: text/plain"
+ // so the body text can be displayed normally (instead of in an attachment).
+ if (header == CMapiMessageHeaders::hdrContentType)
+ if (stricmp(newVal, "application/ms-tnef") == 0)
+ newVal = "text/plain";
+ // End Bug 145150
+ if (oldVal) {
+ if (strcmp(oldVal, newVal) == 0)
+ return;
+ // Backup the old header value
+ nsCString backupHdrName("X-MozillaBackup-");
+ backupHdrName += CMapiMessageHeaders::SpecialName(header);
+ oldHeaders.SetValue(backupHdrName.get(), oldVal, false);
+ }
+ // Now replace it with new value
+ oldHeaders.SetValue(header, newVal);
+}
+
+void nsOutlookCompose::UpdateHeaders(CMapiMessageHeaders& oldHeaders, const CMapiMessageHeaders& newHeaders)
+{
+ // Well, ain't this a peach?
+ // This is rather disgusting but there really isn't much to be done about it....
+
+ // 1. For each "old" header, replace it with the new one if we want,
+ // then right it out.
+ // 2. Then if we haven't written the "important" new headers, write them out
+ // 3. Terminate the headers with an extra eol.
+
+ // Important headers:
+ // "Content-type",
+ // "MIME-Version",
+ // "Content-transfer-encoding"
+ // consider "X-Accept-Language"?
+
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentType);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrMimeVersion);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentTransferEncoding);
+
+ // Other replaced headers (only if they exist):
+ // "From",
+ // "To",
+ // "Subject",
+ // "Reply-to",
+ // "Cc"
+
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrFrom, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrTo, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrSubject, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrReplyTo, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrCc, false);
+}
+
+// Bug 593907
+// This is just a workaround of the deficiency of the nsMsgComposeAndSend::EnsureLineBreaks().
+// The import from Outlook will stay OK (I hope), but other messages may still suffer.
+// However, I cannot deny the possibility that the (possible) recode of the body
+// may interfere with this hack. A possible scenario is if a multi-byte character will either
+// contain 0x0D 0x0A sequence, or end with 0x0D, after which MAC-style standalone LF will go.
+// I hope that this possibility is insignificant (eg, utf-8 doesn't contain such sequences).
+// This hack will slow down the import, but as the import is one-time procedure, I hope that
+// the user will agree to wait a little longer to get better results.
+
+// The process of composing the message differs depending on whether the editor is present or not.
+// If the editor is absent, the "attachment1_body" parameter of CreateAndSendMessage() is taken as is,
+// while in the presence o the editor, the body that is taken from it is further processed in the
+// nsMsgComposeAndSend::GetBodyFromEditor(). Specifically, the TXTToHTML::ScanHTML() first calls
+// UnescapeStr() to properly handle a limited number of HTML character entities (namely &amp; &lt; &gt; &quot;)
+// and then calls ScanTXT() where escapes all ampersands and quotes again. As the UnescapeStr() works so
+// selectively (i.e. handling only a subset of valid entities), the so often seen "&nbsp;" becomes "&amp;nbsp;"
+// in the resulting body, which leads to text "&nbsp;" interspersed all over the imported mail. The same
+// applies to html &#XXXX; (where XXXX is unicode codepoint).
+// See also Bug 503690, where the same issue in Eudora import is reported.
+// By the way, the root of the Bug 359303 lies in the same place - the nsMsgComposeAndSend::GetBodyFromEditor()
+// changes the 0xA0 codes to 0x20 when it converts the body to plain text.
+// We scan the body here to find all the & and convert them to the safe character sequense to revert later.
+
+void nsOutlookCompose::HackBody(const wchar_t* orig, size_t origLen, nsString& hack)
+{
+#ifdef MOZILLA_INTERNAL_API
+ hack.SetCapacity(static_cast<size_t>(origLen*1.4));
+#endif
+ hack.Assign(hackBeginW);
+ hack.Append(m_hackedPostfix);
+
+ while (*orig) {
+ if (*orig == L'&') {
+ hack.Append(hackAmpersandW);
+ hack.Append(m_hackedPostfix);
+ } else if ((*orig == L'\x0D') && (*(orig+1) == L'\x0A')) {
+ hack.Append(hackCRLFW);
+ hack.Append(m_hackedPostfix);
+ ++orig;
+ } else
+ hack.Append(*orig);
+ ++orig;
+ }
+
+ hack.Append(hackEndW);
+ hack.Append(m_hackedPostfix);
+}
+
+void nsOutlookCompose::UnhackBody(nsCString& txt)
+{
+ nsCString hackedPostfixA;
+ LossyCopyUTF16toASCII(m_hackedPostfix, hackedPostfixA);
+
+ nsCString hackedString(hackBeginA);
+ hackedString.Append(hackedPostfixA);
+ int32_t begin = txt.Find(hackedString);
+ if (begin == kNotFound)
+ return;
+ txt.Cut(begin, hackedString.Length());
+
+ hackedString.Assign(hackEndA);
+ hackedString.Append(hackedPostfixA);
+ int32_t end = MsgFind(txt, hackedString, false, begin);
+ if (end == kNotFound)
+ return; // ?
+ txt.Cut(end, hackedString.Length());
+
+ nsCString range;
+ range.Assign(Substring(txt, begin, end - begin));
+ // 1. Remove all CRLFs from the selected range
+ MsgReplaceSubstring(range, MSG_LINEBREAK, "");
+ // 2. Restore the original CRLFs
+ hackedString.Assign(hackCRLFA);
+ hackedString.Append(hackedPostfixA);
+ MsgReplaceSubstring(range, hackedString.get(), MSG_LINEBREAK);
+
+ // 3. Restore the original ampersands
+ hackedString.Assign(hackAmpersandA);
+ hackedString.Append(hackedPostfixA);
+ MsgReplaceSubstring(range, hackedString.get(), "&");
+
+ txt.Replace(begin, end - begin, range);
+}
+
+bool nsOutlookCompose::GenerateHackSequence(const wchar_t* body, size_t origLen)
+{
+ nsDependentString nsBody(body, origLen);
+ const wchar_t* hack_base = L"hacked";
+ int i = 0;
+ do {
+ if (++i == 0) { // Cycle complete :) - could not generate an unique string
+ m_hackedPostfix.Truncate();
+ return false;
+ }
+ m_hackedPostfix.Assign(hack_base);
+ m_hackedPostfix.AppendInt(i);
+ } while (nsBody.Find(m_hackedPostfix) != kNotFound);
+
+ return true;
+}
+// End Bug 593907
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CCompositionFile::CCompositionFile(nsIFile* aFile, void* fifoBuffer,
+ uint32_t fifoBufferSize, bool convertCRs)
+ : m_pFile(aFile), m_fileSize(0), m_fileReadPos(0),
+ m_fifoBuffer(static_cast<char*>(fifoBuffer)),
+ m_fifoBufferSize(fifoBufferSize),
+ m_fifoBufferReadPos(static_cast<char*>(fifoBuffer)),
+ m_fifoBufferWrittenPos(static_cast<char*>(fifoBuffer)),
+ m_convertCRs(convertCRs),
+ m_lastChar(0)
+{
+ m_pFile->GetFileSize(&m_fileSize);
+ if (!m_fileSize) {
+ IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n");
+ return;
+ }
+
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_pInputStream), m_pFile);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error, unable to open composed message file\n");
+ return;
+ }
+}
+
+nsresult CCompositionFile::EnsureHasDataInBuffer()
+{
+ if (m_fifoBufferReadPos < m_fifoBufferWrittenPos)
+ return NS_OK;
+ // Populate the buffer with new data!
+ uint32_t count = m_fifoBufferSize;
+ if ((m_fileReadPos + count) > m_fileSize)
+ count = m_fileSize - m_fileReadPos;
+ if (!count)
+ return NS_ERROR_FAILURE; // Isn't there a "No more data" error?
+
+ uint32_t bytesRead = 0;
+ nsresult rv = m_pInputStream->Read(m_fifoBuffer, count, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesRead || (bytesRead > count))
+ return NS_ERROR_FAILURE;
+ m_fifoBufferWrittenPos = m_fifoBuffer+bytesRead;
+ m_fifoBufferReadPos = m_fifoBuffer;
+ m_fileReadPos += bytesRead;
+
+ return NS_OK;
+}
+
+class CTermGuard {
+public:
+ CTermGuard(const char* term, int termSize)
+ : m_term(term),
+ m_termSize(term ? ((termSize!=-1) ? termSize : strlen(term)) : 0),
+ m_matchPos(0)
+ {}
+
+ // if the guard triggered
+ inline bool IsTriggered() const {
+ return m_termSize && (m_matchPos == m_termSize); }
+ // indicates if the guard has something to check
+ inline bool IsChecking() const { return m_termSize; }
+
+ bool Check(char c) // returns true only if the whole sequence passed
+ {
+ if (!m_termSize) // no guard
+ return false;
+ if (m_matchPos >= m_termSize) // check past success!
+ m_matchPos = 0;
+ if (m_term[m_matchPos] != c) // Reset sequence
+ m_matchPos = 0;
+ if (m_term[m_matchPos] == c) { // Sequence continues
+ return ++m_matchPos == m_termSize; // If equal then sequence complete!
+ }
+ // Sequence broken
+ return false;
+ }
+private:
+ const char* m_term;
+ int m_termSize;
+ int m_matchPos;
+};
+
+template <class _OutFn>
+nsresult CCompositionFile::ToDest(_OutFn dest, const char* term, int termSize)
+{
+ CTermGuard guard(term, termSize);
+
+#ifdef MOZILLA_INTERNAL_API
+ // We already know the required string size, so reduce future reallocations
+ if (!guard.IsChecking() && !m_convertCRs)
+ dest.SetCapacity(m_fileSize - m_fileReadPos);
+#endif
+
+ bool wasCR = false;
+ char c = 0;
+ nsresult rv;
+ while (NS_SUCCEEDED(rv = EnsureHasDataInBuffer())) {
+ if (!guard.IsChecking() && !m_convertCRs) { // Use efficient algorithm
+ dest.Append(m_fifoBufferReadPos, m_fifoBufferWrittenPos-m_fifoBufferReadPos);
+ }
+ else { // Check character by character to convert CRs and find terminating sequence
+ while (m_fifoBufferReadPos < m_fifoBufferWrittenPos) {
+ c = *m_fifoBufferReadPos;
+ if (m_convertCRs && wasCR) {
+ wasCR = false;
+ if (c != nsCRT::LF) {
+ const char kTmpLF = nsCRT::LF;
+ dest.Append(&kTmpLF, 1);
+ if (guard.Check(nsCRT::LF)) {
+ c = nsCRT::LF; // save last char
+ break;
+ }
+ }
+ }
+ dest.Append(&c, 1);
+ m_fifoBufferReadPos++;
+
+ if (guard.Check(c))
+ break;
+
+ if (m_convertCRs && (c == nsCRT::CR))
+ wasCR = true;
+ }
+ if (guard.IsTriggered())
+ break;
+ }
+ }
+
+ // check for trailing CR (only if caller didn't specify the terminating sequence that ends with CR -
+ // in this case he knows what he does!)
+ if (m_convertCRs && !guard.IsTriggered() && (c == nsCRT::CR)) {
+ c = nsCRT::LF;
+ dest.Append(&c, 1);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_lastChar = c;
+ return NS_OK;
+}
+
+class dest_nsCString {
+public:
+ dest_nsCString(nsCString& str) : m_str(str) { m_str.Truncate(); }
+#ifdef MOZILLA_INTERNAL_API
+ void SetCapacity(int32_t sz) { m_str.SetCapacity(sz); }
+#endif
+ nsresult Append(const char* buf, uint32_t count) {
+ m_str.Append(buf, count); return NS_OK; }
+private:
+ nsCString& m_str;
+};
+
+class dest_Stream {
+public:
+ dest_Stream(nsIOutputStream *dest) : m_stream(dest) {}
+#ifdef MOZILLA_INTERNAL_API
+ void SetCapacity(int32_t) { /*do nothing*/ }
+#endif
+ // const_cast here is due to the poor design of the EscapeFromSpaceLine()
+ // that requires a non-constant pointer while doesn't modify its data
+ nsresult Append(const char* buf, uint32_t count) {
+ return EscapeFromSpaceLine(m_stream, const_cast<char*>(buf), buf+count); }
+private:
+ nsIOutputStream *m_stream;
+};
+
+nsresult CCompositionFile::ToString(nsCString& dest, const char* term,
+ int termSize)
+{
+ return ToDest(dest_nsCString(dest), term, termSize);
+}
+
+nsresult CCompositionFile::ToStream(nsIOutputStream *dest, const char* term,
+ int termSize)
+{
+ return ToDest(dest_Stream(dest), term, termSize);
+}