mailnews/local/src/nsPop3Sink.cpp
author ISHIKAWA, Chiaki <ishikawa@yk.rim.or.jp>
Mon, 23 May 2022 03:03:51 +0900
changeset 116832 9b51773175d639290d81cccd461db8005eebf9ff
parent 116831 89c39ab9a6e9f5dc0e6bd34615da3a18c6f8bfa0
permissions -rw-r--r--
Adding Close before Remove (possible Windows problem)

/* -*- 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;
}