summaryrefslogtreecommitdiffstats
path: root/netwerk/base/nsStandardURL.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/base/nsStandardURL.cpp4034
1 files changed, 4034 insertions, 0 deletions
diff --git a/netwerk/base/nsStandardURL.cpp b/netwerk/base/nsStandardURL.cpp
new file mode 100644
index 0000000000..de3578c439
--- /dev/null
+++ b/netwerk/base/nsStandardURL.cpp
@@ -0,0 +1,4034 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 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 "ipc/IPCMessageUtils.h"
+
+#include "nsASCIIMask.h"
+#include "nsStandardURL.h"
+#include "nsCRT.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIIDNService.h"
+#include "mozilla/Logging.h"
+#include "nsIURLParser.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/TextUtils.h"
+#include <algorithm>
+#include "nsContentUtils.h"
+#include "prprf.h"
+#include "nsReadableUtils.h"
+#include "mozilla/net/MozURL_ffi.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+#include "nsIClassInfoImpl.h"
+#include <string.h>
+
+//
+// setenv MOZ_LOG nsStandardURL:5
+//
+static mozilla::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);
+
+// This will always be initialized and destroyed on the main thread, but
+// can be safely used on other threads.
+StaticRefPtr<nsIIDNService> nsStandardURL::gIDN;
+
+// This value will only be updated on the main thread once.
+static Atomic<bool, Relaxed> gInitialized{false};
+
+const char nsStandardURL::gHostLimitDigits[] = {'/', '\\', '?', '#', 0};
+
+// Invalid host characters
+// 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 == 0x7F || // // DEL (delete)
+ c == ' ' || c == '#' || c == '/' || c == ':' || c == '?' || c == '@' ||
+ c == '[' || c == '\\' || c == ']' || c == '*' || c == '<' ||
+ c == '^' ||
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ // Mailnews %-escapes file paths into URLs.
+ c == '>' || c == '|' || c == '"';
+#else
+ c == '>' || c == '|' || c == '"' || c == '%';
+#endif
+}
+constexpr ASCIIMaskArray sInvalidHostChars =
+ CreateASCIIMask(TestForInvalidHostCharacters);
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsSegmentEncoder
+//----------------------------------------------------------------------------
+
+nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding* encoding)
+ : mEncoding(encoding) {
+ if (mEncoding == UTF_8_ENCODING) {
+ mEncoding = nullptr;
+ }
+}
+
+int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount(
+ const char* aStr, const URLSegment& aSeg, int16_t aMask, nsCString& aOut,
+ bool& aAppended, uint32_t aExtraLen) {
+ // aExtraLen is characters outside the segment that will be
+ // added when the segment is not empty (like the @ following
+ // a username).
+ if (!aStr || aSeg.mLen <= 0) {
+ // Empty segment, so aExtraLen not added per above.
+ aAppended = false;
+ return 0;
+ }
+
+ uint32_t origLen = aOut.Length();
+
+ Span<const char> span = Span(aStr + aSeg.mPos, aSeg.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.
+ if (mEncoding) {
+ size_t upTo;
+ if (MOZ_UNLIKELY(mEncoding == ISO_2022_JP_ENCODING)) {
+ upTo = Encoding::ISO2022JPASCIIValidUpTo(AsBytes(span));
+ } else {
+ upTo = Encoding::ASCIIValidUpTo(AsBytes(span));
+ }
+ if (upTo != span.Length()) {
+ // we have to encode this segment
+ char bufferArr[512];
+ Span<char> buffer = Span(bufferArr);
+
+ auto encoder = mEncoding->NewEncoder();
+
+ nsAutoCString valid; // has to be declared in this scope
+ if (MOZ_UNLIKELY(!IsUtf8(span.From(upTo)))) {
+ MOZ_ASSERT_UNREACHABLE("Invalid UTF-8 passed to nsStandardURL.");
+ // It's UB to pass invalid UTF-8 to
+ // EncodeFromUTF8WithoutReplacement(), so let's make our input valid
+ // UTF-8 by replacing invalid sequences with the REPLACEMENT
+ // CHARACTER.
+ UTF_8_ENCODING->Decode(
+ nsDependentCSubstring(span.Elements(), span.Length()), valid);
+ // This assigment is OK. `span` can't be used after `valid` has
+ // been destroyed because the only way out of the scope that `valid`
+ // was declared in is via return inside the loop below. Specifically,
+ // the return is the only way out of the loop.
+ span = valid;
+ }
+
+ size_t totalRead = 0;
+ for (;;) {
+ auto [encoderResult, read, written] =
+ encoder->EncodeFromUTF8WithoutReplacement(
+ AsBytes(span.From(totalRead)), AsWritableBytes(buffer), true);
+ totalRead += read;
+ auto bufferWritten = buffer.To(written);
+ if (!NS_EscapeURLSpan(bufferWritten, aMask, aOut)) {
+ aOut.Append(bufferWritten);
+ }
+ if (encoderResult == kInputEmpty) {
+ aAppended = true;
+ // Difference between original and current output
+ // string lengths plus extra length
+ return aOut.Length() - origLen + aExtraLen;
+ }
+ if (encoderResult == kOutputFull) {
+ continue;
+ }
+ aOut.AppendLiteral("%26%23");
+ aOut.AppendInt(encoderResult);
+ aOut.AppendLiteral("%3B");
+ }
+ MOZ_RELEASE_ASSERT(
+ false,
+ "There's supposed to be no way out of the above loop except return.");
+ }
+ }
+
+ if (NS_EscapeURLSpan(span, aMask, aOut)) {
+ aAppended = true;
+ // Difference between original and current output
+ // string lengths plus extra length
+ return aOut.Length() - origLen + aExtraLen;
+ }
+ aAppended = false;
+ // Original segment length plus extra length
+ return span.Length() + aExtraLen;
+}
+
+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 StaticMutex gAllURLsMutex MOZ_UNANNOTATED;
+static LinkedList<nsStandardURL> gAllURLs;
+#endif
+
+nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
+ : mURLType(URLTYPE_STANDARD),
+ 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.
+ MOZ_ASSERT(gInitialized);
+
+ // default parser in case nsIStandardURL::Init is never called
+ mParser = net_GetStdURLParser();
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ if (aTrackURL) {
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ gAllURLs.insertBack(this);
+ }
+#endif
+}
+
+bool nsStandardURL::IsValid() {
+ auto checkSegment = [&](const nsStandardURL::URLSegment& aSeg) {
+#ifdef EARLY_BETA_OR_EARLIER
+ // If the parity is not the same, we assume that this is caused by a memory
+ // error. In this case, we think this URLSegment is valid.
+ if ((aSeg.mPos.Parity() != aSeg.mPos.CalculateParity()) ||
+ (aSeg.mLen.Parity() != aSeg.mLen.CalculateParity())) {
+ MOZ_ASSERT(false);
+ return true;
+ }
+#endif
+ // Bad value
+ if (NS_WARN_IF(aSeg.mLen < -1)) {
+ return false;
+ }
+ if (aSeg.mLen == -1) {
+ return true;
+ }
+
+ // Points out of string
+ if (NS_WARN_IF(aSeg.mPos + aSeg.mLen > mSpec.Length())) {
+ return false;
+ }
+
+ // Overflow
+ if (NS_WARN_IF(aSeg.mPos + aSeg.mLen < aSeg.mPos)) {
+ return false;
+ }
+
+ return true;
+ };
+
+ bool allSegmentsValid = checkSegment(mScheme) && checkSegment(mAuthority) &&
+ checkSegment(mUsername) && checkSegment(mPassword) &&
+ checkSegment(mHost) && checkSegment(mPath) &&
+ checkSegment(mFilepath) && checkSegment(mDirectory) &&
+ checkSegment(mBasename) && checkSegment(mExtension) &&
+ checkSegment(mQuery) && checkSegment(mRef);
+ if (!allSegmentsValid) {
+ return false;
+ }
+
+ if (mScheme.mPos != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsStandardURL::SanityCheck() {
+ if (!IsValid()) {
+ nsPrintfCString msg(
+ "mLen:%zX, mScheme (%X,%X), mAuthority (%X,%X), mUsername (%X,%X), "
+ "mPassword (%X,%X), mHost (%X,%X), mPath (%X,%X), mFilepath (%X,%X), "
+ "mDirectory (%X,%X), mBasename (%X,%X), mExtension (%X,%X), mQuery "
+ "(%X,%X), mRef (%X,%X)",
+ mSpec.Length(), (uint32_t)mScheme.mPos, (int32_t)mScheme.mLen,
+ (uint32_t)mAuthority.mPos, (int32_t)mAuthority.mLen,
+ (uint32_t)mUsername.mPos, (int32_t)mUsername.mLen,
+ (uint32_t)mPassword.mPos, (int32_t)mPassword.mLen, (uint32_t)mHost.mPos,
+ (int32_t)mHost.mLen, (uint32_t)mPath.mPos, (int32_t)mPath.mLen,
+ (uint32_t)mFilepath.mPos, (int32_t)mFilepath.mLen,
+ (uint32_t)mDirectory.mPos, (int32_t)mDirectory.mLen,
+ (uint32_t)mBasename.mPos, (int32_t)mBasename.mLen,
+ (uint32_t)mExtension.mPos, (int32_t)mExtension.mLen,
+ (uint32_t)mQuery.mPos, (int32_t)mQuery.mLen, (uint32_t)mRef.mPos,
+ (int32_t)mRef.mLen);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URLSegments,
+ msg);
+
+ MOZ_CRASH("nsStandardURL::SanityCheck failed");
+ }
+}
+
+nsStandardURL::~nsStandardURL() {
+ LOG(("Destroying nsStandardURL @%p\n", this));
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ {
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ if (isInList()) {
+ remove();
+ }
+ }
+#endif
+}
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+struct DumpLeakedURLs {
+ DumpLeakedURLs() = default;
+ ~DumpLeakedURLs();
+};
+
+DumpLeakedURLs::~DumpLeakedURLs() {
+ MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ if (!gAllURLs.isEmpty()) {
+ printf("Leaked URLs:\n");
+ for (auto* url : gAllURLs) {
+ url->PrintSpec();
+ }
+ gAllURLs.clear();
+ }
+}
+#endif
+
+void nsStandardURL::InitGlobalObjects() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (gInitialized) {
+ return;
+ }
+
+ gInitialized = true;
+
+ nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
+ if (serv) {
+ gIDN = serv;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(gIDN);
+
+ // Make sure nsURLHelper::InitGlobals() gets called on the main thread
+ nsCOMPtr<nsIURLParser> parser = net_GetStdURLParser();
+ MOZ_DIAGNOSTIC_ASSERT(parser);
+ Unused << parser;
+}
+
+void nsStandardURL::ShutdownGlobalObjects() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ gIDN = nullptr;
+
+#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().
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ 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;
+ }
+}
+
+// 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.
+//
+// length is assumed to be <= host.Length(); the caller 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, bool trailingDot) {
+ 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 == '.') {
+ // A dot should not follow a dot, or be first - it can follow an x though.
+ if (!(lastWasNumber ||
+ (i >= 2 && (host[i - 1] == 'X' || host[i - 1] == 'x') &&
+ host[i - 2] == '0')) ||
+ (i == (length - 1) && trailingDot)) {
+ return -1;
+ }
+
+ 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;
+ [[fallthrough]];
+ case 8:
+ ++current;
+ break;
+ case 10:
+ default:
+ break;
+ }
+ for (; current < end; ++current) {
+ value *= base;
+ char c = *current;
+ MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
+ (base == 8 && c >= '0' && c <= '7') ||
+ (base == 16 && IsAsciiHexDigit(c)));
+ if (IsAsciiDigit(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
+
+ // Use "length" rather than host.Length() after call to
+ // ValidateIPv4Number because of potential trailing period.
+ nsDependentCSubstring filteredHost;
+ bool trailingDot = false;
+ if (host.Length() > 0 && host.Last() == '.') {
+ trailingDot = true;
+ filteredHost.Rebind(host.BeginReading(), host.Length() - 1);
+ } else {
+ filteredHost.Rebind(host.BeginReading(), host.Length());
+ }
+
+ int32_t length = static_cast<int32_t>(filteredHost.Length());
+ int32_t dotCount = ValidateIPv4Number(filteredHost, bases, dotIndex,
+ onlyBase10, length, trailingDot);
+ 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);
+
+ // parse the last part first
+ 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;
+ }
+
+ // parse remaining parts starting from first part
+ 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));
+ }
+
+ // A special case for ipv4 URL like "127." should have the same result as
+ // "127".
+ if (dotCount == 1 && dotIndex[0] == length - 1) {
+ ipv4 = (ipv4 & 0xff000000) >> 24;
+ }
+
+ 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 nsCString& host, nsCString& result) {
+ result.Truncate();
+ mDisplayHost.Truncate();
+ nsresult rv;
+
+ if (!gIDN) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Even if it's already ACE, we must still call ConvertUTF8toACE in order
+ // for the input normalization to take place.
+ rv = gIDN->ConvertUTF8toACE(host, result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the ASCII representation doesn't contain the xn-- token then we don't
+ // need to call ConvertToDisplayIDN as that would not change anything.
+ if (!StringBeginsWith(result, "xn--"_ns) &&
+ result.Find(".xn--"_ns) == kNotFound) {
+ mCheckedIfHostA = true;
+ return NS_OK;
+ }
+
+ bool isAscii = true;
+ nsAutoCString displayHost;
+ rv = gIDN->ConvertToDisplayIDN(result, &isAscii, displayHost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mCheckedIfHostA = true;
+ if (!isAscii) {
+ mDisplayHost = displayHost;
+ }
+ 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(Substring(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;
+}
+
+static bool ContainsOnlyAsciiDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiDigit(*c)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool ContainsOnlyAsciiHexDigits(const nsDependentCSubstring& input) {
+ for (const auto* c = input.BeginReading(); c < input.EndReading(); c++) {
+ if (!IsAsciiHexDigit(*c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// https://url.spec.whatwg.org/#ends-in-a-number-checker
+static bool EndsInANumber(const nsCString& input) {
+ // 1. Let parts be the result of strictly splitting input on U+002E (.).
+ nsTArray<nsDependentCSubstring> parts;
+ for (const nsDependentCSubstring& part : input.Split('.')) {
+ parts.AppendElement(part);
+ }
+
+ if (parts.Length() == 0) {
+ return false;
+ }
+
+ // 2.If the last item in parts is the empty string, then:
+ // 1. If parts’s size is 1, then return false.
+ // 2. Remove the last item from parts.
+ if (parts.LastElement().IsEmpty()) {
+ if (parts.Length() == 1) {
+ return false;
+ }
+ Unused << parts.PopLastElement();
+ }
+
+ // 3. Let last be the last item in parts.
+ const nsDependentCSubstring& last = parts.LastElement();
+
+ // 4. If last is non-empty and contains only ASCII digits, then return true.
+ // The erroneous input "09" will be caught by the IPv4 parser at a later
+ // stage.
+ if (!last.IsEmpty()) {
+ if (ContainsOnlyAsciiDigits(last)) {
+ return true;
+ }
+ }
+
+ // 5. If parsing last as an IPv4 number does not return failure, then return
+ // true. This is equivalent to checking that last is "0X" or "0x", followed by
+ // zero or more ASCII hex digits.
+ if (StringBeginsWith(last, "0x"_ns) || StringBeginsWith(last, "0X"_ns)) {
+ if (ContainsOnlyAsciiHexDigits(Substring(last, 2))) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// 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);
+ // Username@
+ approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username,
+ encUsername, useEncUsername, 0);
+ approxLen += 1; // reserve length for @
+ // :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 (EndsInANumber(encHost)) {
+ rv = NormalizeIPv4(encHost, ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ 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;
+ }
+ } else {
+ // empty host means empty mDisplayHost
+ mDisplayHost.Truncate();
+ mCheckedIfHostA = true;
+ }
+
+ // 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 directory(mDirectory);
+ URLSegment basename(mBasename);
+ URLSegment extension(mExtension);
+ URLSegment query(mQuery);
+ URLSegment ref(mRef);
+
+ // The encoded string could be longer than the original input, so we need
+ // to check the final URI isn't longer than the max length.
+ if (approxLen + 1 > StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // 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();
+ 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 || mPassword.mLen > 0) {
+ if (mUsername.mLen > 0) {
+ i = AppendSegmentToBuf(buf, i, spec, username, mUsername, &encUsername,
+ useEncUsername, &diff);
+ ShiftFromPassword(diff);
+ } else {
+ mUsername.mLen = -1;
+ }
+ if (password.mLen > 0) {
+ buf[i++] = ':';
+ i = AppendSegmentToBuf(buf, i, spec, password, mPassword, &encPassword,
+ useEncPassword, &diff);
+ ShiftFromHost(diff);
+ } else {
+ mPassword.mLen = -1;
+ }
+ buf[i++] = '@';
+ } else {
+ mUsername.mLen = -1;
+ mPassword.mLen = -1;
+ }
+ 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] == '/' && 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.Truncate(strlen(buf));
+ NS_ASSERTION(mSpec.Length() <= approxLen,
+ "We've overflowed the mSpec buffer!");
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ 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 !nsCRT::strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+ }
+
+ 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 !nsCRT::strncasecmp(spec + seg.mPos, val, seg.mLen) &&
+ (val[seg.mLen] == '\0');
+ }
+
+ 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 !nsCRT::strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos,
+ seg1.mLen);
+ }
+
+ 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 > (int32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ //
+ // parse given URL string
+ //
+ uint32_t schemePos = mScheme.mPos;
+ int32_t schemeLen = mScheme.mLen;
+ uint32_t authorityPos = mAuthority.mPos;
+ int32_t authorityLen = mAuthority.mLen;
+ uint32_t pathPos = mPath.mPos;
+ int32_t pathLen = mPath.mLen;
+ rv = mParser->ParseURL(spec, specLen, &schemePos, &schemeLen, &authorityPos,
+ &authorityLen, &pathPos, &pathLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mScheme.mPos = schemePos;
+ mScheme.mLen = schemeLen;
+ mAuthority.mPos = authorityPos;
+ mAuthority.mLen = authorityLen;
+ mPath.mPos = pathPos;
+ mPath.mLen = pathLen;
+
+#ifdef DEBUG
+ if (mScheme.mLen <= 0) {
+ printf("spec=%s\n", spec);
+ NS_WARNING("malformed url: no scheme");
+ }
+#endif
+
+ if (mAuthority.mLen > 0) {
+ uint32_t usernamePos = mUsername.mPos;
+ int32_t usernameLen = mUsername.mLen;
+ uint32_t passwordPos = mPassword.mPos;
+ int32_t passwordLen = mPassword.mLen;
+ uint32_t hostPos = mHost.mPos;
+ int32_t hostLen = mHost.mLen;
+ rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
+ &usernamePos, &usernameLen, &passwordPos,
+ &passwordLen, &hostPos, &hostLen, &mPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mUsername.mPos = usernamePos;
+ mUsername.mLen = usernameLen;
+ mPassword.mPos = passwordPos;
+ mPassword.mLen = passwordLen;
+ mHost.mPos = hostPos;
+ mHost.mLen = hostLen;
+
+ // 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 > (int32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ uint32_t filePathPos = mFilepath.mPos;
+ int32_t filePathLen = mFilepath.mLen;
+ uint32_t queryPos = mQuery.mPos;
+ int32_t queryLen = mQuery.mLen;
+ uint32_t refPos = mRef.mPos;
+ int32_t refLen = mRef.mLen;
+ nsresult rv =
+ mParser->ParsePath(spec + pathPos, pathLen, &filePathPos, &filePathLen,
+ &queryPos, &queryLen, &refPos, &refLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mFilepath.mPos = filePathPos;
+ mFilepath.mLen = filePathLen;
+ mQuery.mPos = queryPos;
+ mQuery.mLen = queryLen;
+ mRef.mPos = refPos;
+ mRef.mLen = refLen;
+
+ mFilepath.mPos += pathPos;
+ mQuery.mPos += pathPos;
+ mRef.mPos += pathPos;
+
+ if (mFilepath.mLen > 0) {
+ uint32_t directoryPos = mDirectory.mPos;
+ int32_t directoryLen = mDirectory.mLen;
+ uint32_t basenamePos = mBasename.mPos;
+ int32_t basenameLen = mBasename.mLen;
+ uint32_t extensionPos = mExtension.mPos;
+ int32_t extensionLen = mExtension.mLen;
+ rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
+ &directoryPos, &directoryLen, &basenamePos,
+ &basenameLen, &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDirectory.mPos = directoryPos;
+ mDirectory.mLen = directoryLen;
+ mBasename.mPos = basenamePos;
+ mBasename.mLen = basenameLen;
+ mExtension.mPos = extensionPos;
+ mExtension.mLen = extensionLen;
+
+ 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);
+ 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;
+
+ uint32_t pos = seg.mPos;
+ rv = stream->Read32(&pos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ seg.mPos = pos;
+
+ uint32_t len = seg.mLen;
+ rv = stream->Read32(&len);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CheckedInt<int32_t> checkedLen(len);
+ if (!checkedLen.isValid()) {
+ seg.mLen = -1;
+ } else {
+ seg.mLen = len;
+ }
+
+ 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;
+}
+
+#define SHIFT_FROM(name, what) \
+ void nsStandardURL::name(int32_t diff) { \
+ if (!diff) return; \
+ if ((what).mLen >= 0) { \
+ CheckedInt<int32_t> pos = (uint32_t)(what).mPos; \
+ pos += diff; \
+ MOZ_ASSERT(pos.isValid()); \
+ (what).mPos = pos.value(); \
+ } else { \
+ MOZ_RELEASE_ASSERT((what).mLen == -1); \
+ }
+
+#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::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMPL_CLASSINFO(nsStandardURL, nullptr, nsIClassInfo::THREADSAFE,
+ NS_STANDARDURL_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsStandardURL)
+
+//----------------------------------------------------------------------------
+// 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_IMPL_QUERY_CLASSINFO(nsStandardURL)
+ 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() <= StaticPrefs::network_standard_url_max_length(),
+ "The spec should never be this long, we missed a check.");
+ result = mSpec;
+ return NS_OK;
+}
+
+// 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);
+ MOZ_ASSERT(mCheckedIfHostA);
+ 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) {
+ 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)) {
+ 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) {
+ 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();
+ MOZ_ASSERT(mCheckedIfHostA);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDisplayPrePath(nsACString& result) {
+ result = Prepath();
+ 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) {
+ return GetAsciiHostPort(result);
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHost(nsACString& result) { return GetAsciiHost(result); }
+
+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) {
+ result = mSpec;
+ 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) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
+
+ if (input.Length() > StaticPrefs::network_standard_url_max_length()) {
+ 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 current URL
+ nsStandardURL prevURL(false, false);
+ prevURL.CopyMembers(this, eHonorRef, ""_ns);
+ Clear();
+
+ if (IsSpecialProtocol(filteredURI)) {
+ // Bug 652186: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ auto* start = filteredURI.BeginWriting();
+ auto* end = filteredURI.EndWriting();
+ 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 (mScheme.mLen <= 0) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ 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, ""_ns);
+ return rv;
+ }
+
+ if (LOG_ENABLED()) {
+ LOG((" spec = %s\n", mSpec.get()));
+ LOG((" port = %d\n", mPort));
+ LOG((" scheme = (%u,%d)\n", (uint32_t)mScheme.mPos,
+ (int32_t)mScheme.mLen));
+ LOG((" authority = (%u,%d)\n", (uint32_t)mAuthority.mPos,
+ (int32_t)mAuthority.mLen));
+ LOG((" username = (%u,%d)\n", (uint32_t)mUsername.mPos,
+ (int32_t)mUsername.mLen));
+ LOG((" password = (%u,%d)\n", (uint32_t)mPassword.mPos,
+ (int32_t)mPassword.mLen));
+ LOG((" hostname = (%u,%d)\n", (uint32_t)mHost.mPos, (int32_t)mHost.mLen));
+ LOG((" path = (%u,%d)\n", (uint32_t)mPath.mPos, (int32_t)mPath.mLen));
+ LOG((" filepath = (%u,%d)\n", (uint32_t)mFilepath.mPos,
+ (int32_t)mFilepath.mLen));
+ LOG((" directory = (%u,%d)\n", (uint32_t)mDirectory.mPos,
+ (int32_t)mDirectory.mLen));
+ LOG((" basename = (%u,%d)\n", (uint32_t)mBasename.mPos,
+ (int32_t)mBasename.mLen));
+ LOG((" extension = (%u,%d)\n", (uint32_t)mExtension.mPos,
+ (int32_t)mExtension.mLen));
+ LOG((" query = (%u,%d)\n", (uint32_t)mQuery.mPos,
+ (int32_t)mQuery.mLen));
+ LOG((" ref = (%u,%d)\n", (uint32_t)mRef.mPos, (int32_t)mRef.mLen));
+ }
+
+ SanityCheck();
+ return rv;
+}
+
+nsresult nsStandardURL::SetScheme(const nsACString& input) {
+ // Strip tabs, newlines, carriage returns from input
+ nsAutoCString scheme(input);
+ scheme.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ 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() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ 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);
+
+ // If the scheme changes the default port also changes.
+ if (Scheme() == "http"_ns || Scheme() == "ws"_ns) {
+ mDefaultPort = 80;
+ } else if (Scheme() == "https"_ns || Scheme() == "wss"_ns) {
+ mDefaultPort = 443;
+ }
+ if (mPort == mDefaultPort) {
+ MOZ_ALWAYS_SUCCEEDS(SetPort(-1));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetUserPass(const nsACString& input) {
+ 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() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ InvalidateCache();
+
+ 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 || passwordLen > 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 && mPassword.mLen < 0) {
+ buf.Append('@');
+ }
+ }
+
+ int32_t shift = 0;
+
+ if (mUsername.mLen < 0 && mPassword.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 = 0;
+ if (mUsername.mLen > 0) {
+ userpassLen += mUsername.mLen;
+ }
+ if (mPassword.mLen > 0) {
+ userpassLen += (mPassword.mLen + 1);
+ }
+ if (buf.IsEmpty()) {
+ // remove `@` character too
+ userpassLen++;
+ }
+ mSpec.Replace(mAuthority.mPos, userpassLen, buf);
+ shift = buf.Length() - userpassLen;
+ }
+ if (shift) {
+ ShiftFromHost(shift);
+ MOZ_DIAGNOSTIC_ASSERT(mAuthority.mLen >= -shift);
+ mAuthority.mLen += shift;
+ }
+ // update positions and lengths
+ mUsername.mLen = usernameLen > 0 ? usernameLen : -1;
+ mUsername.mPos = mAuthority.mPos;
+ mPassword.mLen = passwordLen > 0 ? passwordLen : -1;
+ if (passwordLen > 0) {
+ if (mUsername.mLen > 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ } else {
+ mPassword.mPos = mAuthority.mPos + 1;
+ }
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetUsername(const nsACString& input) {
+ 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 (mSpec.Length() + input.Length() - Username().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ // escape username if necessary
+ nsAutoCString buf;
+ nsSegmentEncoder encoder;
+ const nsACString& escUsername =
+ encoder.EncodeSegment(username, esc_Username, buf);
+
+ int32_t shift = 0;
+
+ if (mUsername.mLen < 0 && escUsername.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (mUsername.mLen < 0 && mPassword.mLen < 0) {
+ MOZ_ASSERT(!escUsername.IsEmpty(), "Should not be empty at this point");
+ mUsername.mPos = mAuthority.mPos;
+ mSpec.Insert(escUsername + "@"_ns, mUsername.mPos);
+ shift = escUsername.Length() + 1;
+ mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
+ } else {
+ uint32_t pos = mUsername.mLen < 0 ? mAuthority.mPos : mUsername.mPos;
+ int32_t len = mUsername.mLen < 0 ? 0 : mUsername.mLen;
+
+ if (mPassword.mLen < 0 && escUsername.IsEmpty()) {
+ len++; // remove the @ character too
+ }
+ shift = ReplaceSegment(pos, len, escUsername);
+ mUsername.mLen = escUsername.Length() > 0 ? escUsername.Length() : -1;
+ mUsername.mPos = pos;
+ }
+
+ if (shift) {
+ mAuthority.mLen += shift;
+ ShiftFromPassword(shift);
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetPassword(const nsACString& input) {
+ const nsPromiseFlatCString& password = PromiseFlatCString(input);
+
+ auto clearedPassword = MakeScopeExit([&password, this]() {
+ // Check that if this method is called with the empty string then the
+ // password is definitely cleared when exiting this method.
+ if (password.IsEmpty()) {
+ MOZ_DIAGNOSTIC_ASSERT(this->Password().IsEmpty());
+ }
+ Unused << this; // silence compiler -Wunused-lambda-capture
+ });
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ 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 (mSpec.Length() + input.Length() - Password().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (password.IsEmpty()) {
+ if (mPassword.mLen > 0) {
+ // cut(":password")
+ int32_t len = mPassword.mLen;
+ if (mUsername.mLen < 0) {
+ len++; // also cut the @ character
+ }
+ len++; // for the : character
+ mSpec.Cut(mPassword.mPos - 1, len);
+ ShiftFromHost(-len);
+ mAuthority.mLen -= len;
+ mPassword.mLen = -1;
+ }
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ 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) {
+ if (mUsername.mLen > 0) {
+ mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
+ mSpec.Insert(":"_ns + escPassword, mPassword.mPos - 1);
+ shift = escPassword.Length() + 1;
+ } else {
+ mPassword.mPos = mAuthority.mPos + 1;
+ mSpec.Insert(":"_ns + escPassword + "@"_ns, mPassword.mPos - 1);
+ shift = escPassword.Length() + 2;
+ }
+ } else {
+ shift = ReplaceSegment(mPassword.mPos, mPassword.mLen, escPassword);
+ }
+
+ if (shift) {
+ mPassword.mLen = escPassword.Length();
+ mAuthority.mLen += shift;
+ ShiftFromHost(shift);
+ }
+
+ MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
+ 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!!!
+nsresult nsStandardURL::SetHostPort(const nsACString& aValue) {
+ // 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;
+ }
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ nsresult rv = SetHost(Substring(start, iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (iter == end) {
+ // does not end in colon
+ return NS_OK;
+ }
+
+ iter++; // advance over the colon
+ if (iter == end) {
+ // port number is missing
+ return NS_OK;
+ }
+
+ nsCString portStr(Substring(iter, end));
+ int32_t port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ // Failure parsing the port number
+ return NS_OK;
+ }
+
+ Unused << SetPort(port);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetHost(const nsACString& input) {
+ nsAutoCString hostname(input);
+ hostname.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ nsACString::const_iterator start, end;
+ hostname.BeginReading(start);
+ hostname.EndReading(end);
+
+ FindHostLimit(start, end);
+
+ // Do percent decoding on the the input.
+ nsAutoCString flat;
+ NS_UnescapeURL(hostname.BeginReading(), end - start,
+ 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;
+ }
+ 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() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ 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 (EndsInANumber(hostBuf)) {
+ rv = NormalizeIPv4(hostBuf, ipString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ 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;
+}
+
+nsresult nsStandardURL::SetPort(int32_t port) {
+ 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;
+ }
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ 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) {
+ 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");
+
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ // 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);
+}
+
+nsresult nsStandardURL::SetPathQueryRef(const nsACString& input) {
+ const nsPromiseFlatCString& path = PromiseFlatCString(input);
+ LOG(("nsStandardURL::SetPathQueryRef [path=%s]\n", path.get()));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ InvalidateCache();
+
+ if (!path.IsEmpty()) {
+ nsAutoCString spec;
+
+ spec.Assign(mSpec.get(), mPath.mPos);
+ if (path.First() != '/') {
+ spec.Append('/');
+ }
+ spec.Append(path);
+
+ return SetSpecInternal(spec);
+ }
+ 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;
+}
+
+// When updating this also update SubstitutingURL::Mutator
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsStandardURL::Mutator, nsIURISetters,
+ nsIURIMutator, nsIStandardURLMutator,
+ nsIURLMutator, nsIFileURLMutator,
+ nsISerializable)
+
+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);
+ MOZ_ASSERT(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) {
+ MOZ_ASSERT(result, "null pointer");
+ if (!scheme) {
+ *result = false;
+ return NS_OK;
+ }
+
+ *result = SegmentIs(mScheme, scheme);
+ return NS_OK;
+}
+
+/* virtual */ nsStandardURL* nsStandardURL::StartClone() {
+ nsStandardURL* clone = new nsStandardURL();
+ return clone;
+}
+
+nsresult nsStandardURL::Clone(nsIURI** aURI) {
+ return CloneInternal(eHonorRef, ""_ns, aURI);
+}
+
+nsresult nsStandardURL::CloneInternal(
+ nsStandardURL::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef,
+ nsIURI** aClone)
+
+{
+ 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, aRefHandlingMode, aNewRef, true);
+
+ clone.forget(aClone);
+ return NS_OK;
+}
+
+nsresult nsStandardURL::CopyMembers(
+ nsStandardURL* source, nsStandardURL::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef, bool copyCached) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ 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;
+ mSupportsFileURL = source->mSupportsFileURL;
+ mCheckedIfHostA = source->mCheckedIfHostA;
+ mDisplayHost = source->mDisplayHost;
+
+ if (copyCached) {
+ mFile = source->mFile;
+ } else {
+ InvalidateCache(true);
+ }
+
+ if (refHandlingMode == eIgnoreRef) {
+ SetRef(""_ns);
+ } 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;
+
+ nsAutoCString baseProtocol(Scheme());
+ nsAutoCString protocol;
+ rv = net_ExtractURLScheme(buf, protocol);
+
+ // Normally, if we parse a scheme, then it's an absolute URI. But because
+ // we still support a deprecated form of relative URIs such as: http:file or
+ // http:/path/file we can't do that for all protocols.
+ // So we just make sure that if there a protocol, it's the same as the
+ // current one, otherwise we treat it as an absolute URI.
+ if (NS_SUCCEEDED(rv) && protocol != baseProtocol) {
+ out = buf;
+ return NS_OK;
+ }
+
+ // relative urls should never contain a host, so we always want to use
+ // the noauth url parser.
+ // use it to extract a possible scheme
+ uint32_t schemePos = scheme.mPos;
+ int32_t schemeLen = scheme.mLen;
+ rv = mParser->ParseURL(relpath, relpathLen, &schemePos, &schemeLen, 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();
+ }
+
+ scheme.mPos = schemePos;
+ scheme.mLen = schemeLen;
+
+ protocol.Assign(Segment(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)) {
+ auto* start = buf.BeginWriting();
+ auto* end = buf.EndWriting();
+ 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_xstrdup(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_xstrdup(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("%2F"_ns, 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 = strstr(result, "://");
+ if (resultPath) {
+ // If there are multiple slashes after :// we must ignore them
+ // otherwise net_CoalesceDirs may think the host is a part of the path.
+ resultPath += 3;
+ if (protocol.IsEmpty() && Scheme() != "file") {
+ while (*resultPath == '/') {
+ resultPath++;
+ }
+ }
+ resultPath = strchr(resultPath, '/');
+ 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
+ RefPtr<nsStandardURL> stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(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) {
+ 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());
+
+ 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;
+ }
+
+ RefPtr<nsStandardURL> stdurl2;
+ nsresult rv = uri2->QueryInterface(kThisImplCID, getter_AddRefs(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) {
+ 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 != '/')) {
+ 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));
+
+ 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;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasQuery(bool* result) {
+ *result = (mQuery.mLen >= 0);
+ 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;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasUserPass(bool* result) {
+ *result = (mUsername.mLen >= 0) || (mPassword.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;
+}
+
+nsresult nsStandardURL::SetFilePath(const nsACString& input) {
+ nsAutoCString str(input);
+ str.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+ const char* filepath = str.get();
+
+ LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ // 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(str);
+ }
+
+ if (!str.IsEmpty()) {
+ nsAutoCString spec;
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+ nsresult rv;
+
+ if (IsSpecialProtocol(mSpec)) {
+ // Bug 1873955: Replace all backslashes with slashes when parsing paths
+ // Stop when we reach the query or the hash.
+ auto* start = str.BeginWriting();
+ auto* end = str.EndWriting();
+ while (start != end) {
+ if (*start == '?' || *start == '#') {
+ break;
+ }
+ if (*start == '\\') {
+ *start = '/';
+ }
+ start++;
+ }
+ }
+
+ rv = mParser->ParseFilePath(filepath, str.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);
+ }
+ if (mPath.mLen > 1) {
+ mSpec.Cut(mPath.mPos + 1, mFilepath.mLen - 1);
+ // left shift query, and ref
+ ShiftFromQuery(1 - mFilepath.mLen);
+ // One character for '/', and if we have a query or ref we add their
+ // length and one extra for each '?' or '#' characters
+ mPath.mLen = 1 + (mQuery.mLen >= 0 ? (mQuery.mLen + 1) : 0) +
+ (mRef.mLen >= 0 ? (mRef.mLen + 1) : 0);
+ // these contain only a '/'
+ 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;
+}
+
+nsresult nsStandardURL::SetQuery(const nsACString& input) {
+ return SetQueryWithEncoding(input, nullptr);
+}
+
+nsresult nsStandardURL::SetQueryWithEncoding(const nsACString& input,
+ const Encoding* encoding) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* query = flat.get();
+
+ LOG(("nsStandardURL::SetQuery [query=%s]\n", query));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (IsUTFEncoding(encoding)) {
+ encoding = nullptr;
+ }
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Query().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (flat.IsEmpty()) {
+ // 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;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ query = filteredURI.get();
+ int32_t queryLen = filteredURI.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;
+}
+
+nsresult nsStandardURL::SetRef(const nsACString& input) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* ref = flat.get();
+
+ LOG(("nsStandardURL::SetRef [ref=%s]\n", ref));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Ref().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ if (input.IsEmpty()) {
+ // 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;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ filteredURI.StripTaggedASCII(ASCIIMask::MaskCRLFTab());
+
+ ref = filteredURI.get();
+ int32_t refLen = filteredURI.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;
+}
+
+nsresult nsStandardURL::SetFileNameInternal(const nsACString& input) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* filename = flat.get();
+
+ LOG(("nsStandardURL::SetFileNameInternal [filename=%s]\n", filename));
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+
+ if (mPath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (mSpec.Length() + input.Length() - Filename().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ 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;
+ uint32_t basenamePos = 0;
+ int32_t basenameLen = -1;
+ uint32_t extensionPos = 0;
+ int32_t extensionLen = -1;
+ // let the parser locate the basename and extension
+ rv = mParser->ParseFileName(filename, flat.Length(), &basenamePos,
+ &basenameLen, &extensionPos, &extensionLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ URLSegment basename(basenamePos, basenameLen);
+ URLSegment extension(extensionPos, extensionLen);
+
+ 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;
+}
+
+nsresult nsStandardURL::SetFileBaseNameInternal(const nsACString& input) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ nsAutoCString extension;
+ nsresult rv = GetFileExtension(extension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newFileName(input);
+
+ if (!extension.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(extension);
+ }
+
+ return SetFileNameInternal(newFileName);
+}
+
+nsresult nsStandardURL::SetFileExtensionInternal(const nsACString& input) {
+ auto onExitGuard = MakeScopeExit([&] { SanityCheck(); });
+ nsAutoCString newFileName;
+ nsresult rv = GetFileBaseName(newFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!input.IsEmpty()) {
+ newFileName.Append('.');
+ newFileName.Append(input);
+ }
+
+ return SetFileNameInternal(newFileName);
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsIFileURL
+//----------------------------------------------------------------------------
+
+nsresult nsStandardURL::EnsureFile() {
+ MOZ_ASSERT(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) {
+ MOZ_ASSERT(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);
+}
+
+nsresult nsStandardURL::SetFile(nsIFile* file) {
+ 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
+//----------------------------------------------------------------------------
+
+nsresult nsStandardURL::Init(uint32_t urlType, int32_t defaultPort,
+ const nsACString& spec, const char* charset,
+ nsIURI* baseURI) {
+ if (spec.Length() > StaticPrefs::network_standard_url_max_length() ||
+ 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:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return NS_ERROR_INVALID_ARG;
+ }
+ mDefaultPort = defaultPort;
+ mURLType = urlType;
+
+ const 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);
+}
+
+nsresult nsStandardURL::SetDefaultPort(int32_t aNewDefaultPort) {
+ 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;
+}
+
+//----------------------------------------------------------------------------
+// nsStandardURL::nsISerializable
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsStandardURL::Read(nsIObjectInputStream* stream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsStandardURL::ReadPrivate(nsIObjectInputStream* stream) {
+ MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
+
+ // If we exit early, make sure to clear the URL so we don't fail the sanity
+ // check in the destructor
+ auto clearOnExit = MakeScopeExit([&] { Clear(); });
+
+ 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:
+ MOZ_ASSERT_UNREACHABLE("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;
+ }
+ Unused << 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);
+ }
+
+ rv = CheckIfHostIsAscii();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsValid()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ clearOnExit.release();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::Write(nsIObjectOutputStream* stream) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "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, "");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // former mMutable
+ rv = stream->WriteBoolean(false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->WriteBoolean(mSupportsFileURL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // mDisplayHost is just a cache that can be recovered as needed.
+
+ return NS_OK;
+}
+
+inline ipc::StandardURLSegment ToIPCSegment(
+ const nsStandardURL::URLSegment& aSegment) {
+ return ipc::StandardURLSegment(aSegment.mPos, aSegment.mLen);
+}
+
+[[nodiscard]] inline bool FromIPCSegment(
+ const nsACString& aSpec, const ipc::StandardURLSegment& aSegment,
+ nsStandardURL::URLSegment& aTarget) {
+ // This seems to be just an empty segment.
+ if (aSegment.length() == -1) {
+ aTarget = nsStandardURL::URLSegment();
+ return true;
+ }
+
+ // A value of -1 means an empty segment, but < -1 is undefined.
+ if (NS_WARN_IF(aSegment.length() < -1)) {
+ return false;
+ }
+
+ CheckedInt<uint32_t> segmentLen = aSegment.position();
+ segmentLen += aSegment.length();
+ // Make sure the segment does not extend beyond the spec.
+ if (NS_WARN_IF(!segmentLen.isValid() ||
+ segmentLen.value() > aSpec.Length())) {
+ return false;
+ }
+
+ aTarget.mPos = aSegment.position();
+ aTarget.mLen = aSegment.length();
+
+ return true;
+}
+
+void nsStandardURL::Serialize(URIParams& aParams) {
+ MOZ_ASSERT(mSpec.Length() <= StaticPrefs::network_standard_url_max_length(),
+ "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.supportsFileURL() = !!mSupportsFileURL;
+ params.isSubstituting() = false;
+ // mDisplayHost is just a cache that can be recovered as needed.
+
+ aParams = params;
+}
+
+bool nsStandardURL::Deserialize(const URIParams& aParams) {
+ MOZ_ASSERT(mDisplayHost.IsEmpty(), "Shouldn't have cached unicode host");
+ MOZ_ASSERT(!mFile, "Shouldn't have cached file");
+
+ if (aParams.type() != URIParams::TStandardURLParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ // If we exit early, make sure to clear the URL so we don't fail the sanity
+ // check in the destructor
+ auto clearOnExit = MakeScopeExit([&] { Clear(); });
+
+ 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:
+ MOZ_ASSERT_UNREACHABLE("bad urlType");
+ return false;
+ }
+
+ mPort = params.port();
+ mDefaultPort = params.defaultPort();
+ mSpec = params.spec();
+ NS_ENSURE_TRUE(
+ mSpec.Length() <= StaticPrefs::network_standard_url_max_length(), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.scheme(), mScheme), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.authority(), mAuthority), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.username(), mUsername), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.password(), mPassword), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.host(), mHost), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.path(), mPath), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.filePath(), mFilepath), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.directory(), mDirectory), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.baseName(), mBasename), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.extension(), mExtension), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.query(), mQuery), false);
+ NS_ENSURE_TRUE(FromIPCSegment(mSpec, params.ref(), mRef), false);
+
+ mSupportsFileURL = params.supportsFileURL();
+
+ nsresult rv = CheckIfHostIsAscii();
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // Some sanity checks
+ NS_ENSURE_TRUE(mScheme.mPos == 0, false);
+ NS_ENSURE_TRUE(mScheme.mLen > 0, false);
+ // Make sure scheme is followed by :// (3 characters)
+ NS_ENSURE_TRUE(mScheme.mLen < INT32_MAX - 3, false); // avoid overflow
+ NS_ENSURE_TRUE(mSpec.Length() >= (uint32_t)mScheme.mLen + 3, false);
+ NS_ENSURE_TRUE(
+ nsDependentCSubstring(mSpec, mScheme.mLen, 3).EqualsLiteral("://"),
+ false);
+ NS_ENSURE_TRUE(mPath.mLen != -1 && mSpec.CharAt(mPath.mPos) == '/', false);
+ NS_ENSURE_TRUE(mPath.mPos == mFilepath.mPos, false);
+ NS_ENSURE_TRUE(mQuery.mLen == -1 ||
+ (mQuery.mPos > 0 && mSpec.CharAt(mQuery.mPos - 1) == '?'),
+ false);
+ NS_ENSURE_TRUE(
+ mRef.mLen == -1 || (mRef.mPos > 0 && mSpec.CharAt(mRef.mPos - 1) == '#'),
+ false);
+
+ if (!IsValid()) {
+ return false;
+ }
+
+ clearOnExit.release();
+
+ return true;
+}
+
+//----------------------------------------------------------------------------
+// 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 mozilla::net::nsStandardURL::NormalizeIPv4(host, result);
+}
+
+// For unit tests. Including nsStandardURL.h seems to cause problems
+nsresult Test_ParseIPv4Number(const nsACString& input, int32_t base,
+ uint32_t& number, uint32_t maxNumber) {
+ return mozilla::net::ParseIPv4Number(input, base, number, maxNumber);
+}
+
+int32_t Test_ValidateIPv4Number(const nsACString& host, int32_t bases[4],
+ int32_t dotIndex[3], bool& onlyBase10,
+ int32_t length) {
+ return mozilla::net::ValidateIPv4Number(host, bases, dotIndex, onlyBase10,
+ length, false);
+}