/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "msgCore.h" // precompiled header...
#include "nsPop3Sink.h"
#include "prprf.h"
#include "prlog.h"
#include "nscore.h"
#include <stdio.h>
#include <time.h>
#include "nsParseMailbox.h"
#include "nsIMsgLocalMailFolder.h"
#include "nsIMsgIncomingServer.h"
#include "nsLocalUtils.h"
#include "nsMsgLocalFolderHdrs.h"
#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
#include "nsMsgMessageFlags.h"
#include "nsMailHeaders.h"
#include "nsIMsgAccountManager.h"
#include "nsIPop3Protocol.h"
#include "nsLocalMailFolder.h"
#include "nsIInputStream.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIPromptService.h"
#include "nsIDocShell.h"
#include "mozIDOMWindow.h"
#include "nsEmbedCID.h"
#include "nsMsgUtils.h"
#include "nsMsgBaseCID.h"
#include "nsServiceManagerUtils.h"
#include "nsIPop3Service.h"
#include "nsMsgLocalCID.h"
#include "mozilla/Services.h"
#include "mozilla/Logging.h"
#include "nsNetUtil.h"
/* for logging to Error Console */
#include "nsIScriptError.h"
extern mozilla::LazyLogModule POP3LOGMODULE; // defined in nsPop3Protocol.cpp
#define POP3LOG(str) "sink: [this=%p] " str, this
NS_IMPL_ISUPPORTS(nsPop3Sink, nsIPop3Sink)
nsPop3Sink::nsPop3Sink() {
m_authed = false;
m_biffState = 0;
m_numNewMessages = 0;
m_numNewMessagesInFolder = 0;
m_numMsgsDownloaded = 0;
m_senderAuthed = false;
m_outFileStream = nullptr;
m_uidlDownload = false;
m_buildMessageUri = false;
}
nsPop3Sink::~nsPop3Sink() {
MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
(POP3LOG("Calling ReleaseFolderLock from ~nsPop3Sink")));
ReleaseFolderLock();
}
nsresult nsPop3Sink::SetUserAuthenticated(bool authed) {
m_authed = authed;
m_popServer->SetAuthenticated(authed);
return NS_OK;
}
nsresult nsPop3Sink::GetUserAuthenticated(bool* authed) {
return m_popServer->GetAuthenticated(authed);
}
nsresult nsPop3Sink::SetSenderAuthedFlag(bool authed) {
m_authed = authed;
return NS_OK;
}
nsresult nsPop3Sink::SetMailAccountURL(const nsACString& urlString) {
m_accountUrl.Assign(urlString);
return NS_OK;
}
nsresult nsPop3Sink::GetMailAccountURL(nsACString& urlString) {
urlString.Assign(m_accountUrl);
return NS_OK;
}
partialRecord::partialRecord() : m_msgDBHdr(nullptr) {}
partialRecord::~partialRecord() {}
// Walk through all the messages in this folder and look for any
// PARTIAL messages. For each of those, dig through the mailbox and
// find the Account that the message belongs to. If that Account
// matches the current Account, then look for the Uidl and save
// this message for later processing.
nsresult nsPop3Sink::FindPartialMessages() {
nsCOMPtr<nsIMsgEnumerator> messages;
bool hasMore = false;
bool isOpen = false;
nsLocalFolderScanState folderScanState;
nsCOMPtr<nsIMsgDatabase> db;
nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
m_folder->GetMsgDatabase(getter_AddRefs(db));
if (!localFolder || !db)
return NS_ERROR_FAILURE; // we need it to grub through the folder
nsresult rv = db->EnumerateMessages(getter_AddRefs(messages));
if (messages) messages->HasMoreElements(&hasMore);
while (hasMore && NS_SUCCEEDED(rv)) {
uint32_t flags = 0;
nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
rv = messages->GetNext(getter_AddRefs(msgDBHdr));
if (!NS_SUCCEEDED(rv)) break;
msgDBHdr->GetFlags(&flags);
if (flags & nsMsgMessageFlags::Partial) {
// Open the various streams we need to seek and read from the mailbox
if (!isOpen) {
rv = localFolder->GetFolderScanState(&folderScanState);
if (NS_SUCCEEDED(rv))
isOpen = true;
else
break;
}
rv = localFolder->GetUidlFromFolder(&folderScanState, msgDBHdr);
if (!NS_SUCCEEDED(rv)) break;
// If we got the uidl, see if this partial message belongs to this
// account. Add it to the array if so...
if (folderScanState.m_uidl &&
m_accountKey.Equals(folderScanState.m_accountKey,
nsCaseInsensitiveCStringComparator)) {
partialRecord* partialMsg = new partialRecord();
if (partialMsg) {
partialMsg->m_uidl = folderScanState.m_uidl;
partialMsg->m_msgDBHdr = msgDBHdr;
m_partialMsgsArray.AppendElement(partialMsg);
}
}
}
messages->HasMoreElements(&hasMore);
}
if (isOpen && folderScanState.m_inputStream) {
CLOSESTREAM(folderScanState.m_inputStream);
nsresult rv3 = folderScanState.m_inputStream->Close();
#ifdef DEBUG
fprintf(
stderr,
"{debug} AbortMailDelivery: folderScanState.m_inputStream(%p)->Close() = 0x%" PRIx32
"\n",
(void *)folderScanState.m_inputStream, static_cast<uint32_t>(rv3));
#endif
if (NS_FAILED(rv3))
NS_WARNING("folderScanState.m_inputStream->Close() failed.");
folderScanState.m_inputStream = nullptr; // code uniformity
}
return rv;
}
// For all the partial messages saved by FindPartialMessages,
// ask the protocol handler if they still exist on the server.
// Any messages that don't exist any more are deleted from the
// msgDB.
void nsPop3Sink::CheckPartialMessages(nsIPop3Protocol* protocol) {
uint32_t count = m_partialMsgsArray.Length();
bool deleted = false;
for (uint32_t i = 0; i < count; i++) {
partialRecord* partialMsg;
bool found = true;
partialMsg = m_partialMsgsArray.ElementAt(i);
protocol->CheckMessage(partialMsg->m_uidl.get(), &found);
if (!found && partialMsg->m_msgDBHdr) {
if (m_newMailParser)
m_newMailParser->m_mailDB->DeleteHeader(partialMsg->m_msgDBHdr, nullptr,
false, true);
deleted = true;
}
delete partialMsg;
}
m_partialMsgsArray.Clear();
if (deleted) {
nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
if (localFolder) localFolder->NotifyDelete();
}
}
nsresult nsPop3Sink::BeginMailDelivery(bool uidlDownload,
nsIMsgWindow* aMsgWindow, bool* aBool) {
nsresult rv;
nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
if (!server) return NS_ERROR_UNEXPECTED;
m_window = aMsgWindow;
nsCOMPtr<nsIMsgAccountManager> acctMgr =
do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
nsCOMPtr<nsIMsgAccount> account;
NS_ENSURE_SUCCESS(rv, rv);
acctMgr->FindAccountForServer(server, getter_AddRefs(account));
if (account) account->GetKey(m_accountKey);
bool isLocked;
nsCOMPtr<nsISupports> supports =
do_QueryInterface(static_cast<nsIPop3Sink*>(this));
m_folder->GetLocked(&isLocked);
if (!isLocked) {
MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
(POP3LOG("BeginMailDelivery acquiring semaphore")));
m_folder->AcquireSemaphore(supports);
} else {
MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
(POP3LOG("BeginMailDelivery folder locked")));
return NS_MSG_FOLDER_BUSY;
}
m_uidlDownload = uidlDownload;
if (!uidlDownload) FindPartialMessages();
m_folder->GetNumNewMessages(false, &m_numNewMessagesInFolder);
#ifdef DEBUG
printf("Begin mail message delivery.\n");
#endif
nsCOMPtr<nsIPop3Service> pop3Service(
do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
NS_ENSURE_SUCCESS(rv, rv);
pop3Service->NotifyDownloadStarted(m_folder);
if (aBool) *aBool = true;
return NS_OK;
}
//
// XXX TODO add better and proper I/O error processing, especially
// with regard to output error (file system filling up, etc.)
//
nsresult nsPop3Sink::EndMailDelivery(nsIPop3Protocol* protocol) {
CheckPartialMessages(protocol);
if (m_newMailParser) {
if (m_outFileStream) {
nsresult rv3 = m_outFileStream->Flush(); // try this.
if (NS_FAILED(rv3)) NS_WARNING("m_outFileStream->Flush() failed;");
}
m_newMailParser->OnStopRequest(nullptr, NS_OK);
m_newMailParser->EndMsgDownload();
}
CLOSESTREAM2(m_outFileStream, "before possible Close()");
if (m_outFileStream) {
nsresult rv3 = m_outFileStream->Close();
#ifdef DEBUG
fprintf(stderr, "m_outFileStream(%p)->Close() = 0x%" PRIx32 "\n",
(void *)m_outFileStream, static_cast<uint32_t>(rv3));
#endif
if (NS_FAILED(rv3)) NS_WARNING("m_outFileStream->Close() failed;");
m_outFileStream = nullptr;
}
// tell the parser to mark the db valid *after* closing the mailbox.
if (m_newMailParser) m_newMailParser->UpdateDBFolderInfo();
// XXX TODO Investigate if this is the correct place to release the lock.
// Shouldn't we release it after filter plugins are run below?
MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
(POP3LOG("Calling ReleaseFolderLock from EndMailDelivery")));
nsresult rv = ReleaseFolderLock();
NS_ASSERTION(NS_SUCCEEDED(rv), "folder lock not released successfully");
bool filtersRun;
m_folder->CallFilterPlugins(nullptr,
&filtersRun); // XXX ??? do we need msgWindow?
int32_t numNewMessagesInFolder;
// if filters have marked msgs read or deleted, the num new messages count
// will go negative by the number of messages marked read or deleted,
// so if we add that number to the number of msgs downloaded, that will give
// us the number of actual new messages.
m_folder->GetNumNewMessages(false, &numNewMessagesInFolder);
m_numNewMessages -= (m_numNewMessagesInFolder - numNewMessagesInFolder);
m_folder->SetNumNewMessages(
m_numNewMessages); // we'll adjust this for spam later
if (!filtersRun && m_numNewMessages > 0) {
nsCOMPtr<nsIMsgIncomingServer> server;
m_folder->GetServer(getter_AddRefs(server));
if (server) {
server->SetPerformingBiff(true);
m_folder->SetBiffState(m_biffState);
server->SetPerformingBiff(false);
} else {
// XXX TODO What do we do if server was null? Shouldn't we warn with
// a debug print?
}
}
// note that size on disk has possibly changed.
nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
if (localFolder) (void)localFolder->RefreshSizeOnDisk();
nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
if (server) {
nsCOMPtr<nsIMsgFilterList> filterList;
rv = server->GetFilterList(nullptr, getter_AddRefs(filterList));
NS_ENSURE_SUCCESS(rv, rv);
if (filterList) (void)filterList->FlushLogIfNecessary();
}
// fix for bug #161999
// we should update the summary totals for the folder (inbox)
// in case it's not the open folder
m_folder->UpdateSummaryTotals(true);
// check if the folder open in this window is not the current folder, and if
// it has new message, in which case we need to try to run the filter plugin.
if (m_newMailParser) {
nsCOMPtr<nsIMsgWindow> msgWindow;
m_newMailParser->GetMsgWindow(getter_AddRefs(msgWindow));
// this breaks down if it's biff downloading new mail because
// there's no msgWindow...
if (msgWindow) {
nsCOMPtr<nsIMsgFolder> openFolder;
(void)msgWindow->GetOpenFolder(getter_AddRefs(openFolder));
if (openFolder && openFolder != m_folder) {
// only call filter plugins if folder is a local folder, because only
// local folders get messages filtered into them synchronously by pop3.
nsCOMPtr<nsIMsgLocalMailFolder> localFolder =
do_QueryInterface(openFolder);
if (localFolder) {
bool hasNew, isLocked;
(void)openFolder->GetHasNewMessages(&hasNew);
if (hasNew) {
// if the open folder is locked, we shouldn't run the spam filters
// on it because someone is using the folder. see 218433.
// Ideally, the filter plugin code would try to grab the folder lock
// and hold onto it until done, but that's more difficult and I
// think this will actually fix the problem.
openFolder->GetLocked(&isLocked);
if (!isLocked) openFolder->CallFilterPlugins(nullptr, &filtersRun);
}
}
}
}
}
#ifdef DEBUG
printf("End mail message delivery.\n");
#endif
nsCOMPtr<nsIPop3Service> pop3Service(
do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
NS_ENSURE_SUCCESS(rv, rv);
pop3Service->NotifyDownloadCompleted(m_folder, m_numNewMessages);
return NS_OK;
}
nsresult nsPop3Sink::ReleaseFolderLock() {
nsresult result = NS_OK;
if (!m_folder) return result;
bool haveSemaphore;
nsCOMPtr<nsISupports> supports =
do_QueryInterface(static_cast<nsIPop3Sink*>(this));
result = m_folder->TestSemaphore(supports, &haveSemaphore);
MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
(POP3LOG("ReleaseFolderLock haveSemaphore = %s"),
haveSemaphore ? "TRUE" : "FALSE"));
if (NS_SUCCEEDED(result) && haveSemaphore)
result = m_folder->ReleaseSemaphore(supports);
return result;
}
nsresult nsPop3Sink::AbortMailDelivery(nsIPop3Protocol* protocol) {
CheckPartialMessages(protocol);
// ### PS TODO - discard any new message?
CLOSESTREAM2(m_outFileStream, "before possible Close()");
if (m_outFileStream) {
nsresult rv3 = m_outFileStream->Close();
#ifdef DEBUG
fprintf(
stderr,
"{debug} AbortMailDelivery: m_outFileStream(%p)->Close() = 0x%" PRIx32
"\n",
(void *)m_outFileStream, static_cast<uint32_t>(rv3));
#endif
if (NS_FAILED(rv3)) NS_WARNING("m_outFileStream->Close() failed;");
m_outFileStream = nullptr;
}
/* tell the parser to mark the db valid *after* closing the mailbox.
we have truncated the inbox, so berkeley mailbox and msf file are in sync*/
if (m_newMailParser) m_newMailParser->UpdateDBFolderInfo();
MOZ_LOG(POP3LOGMODULE, mozilla::LogLevel::Debug,
(POP3LOG("Calling ReleaseFolderLock from AbortMailDelivery")));
nsresult rv = ReleaseFolderLock();
NS_ASSERTION(NS_SUCCEEDED(rv), "folder lock not released successfully");
#ifdef DEBUG
printf("Abort mail message delivery.\n");
#endif
nsCOMPtr<nsIPop3Service> pop3Service(
do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
NS_ENSURE_SUCCESS(rv, rv);
pop3Service->NotifyDownloadCompleted(m_folder, 0);
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::IncorporateBegin(const char* uidlString, uint32_t flags) {
#ifdef DEBUG
printf("Incorporate message begin:\n");
if (uidlString) printf("uidl string: %s\n", uidlString);
#endif
nsresult rv;
nsCOMPtr<nsIMsgDBHdr> newHdr;
nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(m_popServer);
if (!server) return NS_ERROR_UNEXPECTED;
rv = server->GetMsgStore(getter_AddRefs(m_msgStore));
#ifdef DEBUG
if (NS_FAILED(rv)) {
fflush(stdout);
fprintf(stderr,
"(debug) IncorporateBegin: "
"m_msgStore->GetMsgStore(....) failed. "
"rv = 0x%" PRIx32 "\n",
static_cast<uint32_t>(rv));
}
#endif
NS_ENSURE_SUCCESS(rv, rv);
rv = m_msgStore->GetNewMsgOutputStream(m_folder, getter_AddRefs(newHdr),
getter_AddRefs(m_outFileStream));
#ifdef DEBUG
if (NS_FAILED(rv)) {
fflush(stdout);
fprintf(stderr,
"(debug) IncorporateBegin: "
"m_msgStore->GetNewMsgOutputStream(....) failed. "
"rv = 0x%" PRIx32 "\n",
static_cast<uint32_t>(rv));
}
#endif
PSTREAM2(m_outFileStream, "after GetNewMsgOutputStream(m_folder, getter_AddRefs(newHdr), "
"getter_AddRefs(m_outFileStream)); irrespective of error");
// The error code ought to be something along the line of
// "Could not create a writable stream/file", etc.
// NS_ERROR_OUT_OF_MEMORY seems out of context.
NS_ENSURE_SUCCESS(rv, rv);
#if 0
// ---
// The following code may be no longer necessary, or in the wrong
// place, or need modification now that BENC changed the
// code to download to a temporary file.
//
// Problem: when |m_outFileStream| has become a buffered output stream
// the following code failed.
// nsCOMPtr <nsIInputStream> inboxInputStream =
// do_QueryInterface(m_outFileStream, &rv); So we execute this BEFORE
// |m_outFileStream| becomes buffered, and save the resulting
// |inboxInputStream| in |m_inboxInputStream|. See Bug 1159777 for details.
PSTREAM2(m_outFileStream,
"Just before nsCOMPtr<nsIInputStream> inboxInputStream ="
"do_QueryInterface(m_outFileStream, &rv);" );
nsCOMPtr<nsIInputStream> inboxInputStream =
do_QueryInterface(m_outFileStream, &rv);
if (NS_FAILED(rv)) {
#ifdef DEBUG
fprintf(stderr,
"(debug) SERIOUS ERROR: nsCOMPtr <nsIInputStream> inboxInputStream = "
"do_QueryInterface(m_outFileStream, &rv);"
"failed with rv=0x%" PRIx32 "\n",
static_cast<uint32_t>(rv));
#endif
m_inboxInputStream = nullptr;
} else {
#ifdef DEBUG
fprintf(stderr,
"(debug) nsCOMPtr <nsIInputStream> inboxInputStream = "
"do_QueryInterface(m_outFileStream, &rv);"
"succeeded\n");
#endif
m_inboxInputStream = inboxInputStream;
}
#endif // 0
// XXX Does this buffering code conflict with aceman's patch?
// Well, not quite, but the buffering interferes with
// nsCOMPtr <nsIInputStream> inboxInputStream =
// do_QueryInterface(m_outFileStream, &rv); A buffered output stream can't be
// used for producing non-nullptr inboxInputStream by do_QueryInterface(). The
// next code is executed when downloading to temporary file is done to switch
// from writing the temporary file to reading from that file.
// nsCOMPtr <nsIInputStream> inboxInputStream = do_QueryInterface(m_outFileStream);
// But this fails when the m_outFileStream is a buffered stream. So
// I moved the generation of inboxInputStream before buffering is
// enabled for m_outFileStream and save the value for later use in
// |IncorporateComplete()| See Bug 1159777 for details.
// WATCH OUT: Enabling buffering caused the failure to incorporate
// messages into Inbox in Jan 2015 especially when artificial
// network errors were introduced, etc., but but after cleaning up
// error handling in message download code, I have not seen
// corruption since (April, 2015).
#ifdef DEBUG
NS_WARNING(
"(debug) We are enabling buffering for m_outFileStream in "
"nsPop3Sink::IncorporateBegin in nsPop3Sink.cpp.");
#endif
// Enable Buffering.
nsresult rv1;
rv1 = m_outFileStream->Flush(); // Should be NOOP immediately after creation
// but just in case.
#ifdef DEBUG
fflush(stdout);
fprintf(stderr,
"(debug) %d: m_outFileStream->Flush() returned "
"0x%" PRIx32 "\n",
__LINE__, static_cast<uint32_t>(rv1));
#endif
if (NS_FAILED(rv1)) {
NS_WARNING("m_outFileStream->Flush() returned error.");
}
PSTREAM2(m_outFileStream, "Before buffering");
// Finally buffering! But we have to remove offending |Seek|.
// CI: Is forget() the correct call? It seems so.
//
// clang and GCC has a different order of argument evaluation.
// We have to save m_outFileSream because
// geter_AddRefs(m_outFileStream) may trash it depending on the
// order of evaluation of passed arguments!
//
nsCOMPtr<nsIOutputStream> tmp_p = m_outFileStream;
rv1 = NS_NewBufferedOutputStream(getter_AddRefs(m_outFileStream),
tmp_p.forget(), 64 * 1024);
NS_ASSERTION(NS_SUCCEEDED(rv1),
"buffering by NS_NewBufferedOutputStream failed.");
PSTREAM2(m_outFileStream, "after NS_NewBufferedOutputStream");
nsCOMPtr<nsISeekableStream> seekableOutStream =
do_QueryInterface(m_outFileStream);
NS_ASSERTION(seekableOutStream, "seekableOutStream from m_outFileStream");
// ---
// create a new mail parser
if (!m_newMailParser) m_newMailParser = new nsParseNewMailState;
NS_ENSURE_TRUE(m_newMailParser, NS_ERROR_OUT_OF_MEMORY);
// We are disabling filters |if (m_uidlDownload)| to deal with the
// option to fetch only headers or the first part of a
// message. There is then UI to download the rest of the message,
// but the message would already have been filtered, so there's no
// need to filter it again.
if (m_uidlDownload) m_newMailParser->DisableFilters();
nsCOMPtr<nsIMsgFolder> serverFolder;
// GetServerFolder is defined in this file later.
rv = GetServerFolder(getter_AddRefs(serverFolder));
if (NS_FAILED(rv)) return rv;
rv = m_newMailParser->Init(serverFolder, m_folder, m_window, newHdr,
m_outFileStream);
// If we failed to initialize the parser, then just don't use it!!!
// We can still continue without one.
if (NS_FAILED(rv)) {
m_newMailParser = nullptr;
rv = NS_OK;
}
#ifdef DEBUG
// Debugging, see bug 1116055.
int64_t first_pre_seek_pos;
nsresult rv3 = seekableOutStream->Tell(&first_pre_seek_pos);
if (NS_FAILED(rv3)) {
fflush(stdout);
fprintf(stderr,
"(debug) Tell in nsPop3Sink.cpp failed: "
"rv3=0x%" PRIx32 "\n",
static_cast<uint32_t>(rv3));
}
#endif
// Bug 1116055: We need to seek to the end of the m_outputstream
// here ONCE per message. This is because function(s) to enable
// buffering rewinds the file seek position to the beginning.
// Thus we need to call |Seek()| once per message, but we don't
// need to call it per output line in |WriteLineToMailBox|. The
// call in |Seek()| in |WriteLineToMailBox| negated the effect of
// buffering and resulted in loss of output peformance.
// XXX Handle error such as network error for remote file system.
rv = seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
if (NS_FAILED(rv)) {
// XXX Better error recovery.
// What do we want to do after seek error?
// Probably abort downloading subsequent messages.
#ifdef DEBUG
fflush(stdout);
fprintf(stderr,
"(debug) Seek in nsPop3Sink.cpp failed: "
"rv=0x%" PRIx32 "\n",
static_cast<uint32_t>(rv));
#endif
}
nsCString outputString(GetDummyEnvelope());
rv = WriteLineToMailbox(outputString);
NS_ENSURE_SUCCESS(rv, rv);
// Write out account-key before UIDL so the code that looks for
// UIDL will find the account first and know it can stop looking
// once it finds the UIDL line.
if (!m_accountKey.IsEmpty()) {
outputString.AssignLiteral(HEADER_X_MOZILLA_ACCOUNT_KEY ": ");
outputString.Append(m_accountKey);
outputString.AppendLiteral(MSG_LINEBREAK);
rv = WriteLineToMailbox(outputString);
NS_ENSURE_SUCCESS(rv, rv);
}
if (uidlString) {
outputString.AssignLiteral("X-UIDL: ");
outputString.Append(uidlString);
outputString.AppendLiteral(MSG_LINEBREAK);
rv = WriteLineToMailbox(outputString);
NS_ENSURE_SUCCESS(rv, rv);
}
// Explanation: actually 8000 part will be overwritten to
// reflect the value of |flags|.
// WriteLineToMailbox("X-Mozilla-Status: 8000" MSG_LINEBREAK);
char* statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flags);
outputString.Assign(statusLine);
rv = WriteLineToMailbox(outputString);
PR_smprintf_free(statusLine);
NS_ENSURE_SUCCESS(rv, rv);
rv = WriteLineToMailbox("X-Mozilla-Status2: 00000000"_ns MSG_LINEBREAK);
NS_ENSURE_SUCCESS(rv, rv);
// leave space for 60 bytes worth of keys/tags
rv = WriteLineToMailbox(nsLiteralCString(X_MOZILLA_KEYWORDS));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::SetPopServer(nsIPop3IncomingServer* server) {
m_popServer = server;
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::GetPopServer(nsIPop3IncomingServer** aServer) {
NS_ENSURE_ARG_POINTER(aServer);
NS_IF_ADDREF(*aServer = m_popServer);
return NS_OK;
}
NS_IMETHODIMP nsPop3Sink::GetFolder(nsIMsgFolder** aFolder) {
NS_ENSURE_ARG_POINTER(aFolder);
NS_IF_ADDREF(*aFolder = m_folder);
return NS_OK;
}
NS_IMETHODIMP nsPop3Sink::SetFolder(nsIMsgFolder* aFolder) {
m_folder = aFolder;
return NS_OK;
}
nsresult nsPop3Sink::GetServerFolder(nsIMsgFolder** aFolder) {
NS_ENSURE_ARG_POINTER(aFolder);
if (m_popServer) {
// not sure what this is used for - might be wrong if we have a deferred
// account.
nsCOMPtr<nsIMsgIncomingServer> incomingServer =
do_QueryInterface(m_popServer);
if (incomingServer) return incomingServer->GetRootFolder(aFolder);
}
*aFolder = nullptr;
return NS_ERROR_NULL_POINTER;
}
// set # of messages to download for progress notifications
// (downloading 1 of N).
NS_IMETHODIMP nsPop3Sink::SetMsgsToDownload(uint32_t aNumMessages) {
m_numNewMessages = aNumMessages;
return NS_OK;
}
char* nsPop3Sink::GetDummyEnvelope(void) {
static char result[75];
char* ct;
time_t now = time((time_t*)0);
#if defined(XP_WIN)
if (now < 0 || now > 0x7FFFFFFF) now = 0x7FFFFFFF;
#endif
ct = ctime(&now);
PR_ASSERT(ct[24] == '\r' || ct[24] == '\n');
ct[24] = 0;
/* This value must be in ctime() format, with English abbreviations.
strftime("... %c ...") is no good, because it is localized. */
PL_strcpy(result, "From - ");
PL_strcpy(result + 7, ct);
PL_strcpy(result + 7 + 24, MSG_LINEBREAK);
return result;
}
//
// Append a block into internal buffer (m_outputBuffer)
// with "From" escaping, and then write it as a single line. using
// WriteLineToMailBox
// This function is called by nsPop3Protocol::HandleLine(char *line, uint32_t
// line_length) in nsPop3Protocol.cpp
//
nsresult nsPop3Sink::IncorporateWrite(const char* block, int32_t length) {
m_outputBuffer.Truncate();
if (!strncmp(block, "From ", 5)) m_outputBuffer.Assign('>');
m_outputBuffer.Append(block);
return WriteLineToMailbox(m_outputBuffer);
}
nsresult nsPop3Sink::WriteLineToMailbox(const nsACString& buffer) {
if (!buffer.IsEmpty()) {
uint32_t bufferLen = buffer.Length();
if (m_newMailParser)
m_newMailParser->HandleLine(buffer.BeginReading(), bufferLen);
// The following (!m_outFileStream etc) was added to make sure that we don't
// write somewhere where for some reason or another we can't write to and
// lose the messages See bug 62480
// Too many dumps, thus commented out.
// PSTREAM2(m_outFileStream,"inside nsPop3Sink::WriteLinetoMailbox");
NS_ENSURE_TRUE(m_outFileStream, NS_ERROR_OUT_OF_MEMORY);
// To remove seeking to the end for each line to be written, remove the
// following line. See bug 1116055 for details.
#if 0
// #define SEEK_TO_END
#endif
#ifdef SEEK_TO_END
// seek to the end in case someone else has sought elsewhere in our stream.
nsCOMPtr<nsISeekableStream> seekableOutStream =
do_QueryInterface(m_outFileStream);
if (seekableOutStream) {
int64_t before_seek_pos;
nsresult rv2 = seekableOutStream->Tell(&before_seek_pos);
MOZ_ASSERT(NS_SUCCEEDED(rv2),
"seekableOutStream->Tell(&before_seek_pos) failed");
// XXX Handle error such as network error for remote file system.
nsresult rv = seekableOutStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
if (NS_FAILED(rv)) {
# ifdef DEBUG
fflush(stdout);
fprintf(stderr,
"(seekdebug) Seek() failed in nsPop3Sink.cpp: "
"rv = 0x%" PRIx32 "\n",
static_cast<uint32_t>(rv));
# endif
}
int64_t after_seek_pos;
nsresult rv3 = seekableOutStream->Tell(&after_seek_pos);
MOZ_ASSERT(NS_SUCCEEDED(rv3),
"seekableOutStream->Tell(&after_seek_pos) failed");
if (NS_SUCCEEDED(rv2) && NS_SUCCEEDED(rv3)) {
if (before_seek_pos != after_seek_pos) {
nsString folderName;
if (m_folder) m_folder->GetPrettyName(folderName);
// This merits a console message, it's poor man's telemetry.
MsgLogToConsole4(
u"Unexpected file position change detected"_ns +
(folderName.IsEmpty() ? EmptyString() : u" in folder "_ns) +
(folderName.IsEmpty() ? EmptyString() : folderName) +
u". "
"If you can reliably reproduce this, please report the "
"steps you used to dev-apps-thunderbird@lists.mozilla.org "
"or to bug 1308335 at bugzilla.mozilla.org. "
"Resolving this problem will allow speeding up message "
"downloads."_ns,
NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
nsIScriptError::errorFlag);
# ifdef DEBUG
// Debugging, see bug 1116055.
if (!folderName.IsEmpty()) {
fprintf(stderr,
"(seekdebug) WriteLineToMailbox() detected an unexpected "
"file position change in folder %s.\n",
NS_ConvertUTF16toUTF8(folderName).get());
} else {
fprintf(stderr,
"(seekdebug) WriteLineToMailbox() detected an unexpected "
"file position change.\n");
}
fprintf(stderr,
"(seekdebug) before_seek_pos=0x%016llx, "
"after_seek_pos=0x%016llx\n",
(long long unsigned int)before_seek_pos,
(long long unsigned int)after_seek_pos);
# endif
}
}
}
#endif
// We do not need to call |Seek()| here, but call it once per
// message in |IncorporateBegin()|.
uint32_t bytesWritten;
nsresult rv =
m_outFileStream->Write(buffer.BeginReading(), bufferLen, &bytesWritten);
#ifdef DEBUG
// During test, this Write failed.
if (NS_FAILED(rv)) {
fprintf(stderr,
"(debug) m_outFileStream->Write(buffer.BeginReading(), "
"bufferLen, &bytesWritten); failed. "
"rv = 0x%" PRIx32 "\n",
static_cast<uint32_t>(rv));
} else if (bytesWritten != bufferLen) {
fprintf(stderr,
"(debug) written length mismatch: bytesWritten=%" PRIu32
" != bufferLen=%" PRIu32 "\n",
bytesWritten, bufferLen);
}
#endif
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(bytesWritten == bufferLen, NS_ERROR_FAILURE);
}
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::IncorporateComplete(nsIMsgWindow* aMsgWindow, int32_t aSize) {
// Given that m_newMailPaser can't be null due to the assertion in
// the middle of this function
// we might as well check this here, and simplify a few things.
NS_ASSERTION(m_newMailParser, "could not get m_newMailParser");
// We are extracting a msgKey from m_newMsgHdr and create a local message URI.
// A message URI is what the front end uses to actually view a
// message. This is because it has to go though all the gubbins used
// for displaying web pages.
if (m_buildMessageUri && !m_baseMessageUri.IsEmpty() && m_newMailParser &&
m_newMailParser->m_newMsgHdr) {
nsMsgKey msgKey;
m_newMailParser->m_newMsgHdr->GetMessageKey(&msgKey);
m_messageUri.Truncate();
nsBuildLocalMessageURI(m_baseMessageUri, msgKey, m_messageUri);
}
nsresult rv = WriteLineToMailbox(nsLiteralCString(MSG_LINEBREAK));
NS_ENSURE_SUCCESS(rv, rv);
bool leaveOnServer = false;
m_popServer->GetLeaveMessagesOnServer(&leaveOnServer);
// We need to flush the output stream, in case mail filters move
// the new message, which relies on all the data being flushed.
rv =
m_outFileStream->Flush(); // Make sure the message is written to the disk
#ifdef DEBUG
// During testing using Linux CIFS server as mail store from a TB
// running on another Linux, we get error on this flush.
if (NS_FAILED(rv)) {
fprintf(stderr,
"(debug) m_outFileStream->Flush() returned "
"0x%" PRIx32 "\n",
static_cast<uint32_t>(rv));
}
#endif
// For now, we have no good error recovery strategy, and so just
// return.
NS_ENSURE_SUCCESS(rv, rv);
// XXX TODO Devise a better error recovery strategy here.
// For example, when flush failed, would we want to return by
// NS_ENSURE_SUCCESS above without closing the m_outFileStream here?
// Or is it closed somewhere?
// We DO need a good error recovery strategy here.
NS_ASSERTION(m_newMailParser, "could not get m_newMailParser");
if (m_newMailParser) {
// PublishMsgHdr clears m_newMsgHdr, so we need a comptr to
// hold onto it.
nsCOMPtr<nsIMsgDBHdr> hdr = m_newMailParser->m_newMsgHdr;
NS_ASSERTION(hdr, "m_newMailParser->m_newMsgHdr wasn't set");
if (!hdr) return NS_ERROR_FAILURE;
nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
bool doSelect = false;
// aSize is only set for partial messages. For full messages,
// check to see if we're replacing an old partial message.
if (!aSize && localFolder)
(void)localFolder->DeleteDownloadMsg(hdr, &doSelect);
// If a header already exists for this message (for example, when
// getting a complete message when a partial exists), then update the new
// header from the old.
if (!m_origMessageUri.IsEmpty() && localFolder) {
nsCOMPtr<nsIMsgDBHdr> oldMsgHdr;
rv = GetMsgDBHdrFromURI(m_origMessageUri, getter_AddRefs(oldMsgHdr));
if (NS_SUCCEEDED(rv) && oldMsgHdr)
localFolder->UpdateNewMsgHdr(oldMsgHdr, hdr);
}
// XXX TODO I wonder if I should check the error code of FinishNewMessage.
// In some other places, yes I do.
// Am I being paranoid?
nsresult rv2 = m_msgStore->FinishNewMessage(m_outFileStream, hdr);
// FinishNewMessage used to close stream (before Bug 1121842, 1122698)
// And it does so now again.
ASSIGNSTREAM2(m_outFileStream, nullptr, "after FinishNewMessage");
m_outFileStream = nullptr; // avoid accessing closed stream
// delayed processing of FinishNewMessage return value.
if (NS_FAILED(rv2)) NS_WARNING("m_msgStore->FinishNewMessage failed;");
// PublishMsgHeader updates UI.
m_newMailParser->PublishMsgHeader(aMsgWindow);
// run any reply/forward filter after we've finished moving the message to another folder.
#ifdef DEBUG
NS_WARNING("(debug): ApplyForwardAndReplyFilter getting called.");
#endif
m_newMailParser->ApplyForwardAndReplyFilter(aMsgWindow);
if (aSize) hdr->SetUint32Property("onlineSize", aSize);
// |doSelect| is not set if |aSize| is and thus we use |else if.|
// if DeleteDownloadMsg requested it, select the new message
else if (doSelect)
(void)localFolder->SelectDownloadMsg();
}
#ifdef DEBUG
printf("Incorporate message complete.\n");
#endif
nsCOMPtr<nsIPop3Service> pop3Service(
do_GetService(NS_POP3SERVICE_CONTRACTID1, &rv));
#ifdef DEBUG
if (NS_FAILED(rv)) {
printf(
"pop3Service could not be obtained: we can not notify download "
"progress info.");
}
#endif
NS_ENSURE_SUCCESS(rv, rv);
pop3Service->NotifyDownloadProgress(m_folder, ++m_numMsgsDownloaded,
m_numNewMessages);
return NS_OK;
}
//
// CAUTION: Inside |IncorporateAbort|, |m_outFileStream| can be
// nullptr due to the way error handling/recovery works.
// When the stream associated with |m_outFileStream| is closed
// during error processing, |m_outFileStream| is set to nullptr.
//
NS_IMETHODIMP
nsPop3Sink::IncorporateAbort(bool uidlDownload) {
NS_ENSURE_STATE(m_outFileStream);
nsresult rv = NS_OK;
// Note m_outFileStream can be nullptr.
CLOSESTREAM2(m_outFileStream, "before possible Close()");
if (m_outFileStream) {
rv = m_outFileStream->Close();
#ifdef DEBUG
fprintf(stderr,
"{debug} IncorporateAbort: "
"m_outFileStream(%p)->Close() = "
"0x%" PRIx32 "\n",
(void *)m_outFileStream, static_cast<uint32_t>(rv));
if (NS_FAILED(rv)) {
fflush(stdout);
fprintf(stderr, "(debug) m_outFileStream->Close() failed. rv=0x%08x\n",
(unsigned)rv);
fflush(stderr);
}
#endif
m_outFileStream = nullptr;
} else {
#ifdef DEBUG
fprintf(
stderr,
"(debug) m_outFileStream was nullptr on entry to IncorporateAbort.\n");
#endif
}
NS_ENSURE_SUCCESS(rv, rv);
if (m_msgStore && m_newMailParser && m_newMailParser->m_newMsgHdr) {
rv = m_msgStore->DiscardNewMessage(m_outFileStream,
m_newMailParser->m_newMsgHdr);
// Lucky us: DiscardNewMessage now tries to close file stream always unless
// stream is nullptr. We already set |m_outFileStream| to nullptr at the
// beginning of this function! But it is hard to tell/read and so setting to
// nullptr again.
m_outFileStream = nullptr;
}
#ifdef DEBUG
printf("Incorporate message abort.\n");
#endif
return rv;
}
nsresult nsPop3Sink::BiffGetNewMail() {
#ifdef DEBUG
printf("Biff get new mail.\n");
#endif
return NS_OK;
}
//
// "FE" stands for Front End. i.e. User Interface (UI).
//
nsresult nsPop3Sink::SetBiffStateAndUpdateFE(uint32_t aBiffState,
int32_t numNewMessages,
bool notify) {
m_biffState = aBiffState;
if (m_newMailParser) numNewMessages -= m_newMailParser->m_numNotNewMessages;
if (notify && m_folder && numNewMessages > 0 &&
numNewMessages != m_numNewMessages &&
aBiffState == nsIMsgFolder::nsMsgBiffState_NewMail) {
m_folder->SetNumNewMessages(numNewMessages);
m_folder->SetBiffState(aBiffState);
}
m_numNewMessages = numNewMessages;
return NS_OK;
}
//
// m_buildMessageUri is set by SetBuildMessageUri() defined immediately below.
// It is set to true by a call in nsPop3Service::NewURI() in nsPop3Service.cpp
//
NS_IMETHODIMP
nsPop3Sink::GetBuildMessageUri(bool* bVal) {
NS_ENSURE_ARG_POINTER(bVal);
*bVal = m_buildMessageUri;
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::SetBuildMessageUri(bool bVal) {
m_buildMessageUri = bVal;
return NS_OK;
}
//
// XXX TODO Better comment/documentation
// A series of URIs are handled: what are the differences?
// Also the way they are set and retrieved are different.
// MessageUri set from char *
// BaseMessageUri set from char *
// OrigMessageUri set from nsACString& aOrigMessageUri
//
NS_IMETHODIMP
nsPop3Sink::GetMessageUri(nsACString& messageUri) {
NS_ENSURE_TRUE(!m_messageUri.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
messageUri = m_messageUri;
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::SetMessageUri(const nsACString& messageUri) {
m_messageUri = messageUri;
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::GetBaseMessageUri(nsACString& baseMessageUri) {
// XXX Can't we be more imaginative by using other error code
// than NS_ERROR_FAILURE?
NS_ENSURE_TRUE(!m_baseMessageUri.IsEmpty(), NS_ERROR_FAILURE);
baseMessageUri = m_baseMessageUri;
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::SetBaseMessageUri(const nsACString& baseMessageUri) {
m_baseMessageUri = baseMessageUri;
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::GetOrigMessageUri(nsACString& aOrigMessageUri) {
aOrigMessageUri.Assign(m_origMessageUri);
return NS_OK;
}
NS_IMETHODIMP
nsPop3Sink::SetOrigMessageUri(const nsACString& aOrigMessageUri) {
m_origMessageUri.Assign(aOrigMessageUri);
return NS_OK;
}