diff options
Diffstat (limited to 'mailnews/import/outlook/src/nsOutlookCompose.cpp')
-rw-r--r-- | mailnews/import/outlook/src/nsOutlookCompose.cpp | 814 |
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 & < > ") +// 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 " " becomes "&nbsp;" +// in the resulting body, which leads to text " " 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); +} |