/* -*- 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: \n\ From: \"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 m_pFile; nsCOMPtr 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 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 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 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 embeddedObjects; if (msg.BodyIsHtml()) { for (unsigned int i = 0; i imageData = new nsImportEmbeddedImageData(uri, nsDependentCString(cid), nsDependentCString(name)); embeddedObjects->AppendElement(imageData, false); } } } nsCString bodyA; nsMsgI18NConvertFromUnicode(msg.GetBodyCharset(), bodyW, bodyA); nsCOMPtr 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(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(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 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(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(fifoBuffer)), m_fifoBufferSize(fifoBufferSize), m_fifoBufferReadPos(static_cast(fifoBuffer)), m_fifoBufferWrittenPos(static_cast(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 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(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); }