netwerk/base/nsStandardURL.cpp
author Masatoshi Kimura <VYV03354@nifty.ne.jp>
Fri, 15 Dec 2017 20:21:19 +0900
changeset 401149 74e11cfb3b5a03b40a1f71a9f6efaf5d8b740a12
parent 400533 2c575454e11e66ab84ddb103afe4f52ebd7e9d48
child 401614 67185e1252ebe1dd66217f5290d23e8718941cda
permissions -rw-r--r--
Bug 685236 - Stop using GetNativePath in netwerk/. r=mayhemer MozReview-Commit-ID: JdHhXPiS0fv

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set ts=4 sw=4 sts=4 et cindent: */
/* 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 "IPCMessageUtils.h"

#include "nsASCIIMask.h"
#include "nsStandardURL.h"
#include "nsCRT.h"
#include "nsEscape.h"
#include "nsIFile.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIIDNService.h"
#include "mozilla/Logging.h"
#include "nsAutoPtr.h"
#include "nsIURLParser.h"
#include "nsNetCID.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/ipc/URIUtils.h"
#include <algorithm>
#include "mozilla/SyncRunnable.h"
#include "nsContentUtils.h"
#include "prprf.h"
#include "nsReadableUtils.h"
#include "rust-url-capi/src/rust-url-capi.h"


//
// setenv MOZ_LOG nsStandardURL:5
//
static LazyLogModule gStandardURLLog("nsStandardURL");

// The Chromium code defines its own LOG macro which we don't want
#undef LOG
#define LOG(args)     MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
#undef LOG_ENABLED
#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)

using namespace mozilla::ipc;

namespace mozilla {
namespace net {

static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID);

// This will always be initialized and destroyed on the main thread, but
// can be safely used on other threads.
nsIIDNService *nsStandardURL::gIDN = nullptr;

// This value will only be updated on the main thread once. Worker threads
// may race when reading this values, but that's OK because in the worst
// case we will just dispatch a noop runnable to the main thread.
bool nsStandardURL::gInitialized = false;

const char nsStandardURL::gHostLimitDigits[] = { '/', '\\', '?', '#', 0 };
bool nsStandardURL::gPunycodeHost = true;

// Invalid host characters
// We still allow % because it is in the ID of addons.
// Any percent encoded ASCII characters that are not allowed in the
// hostname are not percent decoded, and will be parsed just fine.
//
// Note that the array below will be initialized at compile time,
// so we do not need to "optimize" TestForInvalidHostCharacters.
//
constexpr bool TestForInvalidHostCharacters(char c)
{
    // Testing for these:
    // CONTROL_CHARACTERS " #/:?@[\\]*<>|\"";
    return (c > 0 && c < 32) || // The control characters are [1, 31]
           c == ' ' || c == '#' || c == '/' || c == ':' || c == '?' ||
           c == '@' || c == '[' || c == '\\' || c == ']' || c == '*' ||
           c == '<' || c == '>' || c == '|' || c == '"';
}
constexpr ASCIIMaskArray sInvalidHostChars = CreateASCIIMask(TestForInvalidHostCharacters);

//----------------------------------------------------------------------------

#define ENSURE_MUTABLE() \
  PR_BEGIN_MACRO \
    if (!mMutable) { \
        NS_WARNING("attempt to modify an immutable nsStandardURL"); \
        return NS_ERROR_ABORT; \
    } \
  PR_END_MACRO

//----------------------------------------------------------------------------
// nsStandardURL::nsPrefObserver
//----------------------------------------------------------------------------

NS_IMPL_ISUPPORTS(nsStandardURL::nsPrefObserver, nsIObserver)

NS_IMETHODIMP nsStandardURL::
nsPrefObserver::Observe(nsISupports *subject,
                        const char *topic,
                        const char16_t *data)
{
    MOZ_ASSERT(NS_IsMainThread());

    if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
        nsCOMPtr<nsIPrefBranch> prefBranch( do_QueryInterface(subject) );
        if (prefBranch) {
            PrefsChanged(prefBranch, NS_ConvertUTF16toUTF8(data).get());
        }
    }
    return NS_OK;
}

//----------------------------------------------------------------------------
// nsStandardURL::nsSegmentEncoder
//----------------------------------------------------------------------------

nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding* encoding)
  : mEncoding(encoding)
{
  if (mEncoding == UTF_8_ENCODING) {
    mEncoding = nullptr;
  }
}

int32_t nsStandardURL::
nsSegmentEncoder::EncodeSegmentCount(const char *str,
                                     const URLSegment &seg,
                                     int16_t mask,
                                     nsCString& result,
                                     bool &appended,
                                     uint32_t extraLen)
{
    // extraLen is characters outside the segment that will be
    // added when the segment is not empty (like the @ following
    // a username).
    appended = false;
    if (!str)
        return 0;
    int32_t len = 0;
    if (seg.mLen > 0) {
        uint32_t pos = seg.mPos;
        len = seg.mLen;

        // first honor the origin charset if appropriate. as an optimization,
        // only do this if the segment is non-ASCII.  Further, if mEncoding is
        // null, then the origin charset is UTF-8 and there is nothing to do.
        nsAutoCString encBuf;
        if (mEncoding && !nsCRT::IsAscii(str + pos, len)) {
          // we have to encode this segment
          nsresult rv;
          const Encoding* ignored;
          Tie(rv, ignored) =
            mEncoding->Encode(Substring(str + pos, str + pos + len), encBuf);
          if (NS_SUCCEEDED(rv)) {
            str = encBuf.get();
            pos = 0;
            len = encBuf.Length();
          }
          // else some failure occurred... assume UTF-8 is ok.
        }

        uint32_t initLen = result.Length();

        // now perform any required escaping
        if (NS_EscapeURL(str + pos, len, mask, result)) {
            len = result.Length() - initLen;
            appended = true;
        }
        else if (str == encBuf.get()) {
            result += encBuf; // append only!!
            len = encBuf.Length();
            appended = true;
        }
        len += extraLen;
    }
    return len;
}

const nsACString &nsStandardURL::
nsSegmentEncoder::EncodeSegment(const nsACString& str,
                                int16_t mask,
                                nsCString& result)
{
    const char *text;
    bool encoded;
    EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask, result, encoded);
    if (encoded)
        return result;
    return str;
}

//----------------------------------------------------------------------------
// nsStandardURL <public>
//----------------------------------------------------------------------------

#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
static LinkedList<nsStandardURL> gAllURLs;
#endif

nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
    : mDefaultPort(-1)
    , mPort(-1)
    , mDisplayHost(nullptr)
    , mSpecEncoding(eEncoding_Unknown)
    , mURLType(URLTYPE_STANDARD)
    , mMutable(true)
    , mSupportsFileURL(aSupportsFileURL)
    , mCheckedIfHostA(false)
{
    LOG(("Creating nsStandardURL @%p\n", this));

    // gInitialized changes value only once (false->true) on the main thread.
    // It's OK to race here because in the worst case we'll just
    // dispatch a noop runnable to the main thread.
    if (!gInitialized) {
        InitGlobalObjects();
    }

    // default parser in case nsIStandardURL::Init is never called
    mParser = net_GetStdURLParser();

#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
    if (NS_IsMainThread()) {
        if (aTrackURL) {
            gAllURLs.insertBack(this);
        }
    }
#endif

}

nsStandardURL::~nsStandardURL()
{
    LOG(("Destroying nsStandardURL @%p\n", this));
}

#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
struct DumpLeakedURLs {
    DumpLeakedURLs() {}
    ~DumpLeakedURLs();
};

DumpLeakedURLs::~DumpLeakedURLs()
{
    MOZ_ASSERT(NS_IsMainThread());
    if (!gAllURLs.isEmpty()) {
        printf("Leaked URLs:\n");
        for (auto url : gAllURLs) {
            url->PrintSpec();
        }
        gAllURLs.clear();
    }
}
#endif

void
nsStandardURL::InitGlobalObjects()
{
    if (!NS_IsMainThread()) {
        RefPtr<Runnable> r =
            NS_NewRunnableFunction("nsStandardURL::InitGlobalObjects",
                                   &nsStandardURL::InitGlobalObjects);
        SyncRunnable::DispatchToThread(GetMainThreadEventTarget(), r, false);
        return;
    }

    if (gInitialized) {
        return;
    }

    MOZ_ASSERT(NS_IsMainThread());
    gInitialized = true;

    nsCOMPtr<nsIPrefBranch> prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID) );
    if (prefBranch) {
        nsCOMPtr<nsIObserver> obs( new nsPrefObserver() );
        PrefsChanged(prefBranch, nullptr);
    }

    Preferences::AddBoolVarCache(&gPunycodeHost, "network.standard-url.punycode-host", true);
    nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
    if (serv) {
        NS_ADDREF(gIDN = serv.get());
        MOZ_ASSERT(gIDN);
    }
}

void
nsStandardURL::ShutdownGlobalObjects()
{
    MOZ_ASSERT(NS_IsMainThread());
    NS_IF_RELEASE(gIDN);

#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
    if (gInitialized) {
        // This instanciates a dummy class, and will trigger the class
        // destructor when libxul is unloaded. This is equivalent to atexit(),
        // but gracefully handles dlclose().
        static DumpLeakedURLs d;
    }
#endif
}

//----------------------------------------------------------------------------
// nsStandardURL <private>
//----------------------------------------------------------------------------

void
nsStandardURL::Clear()
{
    mSpec.Truncate();

    mPort = -1;

    mScheme.Reset();
    mAuthority.Reset();
    mUsername.Reset();
    mPassword.Reset();
    mHost.Reset();

    mPath.Reset();
    mFilepath.Reset();
    mDirectory.Reset();
    mBasename.Reset();

    mExtension.Reset();
    mQuery.Reset();
    mRef.Reset();

    InvalidateCache();
}

void
nsStandardURL::InvalidateCache(bool invalidateCachedFile)
{
    if (invalidateCachedFile) {
        mFile = nullptr;
    }
    mDisplayHost.Truncate();
    mCheckedIfHostA = false;
    mSpecEncoding = eEncoding_Unknown;
}

// Return the number of "dots" in the string, or -1 if invalid.  Note that the
// number of relevant entries in the bases/starts/ends arrays is number of
// dots + 1.
// Since the trailing dot is allowed, we pass and adjust "length".
//
// length is assumed to be <= host.Length(); the callers is responsible for that
//
// Note that the value returned is guaranteed to be in [-1, 3] range.
inline int32_t
ValidateIPv4Number(const nsACString& host,
                   int32_t bases[4], int32_t dotIndex[3],
                   bool& onlyBase10, int32_t& length)
{
    MOZ_ASSERT(length <= (int32_t)host.Length());
    if (length <= 0) {
        return -1;
    }

    bool lastWasNumber = false; // We count on this being false for i == 0
    int32_t dotCount = 0;
    onlyBase10 = true;

    for (int32_t i = 0; i < length; i++) {
        char current = host[i];
        if (current == '.') {
            if (!lastWasNumber) { // A dot should not follow an X or a dot, or be first
                return -1;
            }

            if (dotCount > 0 && i == (length - 1)) { // Trailing dot is OK; shorten and return
                length--;
                return dotCount;
            }

            if (dotCount > 2) {
                return -1;
            }
            lastWasNumber = false;
            dotIndex[dotCount] = i;
            dotCount ++;
        } else if (current == 'X' || current == 'x') {
            if (!lastWasNumber ||                       // An X should not follow an X or a dot or be first
                i == (length - 1) ||                    // No trailing Xs allowed
                (dotCount == 0 && i != 1) ||            // If we had no dots, an X should be second
                host[i-1] != '0' ||                     // X should always follow a 0.  Guaranteed i > 0
                                                        // as lastWasNumber is true
                (dotCount > 0 && host[i - 2] != '.')) { // And that zero follows a dot if it exists
                return -1;
            }
            lastWasNumber = false;
            bases[dotCount] = 16;
            onlyBase10 = false;

        } else if (current == '0') {
            if (i < length - 1 &&                 // Trailing zero doesn't signal octal
                host[i + 1] != '.' &&             // Lone zero is not octal
                (i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot is
                                                  // a candidate for octal
                bases[dotCount] = 8;              // This will turn to 16 above if X shows up
                onlyBase10 = false;
            }
            lastWasNumber = true;

        } else if (current >= '1' && current <= '7') {
            lastWasNumber = true;

        } else if (current >= '8' && current <= '9') {
            if (bases[dotCount] == 8) {
                return -1;
            }
            lastWasNumber = true;

        } else if ((current >= 'a' && current <= 'f') ||
                   (current >= 'A' && current <= 'F')) {
            if (bases[dotCount] != 16) {
                return -1;
            }
            lastWasNumber = true;

        } else {
            return -1;
        }
    }

    return dotCount;
}

inline nsresult
ParseIPv4Number10(const nsACString& input, uint32_t& number, uint32_t maxNumber)
{
    uint64_t value = 0;
    const char* current = input.BeginReading();
    const char* end = input.EndReading();
    for (; current < end; ++current) {
        char c = *current;
        MOZ_ASSERT(c >= '0' && c <= '9');
        value *= 10;
        value += c - '0';
    }
    if (value <= maxNumber) {
        number = value;
        return NS_OK;
    }

    // The error case
    number = 0;
    return NS_ERROR_FAILURE;
}

inline nsresult
ParseIPv4Number(const nsACString& input, int32_t base, uint32_t& number, uint32_t maxNumber)
{
    // Accumulate in the 64-bit value
    uint64_t value = 0;
    const char* current = input.BeginReading();
    const char* end = input.EndReading();
    switch(base) {
      case 16:
        ++current;
        MOZ_FALLTHROUGH;
      case 8:
        ++current;
        break;
      case 10:
      default:
        break;
    }
    for (; current < end; ++current) {
        value *= base;
        char c = *current;
        MOZ_ASSERT((base == 10 && isdigit(c)) ||
                   (base == 8 && c >= '0' && c <= '7') ||
                   (base == 16 && isxdigit(c)));
        if (isdigit(c)) {
            value += c - '0';
        } else if (c >= 'a' && c <= 'f') {
            value += c - 'a' + 10;
        } else if (c >= 'A' && c <= 'F') {
            value += c - 'A' + 10;
        }
    }

    if (value <= maxNumber) {
        number = value;
        return NS_OK;
    }

    // The error case
    number = 0;
    return NS_ERROR_FAILURE;
}

// IPv4 parser spec: https://url.spec.whatwg.org/#concept-ipv4-parser
/* static */ nsresult
nsStandardURL::NormalizeIPv4(const nsACString& host, nsCString& result)
{
    int32_t bases[4] = {10,10,10,10};
    bool onlyBase10 = true;           // Track this as a special case
    int32_t dotIndex[3];              // The positions of the dots in the string

    // The length may be adjusted by ValidateIPv4Number (ignoring the trailing period)
    // so use "length", rather than host.Length() after that call.
    int32_t length = static_cast<int32_t>(host.Length());
    int32_t dotCount = ValidateIPv4Number(host, bases, dotIndex,
                                          onlyBase10, length);
    if (dotCount < 0 || length <= 0) {
        return NS_ERROR_FAILURE;
    }

    // Max values specified by the spec
    static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu,
                                           0xffffu,     0xffu};
    uint32_t ipv4;
    int32_t start = (dotCount > 0 ? dotIndex[dotCount-1] + 1 : 0);

    nsresult res;
    // Doing a special case for all items being base 10 gives ~35% speedup
    res = (onlyBase10 ?
           ParseIPv4Number10(Substring(host, start, length - start),
                             ipv4, upperBounds[dotCount]) :
           ParseIPv4Number(Substring(host, start, length - start),
                           bases[dotCount],
                           ipv4, upperBounds[dotCount]));
    if (NS_FAILED(res)) {
        return NS_ERROR_FAILURE;
    }

    int32_t lastUsed = -1;
    for (int32_t i = 0; i < dotCount; i++) {
        uint32_t number;
        start = lastUsed + 1;
        lastUsed = dotIndex[i];
        res = (onlyBase10 ?
               ParseIPv4Number10(Substring(host, start, lastUsed - start),
                                 number, 255) :
               ParseIPv4Number(Substring(host, start, lastUsed - start),
                               bases[i], number, 255));
        if (NS_FAILED(res)) {
            return NS_ERROR_FAILURE;
        }
        ipv4 += number << (8 * (3 - i));
    }

    uint8_t ipSegments[4];
    NetworkEndian::writeUint32(ipSegments, ipv4);
    result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
                                            ipSegments[2], ipSegments[3]);
    return NS_OK;
}

nsresult
nsStandardURL::NormalizeIDN(const nsACString& host, nsCString& result)
{
    // If host is ACE, then convert to UTF-8.  Else, if host is already UTF-8,
    // then make sure it is normalized per IDN.

    // this function returns true if normalization succeeds.

    result.Truncate();
    nsresult rv;

    if (!gIDN) {
        return NS_ERROR_UNEXPECTED;
    }

    bool isAscii;
    nsAutoCString normalized;
    rv = gIDN->ConvertToDisplayIDN(host, &isAscii, normalized);
    if (NS_FAILED(rv)) {
        return rv;
    }

    // The result is ASCII. No need to convert to ACE.
    if (isAscii) {
        result = normalized;
        mCheckedIfHostA = true;
        mDisplayHost.Truncate();
        return NS_OK;
    }

    rv = gIDN->ConvertUTF8toACE(normalized, result);
    if (NS_FAILED(rv)) {
        return rv;
    }

    mCheckedIfHostA = true;
    mDisplayHost = normalized;

    return NS_OK;
}

bool
nsStandardURL::ValidIPv6orHostname(const char *host, uint32_t length)
{
    if (!host || !*host) {
        // Should not be NULL or empty string
        return false;
    }

    if (length != strlen(host)) {
        // Embedded null
        return false;
    }

    bool openBracket = host[0] == '[';
    bool closeBracket = host[length - 1] == ']';

    if (openBracket && closeBracket) {
        return net_IsValidIPv6Addr(host + 1, length - 2);
    }

    if (openBracket || closeBracket) {
        // Fail if only one of the brackets is present
        return false;
    }

    const char* end = host + length;
    const char* iter = host;
    for (; iter != end && *iter; ++iter) {
        if (ASCIIMask::IsMasked(sInvalidHostChars, *iter)) {
            return false;
        }
    }
    return true;
}

void
nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char *path)
{
    net_CoalesceDirs(coalesceFlag, path);
    int32_t newLen = strlen(path);
    if (newLen < mPath.mLen) {
        int32_t diff = newLen - mPath.mLen;
        mPath.mLen = newLen;
        mDirectory.mLen += diff;
        mFilepath.mLen += diff;
        ShiftFromBasename(diff);
    }
}

uint32_t
nsStandardURL::AppendSegmentToBuf(char *buf, uint32_t i, const char *str,
                                  const URLSegment &segInput, URLSegment &segOutput,
                                  const nsCString *escapedStr,
                                  bool useEscaped, int32_t *diff)
{
    MOZ_ASSERT(segInput.mLen == segOutput.mLen);

    if (diff) *diff = 0;

    if (segInput.mLen > 0) {
        if (useEscaped) {
            MOZ_ASSERT(diff);
            segOutput.mLen = escapedStr->Length();
            *diff = segOutput.mLen - segInput.mLen;
            memcpy(buf + i, escapedStr->get(), segOutput.mLen);
        } else {
            memcpy(buf + i, str + segInput.mPos, segInput.mLen);
        }
        segOutput.mPos = i;
        i += segOutput.mLen;
    } else {
        segOutput.mPos = i;
    }
    return i;
}

uint32_t
nsStandardURL::AppendToBuf(char *buf, uint32_t i, const char *str, uint32_t len)
{
    memcpy(buf + i, str, len);
    return i + len;
}

// basic algorithm:
//  1- escape url segments (for improved GetSpec efficiency)
//  2- allocate spec buffer
//  3- write url segments
//  4- update url segment positions and lengths
nsresult
nsStandardURL::BuildNormalizedSpec(const char *spec,
                                   const Encoding* encoding)
{
    // Assumptions: all member URLSegments must be relative the |spec| argument
    // passed to this function.

    // buffers for holding escaped url segments (these will remain empty unless
    // escaping is required).
    nsAutoCString encUsername, encPassword, encHost, encDirectory,
      encBasename, encExtension, encQuery, encRef;
    bool useEncUsername, useEncPassword, useEncHost = false,
      useEncDirectory, useEncBasename, useEncExtension, useEncQuery, useEncRef;
    nsAutoCString portbuf;

    //
    // escape each URL segment, if necessary, and calculate approximate normalized
    // spec length.
    //
    // [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]

    uint32_t approxLen = 0;

    // the scheme is already ASCII
    if (mScheme.mLen > 0)
        approxLen += mScheme.mLen + 3; // includes room for "://", which we insert always

    // encode URL segments; convert UTF-8 to origin charset and possibly escape.
    // results written to encXXX variables only if |spec| is not already in the
    // appropriate encoding.
    {
        nsSegmentEncoder encoder;
        nsSegmentEncoder queryEncoder(encoding);
        // Items using an extraLen of 1 don't add anything unless mLen > 0
        // Username@
        approxLen += encoder.EncodeSegmentCount(spec, mUsername,  esc_Username,      encUsername,  useEncUsername, 1);
        // :password - we insert the ':' even if there's no actual password if "user:@" was in the spec
        if (mPassword.mLen >= 0)
            approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword,  esc_Password,      encPassword,  useEncPassword);
        // mHost is handled differently below due to encoding differences
        MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
        if (mPort != -1 && mPort != mDefaultPort)
        {
            // :port
            portbuf.AppendInt(mPort);
            approxLen += portbuf.Length() + 1;
        }

        approxLen += 1; // reserve space for possible leading '/' - may not be needed
        // Should just use mPath?  These are pessimistic, and thus waste space
        approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory,     encDirectory, useEncDirectory, 1);
        approxLen += encoder.EncodeSegmentCount(spec, mBasename,  esc_FileBaseName,  encBasename,  useEncBasename);
        approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension, encExtension, useEncExtension, 1);

        // These next ones *always* add their leading character even if length is 0
        // Handles items like "http://#"
        // ?query
        if (mQuery.mLen >= 0)
            approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query,        encQuery,     useEncQuery);
        // #ref

        if (mRef.mLen >= 0) {
            approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref,
                                                        encRef, useEncRef);
        }
    }

    // do not escape the hostname, if IPv6 address literal, mHost will
    // already point to a [ ] delimited IPv6 address literal.
    // However, perform Unicode normalization on it, as IDN does.
    // Note that we don't disallow URLs without a host - file:, etc
    if (mHost.mLen > 0) {
        nsAutoCString tempHost;
        NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host, tempHost);
        if (tempHost.Contains('\0'))
            return NS_ERROR_MALFORMED_URI;  // null embedded in hostname
        if (tempHost.Contains(' '))
            return NS_ERROR_MALFORMED_URI;  // don't allow spaces in the hostname
        nsresult rv = NormalizeIDN(tempHost, encHost);
        if (NS_FAILED(rv)) {
            return rv;
        }
        if (!SegmentIs(spec, mScheme, "resource") &&
            !SegmentIs(spec, mScheme, "chrome")) {
            nsAutoCString ipString;
            if (encHost.Length() > 0 &&
                encHost.First() == '[' && encHost.Last() == ']' &&
                ValidIPv6orHostname(encHost.get(), encHost.Length())) {
                rv = (nsresult) rusturl_parse_ipv6addr(&encHost, &ipString);
                if (NS_FAILED(rv)) {
                    return rv;
                }
                encHost = ipString;
            } else if (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) {
                encHost = ipString;
            }
        }

        // NormalizeIDN always copies, if the call was successful.
        useEncHost = true;
        approxLen += encHost.Length();

        if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
            return NS_ERROR_MALFORMED_URI;
        }
    }

    // We must take a copy of every single segment because they are pointing to
    // the |spec| while we are changing their value, in case we must use
    // encoded strings.
    URLSegment username(mUsername);
    URLSegment password(mPassword);
    URLSegment host(mHost);
    URLSegment path(mPath);
    URLSegment filepath(mFilepath);
    URLSegment directory(mDirectory);
    URLSegment basename(mBasename);
    URLSegment extension(mExtension);
    URLSegment query(mQuery);
    URLSegment ref(mRef);

    //
    // generate the normalized URL string
    //
    // approxLen should be correct or 1 high
    if (!mSpec.SetLength(approxLen+1, fallible)) // buf needs a trailing '\0' below
        return NS_ERROR_OUT_OF_MEMORY;
    char *buf;
    mSpec.BeginWriting(buf);
    uint32_t i = 0;
    int32_t diff = 0;

    if (mScheme.mLen > 0) {
        i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme);
        net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen);
        i = AppendToBuf(buf, i, "://", 3);
    }

    // record authority starting position
    mAuthority.mPos = i;

    // append authority
    if (mUsername.mLen > 0) {
        i = AppendSegmentToBuf(buf, i, spec, username, mUsername,
                               &encUsername, useEncUsername, &diff);
        ShiftFromPassword(diff);
        if (password.mLen > 0) {
            buf[i++] = ':';
            i = AppendSegmentToBuf(buf, i, spec, password, mPassword,
                                   &encPassword, useEncPassword, &diff);
            ShiftFromHost(diff);
        } else {
            mPassword.mLen = -1;
        }
        buf[i++] = '@';
    }
    if (host.mLen > 0) {
        i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
                               &diff);
        ShiftFromPath(diff);

        net_ToLowerCase(buf + mHost.mPos, mHost.mLen);
        MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
        if (mPort != -1 && mPort != mDefaultPort) {
            buf[i++] = ':';
            // Already formatted while building approxLen
            i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length());
        }
    }

    // record authority length
    mAuthority.mLen = i - mAuthority.mPos;

    // path must always start with a "/"
    if (mPath.mLen <= 0) {
        LOG(("setting path=/"));
        mDirectory.mPos = mFilepath.mPos = mPath.mPos = i;
        mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1;
        // basename must exist, even if empty (bug 113508)
        mBasename.mPos = i+1;
        mBasename.mLen = 0;
        buf[i++] = '/';
    }
    else {
        uint32_t leadingSlash = 0;
        if (spec[path.mPos] != '/') {
            LOG(("adding leading slash to path\n"));
            leadingSlash = 1;
            buf[i++] = '/';
            // basename must exist, even if empty (bugs 113508, 429347)
            if (mBasename.mLen == -1) {
                mBasename.mPos = basename.mPos = i;
                mBasename.mLen = basename.mLen = 0;
            }
        }

        // record corrected (file)path starting position
        mPath.mPos = mFilepath.mPos = i - leadingSlash;

        i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory,
                               &encDirectory, useEncDirectory, &diff);
        ShiftFromBasename(diff);

        // the directory must end with a '/'
        if (buf[i-1] != '/') {
            buf[i++] = '/';
            mDirectory.mLen++;
        }

        i = AppendSegmentToBuf(buf, i, spec, basename, mBasename,
                               &encBasename, useEncBasename, &diff);
        ShiftFromExtension(diff);

        // make corrections to directory segment if leadingSlash
        if (leadingSlash) {
            mDirectory.mPos = mPath.mPos;
            if (mDirectory.mLen >= 0)
                mDirectory.mLen += leadingSlash;
            else
                mDirectory.mLen = 1;
        }

        if (mExtension.mLen >= 0) {
            buf[i++] = '.';
            i = AppendSegmentToBuf(buf, i, spec, extension, mExtension,
                                   &encExtension, useEncExtension, &diff);
            ShiftFromQuery(diff);
        }
        // calculate corrected filepath length
        mFilepath.mLen = i - mFilepath.mPos;

        if (mQuery.mLen >= 0) {
            buf[i++] = '?';
            i = AppendSegmentToBuf(buf, i, spec, query, mQuery,
                                   &encQuery, useEncQuery,
                                   &diff);
            ShiftFromRef(diff);
        }
        if (mRef.mLen >= 0) {
            buf[i++] = '#';
            i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
                                   &diff);
        }
        // calculate corrected path length
        mPath.mLen = i - mPath.mPos;
    }

    buf[i] = '\0';

    // https://url.spec.whatwg.org/#path-state (1.4.1.2)
    // https://url.spec.whatwg.org/#windows-drive-letter
    if (SegmentIs(buf, mScheme, "file")) {
        char* path = &buf[mPath.mPos];
        if (mPath.mLen >= 3 && path[0] == '/'
            && nsCRT::IsAsciiAlpha(path[1])
            && path[2] == '|') {
            buf[mPath.mPos + 2] = ':';
        }
    }

    if (mDirectory.mLen > 1) {
        netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
        if (SegmentIs(buf,mScheme,"ftp")) {
            coalesceFlag = (netCoalesceFlags) (coalesceFlag
                                        | NET_COALESCE_ALLOW_RELATIVE_ROOT
                                        | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
        }
        CoalescePath(coalesceFlag, buf + mDirectory.mPos);
    }
    mSpec.SetLength(strlen(buf));
    NS_ASSERTION(mSpec.Length() <= approxLen, "We've overflowed the mSpec buffer!");
    return NS_OK;
}

bool
nsStandardURL::SegmentIs(const URLSegment &seg, const char *val, bool ignoreCase)
{
    // one or both may be null
    if (!val || mSpec.IsEmpty())
        return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
    if (seg.mLen < 0)
        return false;
    // if the first |seg.mLen| chars of |val| match, then |val| must
    // also be null terminated at |seg.mLen|.
    if (ignoreCase)
        return !PL_strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen)
            && (val[seg.mLen] == '\0');
    else
        return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen)
            && (val[seg.mLen] == '\0');
}

bool
nsStandardURL::SegmentIs(const char* spec, const URLSegment &seg, const char *val, bool ignoreCase)
{
    // one or both may be null
    if (!val || !spec)
        return (!val && (!spec || seg.mLen < 0));
    if (seg.mLen < 0)
        return false;
    // if the first |seg.mLen| chars of |val| match, then |val| must
    // also be null terminated at |seg.mLen|.
    if (ignoreCase)
        return !PL_strncasecmp(spec + seg.mPos, val, seg.mLen)
            && (val[seg.mLen] == '\0');
    else
        return !strncmp(spec + seg.mPos, val, seg.mLen)
            && (val[seg.mLen] == '\0');
}

bool
nsStandardURL::SegmentIs(const URLSegment &seg1, const char *val, const URLSegment &seg2, bool ignoreCase)
{
    if (seg1.mLen != seg2.mLen)
        return false;
    if (seg1.mLen == -1 || (!val && mSpec.IsEmpty()))
        return true; // both are empty
    if (!val)
        return false;
    if (ignoreCase)
        return !PL_strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
    else
        return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
}

int32_t
nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, const char *val, uint32_t valLen)
{
    if (val && valLen) {
        if (len == 0)
            mSpec.Insert(val, pos, valLen);
        else
            mSpec.Replace(pos, len, nsDependentCString(val, valLen));
        return valLen - len;
    }

    // else remove the specified segment
    mSpec.Cut(pos, len);
    return -int32_t(len);
}

int32_t
nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len, const nsACString &val)
{
    if (len == 0)
        mSpec.Insert(val, pos);
    else
        mSpec.Replace(pos, len, val);
    return val.Length() - len;
}

nsresult
nsStandardURL::ParseURL(const char *spec, int32_t specLen)
{
    nsresult rv;

    if (specLen > net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    //
    // parse given URL string
    //
    rv = mParser->ParseURL(spec, specLen,
                           &mScheme.mPos, &mScheme.mLen,
                           &mAuthority.mPos, &mAuthority.mLen,
                           &mPath.mPos, &mPath.mLen);
    if (NS_FAILED(rv)) return rv;

#ifdef DEBUG
    if (mScheme.mLen <= 0) {
        printf("spec=%s\n", spec);
        NS_WARNING("malformed url: no scheme");
    }
#endif

    if (mAuthority.mLen > 0) {
        rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
                                     &mUsername.mPos, &mUsername.mLen,
                                     &mPassword.mPos, &mPassword.mLen,
                                     &mHost.mPos, &mHost.mLen,
                                     &mPort);
        if (NS_FAILED(rv)) return rv;

        // Don't allow mPort to be set to this URI's default port
        if (mPort == mDefaultPort)
            mPort = -1;

        mUsername.mPos += mAuthority.mPos;
        mPassword.mPos += mAuthority.mPos;
        mHost.mPos += mAuthority.mPos;
    }

    if (mPath.mLen > 0)
        rv = ParsePath(spec, mPath.mPos, mPath.mLen);

    return rv;
}

nsresult
nsStandardURL::ParsePath(const char *spec, uint32_t pathPos, int32_t pathLen)
{
    LOG(("ParsePath: %s pathpos %d len %d\n",spec,pathPos,pathLen));

    if (pathLen > net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    nsresult rv = mParser->ParsePath(spec + pathPos, pathLen,
                                     &mFilepath.mPos, &mFilepath.mLen,
                                     &mQuery.mPos, &mQuery.mLen,
                                     &mRef.mPos, &mRef.mLen);
    if (NS_FAILED(rv)) return rv;

    mFilepath.mPos += pathPos;
    mQuery.mPos += pathPos;
    mRef.mPos += pathPos;

    if (mFilepath.mLen > 0) {
        rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
                                    &mDirectory.mPos, &mDirectory.mLen,
                                    &mBasename.mPos, &mBasename.mLen,
                                    &mExtension.mPos, &mExtension.mLen);
        if (NS_FAILED(rv)) return rv;

        mDirectory.mPos += mFilepath.mPos;
        mBasename.mPos += mFilepath.mPos;
        mExtension.mPos += mFilepath.mPos;
    }
    return NS_OK;
}

char *
nsStandardURL::AppendToSubstring(uint32_t pos,
                                 int32_t len,
                                 const char *tail)
{
    // Verify pos and length are within boundaries
    if (pos > mSpec.Length())
        return nullptr;
    if (len < 0)
        return nullptr;
    if ((uint32_t)len > (mSpec.Length() - pos))
        return nullptr;
    if (!tail)
        return nullptr;

    uint32_t tailLen = strlen(tail);

    // Check for int overflow for proposed length of combined string
    if (UINT32_MAX - ((uint32_t)len + 1) < tailLen)
        return nullptr;

    char *result = (char *) moz_xmalloc(len + tailLen + 1);
    if (result) {
        memcpy(result, mSpec.get() + pos, len);
        memcpy(result + len, tail, tailLen);
        result[len + tailLen] = '\0';
    }
    return result;
}

nsresult
nsStandardURL::ReadSegment(nsIBinaryInputStream *stream, URLSegment &seg)
{
    nsresult rv;

    rv = stream->Read32(&seg.mPos);
    if (NS_FAILED(rv)) return rv;

    rv = stream->Read32((uint32_t *) &seg.mLen);
    if (NS_FAILED(rv)) return rv;

    return NS_OK;
}

nsresult
nsStandardURL::WriteSegment(nsIBinaryOutputStream *stream, const URLSegment &seg)
{
    nsresult rv;

    rv = stream->Write32(seg.mPos);
    if (NS_FAILED(rv)) return rv;

    rv = stream->Write32(uint32_t(seg.mLen));
    if (NS_FAILED(rv)) return rv;

    return NS_OK;
}

/* static */ void
nsStandardURL::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
{
    MOZ_ASSERT(NS_IsMainThread());

    LOG(("nsStandardURL::PrefsChanged [pref=%s]\n", pref));

#define PREF_CHANGED(p) ((pref == nullptr) || !strcmp(pref, p))
#define GOT_PREF(p, b) (NS_SUCCEEDED(prefs->GetBoolPref(p, &b)))

#undef PREF_CHANGED
#undef GOT_PREF
}

#define SHIFT_FROM(name, what)                    \
void                                              \
nsStandardURL::name(int32_t diff)                 \
{                                                 \
    if (!diff) return;                            \
    if (what.mLen >= 0) {                         \
        CheckedInt<int32_t> pos = what.mPos;      \
        pos += diff;                              \
        MOZ_ASSERT(pos.isValid());                \
        what.mPos = pos.value();                  \
    }

#define SHIFT_FROM_NEXT(name, what, next)         \
    SHIFT_FROM(name, what)                        \
    next(diff);                                   \
}

#define SHIFT_FROM_LAST(name, what)               \
    SHIFT_FROM(name, what)                        \
}

SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
SHIFT_FROM_LAST(ShiftFromRef, mRef)

//----------------------------------------------------------------------------
// nsStandardURL::nsISupports
//----------------------------------------------------------------------------

NS_IMPL_ADDREF(nsStandardURL)
NS_IMPL_RELEASE(nsStandardURL)

NS_INTERFACE_MAP_BEGIN(nsStandardURL)
    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
    NS_INTERFACE_MAP_ENTRY(nsIURI)
    NS_INTERFACE_MAP_ENTRY(nsIURL)
    NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
    NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
    NS_INTERFACE_MAP_ENTRY(nsISerializable)
    NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
    NS_INTERFACE_MAP_ENTRY(nsIMutable)
    NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI)
    NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
    // see nsStandardURL::Equals
    if (aIID.Equals(kThisImplCID))
        foundInterface = static_cast<nsIURI *>(this);
    else
    NS_INTERFACE_MAP_ENTRY(nsISizeOf)
NS_INTERFACE_MAP_END

//----------------------------------------------------------------------------
// nsStandardURL::nsIURI
//----------------------------------------------------------------------------

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetSpec(nsACString &result)
{
    MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
               "The spec should never be this long, we missed a check.");
    nsresult rv = NS_OK;
    if (gPunycodeHost) {
        result = mSpec;
    } else { // XXX: This code path may be slow
        rv = GetDisplaySpec(result);
    }
    return rv;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString &result)
{
    nsresult rv = GetSpec(result);
    if (NS_FAILED(rv)) {
        return rv;
    }
    if (mPassword.mLen >= 0) {
      result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****");
    }
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetSpecIgnoringRef(nsACString &result)
{
    // URI without ref is 0 to one char before ref
    if (mRef.mLen < 0) {
        return GetSpec(result);
    }

    URLSegment noRef(0, mRef.mPos - 1);
    result = Segment(noRef);

    CheckIfHostIsAscii();
    MOZ_ASSERT(mCheckedIfHostA);
    if (!gPunycodeHost && !mDisplayHost.IsEmpty()) {
        result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
    }

    return NS_OK;
}

nsresult
nsStandardURL::CheckIfHostIsAscii()
{
    nsresult rv;
    if (mCheckedIfHostA) {
        return NS_OK;
    }

    mCheckedIfHostA = true;

    if (!gIDN) {
        return NS_ERROR_NOT_INITIALIZED;
    }

    nsAutoCString displayHost;
    bool isAscii;
    rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost);
    if (NS_FAILED(rv)) {
        mDisplayHost.Truncate();
        mCheckedIfHostA = false;
        return rv;
    }

    if (!isAscii) {
        mDisplayHost = displayHost;
    }

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetDisplaySpec(nsACString &aUnicodeSpec)
{
    CheckIfHostIsAscii();
    aUnicodeSpec.Assign(mSpec);
    MOZ_ASSERT(mCheckedIfHostA);
    if (!mDisplayHost.IsEmpty()) {
        aUnicodeSpec.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
    }

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetDisplayHostPort(nsACString &aUnicodeHostPort)
{
    nsAutoCString unicodeHostPort;

    nsresult rv = GetDisplayHost(unicodeHostPort);
    if (NS_FAILED(rv)) {
        return rv;
    }

    if (StringBeginsWith(Hostport(), NS_LITERAL_CSTRING("["))) {
        aUnicodeHostPort.AssignLiteral("[");
        aUnicodeHostPort.Append(unicodeHostPort);
        aUnicodeHostPort.AppendLiteral("]");
    } else {
        aUnicodeHostPort.Assign(unicodeHostPort);
    }

    uint32_t pos = mHost.mPos + mHost.mLen;
    if (pos < mPath.mPos)
        aUnicodeHostPort += Substring(mSpec, pos, mPath.mPos - pos);

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetDisplayHost(nsACString &aUnicodeHost)
{
    CheckIfHostIsAscii();
    MOZ_ASSERT(mCheckedIfHostA);
    if (mDisplayHost.IsEmpty()) {
        return GetAsciiHost(aUnicodeHost);
    }

    aUnicodeHost = mDisplayHost;
    return NS_OK;
}


// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetPrePath(nsACString &result)
{
    result = Prepath();
    CheckIfHostIsAscii();
    MOZ_ASSERT(mCheckedIfHostA);
    if (!gPunycodeHost && !mDisplayHost.IsEmpty()) {
        result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
    }
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetDisplayPrePath(nsACString &result)
{
    result = Prepath();
    CheckIfHostIsAscii();
    MOZ_ASSERT(mCheckedIfHostA);
    if (!mDisplayHost.IsEmpty()) {
        result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
    }
    return NS_OK;
}

// result is strictly US-ASCII
NS_IMETHODIMP
nsStandardURL::GetScheme(nsACString &result)
{
    result = Scheme();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetUserPass(nsACString &result)
{
    result = Userpass();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetUsername(nsACString &result)
{
    result = Username();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetPassword(nsACString &result)
{
    result = Password();
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetHostPort(nsACString &result)
{
    nsresult rv;
    if (gPunycodeHost) {
        rv = GetAsciiHostPort(result);
    } else {
        rv = GetDisplayHostPort(result);
    }
    return rv;
}

NS_IMETHODIMP
nsStandardURL::GetHost(nsACString &result)
{
    nsresult rv;
    if (gPunycodeHost) {
        rv = GetAsciiHost(result);
    } else {
        rv = GetDisplayHost(result);
    }
    return rv;
}

NS_IMETHODIMP
nsStandardURL::GetPort(int32_t *result)
{
    // should never be more than 16 bit
    MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
    *result = mPort;
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetPathQueryRef(nsACString &result)
{
    result = Path();
    return NS_OK;
}

// result is ASCII
NS_IMETHODIMP
nsStandardURL::GetAsciiSpec(nsACString &result)
{
    if (mSpecEncoding == eEncoding_Unknown) {
        if (IsASCII(mSpec))
            mSpecEncoding = eEncoding_ASCII;
        else
            mSpecEncoding = eEncoding_UTF8;
    }

    if (mSpecEncoding == eEncoding_ASCII) {
        result = mSpec;
        return NS_OK;
    }

    // try to guess the capacity required for result...
    result.SetCapacity(mSpec.Length() + std::min<uint32_t>(32, mSpec.Length()/10));

    result = Substring(mSpec, 0, mScheme.mLen + 3);

    // This is left infallible as this entire function is expected to be
    // infallible.
    NS_EscapeURL(Userpass(true), esc_OnlyNonASCII | esc_AlwaysCopy, result);

    // get the hostport
    nsAutoCString hostport;
    MOZ_ALWAYS_SUCCEEDS(GetAsciiHostPort(hostport));
    result += hostport;

    // This is left infallible as this entire function is expected to be
    // infallible.
    NS_EscapeURL(Path(), esc_OnlyNonASCII | esc_AlwaysCopy, result);
    return NS_OK;
}

// result is ASCII
NS_IMETHODIMP
nsStandardURL::GetAsciiHostPort(nsACString &result)
{
    result = Hostport();
    return NS_OK;
}

// result is ASCII
NS_IMETHODIMP
nsStandardURL::GetAsciiHost(nsACString &result)
{
    result = Host();
    return NS_OK;
}

static bool
IsSpecialProtocol(const nsACString &input)
{
    nsACString::const_iterator start, end;
    input.BeginReading(start);
    nsACString::const_iterator iterator(start);
    input.EndReading(end);

    while (iterator != end && *iterator != ':') {
        iterator++;
    }

    nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get()));

    return protocol.LowerCaseEqualsLiteral("http") ||
           protocol.LowerCaseEqualsLiteral("https") ||
           protocol.LowerCaseEqualsLiteral("ftp") ||
           protocol.LowerCaseEqualsLiteral("ws") ||
           protocol.LowerCaseEqualsLiteral("wss") ||
           protocol.LowerCaseEqualsLiteral("file") ||
           protocol.LowerCaseEqualsLiteral("gopher");
}

nsresult
nsStandardURL::SetSpecInternal(const nsACString &input)
{
    return SetSpecWithEncoding(input, nullptr);
}

nsresult
nsStandardURL::SetSpecWithEncoding(const nsACString &input,
                                   const Encoding* encoding)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &flat = PromiseFlatCString(input);
    LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));

    if (input.Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    // filter out unexpected chars "\r\n\t" if necessary
    nsAutoCString filteredURI;
    net_FilterURIString(flat, filteredURI);

    if (filteredURI.Length() == 0) {
        return NS_ERROR_MALFORMED_URI;
    }

    // Make a backup of the curent URL
    nsStandardURL prevURL(false,false);
    prevURL.CopyMembers(this, eHonorRef, EmptyCString());
    Clear();

    if (IsSpecialProtocol(filteredURI)) {
        // Bug 652186: Replace all backslashes with slashes when parsing paths
        // Stop when we reach the query or the hash.
        nsAutoCString::iterator start;
        nsAutoCString::iterator end;
        filteredURI.BeginWriting(start);
        filteredURI.EndWriting(end);
        while (start != end) {
            if (*start == '?' || *start == '#') {
                break;
            }
            if (*start == '\\') {
                *start = '/';
            }
            start++;
        }
    }

    const char *spec = filteredURI.get();
    int32_t specLength = filteredURI.Length();

    // parse the given URL...
    nsresult rv = ParseURL(spec, specLength);
    if (NS_SUCCEEDED(rv)) {
        // finally, use the URLSegment member variables to build a normalized
        // copy of |spec|
        rv = BuildNormalizedSpec(spec, encoding);
    }

    // Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
    if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) {
        rv = NS_ERROR_MALFORMED_URI;
    }

    if (NS_FAILED(rv)) {
        Clear();
        // If parsing the spec has failed, restore the old URL
        // so we don't end up with an empty URL.
        CopyMembers(&prevURL, eHonorRef, EmptyCString());
        return rv;
    }

    if (LOG_ENABLED()) {
        LOG((" spec      = %s\n", mSpec.get()));
        LOG((" port      = %d\n", mPort));
        LOG((" scheme    = (%u,%d)\n", mScheme.mPos,    mScheme.mLen));
        LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen));
        LOG((" username  = (%u,%d)\n", mUsername.mPos,  mUsername.mLen));
        LOG((" password  = (%u,%d)\n", mPassword.mPos,  mPassword.mLen));
        LOG((" hostname  = (%u,%d)\n", mHost.mPos,      mHost.mLen));
        LOG((" path      = (%u,%d)\n", mPath.mPos,      mPath.mLen));
        LOG((" filepath  = (%u,%d)\n", mFilepath.mPos,  mFilepath.mLen));
        LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen));
        LOG((" basename  = (%u,%d)\n", mBasename.mPos,  mBasename.mLen));
        LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen));
        LOG((" query     = (%u,%d)\n", mQuery.mPos,     mQuery.mLen));
        LOG((" ref       = (%u,%d)\n", mRef.mPos,       mRef.mLen));
    }

    return rv;
}

NS_IMETHODIMP
nsStandardURL::SetScheme(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &scheme = PromiseFlatCString(input);

    LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));

    if (scheme.IsEmpty()) {
        NS_WARNING("cannot remove the scheme from an url");
        return NS_ERROR_UNEXPECTED;
    }
    if (mScheme.mLen < 0) {
        NS_WARNING("uninitialized");
        return NS_ERROR_NOT_INITIALIZED;
    }

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

    if (mSpec.Length() + input.Length() - Scheme().Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);

    if (shift) {
        mScheme.mLen = scheme.Length();
        ShiftFromAuthority(shift);
    }

    // ensure new scheme is lowercase
    //
    // XXX the string code unfortunately doesn't provide a ToLowerCase
    //     that operates on a substring.
    net_ToLowerCase((char *) mSpec.get(), mScheme.mLen);
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetUserPass(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &userpass = PromiseFlatCString(input);

    LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get()));

    if (mURLType == URLTYPE_NO_AUTHORITY) {
        if (userpass.IsEmpty())
            return NS_OK;
        NS_WARNING("cannot set user:pass on no-auth url");
        return NS_ERROR_UNEXPECTED;
    }
    if (mAuthority.mLen < 0) {
        NS_WARNING("uninitialized");
        return NS_ERROR_NOT_INITIALIZED;
    }

    if (mSpec.Length() + input.Length() - Userpass(true).Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    if (userpass.IsEmpty()) {
        // remove user:pass
        if (mUsername.mLen > 0) {
            if (mPassword.mLen > 0)
                mUsername.mLen += (mPassword.mLen + 1);
            mUsername.mLen++;
            mSpec.Cut(mUsername.mPos, mUsername.mLen);
            mAuthority.mLen -= mUsername.mLen;
            ShiftFromHost(-mUsername.mLen);
            mUsername.mLen = -1;
            mPassword.mLen = -1;
        }

        return NS_OK;
    }

    NS_ASSERTION(mHost.mLen >= 0, "uninitialized");

    nsresult rv;
    uint32_t usernamePos, passwordPos;
    int32_t usernameLen, passwordLen;

    rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(),
                                &usernamePos, &usernameLen,
                                &passwordPos, &passwordLen);
    if (NS_FAILED(rv)) return rv;

    // build new user:pass in |buf|
    nsAutoCString buf;
    if (usernameLen > 0) {
        nsSegmentEncoder encoder;
        bool ignoredOut;
        usernameLen = encoder.EncodeSegmentCount(userpass.get(),
                                                 URLSegment(usernamePos,
                                                            usernameLen),
                                                 esc_Username | esc_AlwaysCopy,
                                                 buf, ignoredOut);
        if (passwordLen > 0) {
            buf.Append(':');
            passwordLen = encoder.EncodeSegmentCount(userpass.get(),
                                                     URLSegment(passwordPos,
                                                                passwordLen),
                                                     esc_Password |
                                                     esc_AlwaysCopy, buf,
                                                     ignoredOut);
        } else {
            passwordLen = -1;
        }
        if (mUsername.mLen < 0)
            buf.Append('@');
    }

    uint32_t shift = 0;

    if (mUsername.mLen < 0) {
        // no existing user:pass
        if (!buf.IsEmpty()) {
            mSpec.Insert(buf, mHost.mPos);
            mUsername.mPos = mHost.mPos;
            shift = buf.Length();
        }
    }
    else {
        // replace existing user:pass
        uint32_t userpassLen = mUsername.mLen;
        if (mPassword.mLen >= 0)
            userpassLen += (mPassword.mLen + 1);
        mSpec.Replace(mUsername.mPos, userpassLen, buf);
        shift = buf.Length() - userpassLen;
    }
    if (shift) {
        ShiftFromHost(shift);
        mAuthority.mLen += shift;
    }
    // update positions and lengths
    mUsername.mLen = usernameLen;
    mPassword.mLen = passwordLen;
    if (passwordLen > 0) {
        mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
    }

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetUsername(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &username = PromiseFlatCString(input);

    LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get()));

    if (mURLType == URLTYPE_NO_AUTHORITY) {
        if (username.IsEmpty())
            return NS_OK;
        NS_WARNING("cannot set username on no-auth url");
        return NS_ERROR_UNEXPECTED;
    }

    if (username.IsEmpty())
        return SetUserPass(username);

    if (mSpec.Length() + input.Length() - Username().Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    // escape username if necessary
    nsAutoCString buf;
    nsSegmentEncoder encoder;
    const nsACString &escUsername =
        encoder.EncodeSegment(username, esc_Username, buf);

    int32_t shift;

    if (mUsername.mLen < 0) {
        mUsername.mPos = mAuthority.mPos;
        mSpec.Insert(escUsername + NS_LITERAL_CSTRING("@"), mUsername.mPos);
        shift = escUsername.Length() + 1;
    }
    else
        shift = ReplaceSegment(mUsername.mPos, mUsername.mLen, escUsername);

    if (shift) {
        mUsername.mLen = escUsername.Length();
        mAuthority.mLen += shift;
        ShiftFromPassword(shift);
    }

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetPassword(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &password = PromiseFlatCString(input);

    LOG(("nsStandardURL::SetPassword [password=%s]\n", password.get()));

    if (mURLType == URLTYPE_NO_AUTHORITY) {
        if (password.IsEmpty())
            return NS_OK;
        NS_WARNING("cannot set password on no-auth url");
        return NS_ERROR_UNEXPECTED;
    }
    if (mUsername.mLen <= 0) {
        NS_WARNING("cannot set password without existing username");
        return NS_ERROR_FAILURE;
    }

    if (mSpec.Length() + input.Length() - Password().Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    if (password.IsEmpty()) {
        if (mPassword.mLen >= 0) {
            // cut(":password")
            mSpec.Cut(mPassword.mPos - 1, mPassword.mLen + 1);
            ShiftFromHost(-(mPassword.mLen + 1));
            mAuthority.mLen -= (mPassword.mLen + 1);
            mPassword.mLen = -1;
        }
        return NS_OK;
    }

    // escape password if necessary
    nsAutoCString buf;
    nsSegmentEncoder encoder;
    const nsACString &escPassword =
        encoder.EncodeSegment(password, esc_Password, buf);

    int32_t shift;

    if (mPassword.mLen < 0) {
        mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
        mSpec.Insert(NS_LITERAL_CSTRING(":") + escPassword, mPassword.mPos - 1);
        shift = escPassword.Length() + 1;
    }
    else
        shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);

    if (shift) {
        mPassword.mLen = escPassword.Length();
        mAuthority.mLen += shift;
        ShiftFromHost(shift);
    }
    return NS_OK;
}

void
nsStandardURL::FindHostLimit(nsACString::const_iterator& aStart,
                             nsACString::const_iterator& aEnd)
{
  for (int32_t i = 0; gHostLimitDigits[i]; ++i) {
    nsACString::const_iterator c(aStart);
    if (FindCharInReadable(gHostLimitDigits[i], c, aEnd)) {
      aEnd = c;
    }
  }
}

// If aValue only has a host part and no port number, the port
// will not be reset!!!
NS_IMETHODIMP
nsStandardURL::SetHostPort(const nsACString &aValue)
{
    ENSURE_MUTABLE();

    // We cannot simply call nsIURI::SetHost because that would treat the name as
    // an IPv6 address (like http:://[server:443]/).  We also cannot call
    // nsIURI::SetHostPort because that isn't implemented.  Sadfaces.

    nsACString::const_iterator start, end;
    aValue.BeginReading(start);
    aValue.EndReading(end);
    nsACString::const_iterator iter(start);
    bool isIPv6 = false;

    FindHostLimit(start, end);

    if (*start == '[') { // IPv6 address
        if (!FindCharInReadable(']', iter, end)) {
            // the ] character is missing
            return NS_ERROR_MALFORMED_URI;
        }
        // iter now at the ']' character
        isIPv6 = true;
    } else {
        nsACString::const_iterator iter2(start);
        if (FindCharInReadable(']', iter2, end)) {
            // if the first char isn't [ then there should be no ] character
            return NS_ERROR_MALFORMED_URI;
        }
    }

    FindCharInReadable(':', iter, end);

    if (!isIPv6 && iter != end) {
        nsACString::const_iterator iter2(iter);
        iter2++; // Skip over the first ':' character
        if (FindCharInReadable(':', iter2, end)) {
            // If there is more than one ':' character it suggests an IPv6
            // The format should be [2001::1]:80 where the port is optional
            return NS_ERROR_MALFORMED_URI;
        }
    }

    nsresult rv = SetHost(Substring(start, iter));
    NS_ENSURE_SUCCESS(rv, rv);

    // Also set the port if needed.
    if (iter != end) {
        iter++;
        if (iter != end) {
            nsCString portStr(Substring(iter, end));
            nsresult rv;
            int32_t port = portStr.ToInteger(&rv);
            if (NS_SUCCEEDED(rv)) {
                rv = SetPort(port);
                NS_ENSURE_SUCCESS(rv, rv);
            } else {
                // Failure parsing port number
                return NS_ERROR_MALFORMED_URI;
            }
        } else {
            // port number is missing
            return NS_ERROR_MALFORMED_URI;
        }
    }
    return NS_OK;
}

// This function is different than SetHostPort in that the port number will be
// reset as well if aValue parameter does not contain a port port number.
NS_IMETHODIMP
nsStandardURL::SetHostAndPort(const nsACString &aValue)
{
  // Reset the port and than call SetHostPort. SetHostPort does not reset
  // the port number.
  nsresult rv = SetPort(-1);
  NS_ENSURE_SUCCESS(rv, rv);
  return SetHostPort(aValue);
}

NS_IMETHODIMP
nsStandardURL::SetHost(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &hostname = PromiseFlatCString(input);

    nsACString::const_iterator start, end;
    hostname.BeginReading(start);
    hostname.EndReading(end);

    FindHostLimit(start, end);

    const nsCString unescapedHost(Substring(start, end));
    // Do percent decoding on the the input.
    nsAutoCString flat;
    NS_UnescapeURL(unescapedHost.BeginReading(), unescapedHost.Length(),
                   esc_AlwaysCopy | esc_Host, flat);
    const char *host = flat.get();

    LOG(("nsStandardURL::SetHost [host=%s]\n", host));

    if (mURLType == URLTYPE_NO_AUTHORITY) {
        if (flat.IsEmpty())
            return NS_OK;
        NS_WARNING("cannot set host on no-auth url");
        return NS_ERROR_UNEXPECTED;
    } else {
        if (flat.IsEmpty()) {
            // Setting an empty hostname is not allowed for
            // URLTYPE_STANDARD and URLTYPE_AUTHORITY.
            return NS_ERROR_UNEXPECTED;
        }
    }

    if (strlen(host) < flat.Length())
        return NS_ERROR_MALFORMED_URI; // found embedded null

    // For consistency with SetSpec/nsURLParsers, don't allow spaces
    // in the hostname.
    if (strchr(host, ' '))
        return NS_ERROR_MALFORMED_URI;

    if (mSpec.Length() + strlen(host) - Host().Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    uint32_t len;
    nsAutoCString hostBuf;
    nsresult rv = NormalizeIDN(flat, hostBuf);
    if (NS_FAILED(rv)) {
        return rv;
    }

    if (!SegmentIs(mScheme, "resource") && !SegmentIs(mScheme, "chrome")) {
        nsAutoCString ipString;
        if (hostBuf.Length() > 0 &&
            hostBuf.First() == '[' && hostBuf.Last() == ']' &&
            ValidIPv6orHostname(hostBuf.get(), hostBuf.Length())) {
            rv = (nsresult) rusturl_parse_ipv6addr(&hostBuf, &ipString);
            if (NS_FAILED(rv)) {
                return rv;
            }
            hostBuf = ipString;
        } else if (NS_SUCCEEDED(NormalizeIPv4(hostBuf, ipString))) {
          hostBuf = ipString;
        }
    }

    // NormalizeIDN always copies if the call was successful
    host = hostBuf.get();
    len = hostBuf.Length();

    if (!ValidIPv6orHostname(host, len)) {
        return NS_ERROR_MALFORMED_URI;
    }

    if (mHost.mLen < 0) {
        int port_length = 0;
        if (mPort != -1) {
            nsAutoCString buf;
            buf.Assign(':');
            buf.AppendInt(mPort);
            port_length = buf.Length();
        }
        if (mAuthority.mLen > 0) {
            mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
            mHost.mLen = 0;
        } else if (mScheme.mLen > 0) {
            mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
            mHost.mLen = 0;
        }
    }

    int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);

    if (shift) {
        mHost.mLen = len;
        mAuthority.mLen += shift;
        ShiftFromPath(shift);
    }

    // Now canonicalize the host to lowercase
    net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetPort(int32_t port)
{
    ENSURE_MUTABLE();

    LOG(("nsStandardURL::SetPort [port=%d]\n", port));

    if ((port == mPort) || (mPort == -1 && port == mDefaultPort))
        return NS_OK;

    // ports must be >= 0 and 16 bit
    // -1 == use default
    if (port < -1 || port > std::numeric_limits<uint16_t>::max())
        return NS_ERROR_MALFORMED_URI;

    if (mURLType == URLTYPE_NO_AUTHORITY) {
        NS_WARNING("cannot set port on no-auth url");
        return NS_ERROR_UNEXPECTED;
    }

    InvalidateCache();
    if (port == mDefaultPort) {
      port = -1;
    }

    ReplacePortInSpec(port);

    mPort = port;
    return NS_OK;
}

/**
 * Replaces the existing port in mSpec with aNewPort.
 *
 * The caller is responsible for:
 *  - Calling InvalidateCache (since our mSpec is changing).
 *  - Checking whether aNewPort is mDefaultPort (in which case the
 *    caller should pass aNewPort=-1).
 */
void
nsStandardURL::ReplacePortInSpec(int32_t aNewPort)
{
    MOZ_ASSERT(mMutable, "Caller should ensure we're mutable");
    NS_ASSERTION(aNewPort != mDefaultPort || mDefaultPort == -1,
                 "Caller should check its passed-in value and pass -1 instead of "
                 "mDefaultPort, to avoid encoding default port into mSpec");

    // Create the (possibly empty) string that we're planning to replace:
    nsAutoCString buf;
    if (mPort != -1) {
        buf.Assign(':');
        buf.AppendInt(mPort);
    }
    // Find the position & length of that string:
    const uint32_t replacedLen = buf.Length();
    const uint32_t replacedStart =
        mAuthority.mPos + mAuthority.mLen - replacedLen;

    // Create the (possibly empty) replacement string:
    if (aNewPort == -1) {
        buf.Truncate();
    } else {
        buf.Assign(':');
        buf.AppendInt(aNewPort);
    }
    // Perform the replacement:
    mSpec.Replace(replacedStart, replacedLen, buf);

    // Bookkeeping to reflect the new length:
    int32_t shift = buf.Length() - replacedLen;
    mAuthority.mLen += shift;
    ShiftFromPath(shift);
}

NS_IMETHODIMP
nsStandardURL::SetPathQueryRef(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &path = PromiseFlatCString(input);
    LOG(("nsStandardURL::SetPathQueryRef [path=%s]\n", path.get()));

    InvalidateCache();

    if (!path.IsEmpty()) {
        nsAutoCString spec;

        spec.Assign(mSpec.get(), mPath.mPos);
        if (path.First() != '/')
            spec.Append('/');
        spec.Append(path);

        return SetSpecInternal(spec);
    }
    else if (mPath.mLen >= 1) {
        mSpec.Cut(mPath.mPos + 1, mPath.mLen - 1);
        // these contain only a '/'
        mPath.mLen = 1;
        mDirectory.mLen = 1;
        mFilepath.mLen = 1;
        // these are no longer defined
        mBasename.mLen = -1;
        mExtension.mLen = -1;
        mQuery.mLen = -1;
        mRef.mLen = -1;
    }
    return NS_OK;
}

NS_IMPL_ISUPPORTS(nsStandardURL::Mutator, nsIURISetters, nsIURIMutator, nsIStandardURLMutator)

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

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

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

nsresult
nsStandardURL::EqualsInternal(nsIURI *unknownOther,
                              nsStandardURL::RefHandlingEnum refHandlingMode,
                              bool *result)
{
    NS_ENSURE_ARG_POINTER(unknownOther);
    NS_PRECONDITION(result, "null pointer");

    RefPtr<nsStandardURL> other;
    nsresult rv = unknownOther->QueryInterface(kThisImplCID,
                                               getter_AddRefs(other));
    if (NS_FAILED(rv)) {
        *result = false;
        return NS_OK;
    }

    // First, check whether one URIs is an nsIFileURL while the other
    // is not.  If that's the case, they're different.
    if (mSupportsFileURL != other->mSupportsFileURL) {
        *result = false;
        return NS_OK;
    }

    // Next check parts of a URI that, if different, automatically make the
    // URIs different
    if (!SegmentIs(mScheme, other->mSpec.get(), other->mScheme) ||
        // Check for host manually, since conversion to file will
        // ignore the host!
        !SegmentIs(mHost, other->mSpec.get(), other->mHost) ||
        !SegmentIs(mQuery, other->mSpec.get(), other->mQuery) ||
        !SegmentIs(mUsername, other->mSpec.get(), other->mUsername) ||
        !SegmentIs(mPassword, other->mSpec.get(), other->mPassword) ||
        Port() != other->Port()) {
        // No need to compare files or other URI parts -- these are different
        // beasties
        *result = false;
        return NS_OK;
    }

    if (refHandlingMode == eHonorRef &&
        !SegmentIs(mRef, other->mSpec.get(), other->mRef)) {
        *result = false;
        return NS_OK;
    }

    // Then check for exact identity of URIs.  If we have it, they're equal
    if (SegmentIs(mDirectory, other->mSpec.get(), other->mDirectory) &&
        SegmentIs(mBasename, other->mSpec.get(), other->mBasename) &&
        SegmentIs(mExtension, other->mSpec.get(), other->mExtension)) {
        *result = true;
        return NS_OK;
    }

    // At this point, the URIs are not identical, but they only differ in the
    // directory/filename/extension.  If these are file URLs, then get the
    // corresponding file objects and compare those, since two filenames that
    // differ, eg, only in case could still be equal.
    if (mSupportsFileURL) {
        // Assume not equal for failure cases... but failures in GetFile are
        // really failures, more or less, so propagate them to caller.
        *result = false;

        rv = EnsureFile();
        nsresult rv2 = other->EnsureFile();
        // special case for resource:// urls that don't resolve to files
        if (rv == NS_ERROR_NO_INTERFACE && rv == rv2)
            return NS_OK;

        if (NS_FAILED(rv)) {
            LOG(("nsStandardURL::Equals [this=%p spec=%s] failed to ensure file",
                this, mSpec.get()));
            return rv;
        }
        NS_ASSERTION(mFile, "EnsureFile() lied!");
        rv = rv2;
        if (NS_FAILED(rv)) {
            LOG(("nsStandardURL::Equals [other=%p spec=%s] other failed to ensure file",
                 other.get(), other->mSpec.get()));
            return rv;
        }
        NS_ASSERTION(other->mFile, "EnsureFile() lied!");
        return mFile->Equals(other->mFile, result);
    }

    // The URLs are not identical, and they do not correspond to the
    // same file, so they are different.
    *result = false;

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SchemeIs(const char *scheme, bool *result)
{
    NS_PRECONDITION(result, "null pointer");

    *result = SegmentIs(mScheme, scheme);
    return NS_OK;
}

/* virtual */ nsStandardURL*
nsStandardURL::StartClone()
{
    nsStandardURL *clone = new nsStandardURL();
    return clone;
}

NS_IMETHODIMP
nsStandardURL::Clone(nsIURI **result)
{
    return CloneInternal(eHonorRef, EmptyCString(), result);
}


NS_IMETHODIMP
nsStandardURL::CloneIgnoringRef(nsIURI **result)
{
    return CloneInternal(eIgnoreRef, EmptyCString(), result);
}

NS_IMETHODIMP
nsStandardURL::CloneWithNewRef(const nsACString& newRef, nsIURI **result)
{
    return CloneInternal(eReplaceRef, newRef, result);
}

nsresult
nsStandardURL::CloneInternal(nsStandardURL::RefHandlingEnum refHandlingMode,
                             const nsACString& newRef,
                             nsIURI **result)

{
    RefPtr<nsStandardURL> clone = StartClone();
    if (!clone)
        return NS_ERROR_OUT_OF_MEMORY;

    // Copy local members into clone.
    // Also copies the cached members mFile, mDisplayHost
    clone->CopyMembers(this, refHandlingMode, newRef, true);

    clone.forget(result);
    return NS_OK;
}

nsresult nsStandardURL::CopyMembers(nsStandardURL * source,
    nsStandardURL::RefHandlingEnum refHandlingMode, const nsACString& newRef,
    bool copyCached)
{
    mSpec = source->mSpec;
    mDefaultPort = source->mDefaultPort;
    mPort = source->mPort;
    mScheme = source->mScheme;
    mAuthority = source->mAuthority;
    mUsername = source->mUsername;
    mPassword = source->mPassword;
    mHost = source->mHost;
    mPath = source->mPath;
    mFilepath = source->mFilepath;
    mDirectory = source->mDirectory;
    mBasename = source->mBasename;
    mExtension = source->mExtension;
    mQuery = source->mQuery;
    mRef = source->mRef;
    mURLType = source->mURLType;
    mParser = source->mParser;
    mMutable = true;
    mSupportsFileURL = source->mSupportsFileURL;

    if (copyCached) {
        mFile = source->mFile;
        mCheckedIfHostA = source->mCheckedIfHostA;
        mDisplayHost = source->mDisplayHost;
        mSpecEncoding = source->mSpecEncoding;
    } else {
        InvalidateCache(true);
    }

    if (refHandlingMode == eIgnoreRef) {
        SetRef(EmptyCString());
    } else if (refHandlingMode == eReplaceRef) {
        SetRef(newRef);
    }


    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::Resolve(const nsACString &in, nsACString &out)
{
    const nsPromiseFlatCString &flat = PromiseFlatCString(in);
    // filter out unexpected chars "\r\n\t" if necessary
    nsAutoCString buf;
    net_FilterURIString(flat, buf);

    const char *relpath = buf.get();
    int32_t relpathLen = buf.Length();

    char *result = nullptr;

    LOG(("nsStandardURL::Resolve [this=%p spec=%s relpath=%s]\n",
        this, mSpec.get(), relpath));

    NS_ASSERTION(mParser, "no parser: unitialized");

    // NOTE: there is no need for this function to produce normalized
    // output.  normalization will occur when the result is used to
    // initialize a nsStandardURL object.

    if (mScheme.mLen < 0) {
        NS_WARNING("unable to Resolve URL: this URL not initialized");
        return NS_ERROR_NOT_INITIALIZED;
    }

    nsresult rv;
    URLSegment scheme;
    char *resultPath = nullptr;
    bool relative = false;
    uint32_t offset = 0;
    netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;

    // relative urls should never contain a host, so we always want to use
    // the noauth url parser.
    // use it to extract a possible scheme
    rv = mParser->ParseURL(relpath,
                           relpathLen,
                           &scheme.mPos, &scheme.mLen,
                           nullptr, nullptr,
                           nullptr, nullptr);

    // if the parser fails (for example because there is no valid scheme)
    // reset the scheme and assume a relative url
    if (NS_FAILED(rv)) scheme.Reset();

    nsAutoCString protocol(Segment(scheme));
    nsAutoCString baseProtocol(Scheme());

    // We need to do backslash replacement for the following cases:
    // 1. The input is an absolute path with a http/https/ftp scheme
    // 2. The input is a relative path, and the base URL has a http/https/ftp scheme
    if ((protocol.IsEmpty() && IsSpecialProtocol(baseProtocol)) ||
         IsSpecialProtocol(protocol)) {

        nsAutoCString::iterator start;
        nsAutoCString::iterator end;
        buf.BeginWriting(start);
        buf.EndWriting(end);
        while (start != end) {
            if (*start == '?' || *start == '#') {
                break;
            }
            if (*start == '\\') {
                *start = '/';
            }
            start++;
        }
    }

    if (scheme.mLen >= 0) {
        // add some flags to coalesceFlag if it is an ftp-url
        // need this later on when coalescing the resulting URL
        if (SegmentIs(relpath, scheme, "ftp", true)) {
            coalesceFlag = (netCoalesceFlags) (coalesceFlag
                                        | NET_COALESCE_ALLOW_RELATIVE_ROOT
                                        | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);

        }
        // this URL appears to be absolute
        // but try to find out more
        if (SegmentIs(mScheme, relpath, scheme, true)) {
            // mScheme and Scheme are the same
            // but this can still be relative
            if (strncmp(relpath + scheme.mPos + scheme.mLen, "://", 3) == 0) {
                // now this is really absolute
                // because a :// follows the scheme
                result = NS_strdup(relpath);
            } else {
                // This is a deprecated form of relative urls like
                // http:file or http:/path/file
                // we will support it for now ...
                relative = true;
                offset = scheme.mLen + 1;
            }
        } else {
            // the schemes are not the same, we are also done
            // because we have to assume this is absolute
            result = NS_strdup(relpath);
        }
    } else {
        // add some flags to coalesceFlag if it is an ftp-url
        // need this later on when coalescing the resulting URL
        if (SegmentIs(mScheme,"ftp")) {
            coalesceFlag = (netCoalesceFlags) (coalesceFlag
                                        | NET_COALESCE_ALLOW_RELATIVE_ROOT
                                        | NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
        }
        if (relpath[0] == '/' && relpath[1] == '/') {
            // this URL //host/path is almost absolute
            result = AppendToSubstring(mScheme.mPos, mScheme.mLen + 1, relpath);
        } else {
            // then it must be relative
            relative = true;
        }
    }
    if (relative) {
        uint32_t len = 0;
        const char *realrelpath = relpath + offset;
        switch (*realrelpath) {
        case '/':
            // overwrite everything after the authority
            len = mAuthority.mPos + mAuthority.mLen;
            break;
        case '?':
            // overwrite the existing ?query and #ref
            if (mQuery.mLen >= 0)
                len = mQuery.mPos - 1;
            else if (mRef.mLen >= 0)
                len = mRef.mPos - 1;
            else
                len = mPath.mPos + mPath.mLen;
            break;
        case '#':
        case '\0':
            // overwrite the existing #ref
            if (mRef.mLen < 0)
                len = mPath.mPos + mPath.mLen;
            else
                len = mRef.mPos - 1;
            break;
        default:
            if (coalesceFlag & NET_COALESCE_DOUBLE_SLASH_IS_ROOT) {
                if (Filename().Equals(NS_LITERAL_CSTRING("%2F"),
                                      nsCaseInsensitiveCStringComparator())) {
                    // if ftp URL ends with %2F then simply
                    // append relative part because %2F also
                    // marks the root directory with ftp-urls
                    len = mFilepath.mPos + mFilepath.mLen;
                } else {
                    // overwrite everything after the directory
                    len = mDirectory.mPos + mDirectory.mLen;
                }
            } else {
                // overwrite everything after the directory
                len = mDirectory.mPos + mDirectory.mLen;
            }
        }
        result = AppendToSubstring(0, len, realrelpath);
        // locate result path
        resultPath = result + mPath.mPos;
    }
    if (!result)
        return NS_ERROR_OUT_OF_MEMORY;

    if (resultPath)
        net_CoalesceDirs(coalesceFlag, resultPath);
    else {
        // locate result path
        resultPath = PL_strstr(result, "://");
        if (resultPath) {
            resultPath = PL_strchr(resultPath + 3, '/');
            if (resultPath)
                net_CoalesceDirs(coalesceFlag,resultPath);
        }
    }
    out.Adopt(result);
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetCommonBaseSpec(nsIURI *uri2, nsACString &aResult)
{
    NS_ENSURE_ARG_POINTER(uri2);

    // if uri's are equal, then return uri as is
    bool isEquals = false;
    if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals)
        return GetSpec(aResult);

    aResult.Truncate();

    // check pre-path; if they don't match, then return empty string
    nsStandardURL *stdurl2;
    nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &stdurl2);
    isEquals = NS_SUCCEEDED(rv)
            && SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme)
            && SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost)
            && SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername)
            && SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword)
            && (Port() == stdurl2->Port());
    if (!isEquals)
    {
        if (NS_SUCCEEDED(rv))
            NS_RELEASE(stdurl2);
        return NS_OK;
    }

    // scan for first mismatched character
    const char *thisIndex, *thatIndex, *startCharPos;
    startCharPos = mSpec.get() + mDirectory.mPos;
    thisIndex = startCharPos;
    thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;
    while ((*thisIndex == *thatIndex) && *thisIndex)
    {
        thisIndex++;
        thatIndex++;
    }

    // backup to just after previous slash so we grab an appropriate path
    // segment such as a directory (not partial segments)
    // todo:  also check for file matches which include '?' and '#'
    while ((thisIndex != startCharPos) && (*(thisIndex-1) != '/'))
        thisIndex--;

    // grab spec from beginning to thisIndex
    aResult = Substring(mSpec, mScheme.mPos, thisIndex - mSpec.get());

    NS_RELEASE(stdurl2);
    return rv;
}

NS_IMETHODIMP
nsStandardURL::GetRelativeSpec(nsIURI *uri2, nsACString &aResult)
{
    NS_ENSURE_ARG_POINTER(uri2);

    aResult.Truncate();

    // if uri's are equal, then return empty string
    bool isEquals = false;
    if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals)
        return NS_OK;

    nsStandardURL *stdurl2;
    nsresult rv = uri2->QueryInterface(kThisImplCID, (void **) &stdurl2);
    isEquals = NS_SUCCEEDED(rv)
            && SegmentIs(mScheme, stdurl2->mSpec.get(), stdurl2->mScheme)
            && SegmentIs(mHost, stdurl2->mSpec.get(), stdurl2->mHost)
            && SegmentIs(mUsername, stdurl2->mSpec.get(), stdurl2->mUsername)
            && SegmentIs(mPassword, stdurl2->mSpec.get(), stdurl2->mPassword)
            && (Port() == stdurl2->Port());
    if (!isEquals)
    {
        if (NS_SUCCEEDED(rv))
            NS_RELEASE(stdurl2);

        return uri2->GetSpec(aResult);
    }

    // scan for first mismatched character
    const char *thisIndex, *thatIndex, *startCharPos;
    startCharPos = mSpec.get() + mDirectory.mPos;
    thisIndex = startCharPos;
    thatIndex = stdurl2->mSpec.get() + mDirectory.mPos;

#ifdef XP_WIN
    bool isFileScheme = SegmentIs(mScheme, "file");
    if (isFileScheme)
    {
        // on windows, we need to match the first segment of the path
        // if these don't match then we need to return an absolute path
        // skip over any leading '/' in path
        while ((*thisIndex == *thatIndex) && (*thisIndex == '/'))
        {
            thisIndex++;
            thatIndex++;
        }
        // look for end of first segment
        while ((*thisIndex == *thatIndex) && *thisIndex && (*thisIndex != '/'))
        {
            thisIndex++;
            thatIndex++;
        }

        // if we didn't match through the first segment, return absolute path
        if ((*thisIndex != '/') || (*thatIndex != '/'))
        {
            NS_RELEASE(stdurl2);
            return uri2->GetSpec(aResult);
        }
    }
#endif

    while ((*thisIndex == *thatIndex) && *thisIndex)
    {
        thisIndex++;
        thatIndex++;
    }

    // backup to just after previous slash so we grab an appropriate path
    // segment such as a directory (not partial segments)
    // todo:  also check for file matches with '#' and '?'
    while ((*(thatIndex-1) != '/') && (thatIndex != startCharPos))
        thatIndex--;

    const char *limit = mSpec.get() + mFilepath.mPos + mFilepath.mLen;

    // need to account for slashes and add corresponding "../"
    for (; thisIndex <= limit && *thisIndex; ++thisIndex)
    {
        if (*thisIndex == '/')
            aResult.AppendLiteral("../");
    }

    // grab spec from thisIndex to end
    uint32_t startPos = stdurl2->mScheme.mPos + thatIndex - stdurl2->mSpec.get();
    aResult.Append(Substring(stdurl2->mSpec, startPos,
                             stdurl2->mSpec.Length() - startPos));

    NS_RELEASE(stdurl2);
    return rv;
}

//----------------------------------------------------------------------------
// nsStandardURL::nsIURL
//----------------------------------------------------------------------------

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetFilePath(nsACString &result)
{
    result = Filepath();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetQuery(nsACString &result)
{
    result = Query();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetRef(nsACString &result)
{
    result = Ref();
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetHasRef(bool *result)
{
    *result = (mRef.mLen >= 0);
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetDirectory(nsACString &result)
{
    result = Directory();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetFileName(nsACString &result)
{
    result = Filename();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetFileBaseName(nsACString &result)
{
    result = Basename();
    return NS_OK;
}

// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetFileExtension(nsACString &result)
{
    result = Extension();
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetFilePath(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &flat = PromiseFlatCString(input);
    const char *filepath = flat.get();

    LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));

    // if there isn't a filepath, then there can't be anything
    // after the path either.  this url is likely uninitialized.
    if (mFilepath.mLen < 0)
        return SetPathQueryRef(flat);

    if (filepath && *filepath) {
        nsAutoCString spec;
        uint32_t dirPos, basePos, extPos;
        int32_t dirLen, baseLen, extLen;
        nsresult rv;

        rv = mParser->ParseFilePath(filepath, flat.Length(),
                                    &dirPos, &dirLen,
                                    &basePos, &baseLen,
                                    &extPos, &extLen);
        if (NS_FAILED(rv)) return rv;

        // build up new candidate spec
        spec.Assign(mSpec.get(), mPath.mPos);

        // ensure leading '/'
        if (filepath[dirPos] != '/')
            spec.Append('/');

        nsSegmentEncoder encoder;

        // append encoded filepath components
        if (dirLen > 0)
            encoder.EncodeSegment(Substring(filepath + dirPos,
                                            filepath + dirPos + dirLen),
                                  esc_Directory | esc_AlwaysCopy, spec);
        if (baseLen > 0)
            encoder.EncodeSegment(Substring(filepath + basePos,
                                            filepath + basePos + baseLen),
                                  esc_FileBaseName | esc_AlwaysCopy, spec);
        if (extLen >= 0) {
            spec.Append('.');
            if (extLen > 0)
                encoder.EncodeSegment(Substring(filepath + extPos,
                                                filepath + extPos + extLen),
                                      esc_FileExtension | esc_AlwaysCopy,
                                      spec);
        }

        // compute the ending position of the current filepath
        if (mFilepath.mLen >= 0) {
            uint32_t end = mFilepath.mPos + mFilepath.mLen;
            if (mSpec.Length() > end)
                spec.Append(mSpec.get() + end, mSpec.Length() - end);
        }

        return SetSpecInternal(spec);
    }
    else if (mPath.mLen > 1) {
        mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
        // left shift query, and ref
        ShiftFromQuery(1 - mFilepath.mLen);
        // these contain only a '/'
        mPath.mLen = 1;
        mDirectory.mLen = 1;
        mFilepath.mLen = 1;
        // these are no longer defined
        mBasename.mLen = -1;
        mExtension.mLen = -1;
    }
    return NS_OK;
}

inline bool
IsUTFEncoding(const Encoding* aEncoding)
{
    return aEncoding == UTF_8_ENCODING ||
           aEncoding == UTF_16BE_ENCODING ||
           aEncoding == UTF_16LE_ENCODING;
}

NS_IMETHODIMP
nsStandardURL::SetQuery(const nsACString &input)
{
    return SetQueryWithEncoding(input, nullptr);
}

NS_IMETHODIMP
nsStandardURL::SetQueryWithEncoding(const nsACString &input,
                                    const Encoding* encoding)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &flat = PromiseFlatCString(input);
    const char *query = flat.get();

    LOG(("nsStandardURL::SetQuery [query=%s]\n", query));

    if (IsUTFEncoding(encoding)) {
        encoding = nullptr;
    }

    if (mPath.mLen < 0)
        return SetPathQueryRef(flat);

    if (mSpec.Length() + input.Length() - Query().Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    if (!query || !*query) {
        // remove existing query
        if (mQuery.mLen >= 0) {
            // remove query and leading '?'
            mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1);
            ShiftFromRef(-(mQuery.mLen + 1));
            mPath.mLen -= (mQuery.mLen + 1);
            mQuery.mPos = 0;
            mQuery.mLen = -1;
        }
        return NS_OK;
    }

    int32_t queryLen = flat.Length();
    if (query[0] == '?') {
        query++;
        queryLen--;
    }

    if (mQuery.mLen < 0) {
        if (mRef.mLen < 0)
            mQuery.mPos = mSpec.Length();
        else
            mQuery.mPos = mRef.mPos - 1;
        mSpec.Insert('?', mQuery.mPos);
        mQuery.mPos++;
        mQuery.mLen = 0;
        // the insertion pushes these out by 1
        mPath.mLen++;
        mRef.mPos++;
    }

    // encode query if necessary
    nsAutoCString buf;
    bool encoded;
    nsSegmentEncoder encoder(encoding);
    encoder.EncodeSegmentCount(query, URLSegment(0, queryLen), esc_Query,
                               buf, encoded);
    if (encoded) {
        query = buf.get();
        queryLen = buf.Length();
    }

    int32_t shift = ReplaceSegment(mQuery.mPos, mQuery.mLen, query, queryLen);

    if (shift) {
        mQuery.mLen = queryLen;
        mPath.mLen += shift;
        ShiftFromRef(shift);
    }
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetRef(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &flat = PromiseFlatCString(input);
    const char *ref = flat.get();

    LOG(("nsStandardURL::SetRef [ref=%s]\n", ref));

    if (mPath.mLen < 0)
        return SetPathQueryRef(flat);

    if (mSpec.Length() + input.Length() - Ref().Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    if (!ref || !*ref) {
        // remove existing ref
        if (mRef.mLen >= 0) {
            // remove ref and leading '#'
            mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1);
            mPath.mLen -= (mRef.mLen + 1);
            mRef.mPos = 0;
            mRef.mLen = -1;
        }
        return NS_OK;
    }

    int32_t refLen = flat.Length();
    if (ref[0] == '#') {
        ref++;
        refLen--;
    }

    if (mRef.mLen < 0) {
        mSpec.Append('#');
        ++mPath.mLen;  // Include the # in the path.
        mRef.mPos = mSpec.Length();
        mRef.mLen = 0;
    }

    // If precent encoding is necessary, `ref` will point to `buf`'s content.
    // `buf` needs to outlive any use of the `ref` pointer.
    nsAutoCString buf;
    // encode ref if necessary
    bool encoded;
    nsSegmentEncoder encoder;
    encoder.EncodeSegmentCount(ref, URLSegment(0, refLen), esc_Ref,
                               buf, encoded);
    if (encoded) {
        ref = buf.get();
        refLen = buf.Length();
    }

    int32_t shift = ReplaceSegment(mRef.mPos, mRef.mLen, ref, refLen);
    mPath.mLen += shift;
    mRef.mLen = refLen;
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetDirectory(const nsACString &input)
{
    MOZ_ASSERT_UNREACHABLE("SetDirectory");
    return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsStandardURL::SetFileName(const nsACString &input)
{
    ENSURE_MUTABLE();

    const nsPromiseFlatCString &flat = PromiseFlatCString(input);
    const char *filename = flat.get();

    LOG(("nsStandardURL::SetFileName [filename=%s]\n", filename));

    if (mPath.mLen < 0)
        return SetPathQueryRef(flat);

    if (mSpec.Length() + input.Length() - Filename().Length() > (uint32_t) net_GetURLMaxLength()) {
        return NS_ERROR_MALFORMED_URI;
    }

    int32_t shift = 0;

    if (!(filename && *filename)) {
        // remove the filename
        if (mBasename.mLen > 0) {
            if (mExtension.mLen >= 0)
                mBasename.mLen += (mExtension.mLen + 1);
            mSpec.Cut(mBasename.mPos, mBasename.mLen);
            shift = -mBasename.mLen;
            mBasename.mLen = 0;
            mExtension.mLen = -1;
        }
    }
    else {
        nsresult rv;
        URLSegment basename, extension;

        // let the parser locate the basename and extension
        rv = mParser->ParseFileName(filename, flat.Length(),
                                    &basename.mPos, &basename.mLen,
                                    &extension.mPos, &extension.mLen);
        if (NS_FAILED(rv)) return rv;

        if (basename.mLen < 0) {
            // remove existing filename
            if (mBasename.mLen >= 0) {
                uint32_t len = mBasename.mLen;
                if (mExtension.mLen >= 0)
                    len += (mExtension.mLen + 1);
                mSpec.Cut(mBasename.mPos, len);
                shift = -int32_t(len);
                mBasename.mLen = 0;
                mExtension.mLen = -1;
            }
        }
        else {
            nsAutoCString newFilename;
            bool ignoredOut;
            nsSegmentEncoder encoder;
            basename.mLen = encoder.EncodeSegmentCount(filename, basename,
                                                       esc_FileBaseName |
                                                       esc_AlwaysCopy,
                                                       newFilename,
                                                       ignoredOut);
            if (extension.mLen >= 0) {
                newFilename.Append('.');
                extension.mLen = encoder.EncodeSegmentCount(filename, extension,
                                                            esc_FileExtension |
                                                            esc_AlwaysCopy,
                                                            newFilename,
                                                            ignoredOut);
            }

            if (mBasename.mLen < 0) {
                // insert new filename
                mBasename.mPos = mDirectory.mPos + mDirectory.mLen;
                mSpec.Insert(newFilename, mBasename.mPos);
                shift = newFilename.Length();
            }
            else {
                // replace existing filename
                uint32_t oldLen = uint32_t(mBasename.mLen);
                if (mExtension.mLen >= 0)
                    oldLen += (mExtension.mLen + 1);
                mSpec.Replace(mBasename.mPos, oldLen, newFilename);
                shift = newFilename.Length() - oldLen;
            }

            mBasename.mLen = basename.mLen;
            mExtension.mLen = extension.mLen;
            if (mExtension.mLen >= 0)
                mExtension.mPos = mBasename.mPos + mBasename.mLen + 1;
        }
    }
    if (shift) {
        ShiftFromQuery(shift);
        mFilepath.mLen += shift;
        mPath.mLen += shift;
    }
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetFileBaseName(const nsACString &input)
{
    nsAutoCString extension;
    nsresult rv = GetFileExtension(extension);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoCString newFileName(input);

    if (!extension.IsEmpty()) {
        newFileName.Append('.');
        newFileName.Append(extension);
    }

    return SetFileName(newFileName);
}

NS_IMETHODIMP
nsStandardURL::SetFileExtension(const nsACString &input)
{
    nsAutoCString newFileName;
    nsresult rv = GetFileBaseName(newFileName);
    NS_ENSURE_SUCCESS(rv, rv);

    if (!input.IsEmpty()) {
        newFileName.Append('.');
        newFileName.Append(input);
    }

    return SetFileName(newFileName);
}

//----------------------------------------------------------------------------
// nsStandardURL::nsIFileURL
//----------------------------------------------------------------------------

nsresult
nsStandardURL::EnsureFile()
{
    NS_PRECONDITION(mSupportsFileURL,
                    "EnsureFile() called on a URL that doesn't support files!");
    if (mFile) {
        // Nothing to do
        return NS_OK;
    }

    // Parse the spec if we don't have a cached result
    if (mSpec.IsEmpty()) {
        NS_WARNING("url not initialized");
        return NS_ERROR_NOT_INITIALIZED;
    }

    if (!SegmentIs(mScheme, "file")) {
        NS_WARNING("not a file URL");
        return NS_ERROR_FAILURE;
    }

    return net_GetFileFromURLSpec(mSpec, getter_AddRefs(mFile));
}

NS_IMETHODIMP
nsStandardURL::GetFile(nsIFile **result)
{
    NS_PRECONDITION(mSupportsFileURL,
                    "GetFile() called on a URL that doesn't support files!");
    nsresult rv = EnsureFile();
    if (NS_FAILED(rv))
        return rv;

    if (LOG_ENABLED()) {
        LOG(("nsStandardURL::GetFile [this=%p spec=%s resulting_path=%s]\n",
            this, mSpec.get(), mFile->HumanReadablePath().get()));
    }

    // clone the file, so the caller can modify it.
    // XXX nsIFileURL.idl specifies that the consumer must _not_ modify the
    // nsIFile returned from this method; but it seems that some folks do
    // (see bug 161921). until we can be sure that all the consumers are
    // behaving themselves, we'll stay on the safe side and clone the file.
    // see bug 212724 about fixing the consumers.
    return mFile->Clone(result);
}

NS_IMETHODIMP
nsStandardURL::SetFile(nsIFile *file)
{
    ENSURE_MUTABLE();

    NS_ENSURE_ARG_POINTER(file);

    nsresult rv;
    nsAutoCString url;

    rv = net_GetURLSpecFromFile(file, url);
    if (NS_FAILED(rv)) return rv;

    uint32_t oldURLType = mURLType;
    uint32_t oldDefaultPort = mDefaultPort;
    rv = Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, url, nullptr, nullptr);

    if (NS_FAILED(rv)) {
        // Restore the old url type and default port if the call to Init fails.
        mURLType = oldURLType;
        mDefaultPort = oldDefaultPort;
        return rv;
    }

    // must clone |file| since its value is not guaranteed to remain constant
    InvalidateCache();
    if (NS_FAILED(file->Clone(getter_AddRefs(mFile)))) {
        NS_WARNING("nsIFile::Clone failed");
        // failure to clone is not fatal (GetFile will generate mFile)
        mFile = nullptr;
    }

    return NS_OK;
}

//----------------------------------------------------------------------------
// nsStandardURL::nsIStandardURL
//----------------------------------------------------------------------------

NS_IMETHODIMP
nsStandardURL::Init(uint32_t urlType,
                    int32_t defaultPort,
                    const nsACString &spec,
                    const char *charset,
                    nsIURI *baseURI)
{
    ENSURE_MUTABLE();

    if (spec.Length() > (uint32_t) net_GetURLMaxLength() ||
        defaultPort > std::numeric_limits<uint16_t>::max()) {
        return NS_ERROR_MALFORMED_URI;
    }

    InvalidateCache();

    switch (urlType) {
    case URLTYPE_STANDARD:
        mParser = net_GetStdURLParser();
        break;
    case URLTYPE_AUTHORITY:
        mParser = net_GetAuthURLParser();
        break;
    case URLTYPE_NO_AUTHORITY:
        mParser = net_GetNoAuthURLParser();
        break;
    default:
        NS_NOTREACHED("bad urlType");
        return NS_ERROR_INVALID_ARG;
    }
    mDefaultPort = defaultPort;
    mURLType = urlType;

    auto encoding =
        charset ? Encoding::ForLabelNoReplacement(MakeStringSpan(charset))
                : nullptr;
    // URI can't be encoded in UTF-16BE or UTF-16LE. Truncate encoding
    // if it is one of utf encodings (since a null encoding implies
    // UTF-8, this is safe even if encoding is UTF-8).
    if (IsUTFEncoding(encoding)) {
        encoding = nullptr;
    }

    if (baseURI && net_IsAbsoluteURL(spec)) {
        baseURI = nullptr;
    }

    if (!baseURI)
        return SetSpecWithEncoding(spec, encoding);

    nsAutoCString buf;
    nsresult rv = baseURI->Resolve(spec, buf);
    if (NS_FAILED(rv)) return rv;

    return SetSpecWithEncoding(buf, encoding);
}

NS_IMETHODIMP
nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort)
{
    ENSURE_MUTABLE();

    InvalidateCache();

    // should never be more than 16 bit
    if (aNewDefaultPort >= std::numeric_limits<uint16_t>::max()) {
        return NS_ERROR_MALFORMED_URI;
    }

    // If we're already using the new default-port as a custom port, then clear
    // it off of our mSpec & set mPort to -1, to indicate that we'll be using
    // the default from now on (which happens to match what we already had).
    if (mPort == aNewDefaultPort) {
        ReplacePortInSpec(-1);
        mPort = -1;
    }
    mDefaultPort = aNewDefaultPort;

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetMutable(bool *value)
{
    *value = mMutable;
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::SetMutable(bool value)
{
    NS_ENSURE_ARG(mMutable || !value);

    mMutable = value;
    return NS_OK;
}

//----------------------------------------------------------------------------
// nsStandardURL::nsISerializable
//----------------------------------------------------------------------------

NS_IMETHODIMP
nsStandardURL::Read(nsIObjectInputStream *stream)
{
    NS_PRECONDITION(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
    NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown,
                    "Shouldn't have spec encoding here");

    nsresult rv;

    uint32_t urlType;
    rv = stream->Read32(&urlType);
    if (NS_FAILED(rv)) return rv;
    mURLType = urlType;
    switch (mURLType) {
      case URLTYPE_STANDARD:
        mParser = net_GetStdURLParser();
        break;
      case URLTYPE_AUTHORITY:
        mParser = net_GetAuthURLParser();
        break;
      case URLTYPE_NO_AUTHORITY:
        mParser = net_GetNoAuthURLParser();
        break;
      default:
        NS_NOTREACHED("bad urlType");
        return NS_ERROR_FAILURE;
    }

    rv = stream->Read32((uint32_t *) &mPort);
    if (NS_FAILED(rv)) return rv;

    rv = stream->Read32((uint32_t *) &mDefaultPort);
    if (NS_FAILED(rv)) return rv;

    rv = NS_ReadOptionalCString(stream, mSpec);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mScheme);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mAuthority);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mUsername);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mPassword);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mHost);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mPath);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mFilepath);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mDirectory);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mBasename);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mExtension);
    if (NS_FAILED(rv)) return rv;

    // handle forward compatibility from older serializations that included mParam
    URLSegment old_param;
    rv = ReadSegment(stream, old_param);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mQuery);
    if (NS_FAILED(rv)) return rv;

    rv = ReadSegment(stream, mRef);
    if (NS_FAILED(rv)) return rv;

    nsAutoCString oldOriginCharset;
    rv = NS_ReadOptionalCString(stream, oldOriginCharset);
    if (NS_FAILED(rv)) return rv;

    bool isMutable;
    rv = stream->ReadBoolean(&isMutable);
    if (NS_FAILED(rv)) return rv;
    mMutable = isMutable;

    bool supportsFileURL;
    rv = stream->ReadBoolean(&supportsFileURL);
    if (NS_FAILED(rv)) return rv;
    mSupportsFileURL = supportsFileURL;

    // wait until object is set up, then modify path to include the param
    if (old_param.mLen >= 0) {  // note that mLen=0 is ";"
        // If this wasn't empty, it marks characters between the end of the
        // file and start of the query - mPath should include the param,
        // query and ref already.  Bump the mFilePath and
        // directory/basename/extension components to include this.
        mFilepath.Merge(mSpec,  ';', old_param);
        mDirectory.Merge(mSpec, ';', old_param);
        mBasename.Merge(mSpec,  ';', old_param);
        mExtension.Merge(mSpec, ';', old_param);
    }

    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::Write(nsIObjectOutputStream *stream)
{
    MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
               "The spec should never be this long, we missed a check.");
    nsresult rv;

    rv = stream->Write32(mURLType);
    if (NS_FAILED(rv)) return rv;

    rv = stream->Write32(uint32_t(mPort));
    if (NS_FAILED(rv)) return rv;

    rv = stream->Write32(uint32_t(mDefaultPort));
    if (NS_FAILED(rv)) return rv;

    rv = NS_WriteOptionalStringZ(stream, mSpec.get());
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mScheme);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mAuthority);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mUsername);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mPassword);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mHost);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mPath);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mFilepath);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mDirectory);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mBasename);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mExtension);
    if (NS_FAILED(rv)) return rv;

    // for backwards compatibility since we removed mParam.  Note that this will mean that
    // an older browser will read "" for mParam, and the param(s) will be part of mPath (as they
    // after the removal of special handling).  It only matters if you downgrade a browser to before
    // the patch.
    URLSegment empty;
    rv = WriteSegment(stream, empty);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mQuery);
    if (NS_FAILED(rv)) return rv;

    rv = WriteSegment(stream, mRef);
    if (NS_FAILED(rv)) return rv;

    // former origin charset
    rv = NS_WriteOptionalStringZ(stream, EmptyCString().get());
    if (NS_FAILED(rv)) return rv;

    rv = stream->WriteBoolean(mMutable);
    if (NS_FAILED(rv)) return rv;

    rv = stream->WriteBoolean(mSupportsFileURL);
    if (NS_FAILED(rv)) return rv;

    // mSpecEncoding and mDisplayHost are just caches that can be recovered as needed.

    return NS_OK;
}

//---------------------------------------------------------------------------
// nsStandardURL::nsIIPCSerializableURI
//---------------------------------------------------------------------------

inline
ipc::StandardURLSegment
ToIPCSegment(const nsStandardURL::URLSegment& aSegment)
{
    return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen);
}

inline
nsStandardURL::URLSegment
FromIPCSegment(const ipc::StandardURLSegment& aSegment)
{
    return nsStandardURL::URLSegment(aSegment.position(), aSegment.length());
}

void
nsStandardURL::Serialize(URIParams& aParams)
{
    MOZ_ASSERT(mSpec.Length() <= (uint32_t) net_GetURLMaxLength(),
               "The spec should never be this long, we missed a check.");
    StandardURLParams params;

    params.urlType() = mURLType;
    params.port() = mPort;
    params.defaultPort() = mDefaultPort;
    params.spec() = mSpec;
    params.scheme() = ToIPCSegment(mScheme);
    params.authority() = ToIPCSegment(mAuthority);
    params.username() = ToIPCSegment(mUsername);
    params.password() = ToIPCSegment(mPassword);
    params.host() = ToIPCSegment(mHost);
    params.path() = ToIPCSegment(mPath);
    params.filePath() = ToIPCSegment(mFilepath);
    params.directory() = ToIPCSegment(mDirectory);
    params.baseName() = ToIPCSegment(mBasename);
    params.extension() = ToIPCSegment(mExtension);
    params.query() = ToIPCSegment(mQuery);
    params.ref() = ToIPCSegment(mRef);
    params.isMutable() = !!mMutable;
    params.supportsFileURL() = !!mSupportsFileURL;
    // mSpecEncoding and mDisplayHost are just caches that can be recovered as needed.

    aParams = params;
}

bool
nsStandardURL::Deserialize(const URIParams& aParams)
{
    NS_PRECONDITION(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
    NS_PRECONDITION(mSpecEncoding == eEncoding_Unknown,
                    "Shouldn't have spec encoding here");
    NS_PRECONDITION(!mFile, "Shouldn't have cached file");

    if (aParams.type() != URIParams::TStandardURLParams) {
        NS_ERROR("Received unknown parameters from the other process!");
        return false;
    }

    const StandardURLParams& params = aParams.get_StandardURLParams();

    mURLType = params.urlType();
    switch (mURLType) {
        case URLTYPE_STANDARD:
            mParser = net_GetStdURLParser();
            break;
        case URLTYPE_AUTHORITY:
            mParser = net_GetAuthURLParser();
            break;
        case URLTYPE_NO_AUTHORITY:
            mParser = net_GetNoAuthURLParser();
            break;
        default:
            NS_NOTREACHED("bad urlType");
            return false;
    }

    mPort = params.port();
    mDefaultPort = params.defaultPort();
    mSpec = params.spec();
    mScheme = FromIPCSegment(params.scheme());
    mAuthority = FromIPCSegment(params.authority());
    mUsername = FromIPCSegment(params.username());
    mPassword = FromIPCSegment(params.password());
    mHost = FromIPCSegment(params.host());
    mPath = FromIPCSegment(params.path());
    mFilepath = FromIPCSegment(params.filePath());
    mDirectory = FromIPCSegment(params.directory());
    mBasename = FromIPCSegment(params.baseName());
    mExtension = FromIPCSegment(params.extension());
    mQuery = FromIPCSegment(params.query());
    mRef = FromIPCSegment(params.ref());
    mMutable = params.isMutable();
    mSupportsFileURL = params.supportsFileURL();

    // mSpecEncoding and mDisplayHost are just caches that can be recovered as needed.
    return true;
}

//----------------------------------------------------------------------------
// nsStandardURL::nsIClassInfo
//----------------------------------------------------------------------------

NS_IMETHODIMP
nsStandardURL::GetInterfaces(uint32_t *count, nsIID * **array)
{
    *count = 0;
    *array = nullptr;
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetScriptableHelper(nsIXPCScriptable **_retval)
{
    *_retval = nullptr;
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetContractID(nsACString& aContractID)
{
    aContractID.SetIsVoid(true);
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetClassDescription(nsACString& aClassDescription)
{
    aClassDescription.SetIsVoid(true);
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetClassID(nsCID * *aClassID)
{
    *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
    if (!*aClassID)
        return NS_ERROR_OUT_OF_MEMORY;
    return GetClassIDNoAlloc(*aClassID);
}

NS_IMETHODIMP
nsStandardURL::GetFlags(uint32_t *aFlags)
{
    *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
    return NS_OK;
}

NS_IMETHODIMP
nsStandardURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
{
    *aClassIDNoAlloc = kStandardURLCID;
    return NS_OK;
}

//----------------------------------------------------------------------------
// nsStandardURL::nsISizeOf
//----------------------------------------------------------------------------

size_t
nsStandardURL::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
  return mSpec.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
         mDisplayHost.SizeOfExcludingThisIfUnshared(aMallocSizeOf);

  // Measurement of the following members may be added later if DMD finds it is
  // worthwhile:
  // - mParser
  // - mFile
}

size_t
nsStandardURL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}

} // namespace net
} // namespace mozilla

// For unit tests.  Including nsStandardURL.h seems to cause problems
nsresult
Test_NormalizeIPv4(const nsACString& host, nsCString& result)
{
    return nsStandardURL::NormalizeIPv4(host, result);
}