/* 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/. */

#undef LOG
#include "ipc/IPCMessageUtils.h"

#include "nsSimpleURI.h"
#include "nscore.h"
#include "nsCRT.h"
#include "nsString.h"
#include "nsURLHelper.h"
#include "nsNetCID.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsEscape.h"
#include "nsError.h"
#include "nsQueryObject.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/TextUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "nsIClassInfoImpl.h"
#include "nsIURIMutator.h"
#include "mozilla/net/MozURL.h"
#include "mozilla/StaticPrefs_network.h"

using namespace mozilla::ipc;

namespace mozilla {
namespace net {

NS_IMPL_CLASSINFO(nsSimpleURI, nullptr, nsIClassInfo::THREADSAFE,
                  NS_SIMPLEURI_CID)
// Empty CI getter. We only need nsIClassInfo for Serialization
NS_IMPL_CI_INTERFACE_GETTER0(nsSimpleURI)

////////////////////////////////////////////////////////////////////////////////
// nsSimpleURI methods:

NS_IMPL_ADDREF(nsSimpleURI)
NS_IMPL_RELEASE(nsSimpleURI)
NS_INTERFACE_TABLE_HEAD(nsSimpleURI)
  NS_INTERFACE_TABLE(nsSimpleURI, nsIURI, nsISerializable,
                     nsIIPCSerializableURI, nsIURIWithSizeOf)
  NS_INTERFACE_TABLE_TO_MAP_SEGUE
  NS_IMPL_QUERY_CLASSINFO(nsSimpleURI)
  NS_INTERFACE_MAP_ENTRY_CONCRETE(nsSimpleURI)
NS_INTERFACE_MAP_END

////////////////////////////////////////////////////////////////////////////////
// nsISerializable methods:

NS_IMETHODIMP
nsSimpleURI::Read(nsIObjectInputStream* aStream) {
  MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsSimpleURI::ReadPrivate(nsIObjectInputStream* aStream) {
  nsresult rv;

  bool isMutable;
  rv = aStream->ReadBoolean(&isMutable);
  if (NS_FAILED(rv)) return rv;
  (void)isMutable;

  nsAutoCString scheme;
  rv = aStream->ReadCString(scheme);
  if (NS_FAILED(rv)) return rv;

  nsAutoCString path;
  rv = aStream->ReadCString(path);
  if (NS_FAILED(rv)) return rv;

  bool isRefValid;
  rv = aStream->ReadBoolean(&isRefValid);
  if (NS_FAILED(rv)) return rv;

  nsAutoCString ref;
  if (isRefValid) {
    rv = aStream->ReadCString(ref);
    if (NS_FAILED(rv)) return rv;
  }

  bool isQueryValid;
  rv = aStream->ReadBoolean(&isQueryValid);
  if (NS_FAILED(rv)) return rv;

  nsAutoCString query;
  if (isQueryValid) {
    rv = aStream->ReadCString(query);
    if (NS_FAILED(rv)) return rv;
  }

  // Re-constitute the spec, and initialize from it.
  nsAutoCString spec = scheme + ":"_ns + path;
  if (isQueryValid) {
    spec += "?"_ns + query;
  }
  if (isRefValid) {
    spec += "#"_ns + ref;
  }
  return SetSpecInternal(spec);
}

NS_IMETHODIMP
nsSimpleURI::Write(nsIObjectOutputStream* aStream) {
  nsresult rv;

  rv = aStream->WriteBoolean(false);  // former mMutable
  if (NS_FAILED(rv)) return rv;

  rv = aStream->WriteCString(Scheme());
  if (NS_FAILED(rv)) return rv;

  rv = aStream->WriteCString(Path());
  if (NS_FAILED(rv)) return rv;

  rv = aStream->WriteBoolean(IsRefValid());
  if (NS_FAILED(rv)) return rv;

  if (IsRefValid()) {
    rv = aStream->WriteCString(Ref());
    if (NS_FAILED(rv)) return rv;
  }

  rv = aStream->WriteBoolean(IsQueryValid());
  if (NS_FAILED(rv)) return rv;

  if (IsQueryValid()) {
    rv = aStream->WriteCString(Query());
    if (NS_FAILED(rv)) return rv;
  }

  return NS_OK;
}

void nsSimpleURI::Serialize(URIParams& aParams) {
  SimpleURIParams params;

  params.spec() = mSpec;

  aParams = params;
}

bool nsSimpleURI::Deserialize(const URIParams& aParams) {
  if (aParams.type() != URIParams::TSimpleURIParams) {
    NS_ERROR("Received unknown parameters from the other process!");
    return false;
  }

  const SimpleURIParams& params = aParams.get_SimpleURIParams();

  nsresult rv = SetSpecInternal(params.spec());
  if (NS_FAILED(rv)) {
    NS_ERROR("Failed to set spec from other process");
    return false;
  }

  return true;
}

////////////////////////////////////////////////////////////////////////////////
// nsIURI methods:

NS_IMETHODIMP
nsSimpleURI::GetSpec(nsACString& result) {
  result = mSpec;
  return NS_OK;
}

uint32_t nsSimpleURI::SpecHash() { return CachedSpecHash(mSpec); }

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsSimpleURI::GetSpecIgnoringRef(nsACString& result) {
  if (!IsRefValid()) {
    // Optimization: If there is no ref which needs to be trimmed, call
    // `GetSpec` to allow result to share the `mSpec` string buffer.
    return GetSpec(result);
  }

  result = SpecIgnoringRef();
  return NS_OK;
}

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

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

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

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

NS_IMETHODIMP
nsSimpleURI::GetHasRef(bool* result) {
  *result = IsRefValid();
  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::GetHasUserPass(bool* result) {
  *result = false;
  return NS_OK;
}

nsresult nsSimpleURI::SetSpecInternal(const nsACString& aSpec,
                                      bool aStripWhitespace) {
  if (StaticPrefs::network_url_max_length() &&
      aSpec.Length() > StaticPrefs::network_url_max_length()) {
    return NS_ERROR_MALFORMED_URI;
  }

  nsAutoCString scheme;
  nsresult rv = net_ExtractURLScheme(aSpec, scheme);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsAutoCString spec;
  rv = net_FilterAndEscapeURI(
      aSpec, esc_OnlyNonASCII,
      aStripWhitespace ? ASCIIMask::MaskWhitespace() : ASCIIMask::MaskCRLFTab(),
      spec);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Copy the filtered string into `mSpec`. We'll try not to mutate this buffer
  // unless it's required so we can share the (potentially very large data: URI)
  // string buffer.
  mSpec = std::move(spec);
  ResetSpecHash();
  mPathSep = mSpec.FindChar(':');
  MOZ_ASSERT(mPathSep != kNotFound, "A colon should be in this string");
  mQuerySep = kNotFound;
  mRefSep = kNotFound;

  // Check if `net_ExtractURLScheme` changed the scheme as written, and update
  // `mSpec` if it did.
  if (Scheme() != scheme) {
    if (!mSpec.Replace(SchemeStart(), SchemeLen(), scheme, fallible)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
    mPathSep = scheme.Length();
    MOZ_ASSERT(mSpec.CharAt(mPathSep) == ':');
  }

  // Populate the remaining members.
  return SetPathQueryRefInternal();
}

NS_IMETHODIMP
nsSimpleURI::GetScheme(nsACString& result) {
  result = Scheme();
  return NS_OK;
}

nsresult nsSimpleURI::SetScheme(const nsACString& input) {
  // Strip tabs, newlines, carriage returns from input
  nsAutoCString scheme(input);
  scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
  ToLowerCase(scheme);

  if (!net_IsValidScheme(scheme)) {
    NS_WARNING("the given url scheme contains invalid characters");
    return NS_ERROR_MALFORMED_URI;
  }

  int32_t delta = static_cast<int32_t>(scheme.Length()) - mPathSep;
  mSpec.Replace(SchemeStart(), SchemeLen(), scheme);

  // Adjust the separator indexes to account for the change in scheme length.
  mPathSep += delta;
  MOZ_ASSERT(mSpec.CharAt(mPathSep) == ':');
  if (IsQueryValid()) {
    mQuerySep += delta;
    MOZ_ASSERT(mSpec.CharAt(mQuerySep) == '?');
  }
  if (IsRefValid()) {
    mRefSep += delta;
    MOZ_ASSERT(mSpec.CharAt(mRefSep) == '#');
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::GetPrePath(nsACString& result) {
  result = Substring(mSpec, 0, PathStart());
  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::GetUserPass(nsACString& result) { return NS_ERROR_FAILURE; }

nsresult nsSimpleURI::SetUserPass(const nsACString& userPass) {
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSimpleURI::GetUsername(nsACString& result) { return NS_ERROR_FAILURE; }

nsresult nsSimpleURI::SetUsername(const nsACString& userName) {
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSimpleURI::GetPassword(nsACString& result) { return NS_ERROR_FAILURE; }

nsresult nsSimpleURI::SetPassword(const nsACString& password) {
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSimpleURI::GetHostPort(nsACString& result) {
  // Note: Audit all callers before changing this to return an empty
  // string -- CAPS and UI code may depend on this throwing.
  // Note: If this is changed, change GetAsciiHostPort as well.
  return NS_ERROR_FAILURE;
}

nsresult nsSimpleURI::SetHostPort(const nsACString& aValue) {
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSimpleURI::GetHost(nsACString& result) {
  // Note: Audit all callers before changing this to return an empty
  // string -- CAPS and UI code depend on this throwing.
  return NS_ERROR_FAILURE;
}

nsresult nsSimpleURI::SetHost(const nsACString& host) {
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSimpleURI::GetPort(int32_t* result) {
  // Note: Audit all callers before changing this to return an empty
  // string -- CAPS and UI code may depend on this throwing.
  return NS_ERROR_FAILURE;
}

nsresult nsSimpleURI::SetPort(int32_t port) { return NS_ERROR_FAILURE; }

NS_IMETHODIMP
nsSimpleURI::GetPathQueryRef(nsACString& result) {
  result = Substring(mSpec, PathStart());
  return NS_OK;
}

nsresult nsSimpleURI::SetPathQueryRef(const nsACString& aPath) {
  if (StaticPrefs::network_url_max_length()) {
    CheckedInt<uint32_t> newLength(mSpec.Length());
    newLength -= PathLen();
    newLength += aPath.Length();
    if (!newLength.isValid() ||
        newLength.value() > StaticPrefs::network_url_max_length()) {
      return NS_ERROR_MALFORMED_URI;
    }
  }

  nsAutoCString path;
  nsresult rv = NS_EscapeURL(aPath, esc_OnlyNonASCII, path, fallible);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Clear out the components being replaced. They'll be re-initialized below.
  mQuerySep = kNotFound;
  mRefSep = kNotFound;

  mSpec.Truncate(PathStart());
  if (!mSpec.Append(path, fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  return SetPathQueryRefInternal();
}

nsresult nsSimpleURI::SetPathQueryRefInternal() {
  MOZ_ASSERT(mSpec.CharAt(mPathSep) == ':');
  MOZ_ASSERT(mQuerySep == kNotFound);
  MOZ_ASSERT(mRefSep == kNotFound);

  // Initialize `mQuerySep` and `mRefSep` if those components are present.
  // Two single-char FindChar scans are used instead of FindCharInSet("?#")
  // because each is SIMD-accelerated, which outperforms the set lookup. See
  // bug 2045537 for the discussion and profiling.
  const int32_t queryPos = mSpec.FindChar('?', PathStart());
  const int32_t refPos = mSpec.FindChar('#', PathStart());
  int32_t pathEnd;
  if (queryPos == kNotFound) {
    pathEnd = refPos;
  } else if (refPos == kNotFound) {
    pathEnd = queryPos;
  } else {
    pathEnd = queryPos < refPos ? queryPos : refPos;
  }
  if (pathEnd != kNotFound) {
    if (mSpec.CharAt(pathEnd) == '?') {
      mQuerySep = pathEnd;
    } else {
      mRefSep = pathEnd;
    }
  }

  if (IsQueryValid()) {
    mRefSep = mSpec.FindChar('#', QueryStart());
  }

  // Unlike the path or query, `mRef` also requires spaces to be escaped.
  if (IsRefValid()) {
    // NOTE: `NS_EscapeURLSpan` could theoretically OOM-fail, but there is no
    // fallible version of `NS_EscapeURL` which won't do an unnecessary copy in
    // the non-escaping case.
    nsAutoCString escapedRef;
    if (NS_EscapeURLSpan(Ref(), esc_Ref, escapedRef)) {
      if (!mSpec.Replace(RefStart(), RefLen(), escapedRef, fallible)) {
        return NS_ERROR_OUT_OF_MEMORY;
      }
    }
  }

  if (Scheme() != "javascript"_ns && !IsQueryValid() && !IsRefValid()) {
    TrimTrailingCharactersFromPath();
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::GetRef(nsACString& result) {
  if (!IsRefValid()) {
    result.Truncate();
  } else {
    result = Ref();
  }

  return NS_OK;
}

// NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
// string (and will result in .spec and .path having a terminal #).
nsresult nsSimpleURI::SetRef(const nsACString& aRef) {
  if (StaticPrefs::network_url_max_length() &&
      aRef.Length() > StaticPrefs::network_url_max_length()) {
    return NS_ERROR_MALFORMED_URI;
  }

  nsAutoCString filteredRef(aRef);
  filteredRef.StripTaggedASCII(ASCIIMask::MaskCRLFTab());

  nsAutoCString ref;
  nsresult rv = NS_EscapeURL(filteredRef, esc_Ref, ref, fallible);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (ref.IsEmpty() && !IsRefValid()) {
    return NS_OK;  // nothing to do
  }

  int32_t cutStart;
  int32_t cutLength;
  if (IsRefValid()) {
    cutStart = mRefSep;
    cutLength = RefEnd() - cutStart;
  } else {
    cutStart = mSpec.Length();
    cutLength = 0;
  }

  nsCString prefix;
  if (!ref.IsEmpty() && ref[0] != '#') {
    // The replace includes the `#` character, so ensure it's present in the
    // ref (unless we're removing the ref).
    prefix = "#"_ns;
  }
  if (!mSpec.Replace(cutStart, cutLength, prefix + ref, fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  if (ref.IsEmpty()) {
    mRefSep = kNotFound;
  } else {
    mRefSep = cutStart;
    MOZ_ASSERT(mSpec.CharAt(mRefSep) == '#');
  }

  // Trim trailing invalid chars when ref and query are removed
  if (Scheme() != "javascript"_ns && !IsRefValid() && !IsQueryValid()) {
    TrimTrailingCharactersFromPath();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::Equals(nsIURI* other, bool* result) {
  return EqualsInternal(other, eHonorRef, result);
}

NS_IMETHODIMP
nsSimpleURI::EqualsExceptRef(nsIURI* other, bool* result) {
  return EqualsInternal(other, eIgnoreRef, result);
}

/* virtual */
nsresult nsSimpleURI::EqualsInternal(
    nsIURI* other, nsSimpleURI::RefHandlingEnum refHandlingMode, bool* result) {
  NS_ENSURE_ARG_POINTER(other);
  MOZ_ASSERT(result, "null pointer");

  RefPtr<nsSimpleURI> otherUri = do_QueryObject(other);
  if (!otherUri) {
    *result = false;
    return NS_OK;
  }

  *result = EqualsInternal(otherUri, refHandlingMode);
  return NS_OK;
}

bool nsSimpleURI::EqualsInternal(nsSimpleURI* otherUri,
                                 RefHandlingEnum refHandlingMode) {
  if (refHandlingMode != eHonorRef) {
    return SpecIgnoringRef() == otherUri->SpecIgnoringRef();
  }

  return mSpec == otherUri->mSpec;
}

NS_IMETHODIMP
nsSimpleURI::SchemeIs(const char* i_Scheme, bool* o_Equals) {
  MOZ_ASSERT(o_Equals, "null pointer");
  if (!i_Scheme) {
    *o_Equals = false;
    return NS_OK;
  }

  *o_Equals = Scheme().EqualsIgnoreCase(i_Scheme);
  return NS_OK;
}

/* virtual */ already_AddRefed<nsSimpleURI> nsSimpleURI::StartClone() {
  RefPtr<nsSimpleURI> url = new nsSimpleURI();
  return url.forget();
}

nsresult nsSimpleURI::Clone(nsIURI** result) {
  RefPtr<nsSimpleURI> url = StartClone();
  if (!url) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  url->mSpec = mSpec;
  url->mPathSep = mPathSep;
  url->mQuerySep = mQuerySep;
  url->mRefSep = mRefSep;

  url.forget(result);
  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::Resolve(const nsACString& relativePath, nsACString& result) {
  nsAutoCString scheme;
  nsresult rv = net_ExtractURLScheme(relativePath, scheme);
  if (NS_SUCCEEDED(rv)) {
    result = relativePath;
    return NS_OK;
  }

  nsAutoCString spec;
  rv = GetAsciiSpec(spec);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    // If getting the spec fails for some reason, preserve behaviour and just
    // return the relative path.
    result = relativePath;
    return NS_OK;
  }

  RefPtr<MozURL> url;
  rv = MozURL::Init(getter_AddRefs(url), spec);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    // If parsing the current url fails, we revert to the previous behaviour
    // and just return the relative path.
    result = relativePath;
    return NS_OK;
  }

  RefPtr<MozURL> url2;
  rv = MozURL::Init(getter_AddRefs(url2), relativePath, url);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    // If parsing the relative url fails, we revert to the previous behaviour
    // and just return the relative path.
    result = relativePath;
    return NS_OK;
  }

  result = url2->Spec();
  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::GetAsciiSpec(nsACString& aResult) {
  nsresult rv = GetSpec(aResult);
  if (NS_FAILED(rv)) return rv;
  MOZ_ASSERT(IsAscii(aResult), "The spec should be ASCII");
  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::GetAsciiHostPort(nsACString& result) {
  // XXX This behavior mimics GetHostPort.
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsSimpleURI::GetAsciiHost(nsACString& result) {
  result.Truncate();
  return NS_OK;
}

// Among the sub-classes that inherit (directly or indirectly) from
// nsSimpleURI, measurement of the following members may be added later if
// DMD finds it is worthwhile:
// - nsJSURI: mBaseURI
// - nsSimpleNestedURI: mInnerURI
// - nsBlobURI: mPrincipal
size_t nsSimpleURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
  return aMallocSizeOf(this) +
         mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
}

NS_IMETHODIMP
nsSimpleURI::GetFilePath(nsACString& aFilePath) {
  aFilePath = Path();
  return NS_OK;
}

nsresult nsSimpleURI::SetFilePath(const nsACString& aFilePath) {
  if (Path().IsEmpty() || Path().First() != '/') {
    // cannot-be-a-base
    return NS_ERROR_MALFORMED_URI;
  }
  const char* current = aFilePath.BeginReading();
  const char* end = aFilePath.EndReading();

  // Only go up to the first ? or # symbol
  for (; current < end; ++current) {
    if (*current == '?' || *current == '#') {
      break;
    }
  }
  return SetPathQueryRef(
      nsDependentCSubstring(aFilePath.BeginReading(), current));
}

NS_IMETHODIMP
nsSimpleURI::GetQuery(nsACString& aQuery) {
  if (!IsQueryValid()) {
    aQuery.Truncate();
  } else {
    aQuery = Query();
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSimpleURI::GetHasQuery(bool* result) {
  *result = IsQueryValid();
  return NS_OK;
}

nsresult nsSimpleURI::SetQuery(const nsACString& aQuery) {
  if (StaticPrefs::network_url_max_length() &&
      aQuery.Length() > StaticPrefs::network_url_max_length()) {
    return NS_ERROR_MALFORMED_URI;
  }
  nsAutoCString query;
  nsresult rv = NS_EscapeURL(aQuery, esc_OnlyNonASCII, query, fallible);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (query.IsEmpty() && !IsQueryValid()) {
    return NS_OK;  // nothing to do
  }

  int32_t cutStart;
  int32_t cutLength;
  if (IsQueryValid()) {
    cutStart = mQuerySep;
    cutLength = QueryEnd() - cutStart;
  } else if (IsRefValid()) {
    cutStart = mRefSep;
    cutLength = 0;
  } else {
    cutStart = mSpec.Length();
    cutLength = 0;
  }

  nsCString prefix;
  if (!query.IsEmpty() && query[0] != '?') {
    // The replace includes the `?` character, so ensure it's present in the
    // query (unless we're removing the query).
    prefix = "?"_ns;
  }
  if (!mSpec.Replace(cutStart, cutLength, prefix + query, fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  // Update `mQuerySep` and `mRefSep`.
  if (query.IsEmpty()) {
    mQuerySep = kNotFound;
  } else {
    mQuerySep = cutStart;
    MOZ_ASSERT(mSpec.CharAt(mQuerySep) == '?');
  }
  if (mRefSep != kNotFound) {
    mRefSep -= cutLength;
    mRefSep += prefix.Length() + query.Length();
    MOZ_ASSERT(mSpec.CharAt(mRefSep) == '#');
  }

  // Trim trailing invalid chars when ref and query are removed
  if (Scheme() != "javascript"_ns && !IsRefValid() && !IsQueryValid()) {
    TrimTrailingCharactersFromPath();
  }

  return NS_OK;
}

nsresult nsSimpleURI::SetQueryWithEncoding(const nsACString& aQuery,
                                           const Encoding* aEncoding) {
  return SetQuery(aQuery);
}

void nsSimpleURI::TrimTrailingCharactersFromPath() {
  MOZ_ASSERT(!IsQueryValid());
  MOZ_ASSERT(!IsRefValid());

  const auto* start = mSpec.BeginReading();
  const auto* end = mSpec.EndReading();

  auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
  const auto* newEnd =
      std::find_if(std::reverse_iterator<decltype(end)>(end),
                   std::reverse_iterator<decltype(start)>(start), charFilter)
          .base();

  auto trailCount = std::distance(newEnd, end);
  if (trailCount) {
    mSpec.Truncate(mSpec.Length() - trailCount);
  }
}

// Queries this list of interfaces. If none match, it queries mURI.
NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleURI::Mutator, nsIURISetters,
                                nsIURIMutator, nsISerializable,
                                nsISimpleURIMutator)

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

}  // namespace net
}  // namespace mozilla
