/* -*- 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"
#include "nsString.h"
#include "nsMemory.h"
#include "nsMsgProtocol.h"
#include "nsIMsgMailNewsUrl.h"
#include "nsIMsgMailSession.h"
#include "nsIStreamTransportService.h"
#include "nsISocketTransportService.h"
#include "nsISocketTransport.h"
#include "nsITLSSocketControl.h"
#include "nsITransportSecurityInfo.h"
#include "nsILoadGroup.h"
#include "nsILoadInfo.h"
#include "nsIIOService.h"
#include "nsNetUtil.h"
#include "nsIFileURL.h"
#include "nsIMsgWindow.h"
#include "nsIMsgStatusFeedback.h"
#include "nsIWebProgressListener.h"
#include "nsIPipe.h"
#include "nsIPrompt.h"
#include "prprf.h"
#include "plbase64.h"
#include "nsIStringBundle.h"
#include "nsIProxyInfo.h"
#include "nsThreadUtils.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsMsgUtils.h"
#include "nsILineInputStream.h"
#include "nsIAsyncInputStream.h"
#include "nsIMsgIncomingServer.h"
#include "nsIInputStreamPump.h"
#include "nsICancelable.h"
#include "nsMimeTypes.h"
#include "mozilla/Components.h"
#include "mozilla/SlicedInputStream.h"
#include "nsContentSecurityManager.h"
#include "nsPrintfCString.h"

#undef PostMessage  // avoid to collision with WinUser.h

using namespace mozilla;

NS_IMPL_ISUPPORTS_INHERITED(nsMsgProtocol, nsHashPropertyBag, nsIMailChannel,
                            nsIChannel, nsIStreamListener, nsIRequestObserver,
                            nsIRequest, nsITransportEventSink)

static char16_t* FormatStringWithHostNameByName(const char16_t* stringName,
                                                nsIMsgMailNewsUrl* msgUri);

nsMsgProtocol::nsMsgProtocol(nsIURI* aURL) {
  m_flags = 0;
  m_readCount = 0;
  mLoadFlags = 0;
  m_socketIsOpen = false;
  mContentLength = -1;
  m_isChannel = false;
  mContentDisposition = nsIChannel::DISPOSITION_INLINE;

  GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "tempMessage.eml",
                                  getter_AddRefs(m_tempMsgFile));

  mSuppressListenerNotifications = false;
  InitFromURI(aURL);
}

nsresult nsMsgProtocol::InitFromURI(nsIURI* aUrl) {
  m_url = aUrl;

  nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
  if (mailUrl) {
    mailUrl->GetLoadGroup(getter_AddRefs(m_loadGroup));
    nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
    mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
    mProgressEventSink = do_QueryInterface(statusFeedback);
  }

  // Reset channel data in case the object is reused and initialised again.
  mCharset.Truncate();

  return NS_OK;
}

nsMsgProtocol::~nsMsgProtocol() {}

static bool gGotTimeoutPref;
static int32_t gSocketTimeout = 60;

nsresult nsMsgProtocol::GetQoSBits(uint8_t* aQoSBits) {
  NS_ENSURE_ARG_POINTER(aQoSBits);
  const char* protocol = GetType();

  if (!protocol) return NS_ERROR_NOT_IMPLEMENTED;

  nsAutoCString prefName("mail.");
  prefName.Append(protocol);
  prefName.AppendLiteral(".qos");

  nsresult rv;
  nsCOMPtr<nsIPrefBranch> prefBranch =
      do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t val;
  rv = prefBranch->GetIntPref(prefName.get(), &val);
  NS_ENSURE_SUCCESS(rv, rv);
  *aQoSBits = (uint8_t)clamped(val, 0, 0xff);
  return NS_OK;
}

nsresult nsMsgProtocol::OpenNetworkSocketWithInfo(
    const char* aHostName, int32_t aGetPort, const char* connectionType,
    nsIProxyInfo* aProxyInfo, nsIInterfaceRequestor* callbacks) {
  NS_ENSURE_ARG(aHostName);

  nsresult rv = NS_OK;
  nsCOMPtr<nsISocketTransportService> socketService(
      do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
  NS_ENSURE_TRUE(socketService, NS_ERROR_FAILURE);

  // with socket connections we want to read as much data as arrives
  m_readCount = -1;

  nsCOMPtr<nsISocketTransport> strans;
  AutoTArray<nsCString, 1> connectionTypeArray;
  if (connectionType) connectionTypeArray.AppendElement(connectionType);
  rv = socketService->CreateTransport(
      connectionTypeArray, nsDependentCString(aHostName), aGetPort, aProxyInfo,
      nullptr, getter_AddRefs(strans));
  if (NS_FAILED(rv)) return rv;

  strans->SetSecurityCallbacks(callbacks);

  // creates cyclic reference!
  nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread());
  strans->SetEventSink(this, currentThread);

  m_socketIsOpen = false;
  m_transport = strans;

  if (!gGotTimeoutPref) {
    nsCOMPtr<nsIPrefBranch> prefBranch =
        do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
    if (prefBranch) {
      prefBranch->GetIntPref("mailnews.tcptimeout", &gSocketTimeout);
      gGotTimeoutPref = true;
    }
  }
  strans->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, gSocketTimeout + 60);
  strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);

  uint8_t qos;
  rv = GetQoSBits(&qos);
  if (NS_SUCCEEDED(rv)) strans->SetQoSBits(qos);

  return SetupTransportState();
}

nsresult nsMsgProtocol::GetFileFromURL(nsIURI* aURL, nsIFile** aResult) {
  NS_ENSURE_ARG_POINTER(aURL);
  NS_ENSURE_ARG_POINTER(aResult);
  // extract the file path from the uri...
  nsAutoCString urlSpec;
  aURL->GetPathQueryRef(urlSpec);
  urlSpec.InsertLiteral("file://", 0);
  nsresult rv;

  // dougt - there should be an easier way!
  nsCOMPtr<nsIURI> uri;
  if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), urlSpec.get()))) return rv;

  nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
  if (!fileURL) return NS_ERROR_FAILURE;

  return fileURL->GetFile(aResult);
  // dougt
}

nsresult nsMsgProtocol::OpenFileSocket(nsIURI* aURL, uint64_t aStartPosition,
                                       int64_t aReadCount) {
  // mscott - file needs to be encoded directly into aURL. I should be able to
  // get rid of this method completely.

  nsresult rv = NS_OK;
  m_readCount = aReadCount;
  nsCOMPtr<nsIFile> file;

  rv = GetFileFromURL(aURL, getter_AddRefs(file));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIInputStream> stream;
  rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
  if (NS_FAILED(rv)) return rv;

  // create input stream transport
  nsCOMPtr<nsIStreamTransportService> sts =
      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return rv;

  // This can be called with aReadCount == -1 which means "read as much as we
  // can". We pass this on as UINT64_MAX, which is in fact uint64_t(-1).
  RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
      stream.forget(), aStartPosition,
      aReadCount == -1 ? UINT64_MAX : uint64_t(aReadCount));
  rv = sts->CreateInputTransport(slicedStream, true,
                                 getter_AddRefs(m_transport));

  m_socketIsOpen = false;
  return rv;
}

nsresult nsMsgProtocol::GetTopmostMsgWindow(nsIMsgWindow** aWindow) {
  nsresult rv;
  nsCOMPtr<nsIMsgMailSession> mailSession(
      do_GetService("@mozilla.org/messenger/services/session;1", &rv));
  NS_ENSURE_SUCCESS(rv, rv);
  return mailSession->GetTopmostMsgWindow(aWindow);
}

nsresult nsMsgProtocol::SetupTransportState() {
  if (!m_socketIsOpen && m_transport) {
    nsresult rv;

    // open buffered, blocking output stream
    rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0,
                                       getter_AddRefs(m_outputStream));
    if (NS_FAILED(rv)) return rv;
    // we want to open the stream
  }  // if m_transport

  return NS_OK;
}

nsresult nsMsgProtocol::CloseSocket() {
  nsresult rv = NS_OK;
  // release all of our socket state
  m_socketIsOpen = false;
  m_outputStream = nullptr;
  if (m_transport) {
    nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
    if (strans) {
      strans->SetEventSink(nullptr, nullptr);  // break cyclic reference!
    }
  }
  // we need to call Cancel so that we remove the socket transport from the
  // mActiveTransportList.  see bug #30648
  if (m_request) {
    rv = m_request->Cancel(NS_BINDING_ABORTED);
  }
  m_request = nullptr;
  if (m_transport) {
    m_transport->Close(NS_BINDING_ABORTED);
    m_transport = nullptr;
  }

  return rv;
}

/*
 * Writes the data contained in dataBuffer into the current output stream. It
 * also informs the transport layer that this data is now available for
 * transmission. Returns a positive number for success, 0 for failure (not all
 * the bytes were written to the stream, etc). We need to make another pass
 * through this file to install an error system (mscott)
 *
 * No logging is done in the base implementation, so aSuppressLogging is
 * ignored.
 */

nsresult nsMsgProtocol::SendData(const char* dataBuffer,
                                 bool aSuppressLogging) {
  uint32_t writeCount = 0;

  if (dataBuffer && m_outputStream)
    return m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer),
                                 &writeCount);
  // TODO make sure all the bytes in PL_strlen(dataBuffer) were written
  else
    return NS_ERROR_INVALID_ARG;
}

// Whenever data arrives from the connection, core netlib notifices the protocol
// by calling OnDataAvailable. We then read and process the incoming data from
// the input stream.
NS_IMETHODIMP nsMsgProtocol::OnDataAvailable(nsIRequest* request,
                                             nsIInputStream* inStr,
                                             uint64_t sourceOffset,
                                             uint32_t count) {
  // right now, this really just means turn around and churn through the state
  // machine
  nsCOMPtr<nsIURI> uri;
  GetURI(getter_AddRefs(uri));

  return ProcessProtocolState(uri, inStr, sourceOffset, count);
}

NS_IMETHODIMP nsMsgProtocol::OnStartRequest(nsIRequest* request) {
  nsresult rv = NS_OK;
  nsCOMPtr<nsIURI> uri;
  GetURI(getter_AddRefs(uri));

  if (uri) {
    nsCOMPtr<nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(uri);
    rv = aMsgUrl->SetUrlState(true, NS_OK);
    if (m_loadGroup)
      m_loadGroup->AddRequest(static_cast<nsIRequest*>(this),
                              nullptr /* context isupports */);
  }

  // if we are set up as a channel, we should notify our channel listener that
  // we are starting... so pass in ourself as the channel and not the underlying
  // socket or file channel the protocol happens to be using
  if (!mSuppressListenerNotifications && m_channelListener) {
    m_isChannel = true;
    rv = m_channelListener->OnStartRequest(this);
  }

  nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);

  if (strans)
    strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);

  NS_ENSURE_SUCCESS(rv, rv);
  return rv;
}

void nsMsgProtocol::ShowAlertMessage(nsIMsgMailNewsUrl* aMsgUrl,
                                     nsresult aStatus) {
  const char16_t* errorString = nullptr;
  switch (aStatus) {
    case NS_ERROR_UNKNOWN_HOST:
    case NS_ERROR_UNKNOWN_PROXY_HOST:
      errorString = u"unknownHostError";
      break;
    case NS_ERROR_CONNECTION_REFUSED:
    case NS_ERROR_PROXY_CONNECTION_REFUSED:
      errorString = u"connectionRefusedError";
      break;
    case NS_ERROR_NET_TIMEOUT:
      errorString = u"netTimeoutError";
      break;
    case NS_ERROR_NET_RESET:
      errorString = u"netResetError";
      break;
    case NS_ERROR_NET_INTERRUPT:
      errorString = u"netInterruptError";
      break;
    case NS_ERROR_OFFLINE:
      // Don't alert when offline as that is already displayed in the UI.
      return;
    default:
      nsPrintfCString msg(
          "Unexpected status passed to ShowAlertMessage: %" PRIx32,
          static_cast<uint32_t>(aStatus));
      NS_WARNING(msg.get());
      return;
  }

  nsString errorMsg;
  errorMsg.Adopt(FormatStringWithHostNameByName(errorString, aMsgUrl));
  if (errorMsg.IsEmpty()) {
    errorMsg.AssignLiteral(u"[StringID ");
    errorMsg.Append(errorString);
    errorMsg.AppendLiteral(u"?]");
  }

  nsCOMPtr<nsIMsgMailSession> mailSession =
      do_GetService("@mozilla.org/messenger/services/session;1");
  if (mailSession) mailSession->AlertUser(errorMsg, aMsgUrl);
}

// stop binding is a "notification" informing us that the stream associated with
// aURL is going away.
NS_IMETHODIMP nsMsgProtocol::OnStopRequest(nsIRequest* request,
                                           nsresult aStatus) {
  nsresult rv = NS_OK;

  // if we are set up as a channel, we should notify our channel listener that
  // we are starting... so pass in ourself as the channel and not the underlying
  // socket or file channel the protocol happens to be using
  if (!mSuppressListenerNotifications && m_channelListener)
    rv = m_channelListener->OnStopRequest(this, aStatus);

  nsCOMPtr<nsIURI> uri;
  GetURI(getter_AddRefs(uri));

  if (uri) {
    nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(uri);
    rv = msgUrl->SetUrlState(false, aStatus);  // Always returns NS_OK.
    if (m_loadGroup)
      m_loadGroup->RemoveRequest(static_cast<nsIRequest*>(this), nullptr,
                                 aStatus);

    // !m_isChannel because if we're set up as a channel, then the remove
    // request above will handle alerting the user, so we don't need to.
    //
    // !NS_BINDING_ABORTED because we don't want to see an alert if the user
    // cancelled the operation.  also, we'll get here because we call Cancel()
    // to force removal of the nsSocketTransport.  see CloseSocket()
    // bugs #30775 and #30648 relate to this
    if (!m_isChannel && NS_FAILED(aStatus) && (aStatus != NS_BINDING_ABORTED))
      ShowAlertMessage(msgUrl, aStatus);
  }  // if we have a mailnews url.

  // Drop notification callbacks to prevent cycles.
  mCallbacks = nullptr;
  mProgressEventSink = nullptr;
  // Call CloseSocket(), in case we got here because the server dropped the
  // connection while reading, and we never get a chance to get back into
  // the protocol state machine via OnDataAvailable.
  if (m_socketIsOpen) CloseSocket();

  return rv;
}

nsresult nsMsgProtocol::LoadUrl(nsIURI* aURL, nsISupports* aConsumer) {
  // nsMsgProtocol implements nsIChannel, and all channels are required to
  // have non-null loadInfo. So if it's still unset, we've not been correctly
  // initialised.
  MOZ_ASSERT(m_loadInfo);

  // okay now kick us off to the next state...
  // our first state is a process state so drive the state machine...
  nsresult rv = NS_OK;
  nsCOMPtr<nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL, &rv);

  if (NS_SUCCEEDED(rv) && aMsgUrl) {
    bool msgIsInLocalCache;
    aMsgUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);

    // Set the url as a url currently being run...
    rv = aMsgUrl->SetUrlState(true, NS_OK);

    // if the url is given a stream consumer then we should use it to forward
    // calls to...
    if (!m_channelListener &&
        aConsumer)  // if we don't have a registered listener already
    {
      m_channelListener = do_QueryInterface(aConsumer);
      m_isChannel = true;
    }

    if (!m_socketIsOpen) {
      if (m_transport) {
        // open buffered, asynchronous input stream
        nsCOMPtr<nsIInputStream> stream;
        rv = m_transport->OpenInputStream(0, 0, 0, getter_AddRefs(stream));
        if (NS_FAILED(rv)) return rv;

        // m_readCount can be -1 which means "read as much as we can".
        // We pass this on as UINT64_MAX, which is in fact uint64_t(-1).
        // We don't clone m_inputStream here, we simply give up ownership
        // since otherwise the original would never be closed.
        RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
            stream.forget(), 0,
            m_readCount == -1 ? UINT64_MAX : uint64_t(m_readCount));
        nsCOMPtr<nsIInputStreamPump> pump;
        rv = NS_NewInputStreamPump(getter_AddRefs(pump), slicedStream.forget());
        if (NS_FAILED(rv)) return rv;

        m_request = pump;  // keep a reference to the pump so we can cancel it

        // Put us in a state where we are always notified of incoming data.
        // OnDataAvailable() will be called when that happens, which will
        // pass that data into ProcessProtocolState().
        rv = pump->AsyncRead(this);
        NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed");
        m_socketIsOpen = true;  // mark the channel as open
      }
    } else if (!msgIsInLocalCache) {
      // The connection is already open so we should begin processing our url.
      rv = ProcessProtocolState(aURL, nullptr, 0, 0);
    }
  }

  return rv;
}

///////////////////////////////////////////////////////////////////////
// The rest of this file is mostly nsIChannel mumbo jumbo stuff
///////////////////////////////////////////////////////////////////////

nsresult nsMsgProtocol::SetUrl(nsIURI* aURL) {
  m_url = aURL;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetLoadGroup(nsILoadGroup* aLoadGroup) {
  m_loadGroup = aLoadGroup;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
  return GetTRRModeImpl(aTRRMode);
}

NS_IMETHODIMP nsMsgProtocol::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
  return SetTRRModeImpl(aTRRMode);
}

NS_IMETHODIMP nsMsgProtocol::GetOriginalURI(nsIURI** aURI) {
  NS_IF_ADDREF(*aURI = m_originalUrl ? m_originalUrl : m_url);
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetOriginalURI(nsIURI* aURI) {
  m_originalUrl = aURI;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetURI(nsIURI** aURI) {
  NS_IF_ADDREF(*aURI = m_url);
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::Open(nsIInputStream** _retval) {
  nsCOMPtr<nsIStreamListener> listener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);
  return NS_ImplementChannelOpen(this, _retval);
}

NS_IMETHODIMP nsMsgProtocol::AsyncOpen(nsIStreamListener* aListener) {
  nsCOMPtr<nsIStreamListener> listener = aListener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t port;
  rv = m_url->GetPort(&port);
  if (NS_FAILED(rv)) return rv;

  nsAutoCString scheme;
  rv = m_url->GetScheme(scheme);
  if (NS_FAILED(rv)) return rv;

  rv = NS_CheckPortSafety(port, scheme.get());
  if (NS_FAILED(rv)) return rv;

  // set the stream listener and then load the url
  m_isChannel = true;

  m_channelListener = listener;
  return LoadUrl(m_url, nullptr);
}

NS_IMETHODIMP nsMsgProtocol::GetLoadFlags(nsLoadFlags* aLoadFlags) {
  *aLoadFlags = mLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetLoadFlags(nsLoadFlags aLoadFlags) {
  mLoadFlags = aLoadFlags;
  return NS_OK;  // don't fail when trying to set this
}

NS_IMETHODIMP nsMsgProtocol::GetContentType(nsACString& aContentType) {
  // as url dispatching matures, we'll be intelligent and actually start
  // opening the url before specifying the content type. This will allow
  // us to optimize the case where the message url actual refers to
  // a part in the message that has a content type that is not message/rfc822

  if (mContentType.IsEmpty())
    aContentType.AssignLiteral("message/rfc822");
  else
    aContentType = mContentType;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetContentType(const nsACString& aContentType) {
  nsAutoCString charset;
  nsresult rv =
      NS_ParseResponseContentType(aContentType, mContentType, charset);
  if (NS_FAILED(rv) || mContentType.IsEmpty())
    mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
  return rv;
}

NS_IMETHODIMP nsMsgProtocol::GetContentCharset(nsACString& aContentCharset) {
  aContentCharset.Assign(mCharset);
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetContentCharset(
    const nsACString& aContentCharset) {
  mCharset.Assign(aContentCharset);
  return NS_OK;
}

NS_IMETHODIMP
nsMsgProtocol::GetContentDisposition(uint32_t* aContentDisposition) {
  *aContentDisposition = mContentDisposition;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgProtocol::SetContentDisposition(uint32_t aContentDisposition) {
  mContentDisposition = aContentDisposition;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgProtocol::GetContentDispositionFilename(
    nsAString& aContentDispositionFilename) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsMsgProtocol::SetContentDispositionFilename(
    const nsAString& aContentDispositionFilename) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsMsgProtocol::GetContentDispositionHeader(
    nsACString& aContentDispositionHeader) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP nsMsgProtocol::GetContentLength(int64_t* aContentLength) {
  *aContentLength = mContentLength;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetContentLength(int64_t aContentLength) {
  mContentLength = aContentLength;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(
    nsITransportSecurityInfo** secInfo) {
  *secInfo = nullptr;
  if (m_transport) {
    nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
    if (strans) {
      nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
      if (NS_SUCCEEDED(
              strans->GetTlsSocketControl(getter_AddRefs(tlsSocketControl)))) {
        nsCOMPtr<nsITransportSecurityInfo> transportSecInfo;
        if (NS_SUCCEEDED(tlsSocketControl->GetSecurityInfo(
                getter_AddRefs(transportSecInfo)))) {
          transportSecInfo.forget(secInfo);
        }
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetName(nsACString& result) {
  if (m_url) return m_url->GetSpec(result);
  result.Truncate();
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports** aPrincipal) {
  NS_IF_ADDREF(*aPrincipal = mOwner);
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports* aPrincipal) {
  mOwner = aPrincipal;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetLoadGroup(nsILoadGroup** aLoadGroup) {
  NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetLoadInfo(nsILoadInfo** aLoadInfo) {
  NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::SetLoadInfo(nsILoadInfo* aLoadInfo) {
  m_loadInfo = aLoadInfo;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgProtocol::GetNotificationCallbacks(
    nsIInterfaceRequestor** aNotificationCallbacks) {
  NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks.get());
  return NS_OK;
}

NS_IMETHODIMP
nsMsgProtocol::SetNotificationCallbacks(
    nsIInterfaceRequestor* aNotificationCallbacks) {
  mCallbacks = aNotificationCallbacks;
  return NS_OK;
}

NS_IMETHODIMP
nsMsgProtocol::OnTransportStatus(nsITransport* transport, nsresult status,
                                 int64_t progress, int64_t progressMax) {
  if ((mLoadFlags & LOAD_BACKGROUND) || !m_url) return NS_OK;

  // these transport events should not generate any status messages
  if (status == NS_NET_STATUS_RECEIVING_FROM ||
      status == NS_NET_STATUS_SENDING_TO)
    return NS_OK;

  if (!mProgressEventSink) {
    NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
    if (!mProgressEventSink) return NS_OK;
  }

  nsAutoCString host;
  m_url->GetHost(host);

  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
  if (mailnewsUrl) {
    nsCOMPtr<nsIMsgIncomingServer> server;
    mailnewsUrl->GetServer(getter_AddRefs(server));
    if (server) server->GetHostName(host);
  }
  mProgressEventSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get());

  return NS_OK;
}

NS_IMETHODIMP
nsMsgProtocol::GetIsDocument(bool* aIsDocument) {
  return NS_GetIsDocumentChannel(this, aIsDocument);
}

////////////////////////////////////////////////////////////////////////////////
// From nsIRequest
////////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP nsMsgProtocol::IsPending(bool* result) {
  *result = m_channelListener != nullptr;
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::GetStatus(nsresult* status) {
  if (m_request) return m_request->GetStatus(status);

  *status = NS_OK;
  return *status;
}

NS_IMETHODIMP nsMsgProtocol::SetCanceledReason(const nsACString& aReason) {
  return SetCanceledReasonImpl(aReason);
}

NS_IMETHODIMP nsMsgProtocol::GetCanceledReason(nsACString& aReason) {
  return GetCanceledReasonImpl(aReason);
}

NS_IMETHODIMP nsMsgProtocol::CancelWithReason(nsresult aStatus,
                                              const nsACString& aReason) {
  return CancelWithReasonImpl(aStatus, aReason);
}

NS_IMETHODIMP nsMsgProtocol::Cancel(nsresult status) {
  if (m_proxyRequest) m_proxyRequest->Cancel(status);

  if (m_request) return m_request->Cancel(status);

  NS_WARNING("no request to cancel");
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP nsMsgProtocol::GetCanceled(bool* aCanceled) {
  nsresult status = NS_ERROR_FAILURE;
  GetStatus(&status);
  *aCanceled = NS_FAILED(status);
  return NS_OK;
}

NS_IMETHODIMP nsMsgProtocol::Suspend() {
  if (m_request) return m_request->Suspend();

  NS_WARNING("no request to suspend");
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP nsMsgProtocol::Resume() {
  if (m_request) return m_request->Resume();

  NS_WARNING("no request to resume");
  return NS_ERROR_NOT_AVAILABLE;
}

nsresult nsMsgProtocol::PostMessage(nsIURI* url, nsIFile* postFile) {
  if (!url || !postFile) return NS_ERROR_NULL_POINTER;

#define POST_DATA_BUFFER_SIZE 2048

  // mscott -- this function should be re-written to use the file url code
  // so it can be asynch
  nsCOMPtr<nsIInputStream> inputStream;
  nsresult rv =
      NS_NewLocalFileInputStream(getter_AddRefs(inputStream), postFile);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsILineInputStream> lineInputStream(
      do_QueryInterface(inputStream, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  bool more = true;
  nsCString line;
  nsCString outputBuffer;

  do {
    lineInputStream->ReadLine(line, &more);

    /* escape starting periods
     */
    if (line.CharAt(0) == '.') line.Insert('.', 0);
    line.AppendLiteral(CRLF);
    outputBuffer.Append(line);
    // test hack by mscott. If our buffer is almost full, then
    // send it off & reset ourselves
    // to make more room.
    if (outputBuffer.Length() > POST_DATA_BUFFER_SIZE || !more) {
      rv = SendData(outputBuffer.get());
      NS_ENSURE_SUCCESS(rv, rv);
      // does this keep the buffer around? That would be best.
      // Maybe SetLength(0) instead?
      outputBuffer.Truncate();
    }
  } while (more);

  return NS_OK;
}

nsresult nsMsgProtocol::DoGSSAPIStep1(const nsACString& service,
                                      const char* username,
                                      nsCString& response) {
  nsresult rv;
#ifdef DEBUG_BenB
  printf("GSSAPI step 1 for service %s, username %s\n", service, username);
#endif

  // if this fails, then it means that we cannot do GSSAPI SASL.
  m_authModule = nsIAuthModule::CreateInstance("sasl-gssapi");

  m_authModule->Init(service, nsIAuthModule::REQ_DEFAULT, u""_ns,
                     NS_ConvertUTF8toUTF16(username), u""_ns);

  void* outBuf;
  uint32_t outBufLen;
  rv = m_authModule->GetNextToken((void*)nullptr, 0, &outBuf, &outBufLen);
  if (NS_SUCCEEDED(rv) && outBuf) {
    char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
    if (base64Str)
      response.Adopt(base64Str);
    else
      rv = NS_ERROR_OUT_OF_MEMORY;
    free(outBuf);
  }

#ifdef DEBUG_BenB
  printf("GSSAPI step 1 succeeded\n");
#endif
  return rv;
}

nsresult nsMsgProtocol::DoGSSAPIStep2(nsCString& commandResponse,
                                      nsCString& response) {
#ifdef DEBUG_BenB
  printf("GSSAPI step 2\n");
#endif
  nsresult rv;
  void *inBuf, *outBuf;
  uint32_t inBufLen, outBufLen;
  uint32_t len = commandResponse.Length();

  // Cyrus SASL may send us zero length tokens (grrrr)
  if (len > 0) {
    // decode into the input secbuffer
    inBufLen = (len * 3) / 4;  // sufficient size (see plbase64.h)
    inBuf = moz_xmalloc(inBufLen);
    if (!inBuf) return NS_ERROR_OUT_OF_MEMORY;

    // strip off any padding (see bug 230351)
    const char* challenge = commandResponse.get();
    while (challenge[len - 1] == '=') len--;

    // We need to know the exact length of the decoded string to give to
    // the GSSAPI libraries. But NSPR's base64 routine doesn't seem capable
    // of telling us that. So, we figure it out for ourselves.

    // For every 4 characters, add 3 to the destination
    // If there are 3 remaining, add 2
    // If there are 2 remaining, add 1
    // 1 remaining is an error
    inBufLen =
        (len / 4) * 3 + ((len % 4 == 3) ? 2 : 0) + ((len % 4 == 2) ? 1 : 0);

    rv = (PL_Base64Decode(challenge, len, (char*)inBuf))
             ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
             : NS_ERROR_FAILURE;

    free(inBuf);
  } else {
    rv = m_authModule->GetNextToken(NULL, 0, &outBuf, &outBufLen);
  }
  if (NS_SUCCEEDED(rv)) {
    // And in return, we may need to send Cyrus zero length tokens back
    if (outBuf) {
      char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
      if (base64Str)
        response.Adopt(base64Str);
      else
        rv = NS_ERROR_OUT_OF_MEMORY;
    } else
      response.Adopt((char*)moz_xmemdup("", 1));
  }

#ifdef DEBUG_BenB
  printf(NS_SUCCEEDED(rv) ? "GSSAPI step 2 succeeded\n"
                          : "GSSAPI step 2 failed\n");
#endif
  return rv;
}

nsresult nsMsgProtocol::DoNtlmStep1(const nsACString& username,
                                    const nsAString& password,
                                    nsCString& response) {
  nsresult rv;

  m_authModule = nsIAuthModule::CreateInstance("ntlm");

  m_authModule->Init(""_ns, 0, u""_ns, NS_ConvertUTF8toUTF16(username),
                     PromiseFlatString(password));

  void* outBuf;
  uint32_t outBufLen;
  rv = m_authModule->GetNextToken((void*)nullptr, 0, &outBuf, &outBufLen);
  if (NS_SUCCEEDED(rv) && outBuf) {
    char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
    if (base64Str)
      response.Adopt(base64Str);
    else
      rv = NS_ERROR_OUT_OF_MEMORY;
    free(outBuf);
  }

  return rv;
}

nsresult nsMsgProtocol::DoNtlmStep2(nsCString& commandResponse,
                                    nsCString& response) {
  nsresult rv;
  void *inBuf, *outBuf;
  uint32_t inBufLen, outBufLen;
  uint32_t len = commandResponse.Length();

  // decode into the input secbuffer
  inBufLen = (len * 3) / 4;  // sufficient size (see plbase64.h)
  inBuf = moz_xmalloc(inBufLen);
  if (!inBuf) return NS_ERROR_OUT_OF_MEMORY;

  // strip off any padding (see bug 230351)
  const char* challenge = commandResponse.get();
  while (challenge[len - 1] == '=') len--;

  rv = (PL_Base64Decode(challenge, len, (char*)inBuf))
           ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
           : NS_ERROR_FAILURE;

  free(inBuf);
  if (NS_SUCCEEDED(rv) && outBuf) {
    char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
    if (base64Str)
      response.Adopt(base64Str);
    else
      rv = NS_ERROR_OUT_OF_MEMORY;
  }

  if (NS_FAILED(rv)) response = "*";

  return rv;
}

/////////////////////////////////////////////////////////////////////
// nsMsgAsyncWriteProtocol subclass and related helper classes
/////////////////////////////////////////////////////////////////////

class nsMsgProtocolStreamProvider : public nsIOutputStreamCallback {
 public:
  // XXX this probably doesn't need to be threadsafe
  NS_DECL_THREADSAFE_ISUPPORTS

  nsMsgProtocolStreamProvider() {}

  void Init(nsMsgAsyncWriteProtocol* aProtInstance,
            nsIInputStream* aInputStream) {
    mMsgProtocol =
        do_GetWeakReference(static_cast<nsIStreamListener*>(aProtInstance));
    mInStream = aInputStream;
  }

  //
  // nsIOutputStreamCallback implementation ...
  //
  NS_IMETHODIMP OnOutputStreamReady(nsIAsyncOutputStream* aOutStream) override {
    NS_ASSERTION(mInStream, "not initialized");

    nsresult rv;
    uint64_t avail;

    // Write whatever is available in the pipe. If the pipe is empty, then
    // return NS_BASE_STREAM_WOULD_BLOCK; we will resume the write when there
    // is more data.

    rv = mInStream->Available(&avail);
    if (NS_FAILED(rv)) return rv;

    nsMsgAsyncWriteProtocol* protInst = nullptr;
    nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mMsgProtocol);
    if (!callback) return NS_ERROR_FAILURE;
    protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());

    if (avail == 0 && !protInst->mAsyncBuffer.Length()) {
      // ok, stop writing...
      protInst->mSuspendedWrite = true;
      return NS_OK;
    }
    protInst->mSuspendedWrite = false;

    uint32_t bytesWritten;

    if (avail) {
      rv = aOutStream->WriteFrom(mInStream,
                                 std::min(avail, uint64_t(FILE_IO_BUFFER_SIZE)),
                                 &bytesWritten);
      // if were full at the time, the input stream may be backed up and we need
      // to read any remains from the last ODA call before we'll get more ODA
      // calls
      if (protInst->mSuspendedRead) protInst->UnblockPostReader();
    } else {
      rv = aOutStream->Write(protInst->mAsyncBuffer.get(),
                             protInst->mAsyncBuffer.Length(), &bytesWritten);
      protInst->mAsyncBuffer.Cut(0, bytesWritten);
    }

    protInst->UpdateProgress(bytesWritten);

    // try to write again...
    if (NS_SUCCEEDED(rv))
      rv = aOutStream->AsyncWait(this, 0, 0, protInst->mProviderThread);

    NS_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED,
                 "unexpected error writing stream");
    return NS_OK;
  }

 protected:
  virtual ~nsMsgProtocolStreamProvider() {}

  nsWeakPtr mMsgProtocol;
  nsCOMPtr<nsIInputStream> mInStream;
};

NS_IMPL_ISUPPORTS(nsMsgProtocolStreamProvider, nsIOutputStreamCallback)

class nsMsgFilePostHelper : public nsIStreamListener {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER

  nsMsgFilePostHelper() { mSuspendedPostFileRead = false; }
  nsresult Init(nsIOutputStream* aOutStream,
                nsMsgAsyncWriteProtocol* aProtInstance, nsIFile* aFileToPost);
  nsCOMPtr<nsIRequest> mPostFileRequest;
  bool mSuspendedPostFileRead;
  void CloseSocket() { mProtInstance = nullptr; }

 protected:
  virtual ~nsMsgFilePostHelper() {}
  nsCOMPtr<nsIOutputStream> mOutStream;
  nsWeakPtr mProtInstance;
};

NS_IMPL_ISUPPORTS(nsMsgFilePostHelper, nsIStreamListener, nsIRequestObserver)

nsresult nsMsgFilePostHelper::Init(nsIOutputStream* aOutStream,
                                   nsMsgAsyncWriteProtocol* aProtInstance,
                                   nsIFile* aFileToPost) {
  nsresult rv = NS_OK;
  mOutStream = aOutStream;
  mProtInstance =
      do_GetWeakReference(static_cast<nsIStreamListener*>(aProtInstance));

  nsCOMPtr<nsIInputStream> stream;
  rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFileToPost);
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIInputStreamPump> pump;
  rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget());
  if (NS_FAILED(rv)) return rv;

  rv = pump->AsyncRead(this);
  if (NS_FAILED(rv)) return rv;

  mPostFileRequest = pump;
  return NS_OK;
}

NS_IMETHODIMP nsMsgFilePostHelper::OnStartRequest(nsIRequest* aChannel) {
  return NS_OK;
}

NS_IMETHODIMP nsMsgFilePostHelper::OnStopRequest(nsIRequest* aChannel,
                                                 nsresult aStatus) {
  nsMsgAsyncWriteProtocol* protInst = nullptr;
  nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
  if (!callback) return NS_OK;
  protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());

  if (!mSuspendedPostFileRead) protInst->PostDataFinished();

  mSuspendedPostFileRead = false;
  protInst->mFilePostHelper = nullptr;
  return NS_OK;
}

NS_IMETHODIMP nsMsgFilePostHelper::OnDataAvailable(nsIRequest* /* aChannel */,
                                                   nsIInputStream* inStr,
                                                   uint64_t sourceOffset,
                                                   uint32_t count) {
  nsMsgAsyncWriteProtocol* protInst = nullptr;
  nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
  if (!callback) return NS_OK;

  protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());

  if (mSuspendedPostFileRead) {
    protInst->UpdateSuspendedReadBytes(count, protInst->mInsertPeriodRequired);
    return NS_OK;
  }

  protInst->ProcessIncomingPostData(inStr, count);

  if (protInst->mSuspendedWrite) {
    // if we got here then we had suspended the write 'cause we didn't have
    // anymore data to write (i.e. the pipe went empty). So resume the channel
    // to kick things off again.
    protInst->mSuspendedWrite = false;
    protInst->mAsyncOutStream->AsyncWait(protInst->mProvider, 0, 0,
                                         protInst->mProviderThread);
  }

  return NS_OK;
}

NS_IMPL_ADDREF_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)
NS_IMPL_RELEASE_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)

NS_INTERFACE_MAP_BEGIN(nsMsgAsyncWriteProtocol)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)

nsMsgAsyncWriteProtocol::nsMsgAsyncWriteProtocol(nsIURI* aURL)
    : nsMsgProtocol(aURL) {
  mSuspendedWrite = false;
  mSuspendedReadBytes = 0;
  mSuspendedRead = false;
  mInsertPeriodRequired = false;
  mGenerateProgressNotifications = false;
  mSuspendedReadBytesPostPeriod = 0;
  mFilePostHelper = nullptr;
  mNumBytesPosted = 0;
  mFilePostSize = 0;
}

nsMsgAsyncWriteProtocol::~nsMsgAsyncWriteProtocol() {}

NS_IMETHODIMP nsMsgAsyncWriteProtocol::Cancel(nsresult status) {
  mGenerateProgressNotifications = false;

  if (m_proxyRequest) {
    m_proxyRequest->Cancel(status);
  }

  if (m_request) m_request->Cancel(status);

  if (mAsyncOutStream) mAsyncOutStream->CloseWithStatus(status);

  return NS_OK;
}

nsresult nsMsgAsyncWriteProtocol::PostMessage(nsIURI* url, nsIFile* file) {
  nsCOMPtr<nsIStreamListener> listener = new nsMsgFilePostHelper();

  if (!listener) return NS_ERROR_OUT_OF_MEMORY;

  // be sure to initialize some state before posting
  mSuspendedReadBytes = 0;
  mNumBytesPosted = 0;
  file->GetFileSize(&mFilePostSize);
  mSuspendedRead = false;
  mInsertPeriodRequired = false;
  mSuspendedReadBytesPostPeriod = 0;
  mGenerateProgressNotifications = true;

  mFilePostHelper = static_cast<nsMsgFilePostHelper*>(
      static_cast<nsIStreamListener*>(listener));

  static_cast<nsMsgFilePostHelper*>(static_cast<nsIStreamListener*>(listener))
      ->Init(m_outputStream, this, file);

  return NS_OK;
}

nsresult nsMsgAsyncWriteProtocol::SuspendPostFileRead() {
  if (mFilePostHelper && !mFilePostHelper->mSuspendedPostFileRead) {
    // uhoh we need to pause reading in the file until we get unblocked...
    mFilePostHelper->mPostFileRequest->Suspend();
    mFilePostHelper->mSuspendedPostFileRead = true;
  }

  return NS_OK;
}

nsresult nsMsgAsyncWriteProtocol::ResumePostFileRead() {
  if (mFilePostHelper) {
    if (mFilePostHelper->mSuspendedPostFileRead) {
      mFilePostHelper->mPostFileRequest->Resume();
      mFilePostHelper->mSuspendedPostFileRead = false;
    }
  } else  // we must be done with the download so send the '.'
  {
    PostDataFinished();
  }

  return NS_OK;
}

nsresult nsMsgAsyncWriteProtocol::UpdateSuspendedReadBytes(
    uint32_t aNewBytes, bool aAddToPostPeriodByteCount) {
  // depending on our current state, we'll either add aNewBytes to
  // mSuspendedReadBytes or mSuspendedReadBytesAfterPeriod.

  mSuspendedRead = true;
  if (aAddToPostPeriodByteCount)
    mSuspendedReadBytesPostPeriod += aNewBytes;
  else
    mSuspendedReadBytes += aNewBytes;

  return NS_OK;
}

nsresult nsMsgAsyncWriteProtocol::PostDataFinished() {
  nsresult rv = SendData("." CRLF);
  if (NS_FAILED(rv)) return rv;
  mGenerateProgressNotifications = false;
  mPostDataStream = nullptr;
  return NS_OK;
}

nsresult nsMsgAsyncWriteProtocol::ProcessIncomingPostData(nsIInputStream* inStr,
                                                          uint32_t count) {
  if (!m_socketIsOpen) return NS_OK;  // kick out if the socket was canceled

  // We need to quote any '.' that occur at the beginning of a line.
  // but I don't want to waste time reading out the data into a buffer and
  // searching let's try to leverage nsIBufferedInputStream and see if we can
  // "peek" into the current contents for this particular case.

  nsCOMPtr<nsISearchableInputStream> bufferInputStr = do_QueryInterface(inStr);
  NS_ASSERTION(
      bufferInputStr,
      "i made a wrong assumption about the type of stream we are getting");
  NS_ASSERTION(mSuspendedReadBytes == 0, "oops, I missed something");

  if (!mPostDataStream) mPostDataStream = inStr;

  if (bufferInputStr) {
    uint32_t amountWritten;

    while (count > 0) {
      bool found = false;
      uint32_t offset = 0;
      bufferInputStr->Search("\012.", true, &found, &offset);  // LF.

      if (!found || offset > count) {
        // push this data into the output stream
        m_outputStream->WriteFrom(inStr, count, &amountWritten);
        // store any remains which need read out at a later date
        if (count > amountWritten)  // stream will block
        {
          UpdateSuspendedReadBytes(count - amountWritten, false);
          SuspendPostFileRead();
        }
        break;
      } else {
        // count points to the LF in a LF followed by a '.'
        // go ahead and write up to offset..
        m_outputStream->WriteFrom(inStr, offset + 1, &amountWritten);
        count -= amountWritten;
        if (offset + 1 > amountWritten) {
          UpdateSuspendedReadBytes(offset + 1 - amountWritten, false);
          mInsertPeriodRequired = true;
          UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
          SuspendPostFileRead();
          break;
        }

        // write out the extra '.'
        m_outputStream->Write(".", 1, &amountWritten);
        if (amountWritten != 1) {
          mInsertPeriodRequired = true;
          // once we do write out the '.',  if we are now blocked we need to
          // remember the remaining count that comes after the '.' so we can
          // perform processing on that once we become unblocked.
          UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
          SuspendPostFileRead();
          break;
        }
      }
    }  // while count > 0
  }

  return NS_OK;
}
nsresult nsMsgAsyncWriteProtocol::UnblockPostReader() {
  uint32_t amountWritten = 0;

  if (!m_socketIsOpen) return NS_OK;  // kick out if the socket was canceled

  if (mSuspendedRead) {
    // (1) attempt to write out any remaining read bytes we need in order to
    // unblock the reader
    if (mSuspendedReadBytes > 0 && mPostDataStream) {
      uint64_t avail = 0;
      mPostDataStream->Available(&avail);

      m_outputStream->WriteFrom(mPostDataStream,
                                std::min(avail, uint64_t(mSuspendedReadBytes)),
                                &amountWritten);
      // hmm sometimes my mSuspendedReadBytes is getting out of whack...so for
      // now, reset it if necessary.
      if (mSuspendedReadBytes > avail) mSuspendedReadBytes = avail;

      if (mSuspendedReadBytes > amountWritten)
        mSuspendedReadBytes -= amountWritten;
      else
        mSuspendedReadBytes = 0;
    }

    // (2) if we are now unblocked, and we need to insert a '.' then do so
    // now...
    if (mInsertPeriodRequired && mSuspendedReadBytes == 0) {
      amountWritten = 0;
      m_outputStream->Write(".", 1, &amountWritten);
      if (amountWritten == 1)  // if we succeeded then clear pending '.' flag
        mInsertPeriodRequired = false;
    }

    // (3) if we inserted a '.' and we still have bytes after the '.' which need
    // processed before the stream is unblocked then fake an ODA call to handle
    // this now...
    if (!mInsertPeriodRequired && mSuspendedReadBytesPostPeriod > 0) {
      // these bytes actually need processed for extra '.''s.....
      uint32_t postbytes = mSuspendedReadBytesPostPeriod;
      mSuspendedReadBytesPostPeriod = 0;
      ProcessIncomingPostData(mPostDataStream, postbytes);
    }

    // (4) determine if we are out of the suspended read state...
    if (mSuspendedReadBytes == 0 && !mInsertPeriodRequired &&
        mSuspendedReadBytesPostPeriod == 0) {
      mSuspendedRead = false;
      ResumePostFileRead();
    }

  }  // if we are in the suspended read state

  return NS_OK;
}

nsresult nsMsgAsyncWriteProtocol::SetupTransportState() {
  nsresult rv = NS_OK;

  if (!m_outputStream && m_transport) {
    // first create a pipe which we'll use to write the data we want to send
    // into.
    nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
    rv = pipe->Init(true, true, 1024, 8);
    NS_ENSURE_SUCCESS(rv, rv);

    nsIAsyncInputStream* inputStream = nullptr;
    // This always succeeds because the pipe is initialized above.
    MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(&inputStream));
    mInStream = dont_AddRef(static_cast<nsIInputStream*>(inputStream));

    nsIAsyncOutputStream* outputStream = nullptr;
    // This always succeeds because the pipe is initialized above.
    MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(&outputStream));
    m_outputStream = dont_AddRef(static_cast<nsIOutputStream*>(outputStream));

    mProviderThread = do_GetCurrentThread();

    nsMsgProtocolStreamProvider* provider = new nsMsgProtocolStreamProvider();

    if (!provider) return NS_ERROR_OUT_OF_MEMORY;

    provider->Init(this, mInStream);
    mProvider = provider;  // ADDREF

    nsCOMPtr<nsIOutputStream> stream;
    rv = m_transport->OpenOutputStream(0, 0, 0, getter_AddRefs(stream));
    if (NS_FAILED(rv)) return rv;

    mAsyncOutStream = do_QueryInterface(stream, &rv);
    if (NS_FAILED(rv)) return rv;

    // wait for the output stream to become writable
    rv = mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
  }  // if m_transport

  return rv;
}

nsresult nsMsgAsyncWriteProtocol::CloseSocket() {
  nsresult rv = NS_OK;
  if (mAsyncOutStream) mAsyncOutStream->CloseWithStatus(NS_BINDING_ABORTED);

  nsMsgProtocol::CloseSocket();

  if (mFilePostHelper) {
    mFilePostHelper->CloseSocket();
    mFilePostHelper = nullptr;
  }

  mAsyncOutStream = nullptr;
  mProvider = nullptr;
  mProviderThread = nullptr;
  mAsyncBuffer.Truncate();
  return rv;
}

void nsMsgAsyncWriteProtocol::UpdateProgress(uint32_t aNewBytes) {
  if (!mGenerateProgressNotifications) return;

  mNumBytesPosted += aNewBytes;
  if (mFilePostSize > 0) {
    nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
    if (!mailUrl) return;

    nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
    mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
    if (!statusFeedback) return;

    nsCOMPtr<nsIWebProgressListener> webProgressListener(
        do_QueryInterface(statusFeedback));
    if (!webProgressListener) return;

    // XXX not sure if m_request is correct here
    webProgressListener->OnProgressChange(nullptr, m_request, mNumBytesPosted,
                                          static_cast<uint32_t>(mFilePostSize),
                                          mNumBytesPosted, mFilePostSize);
  }

  return;
}

nsresult nsMsgAsyncWriteProtocol::SendData(const char* dataBuffer,
                                           bool aSuppressLogging) {
  this->mAsyncBuffer.Append(dataBuffer);
  if (!mAsyncOutStream) return NS_ERROR_FAILURE;
  return mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
}

char16_t* FormatStringWithHostNameByName(const char16_t* stringName,
                                         nsIMsgMailNewsUrl* msgUri) {
  if (!msgUri) return nullptr;

  nsresult rv;

  nsCOMPtr<nsIStringBundleService> sBundleService =
      mozilla::components::StringBundle::Service();
  NS_ENSURE_TRUE(sBundleService, nullptr);

  nsCOMPtr<nsIStringBundle> sBundle;
  rv = sBundleService->CreateBundle(MSGS_URL, getter_AddRefs(sBundle));
  NS_ENSURE_SUCCESS(rv, nullptr);

  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = msgUri->GetServer(getter_AddRefs(server));
  NS_ENSURE_SUCCESS(rv, nullptr);

  nsCString hostName;
  rv = server->GetHostName(hostName);
  NS_ENSURE_SUCCESS(rv, nullptr);

  AutoTArray<nsString, 1> params;
  CopyASCIItoUTF16(hostName, *params.AppendElement());
  nsAutoString str;
  rv = sBundle->FormatStringFromName(NS_ConvertUTF16toUTF8(stringName).get(),
                                     params, str);
  NS_ENSURE_SUCCESS(rv, nullptr);

  return ToNewUnicode(str);
}

// vim: ts=2 sw=2
