mailnews/base/util/nsMsgMailNewsUrl.cpp
author Jorg K <jorgk@jorgk.com>
Fri, 19 Oct 2018 10:32:36 +0200
changeset 32737 fb0fff3cd0f7f3b33d779c393e4dabf06e371488
parent 32576 c7b5816c507193e924cbf75b26d25d062579b865
child 32762 0f116c762e79e2d8236cf7e914104e6fb2a8b3f4
permissions -rw-r--r--
Bug 1500347 - Port Bug 1492648: nsIDocShell.loadURI() takes a nsDocShellLoadState parameter now. rs=bustage-fix

/* -*- 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 "nsMsgMailNewsUrl.h"
#include "nsMsgBaseCID.h"
#include "nsIMsgAccountManager.h"
#include "nsString.h"
#include "nsILoadGroup.h"
#include "nsIDocShell.h"
#include "nsIWebProgress.h"
#include "nsIWebProgressListener.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIIOService.h"
#include "nsNetCID.h"
#include "nsIStreamListener.h"
#include "nsIOutputStream.h"
#include "nsIInputStream.h"
#include "nsNetUtil.h"
#include "nsIFile.h"
#include "prmem.h"
#include <time.h>
#include "nsMsgUtils.h"
#include "mozilla/Services.h"
#include "nsProxyRelease.h"
#include "mozilla/Encoding.h"
#include "nsDocShellLoadState.h"

nsMsgMailNewsUrl::nsMsgMailNewsUrl()
{
  // nsIURI specific state
  m_runningUrl = false;
  m_updatingFolder = false;
  m_msgIsInLocalCache = false;
  m_suppressErrorMsgs = false;
  m_hasNormalizedOrigin = false;  // SetSpecInternal() will set this correctly.
  mMaxProgress = -1;
}

#define NOTIFY_URL_LISTENERS(propertyfunc_, params_)                   \
  PR_BEGIN_MACRO                                                       \
  nsTObserverArray<nsCOMPtr<nsIUrlListener> >::ForwardIterator iter(mUrlListeners); \
  while (iter.HasMore()) {                                             \
    nsCOMPtr<nsIUrlListener> listener = iter.GetNext();                \
    listener->propertyfunc_ params_;                                   \
  }                                                                    \
  PR_END_MACRO

nsMsgMailNewsUrl::~nsMsgMailNewsUrl()
{
  // In IMAP this URL is created and destroyed on the imap thread,
  // so we must ensure that releases of XPCOM objects (which might be
  // implemented by non-threadsafe JS components) are released on the
  // main thread.
  NS_ReleaseOnMainThreadSystemGroup("nsMsgMailNewsUrl::m_baseURL", m_baseURL.forget());
  NS_ReleaseOnMainThreadSystemGroup("nsMsgMailNewsUrl::mMimeHeaders", mMimeHeaders.forget());
  NS_ReleaseOnMainThreadSystemGroup("nsMsgMailNewsUrl::m_searchSession", m_searchSession.forget());
  NS_ReleaseOnMainThreadSystemGroup("nsMsgMailNewsUrl::mMsgHeaderSink", mMsgHeaderSink.forget());

  nsTObserverArray<nsCOMPtr<nsIUrlListener>>::ForwardIterator iter(mUrlListeners);
  while (iter.HasMore()) {
    nsCOMPtr<nsIUrlListener> listener = iter.GetNext();
    if (listener)
      NS_ReleaseOnMainThreadSystemGroup("nsMsgMailNewsUrl::mUrlListeners", listener.forget());
  }
}

NS_IMPL_ADDREF(nsMsgMailNewsUrl)
NS_IMPL_RELEASE(nsMsgMailNewsUrl)

// We want part URLs to QI to nsIURIWithSpecialOrigin so we can give
// them a "normalized" origin. URLs that already have a "normalized"
// origin should not QI to nsIURIWithSpecialOrigin.
NS_INTERFACE_MAP_BEGIN(nsMsgMailNewsUrl)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgMailNewsUrl)
  NS_INTERFACE_MAP_ENTRY(nsIMsgMailNewsUrl)
  NS_INTERFACE_MAP_ENTRY(nsIURL)
  NS_INTERFACE_MAP_ENTRY(nsIURI)
  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIURIWithSpecialOrigin, m_hasNormalizedOrigin)
NS_INTERFACE_MAP_END

// Support for nsIURIWithSpecialOrigin.
NS_IMETHODIMP nsMsgMailNewsUrl::GetOrigin(nsIURI **aOrigin)
{
  MOZ_ASSERT(m_hasNormalizedOrigin,
    "nsMsgMailNewsUrl::GetOrigin() can only be called for URLs with normalized spec");

  if (!m_normalizedOrigin) {
    nsCOMPtr <nsIMsgMessageUrl> msgUrl;
    QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl));

    nsAutoCString spec;
    if (!msgUrl || NS_FAILED(msgUrl->GetNormalizedSpec(spec))) {
      MOZ_ASSERT(false, "Can't get normalized spec");
      // just use the normal spec.
      GetSpec(spec);
    }

    nsresult rv = NS_NewURI(getter_AddRefs(m_normalizedOrigin), spec);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  NS_IF_ADDREF(*aOrigin = m_normalizedOrigin);
  return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////////
// Begin nsIMsgMailNewsUrl specific support
////////////////////////////////////////////////////////////////////////////////////

nsresult nsMsgMailNewsUrl::GetUrlState(bool * aRunningUrl)
{
  if (aRunningUrl)
    *aRunningUrl = m_runningUrl;

  return NS_OK;
}

nsresult nsMsgMailNewsUrl::SetUrlState(bool aRunningUrl, nsresult aExitCode)
{
  // if we already knew this running state, return, unless the url was aborted
  if (m_runningUrl == aRunningUrl && aExitCode != NS_MSG_ERROR_URL_ABORTED)
    return NS_OK;
  m_runningUrl = aRunningUrl;
  nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;

  // put this back - we need it for urls that don't run through the doc loader
  if (NS_SUCCEEDED(GetStatusFeedback(getter_AddRefs(statusFeedback))) && statusFeedback)
  {
    if (m_runningUrl)
      statusFeedback->StartMeteors();
    else
    {
      statusFeedback->ShowProgress(0);
      statusFeedback->StopMeteors();
    }
  }

  if (m_runningUrl)
  {
    NOTIFY_URL_LISTENERS(OnStartRunningUrl, (this));
  }
  else
  {
    NOTIFY_URL_LISTENERS(OnStopRunningUrl, (this, aExitCode));
    mUrlListeners.Clear();
  }

  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::RegisterListener(nsIUrlListener *aUrlListener)
{
  NS_ENSURE_ARG_POINTER(aUrlListener);
  mUrlListeners.AppendElement(aUrlListener);
  return NS_OK;
}

nsresult nsMsgMailNewsUrl::UnRegisterListener(nsIUrlListener *aUrlListener)
{
  NS_ENSURE_ARG_POINTER(aUrlListener);

  // Due to the way mailnews is structured, some listeners attempt to remove
  // themselves twice. This may in fact be an error in the coding, however
  // if they didn't do it as they do currently, then they could fail to remove
  // their listeners.
  mUrlListeners.RemoveElement(aUrlListener);

  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetServer(nsIMsgIncomingServer ** aIncomingServer)
{
  // mscott --> we could cache a copy of the server here....but if we did, we run
  // the risk of leaking the server if any single url gets leaked....of course that
  // shouldn't happen...but it could. so i'm going to look it up every time and
  // we can look at caching it later.

  nsresult rv;

  nsAutoCString urlstr;
  rv = m_baseURL->GetSpec(urlstr);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIURL> url;
  rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID).SetSpec(urlstr).Finalize(url);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString scheme;
  rv = GetScheme(scheme);
  if (NS_SUCCEEDED(rv))
  {
    if (scheme.EqualsLiteral("pop"))
      scheme.AssignLiteral("pop3");
    // we use "nntp" in the server list so translate it here.
    if (scheme.EqualsLiteral("news"))
      scheme.AssignLiteral("nntp");
    rv = NS_MutateURI(url).SetScheme(scheme).Finalize(url);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIMsgAccountManager> accountManager =
      do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIMsgIncomingServer> server;
    rv = accountManager->FindServerByURI(url, false, aIncomingServer);
    if (!*aIncomingServer && scheme.EqualsLiteral("imap"))
    {
      // look for any imap server with this host name so clicking on
      // other users folder urls will work. We could override this method
      // for imap urls, or we could make caching of servers work and
      // just set the server in the imap code for this case.
      rv = NS_MutateURI(url).SetUserPass(EmptyCString()).Finalize(url);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = accountManager->FindServerByURI(url, false, aIncomingServer);
    }
  }

  return rv;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgWindow(nsIMsgWindow **aMsgWindow)
{
  NS_ENSURE_ARG_POINTER(aMsgWindow);
  *aMsgWindow = nullptr;

  nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
  msgWindow.forget(aMsgWindow);
  return *aMsgWindow ? NS_OK : NS_ERROR_NULL_POINTER;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgWindow(nsIMsgWindow *aMsgWindow)
{
#ifdef DEBUG_David_Bienvenu
  NS_ASSERTION(aMsgWindow || !m_msgWindowWeak, "someone crunching non-null msg window");
#endif
  m_msgWindowWeak = do_GetWeakReference(aMsgWindow);
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetStatusFeedback(nsIMsgStatusFeedback **aMsgFeedback)
{
  // note: it is okay to return a null status feedback and not return an error
  // it's possible the url really doesn't have status feedback
  *aMsgFeedback = nullptr;
  if (!m_statusFeedbackWeak)
  {
    nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
    if (msgWindow)
      msgWindow->GetStatusFeedback(aMsgFeedback);
  }
  else
  {
    nsCOMPtr<nsIMsgStatusFeedback> statusFeedback(do_QueryReferent(m_statusFeedbackWeak));
    statusFeedback.forget(aMsgFeedback);
  }
  return *aMsgFeedback ? NS_OK : NS_ERROR_NULL_POINTER;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetStatusFeedback(nsIMsgStatusFeedback *aMsgFeedback)
{
  if (aMsgFeedback)
    m_statusFeedbackWeak = do_GetWeakReference(aMsgFeedback);
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetMaxProgress(int64_t *aMaxProgress)
{
  *aMaxProgress = mMaxProgress;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetMaxProgress(int64_t aMaxProgress)
{
  mMaxProgress = aMaxProgress;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetLoadGroup(nsILoadGroup **aLoadGroup)
{
  *aLoadGroup = nullptr;
  // note: it is okay to return a null load group and not return an error
  // it's possible the url really doesn't have load group
  nsCOMPtr<nsILoadGroup> loadGroup (do_QueryReferent(m_loadGroupWeak));
  if (!loadGroup)
  {
    nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
    if (msgWindow)
    {
      // XXXbz This is really weird... why are we getting some
      // random loadgroup we're not really a part of?
      nsCOMPtr<nsIDocShell> docShell;
      msgWindow->GetRootDocShell(getter_AddRefs(docShell));
      loadGroup = do_GetInterface(docShell);
      m_loadGroupWeak = do_GetWeakReference(loadGroup);
    }
  }
  loadGroup.forget(aLoadGroup);
  return *aLoadGroup ? NS_OK : NS_ERROR_NULL_POINTER;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetUpdatingFolder(bool *aResult)
{
  NS_ENSURE_ARG(aResult);
  *aResult = m_updatingFolder;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetUpdatingFolder(bool updatingFolder)
{
  m_updatingFolder = updatingFolder;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgIsInLocalCache(bool *aMsgIsInLocalCache)
{
  NS_ENSURE_ARG(aMsgIsInLocalCache);
  *aMsgIsInLocalCache = m_msgIsInLocalCache;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgIsInLocalCache(bool aMsgIsInLocalCache)
{
  m_msgIsInLocalCache = aMsgIsInLocalCache;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetSuppressErrorMsgs(bool *aSuppressErrorMsgs)
{
  NS_ENSURE_ARG(aSuppressErrorMsgs);
  *aSuppressErrorMsgs = m_suppressErrorMsgs;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetSuppressErrorMsgs(bool aSuppressErrorMsgs)
{
  m_suppressErrorMsgs = aSuppressErrorMsgs;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::IsUrlType(uint32_t type, bool *isType)
{
  //base class doesn't know about any specific types
  NS_ENSURE_ARG(isType);
  *isType = false;
  return NS_OK;

}

NS_IMETHODIMP nsMsgMailNewsUrl::SetSearchSession(nsIMsgSearchSession *aSearchSession)
{
  if (aSearchSession)
    m_searchSession = aSearchSession;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetSearchSession(nsIMsgSearchSession **aSearchSession)
{
  NS_ENSURE_ARG(aSearchSession);
  NS_IF_ADDREF(*aSearchSession = m_searchSession);
  return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////////
// End nsIMsgMailNewsUrl specific support
////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////
// Begin nsIURI support
////////////////////////////////////////////////////////////////////////////////////


NS_IMETHODIMP nsMsgMailNewsUrl::GetSpec(nsACString &aSpec)
{
  return m_baseURL->GetSpec(aSpec);
}

#define FILENAME_PART_LEN 10

nsresult nsMsgMailNewsUrl::SetSpecInternal(const nsACString &aSpec)
{
  nsAutoCString spec(aSpec);
  // Parse out "filename" attribute if present.
  char *start, *end;
  start = PL_strcasestr(spec.BeginWriting(),"?filename=");
  if (!start)
    start = PL_strcasestr(spec.BeginWriting(),"&filename=");
  if (start)
  { // Make sure we only get our own value.
    end = PL_strcasestr((char*)(start+FILENAME_PART_LEN),"&");
    if (end)
    {
      *end = 0;
      mAttachmentFileName = start+FILENAME_PART_LEN;
      *end = '&';
    }
    else
      mAttachmentFileName = start+FILENAME_PART_LEN;
  }

  // Now, set the rest.
  nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID).SetSpec(aSpec).Finalize(m_baseURL);
  NS_ENSURE_SUCCESS(rv, rv);

  // Check whether the URL is in normalized form.
  nsCOMPtr <nsIMsgMessageUrl> msgUrl;
  QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl));

  nsAutoCString normalizedSpec;
  if (!msgUrl || NS_FAILED(msgUrl->GetNormalizedSpec(normalizedSpec))) {
    // If we can't get the normalized spec, never QI this to nsIURIWithSpecialOrigin.
    m_hasNormalizedOrigin = false;
  } else {
    m_hasNormalizedOrigin = !spec.Equals(normalizedSpec);
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetPrePath(nsACString &aPrePath)
{
  return m_baseURL->GetPrePath(aPrePath);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetScheme(nsACString &aScheme)
{
  return m_baseURL->GetScheme(aScheme);
}

nsresult nsMsgMailNewsUrl::SetScheme(const nsACString &aScheme)
{
  return NS_MutateURI(m_baseURL).SetScheme(aScheme).Finalize(m_baseURL);
}


NS_IMETHODIMP nsMsgMailNewsUrl::GetUserPass(nsACString &aUserPass)
{
  return m_baseURL->GetUserPass(aUserPass);
}

nsresult nsMsgMailNewsUrl::SetUserPass(const nsACString &aUserPass)
{
  return NS_MutateURI(m_baseURL).SetUserPass(aUserPass).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetUsername(nsACString &aUsername)
{
  /* note:  this will return an escaped string */
  return m_baseURL->GetUsername(aUsername);
}

nsresult nsMsgMailNewsUrl::SetUsername(const nsACString &aUsername)
{
  return NS_MutateURI(m_baseURL).SetUsername(aUsername).Finalize(m_baseURL);
}

nsresult nsMsgMailNewsUrl::SetUsernameInternal(const nsACString &aUsername)
{
  return NS_MutateURI(m_baseURL).SetUsername(aUsername).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetPassword(nsACString &aPassword)
{
  return m_baseURL->GetPassword(aPassword);
}

nsresult nsMsgMailNewsUrl::SetPassword(const nsACString &aPassword)
{
  return NS_MutateURI(m_baseURL).SetPassword(aPassword).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetHostPort(nsACString &aHostPort)
{
  return m_baseURL->GetHostPort(aHostPort);
}

nsresult nsMsgMailNewsUrl::SetHostPort(const nsACString &aHostPort)
{
  return NS_MutateURI(m_baseURL).SetHostPort(aHostPort).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetHost(nsACString &aHost)
{
  return m_baseURL->GetHost(aHost);
}

nsresult nsMsgMailNewsUrl::SetHost(const nsACString &aHost)
{
  return NS_MutateURI(m_baseURL).SetHost(aHost).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetPort(int32_t *aPort)
{
  return m_baseURL->GetPort(aPort);
}

nsresult nsMsgMailNewsUrl::SetPort(int32_t aPort)
{
  return NS_MutateURI(m_baseURL).SetPort(aPort).Finalize(m_baseURL);
}

nsresult nsMsgMailNewsUrl::SetPortInternal(int32_t aPort)
{
  return NS_MutateURI(m_baseURL).SetPort(aPort).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetPathQueryRef(nsACString &aPath)
{
  return m_baseURL->GetPathQueryRef(aPath);
}

nsresult nsMsgMailNewsUrl::SetPathQueryRef(const nsACString &aPath)
{
  return NS_MutateURI(m_baseURL).SetPathQueryRef(aPath).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHost(nsACString &aHostA)
{
    return m_baseURL->GetAsciiHost(aHostA);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHostPort(nsACString &aHostPortA)
{
  return m_baseURL->GetAsciiHostPort(aHostPortA);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiSpec(nsACString &aSpecA)
{
    return m_baseURL->GetAsciiSpec(aSpecA);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetBaseURI(nsIURI **aBaseURI)
{
  NS_ENSURE_ARG_POINTER(aBaseURI);
  return m_baseURL->QueryInterface(NS_GET_IID(nsIURI), (void**) aBaseURI);
}

NS_IMETHODIMP nsMsgMailNewsUrl::Equals(nsIURI *other, bool *_retval)
{
  // The passed-in URI might be a mail news url. Pass our inner URL to its
  // Equals method. The other mail news url will then pass its inner URL to
  // to the Equals method of our inner URL. Other URIs will return false.
  if (other)
    return other->Equals(m_baseURL, _retval);

  return m_baseURL->Equals(other, _retval);
}

NS_IMETHODIMP nsMsgMailNewsUrl::EqualsExceptRef(nsIURI *other, bool *result)
{
  // The passed-in URI might be a mail news url. Pass our inner URL to its
  // Equals method. The other mail news url will then pass its inner URL to
  // to the Equals method of our inner URL. Other URIs will return false.
  if (other)
    return other->EqualsExceptRef(m_baseURL, result);

  return m_baseURL->EqualsExceptRef(other, result);
}

NS_IMETHODIMP
nsMsgMailNewsUrl::GetSpecIgnoringRef(nsACString &result)
{
  return m_baseURL->GetSpecIgnoringRef(result);
}

NS_IMETHODIMP
nsMsgMailNewsUrl::GetDisplaySpec(nsACString& aUnicodeSpec)
{
  return GetSpec(aUnicodeSpec);
}

NS_IMETHODIMP
nsMsgMailNewsUrl::GetDisplayHostPort(nsACString& aUnicodeHostPort)
{
  return GetHostPort(aUnicodeHostPort);
}

NS_IMETHODIMP
nsMsgMailNewsUrl::GetDisplayHost(nsACString& aUnicodeHost)
{
  return GetHost(aUnicodeHost);
}

NS_IMETHODIMP
nsMsgMailNewsUrl::GetDisplayPrePath(nsACString& aPrePath)
{
  return GetPrePath(aPrePath);
}

NS_IMETHODIMP
nsMsgMailNewsUrl::GetHasRef(bool *result)
{
  return m_baseURL->GetHasRef(result);
}

NS_IMETHODIMP nsMsgMailNewsUrl::SchemeIs(const char *aScheme, bool *_retval)
{
  return m_baseURL->SchemeIs(aScheme, _retval);
}

nsresult
nsMsgMailNewsUrl::Clone(nsIURI** _retval)
{
  nsresult rv;
  nsAutoCString urlSpec;
  nsCOMPtr<nsIIOService> ioService =
    mozilla::services::GetIOService();
  NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
  rv = GetSpec(urlSpec);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIURI> newUri;
  rv = ioService->NewURI(urlSpec, nullptr, nullptr, getter_AddRefs(newUri));
  NS_ENSURE_SUCCESS(rv, rv);

  // add the msg window to the cloned url
  nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
  if (msgWindow)
  {
    nsCOMPtr<nsIMsgMailNewsUrl> msgMailNewsUrl = do_QueryInterface(newUri, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    msgMailNewsUrl->SetMsgWindow(msgWindow);
  }

  newUri.forget(_retval);
  return rv;
}

NS_IMETHODIMP nsMsgMailNewsUrl::Resolve(const nsACString &relativePath, nsACString &result)
{
  // only resolve anchor urls....i.e. urls which start with '#' against the mailnews url...
  // everything else shouldn't be resolved against mailnews urls.
  nsresult rv = NS_OK;

  if (relativePath.IsEmpty())
  {
    // Return base URL.
    rv = GetSpec(result);
  }
  else if (!relativePath.IsEmpty() && relativePath.First() == '#') // an anchor
  {
    rv = m_baseURL->Resolve(relativePath, result);
  }
  else
  {
    // if relativePath is a complete url with it's own scheme then allow it...
    nsCOMPtr<nsIIOService> ioService =
      mozilla::services::GetIOService();
    NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
    nsAutoCString scheme;

    rv = ioService->ExtractScheme(relativePath, scheme);
    // if we have a fully qualified scheme then pass the relative path back as the result
    if (NS_SUCCEEDED(rv) && !scheme.IsEmpty())
    {
      result = relativePath;
      rv = NS_OK;
    }
    else
    {
      result.Truncate();
      rv = NS_ERROR_FAILURE;
    }
  }

  return rv;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetDirectory(nsACString &aDirectory)
{
  return m_baseURL->GetDirectory(aDirectory);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetFileName(nsACString &aFileName)
{
  if (!mAttachmentFileName.IsEmpty())
  {
    aFileName = mAttachmentFileName;
    return NS_OK;
  }
  return m_baseURL->GetFileName(aFileName);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetFileBaseName(nsACString &aFileBaseName)
{
  return m_baseURL->GetFileBaseName(aFileBaseName);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetFileExtension(nsACString &aFileExtension)
{
  if (!mAttachmentFileName.IsEmpty())
  {
    int32_t pos = mAttachmentFileName.RFindChar(char16_t('.'));
    if (pos > 0)
      aFileExtension = Substring(mAttachmentFileName, pos + 1 /* skip the '.' */);
    return NS_OK;
  }
  return m_baseURL->GetFileExtension(aFileExtension);
}

nsresult nsMsgMailNewsUrl::SetFileNameInternal(const nsACString &aFileName)
{
  mAttachmentFileName = aFileName;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetQuery(nsACString &aQuery)
{
  return m_baseURL->GetQuery(aQuery);
}

nsresult nsMsgMailNewsUrl::SetQuery(const nsACString &aQuery)
{
  return NS_MutateURI(m_baseURL).SetQuery(aQuery).Finalize(m_baseURL);
}

nsresult nsMsgMailNewsUrl::SetQueryInternal(const nsACString &aQuery)
{
  return NS_MutateURI(m_baseURL).SetQuery(aQuery).Finalize(m_baseURL);
}

nsresult nsMsgMailNewsUrl::SetQueryWithEncoding(const nsACString &aQuery, const mozilla::Encoding* aEncoding)
{
  return NS_MutateURI(m_baseURL).SetQueryWithEncoding(aQuery, aEncoding).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetRef(nsACString &aRef)
{
  return m_baseURL->GetRef(aRef);
}

nsresult nsMsgMailNewsUrl::SetRef(const nsACString &aRef)
{
  return NS_MutateURI(m_baseURL).SetRef(aRef).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetFilePath(nsACString &o_DirFile)
{
  return m_baseURL->GetFilePath(o_DirFile);
}

nsresult nsMsgMailNewsUrl::SetFilePath(const nsACString &i_DirFile)
{
  return NS_MutateURI(m_baseURL).SetFilePath(i_DirFile).Finalize(m_baseURL);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetCommonBaseSpec(nsIURI *uri2, nsACString &result)
{
  return m_baseURL->GetCommonBaseSpec(uri2, result);
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetRelativeSpec(nsIURI *uri2, nsACString &result)
{
  return m_baseURL->GetRelativeSpec(uri2, result);
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetMemCacheEntry(nsICacheEntry *memCacheEntry)
{
  m_memCacheEntry = memCacheEntry;
  return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetMemCacheEntry(nsICacheEntry **memCacheEntry)
{
  NS_ENSURE_ARG(memCacheEntry);
  nsresult rv = NS_OK;

  if (m_memCacheEntry)
  {
    NS_ADDREF(*memCacheEntry = m_memCacheEntry);
  }
  else
  {
    *memCacheEntry = nullptr;
    return NS_ERROR_NULL_POINTER;
  }

  return rv;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetMimeHeaders(nsIMimeHeaders * *mimeHeaders)
{
    NS_ENSURE_ARG_POINTER(mimeHeaders);
    NS_IF_ADDREF(*mimeHeaders = mMimeHeaders);
    return (mMimeHeaders) ? NS_OK : NS_ERROR_NULL_POINTER;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetMimeHeaders(nsIMimeHeaders *mimeHeaders)
{
    mMimeHeaders = mimeHeaders;
    return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::LoadURI(nsIDocShell* docShell,
                                        uint32_t aLoadFlags)
{
  NS_ENSURE_ARG_POINTER(docShell);
  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState();
  loadState->SetURI(this);
  loadState->SetLoadFlags(aLoadFlags);
  loadState->SetFirstParty(false);
  return docShell->LoadURI(loadState);
}

#define SAVE_BUF_SIZE FILE_IO_BUFFER_SIZE
class nsMsgSaveAsListener : public nsIStreamListener
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER

  nsMsgSaveAsListener(nsIFile *aFile, bool addDummyEnvelope);
  nsresult SetupMsgWriteStream(nsIFile *aFile, bool addDummyEnvelope);
protected:
  virtual ~nsMsgSaveAsListener();
  nsCOMPtr<nsIOutputStream> m_outputStream;
  nsCOMPtr<nsIFile> m_outputFile;
  bool m_addDummyEnvelope;
  bool m_writtenData;
  uint32_t m_leftOver;
  char m_dataBuffer[SAVE_BUF_SIZE+1]; // temporary buffer for this save operation

};

NS_IMPL_ISUPPORTS(nsMsgSaveAsListener,
                   nsIStreamListener,
                   nsIRequestObserver)

nsMsgSaveAsListener::nsMsgSaveAsListener(nsIFile *aFile, bool addDummyEnvelope)
{
  m_outputFile = aFile;
  m_writtenData = false;
  m_addDummyEnvelope = addDummyEnvelope;
  m_leftOver = 0;
}

nsMsgSaveAsListener::~nsMsgSaveAsListener()
{
}

NS_IMETHODIMP nsMsgSaveAsListener::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
{
  return NS_OK;
}

NS_IMETHODIMP
nsMsgSaveAsListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus)
{
  if (m_outputStream)
  {
    m_outputStream->Flush();
    m_outputStream->Close();
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgSaveAsListener::OnDataAvailable(nsIRequest* request,
                                  nsISupports* aSupport,
                                  nsIInputStream* inStream,
                                  uint64_t srcOffset,
                                  uint32_t count)
{
  nsresult rv;
  uint64_t available;
  rv = inStream->Available(&available);
  if (!m_writtenData)
  {
    m_writtenData = true;
    rv = SetupMsgWriteStream(m_outputFile, m_addDummyEnvelope);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  bool useCanonicalEnding = false;
  nsCOMPtr <nsIMsgMessageUrl> msgUrl = do_QueryInterface(aSupport);
  if (msgUrl)
    msgUrl->GetCanonicalLineEnding(&useCanonicalEnding);

  const char *lineEnding = (useCanonicalEnding) ? CRLF : MSG_LINEBREAK;
  uint32_t lineEndingLength = (useCanonicalEnding) ? 2 : MSG_LINEBREAK_LEN;

  uint32_t readCount, maxReadCount = SAVE_BUF_SIZE - m_leftOver;
  uint32_t writeCount;
  char *start, *end, lastCharInPrevBuf = '\0';
  uint32_t linebreak_len = 0;

  while (count > 0)
  {
      if (count < maxReadCount)
          maxReadCount = count;
      rv = inStream->Read(m_dataBuffer + m_leftOver,
                          maxReadCount,
                          &readCount);
      if (NS_FAILED(rv)) return rv;

      m_leftOver += readCount;
      m_dataBuffer[m_leftOver] = '\0';

      start = m_dataBuffer;
      // make sure we don't insert another LF, accidentally, by ignoring
      // second half of CRLF spanning blocks.
      if (lastCharInPrevBuf == '\r' && *start == '\n')
        start++;

      end = PL_strpbrk(start, "\r\n");
      if (end)
        linebreak_len = (end[0] == '\r' && end[1] == '\n') ? 2 : 1;

      count -= readCount;
      maxReadCount = SAVE_BUF_SIZE - m_leftOver;

      if (!end && count > maxReadCount)
          // must be a very very long line; sorry cannot handle it
          return NS_ERROR_FAILURE;

      while (start && end)
      {
          if (m_outputStream &&
              PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
              PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
              PL_strncmp(start, "From - ", 7))
          {
              rv = m_outputStream->Write(start, end-start, &writeCount);
              nsresult tmp = m_outputStream->Write(lineEnding, lineEndingLength, &writeCount);
              if (NS_FAILED(tmp)) {
                rv = tmp;
              }
          }
          start = end+linebreak_len;
          if (start >= m_dataBuffer + m_leftOver)
          {
              maxReadCount = SAVE_BUF_SIZE;
              m_leftOver = 0;
              break;
          }
          end = PL_strpbrk(start, "\r\n");
          if (end)
            linebreak_len = (end[0] == '\r' && end[1] == '\n') ? 2 : 1;
          if (start && !end)
          {
              m_leftOver -= (start - m_dataBuffer);
              memcpy(m_dataBuffer, start,
                            m_leftOver+1); // including null
              maxReadCount = SAVE_BUF_SIZE - m_leftOver;
          }
      }
      if (NS_FAILED(rv)) return rv;
      if (end)
          lastCharInPrevBuf = *end;
  }
  return rv;

  //  rv = m_outputStream->WriteFrom(inStream, std::min(available, count), &bytesWritten);
}

nsresult nsMsgSaveAsListener::SetupMsgWriteStream(nsIFile *aFile, bool addDummyEnvelope)
{
  // If the file already exists, delete it, but do this before
  // getting the outputstream.
  // Due to bug 328027, the nsSaveMsgListener created in
  // nsMessenger::SaveAs now opens the stream on the nsIFile
  // object, thus creating an empty file. Actual save operations for
  // IMAP and NNTP use this nsMsgSaveAsListener here, though, so we
  // have to close the stream before deleting the file, else data
  // would still be written happily into a now non-existing file.
  // (Windows doesn't care, btw, just unixoids do...)
  aFile->Remove(false);

  nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream),
                                               aFile, -1, 0666);
  NS_ENSURE_SUCCESS(rv, rv);

  if (m_outputStream && addDummyEnvelope)
  {
    nsAutoCString result;
    uint32_t writeCount;

    time_t now = time((time_t*) 0);
    char *ct = ctime(&now);
    // Remove the ending new-line character.
    ct[24] = '\0';
    result = "From - ";
    result += ct;
    result += MSG_LINEBREAK;
    m_outputStream->Write(result.get(), result.Length(), &writeCount);

    result = "X-Mozilla-Status: 0001";
    result += MSG_LINEBREAK;
    result += "X-Mozilla-Status2: 00000000";
    result += MSG_LINEBREAK;
    m_outputStream->Write(result.get(), result.Length(), &writeCount);
  }

  return rv;
}


NS_IMETHODIMP nsMsgMailNewsUrl::GetSaveAsListener(bool addDummyEnvelope,
                                                  nsIFile *aFile, nsIStreamListener **aSaveListener)
{
  NS_ENSURE_ARG_POINTER(aSaveListener);
  nsMsgSaveAsListener *saveAsListener = new nsMsgSaveAsListener(aFile, addDummyEnvelope);
  return saveAsListener->QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aSaveListener);
}


NS_IMETHODIMP nsMsgMailNewsUrl::SetFolder(nsIMsgFolder * /* aFolder */)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetFolder(nsIMsgFolder ** /* aFolder */)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgHeaderSink(nsIMsgHeaderSink * *aMsgHdrSink)
{
    NS_ENSURE_ARG_POINTER(aMsgHdrSink);
    NS_IF_ADDREF(*aMsgHdrSink = mMsgHeaderSink);
    return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgHeaderSink(nsIMsgHeaderSink * aMsgHdrSink)
{
    mMsgHeaderSink = aMsgHdrSink;
    return NS_OK;
}

NS_IMETHODIMP nsMsgMailNewsUrl::GetIsMessageUri(bool *aIsMessageUri)
{
  NS_ENSURE_ARG(aIsMessageUri);
  nsAutoCString scheme;
  m_baseURL->GetScheme(scheme);
  *aIsMessageUri = StringEndsWith(scheme, NS_LITERAL_CSTRING("-message"));
  return NS_OK;
}

NS_IMPL_ISUPPORTS(nsMsgMailNewsUrl::Mutator, nsIURISetters, nsIURIMutator)

NS_IMETHODIMP
nsMsgMailNewsUrl::Mutate(nsIURIMutator** aMutator)
{
  RefPtr<nsMsgMailNewsUrl::Mutator> mutator = new nsMsgMailNewsUrl::Mutator();
  nsresult rv = mutator->InitFromURI(this);
  if (NS_FAILED(rv)) {
    return rv;
  }
  mutator.forget(aMutator);
  return NS_OK;
}