Source code

Revision control

Other Tools

/* -*- 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 "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 "nsNetCID.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/ipc/URIUtils.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"
//
// setenv MOZ_LOG nsStandardURL:5
//
static LazyLogModule gStandardURLLog("nsStandardURL");
// The Chromium code defines its own LOG macro which we don't want
#undef LOG
#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
#undef LOG_ENABLED
#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
using namespace mozilla::ipc;
namespace mozilla {
namespace net {
static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID);
// This will always be initialized and destroyed on the main thread, but
// can be safely used on other threads.
StaticRefPtr<nsIIDNService> nsStandardURL::gIDN;
// This value will only be updated on the main thread once. Worker threads
// may race when reading this values, but that's OK because in the worst
// case we will just dispatch a noop runnable to the main thread.
bool nsStandardURL::gInitialized = false;
const char nsStandardURL::gHostLimitDigits[] = {'/', '\\', '?', '#', 0};
// 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 == ' ' || 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 = MakeSpan(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 = Encoding::ASCIIValidUpTo(AsBytes(span));
if (upTo != span.Length()) {
// we have to encode this segment
char bufferArr[512];
Span<char> buffer = MakeSpan(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 (;;) {
uint32_t encoderResult;
size_t read;
size_t written;
Tie(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;
static LinkedList<nsStandardURL> gAllURLs;
#endif
nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
: mDefaultPort(-1),
mPort(-1),
mDisplayHost(nullptr),
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
}
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.
// Since the trailing dot is allowed, we pass and adjust "length".
//
// length is assumed to be <= host.Length(); the callers is responsible for that
//
// Note that the value returned is guaranteed to be in [-1, 3] range.
inline int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
int32_t dotIndex[3], bool& onlyBase10,
int32_t& length) {
MOZ_ASSERT(length <= (int32_t)host.Length());
if (length <= 0) {
return -1;
}
bool lastWasNumber = false; // We count on this being false for i == 0
int32_t dotCount = 0;
onlyBase10 = true;
for (int32_t i = 0; i < length; i++) {
char current = host[i];
if (current == '.') {
if (!lastWasNumber) { // A dot should not follow an X or a dot, or be
// first
return -1;
}
if (dotCount > 0 &&
i == (length - 1)) { // Trailing dot is OK; shorten and return
length--;
return dotCount;
}
if (dotCount > 2) {
return -1;
}
lastWasNumber = false;
dotIndex[dotCount] = i;
dotCount++;
} else if (current == 'X' || current == 'x') {
if (!lastWasNumber || // An X should not follow an X or a dot or be first
i == (length - 1) || // No trailing Xs allowed
(dotCount == 0 &&
i != 1) || // If we had no dots, an X should be second
host[i - 1] != '0' || // X should always follow a 0. Guaranteed i >
// 0 as lastWasNumber is true
(dotCount > 0 &&
host[i - 2] != '.')) { // And that zero follows a dot if it exists
return -1;
}
lastWasNumber = false;
bases[dotCount] = 16;
onlyBase10 = false;
} else if (current == '0') {
if (i < length - 1 && // Trailing zero doesn't signal octal
host[i + 1] != '.' && // Lone zero is not octal
(i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot
// is a candidate for octal
bases[dotCount] = 8; // This will turn to 16 above if X shows up
onlyBase10 = false;
}
lastWasNumber = true;
} else if (current >= '1' && current <= '7') {
lastWasNumber = true;
} else if (current >= '8' && current <= '9') {
if (bases[dotCount] == 8) {
return -1;
}
lastWasNumber = true;
} else if ((current >= 'a' && current <= 'f') ||
(current >= 'A' && current <= 'F')) {
if (bases[dotCount] != 16) {
return -1;
}
lastWasNumber = true;
} else {
return -1;
}
}
return dotCount;
}
inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
uint32_t maxNumber) {
uint64_t value = 0;
const char* current = input.BeginReading();
const char* end = input.EndReading();
for (; current < end; ++current) {
char c = *current;
MOZ_ASSERT(c >= '0' && c <= '9');
value *= 10;
value += c - '0';
}
if (value <= maxNumber) {
number = value;
return NS_OK;
}
// The error case
number = 0;
return NS_ERROR_FAILURE;
}
inline nsresult ParseIPv4Number(const nsACString& input, int32_t base,
uint32_t& number, uint32_t maxNumber) {
// Accumulate in the 64-bit value
uint64_t value = 0;
const char* current = input.BeginReading();
const char* end = input.EndReading();
switch (base) {
case 16:
++current;
[[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;
}
/* static */
nsresult nsStandardURL::NormalizeIPv4(const nsACString& host,
nsCString& result) {
int32_t bases[4] = {10, 10, 10, 10};
bool onlyBase10 = true; // Track this as a special case
int32_t dotIndex[3]; // The positions of the dots in the string
// The length may be adjusted by ValidateIPv4Number (ignoring the trailing
// period) so use "length", rather than host.Length() after that call.
int32_t length = static_cast<int32_t>(host.Length());
int32_t dotCount =
ValidateIPv4Number(host, bases, dotIndex, onlyBase10, length);
if (dotCount < 0 || length <= 0) {
return NS_ERROR_FAILURE;
}
// Max values specified by the spec
static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu,
0xffu};
uint32_t ipv4;
int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
nsresult res;
// Doing a special case for all items being base 10 gives ~35% speedup
res = (onlyBase10
? ParseIPv4Number10(Substring(host, start, length - start), ipv4,
upperBounds[dotCount])
: ParseIPv4Number(Substring(host, start, length - start),
bases[dotCount], ipv4, upperBounds[dotCount]));
if (NS_FAILED(res)) {
return NS_ERROR_FAILURE;
}
int32_t lastUsed = -1;
for (int32_t i = 0; i < dotCount; i++) {
uint32_t number;
start = lastUsed + 1;
lastUsed = dotIndex[i];
res =
(onlyBase10 ? ParseIPv4Number10(
Substring(host, start, lastUsed - start), number, 255)
: ParseIPv4Number(Substring(host, start, lastUsed - start),
bases[i], number, 255));
if (NS_FAILED(res)) {
return NS_ERROR_FAILURE;
}
ipv4 += number << (8 * (3 - i));
}
uint8_t ipSegments[4];
NetworkEndian::writeUint32(ipSegments, ipv4);
result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
ipSegments[2], ipSegments[3]);
return NS_OK;
}
nsresult nsStandardURL::NormalizeIDN(const nsACString& host,
nsCString& result) {
// If host is ACE, then convert to UTF-8. Else, if host is already UTF-8,
// then make sure it is normalized per IDN.
// this function returns true if normalization succeeds.
result.Truncate();
nsresult rv;
if (!gIDN) {
return NS_ERROR_UNEXPECTED;
}
bool isAscii;
nsAutoCString normalized;
rv = gIDN->ConvertToDisplayIDN(host, &isAscii, normalized);
if (NS_FAILED(rv)) {
return rv;
}
// The result is ASCII. No need to convert to ACE.
if (isAscii) {
result = normalized;
mCheckedIfHostA = true;
mDisplayHost.Truncate();
return NS_OK;
}
rv = gIDN->ConvertUTF8toACE(normalized, result);
if (NS_FAILED(rv)) {
return rv;
}
mCheckedIfHostA = true;
mDisplayHost = normalized;
return NS_OK;
}
bool nsStandardURL::ValidIPv6orHostname(const char* host, uint32_t length) {
if (!host || !*host) {
// Should not be NULL or empty string
return false;
}
if (length != strlen(host)) {
// Embedded null
return false;
}
bool openBracket = host[0] == '[';
bool closeBracket = host[length - 1] == ']';
if (openBracket && closeBracket) {
return net_IsValidIPv6Addr(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;
}
// 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 (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) {
encHost = ipString;
}
}
// NormalizeIDN always copies, if the call was successful.
useEncHost = true;
approxLen += encHost.Length();
if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
return NS_ERROR_MALFORMED_URI;
}
} 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';
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 !PL_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 !PL_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 !PL_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
//
rv = mParser->ParseURL(spec, specLen, &mScheme.mPos, &mScheme.mLen,
&mAuthority.mPos, &mAuthority.mLen, &mPath.mPos,
&mPath.mLen);
if (NS_FAILED(rv)) return rv;
#ifdef DEBUG
if (mScheme.mLen <= 0) {
printf("spec=%s\n", spec);
NS_WARNING("malformed url: no scheme");
}
#endif
if (mAuthority.mLen > 0) {
rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
&mUsername.mPos, &mUsername.mLen,
&mPassword.mPos, &mPassword.mLen, &mHost.mPos,
&mHost.mLen, &mPort);
if (NS_FAILED(rv)) return rv;
// Don't allow mPort to be set to this URI's default port
if (mPort == mDefaultPort) mPort = -1;
mUsername.mPos += mAuthority.mPos;
mPassword.mPos += mAuthority.mPos;
mHost.mPos += mAuthority.mPos;
}
if (mPath.mLen > 0) rv = ParsePath(spec, mPath.mPos, mPath.mLen);
return rv;
}
nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos,
int32_t pathLen) {
LOG(("ParsePath: %s pathpos %d len %d\n", spec, pathPos, pathLen));
if (pathLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
return NS_ERROR_MALFORMED_URI;
}
nsresult rv = mParser->ParsePath(spec + pathPos, pathLen, &mFilepath.mPos,
&mFilepath.mLen, &mQuery.mPos, &mQuery.mLen,
&mRef.mPos, &mRef.mLen);
if (NS_FAILED(rv)) return rv;
mFilepath.mPos += pathPos;
mQuery.mPos += pathPos;
mRef.mPos += pathPos;
if (mFilepath.mLen > 0) {
rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
&mDirectory.mPos, &mDirectory.mLen,
&mBasename.mPos, &mBasename.mLen,
&mExtension.mPos, &mExtension.mLen);
if (NS_FAILED(rv)) return rv;
mDirectory.mPos += mFilepath.mPos;
mBasename.mPos += mFilepath.mPos;
mExtension.mPos += mFilepath.mPos;
}
return NS_OK;
}
char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len,
const char* tail) {
// Verify pos and length are within boundaries
if (pos > mSpec.Length()) return nullptr;
if (len < 0) return nullptr;
if ((uint32_t)len > (mSpec.Length() - pos)) return nullptr;
if (!tail) return nullptr;
uint32_t tailLen = strlen(tail);
// Check for int overflow for proposed length of combined string
if (UINT32_MAX - ((uint32_t)len + 1) < tailLen) return nullptr;
char* result = (char*)moz_xmalloc(len + tailLen + 1);
memcpy(result, mSpec.get() + pos, len);
memcpy(result + len, tail, tailLen);
result[len + tailLen] = '\0';
return result;
}
nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream,
URLSegment& seg) {
nsresult rv;
rv = stream->Read32(&seg.mPos);
if (NS_FAILED(rv)) return rv;
rv = stream->Read32((uint32_t*)&seg.mLen);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream,
const URLSegment& seg) {
nsresult rv;
rv = stream->Write32(seg.mPos);
if (NS_FAILED(rv)) return rv;
rv = stream->Write32(uint32_t(seg.mLen));
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
#define SHIFT_FROM(name, what) \
void nsStandardURL::name(int32_t diff) { \
if (!diff) return; \
if (what.mLen >= 0) { \
CheckedInt<int32_t> pos = what.mPos; \
pos += diff; \
MOZ_ASSERT(pos.isValid()); \
what.mPos = pos.value(); \
}
#define SHIFT_FROM_NEXT(name, what, next) \
SHIFT_FROM(name, what) \
next(diff); \
}
#define SHIFT_FROM_LAST(name, what) \
SHIFT_FROM(name, what) \
}
SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
SHIFT_FROM_LAST(ShiftFromRef, mRef)
//----------------------------------------------------------------------------
// nsStandardURL::nsISupports
//----------------------------------------------------------------------------
NS_IMPL_ADDREF(nsStandardURL)
NS_IMPL_RELEASE(nsStandardURL)
NS_INTERFACE_MAP_BEGIN(nsStandardURL)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
NS_INTERFACE_MAP_ENTRY(nsIURI)
NS_INTERFACE_MAP_ENTRY(nsIURL)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
NS_INTERFACE_MAP_ENTRY(nsISerializable)
NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
NS_INTERFACE_MAP_ENTRY(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.");
nsresult rv = NS_OK;
if (StaticPrefs::network_standard_url_punycode_host()) {
result = mSpec;
} else { // XXX: This code path may be slow
rv = GetDisplaySpec(result);
}
return rv;
}
// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString& result) {
nsresult rv = GetSpec(result);
if (NS_FAILED(rv)) {
return rv;
}
if (mPassword.mLen > 0) {
result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****");
}
return NS_OK;
}
// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetSpecIgnoringRef(nsACString& result) {
// URI without ref is 0 to one char before ref
if (mRef.mLen < 0) {
return GetSpec(result);
}
URLSegment noRef(0, mRef.mPos - 1);
result = Segment(noRef);
MOZ_ASSERT(mCheckedIfHostA);
if (!StaticPrefs::network_standard_url_punycode_host() &&
!mDisplayHost.IsEmpty()) {
result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
}
return NS_OK;
}
nsresult nsStandardURL::CheckIfHostIsAscii() {
nsresult rv;
if (mCheckedIfHostA) {
return NS_OK;
}
mCheckedIfHostA = true;
if (!gIDN) {
return NS_ERROR_NOT_INITIALIZED;
}
nsAutoCString displayHost;
bool isAscii;
rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost);
if (NS_FAILED(rv)) {
mDisplayHost.Truncate();
mCheckedIfHostA = false;
return rv;
}
if (!isAscii) {
mDisplayHost = displayHost;
}
return NS_OK;
}
NS_IMETHODIMP
nsStandardURL::GetDisplaySpec(nsACString& aUnicodeSpec) {
aUnicodeSpec.Assign(mSpec);
MOZ_ASSERT(mCheckedIfHostA);
if (!mDisplayHost.IsEmpty()) {
aUnicodeSpec.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
}
return NS_OK;
}
NS_IMETHODIMP
nsStandardURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
nsAutoCString unicodeHostPort;
nsresult rv = GetDisplayHost(unicodeHostPort);
if (NS_FAILED(rv)) {
return rv;
}
if (StringBeginsWith(Hostport(), NS_LITERAL_CSTRING("["))) {
aUnicodeHostPort.AssignLiteral("[");
aUnicodeHostPort.Append(unicodeHostPort);
aUnicodeHostPort.AppendLiteral("]");
} else {
aUnicodeHostPort.Assign(unicodeHostPort);
}
uint32_t pos = mHost.mPos + mHost.mLen;
if (pos < mPath.mPos)
aUnicodeHostPort += Substring(mSpec, pos, mPath.mPos - pos);
return NS_OK;
}
NS_IMETHODIMP
nsStandardURL::GetDisplayHost(nsACString& aUnicodeHost) {
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);
if (!StaticPrefs::network_standard_url_punycode_host() &&
!mDisplayHost.IsEmpty()) {
result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
}
return NS_OK;
}
// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetDisplayPrePath(nsACString& result) {
result = Prepath();
MOZ_ASSERT(mCheckedIfHostA);
if (!mDisplayHost.IsEmpty()) {
result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
}
return NS_OK;
}
// result is strictly US-ASCII
NS_IMETHODIMP
nsStandardURL::GetScheme(nsACString& result) {
result = Scheme();
return NS_OK;
}
// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetUserPass(nsACString& result) {
result = Userpass();
return NS_OK;
}
// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetUsername(nsACString& result) {
result = Username();
return NS_OK;
}
// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetPassword(nsACString& result) {
result = Password();
return NS_OK;
}
NS_IMETHODIMP
nsStandardURL::GetHostPort(nsACString& result) {
nsresult rv;
if (StaticPrefs::network_standard_url_punycode_host()) {
rv = GetAsciiHostPort(result);
} else {
rv = GetDisplayHostPort(result);
}
return rv;
}
NS_IMETHODIMP
nsStandardURL::GetHost(nsACString& result) {
nsresult rv;
if (StaticPrefs::network_standard_url_punycode_host()) {
rv = GetAsciiHost(result);
} else {
rv = GetDisplayHost(result);
}
return rv;
}
NS_IMETHODIMP
nsStandardURL::GetPort(int32_t* result) {
// should never be more than 16 bit
MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
*result = mPort;
return NS_OK;
}
// result may contain unescaped UTF-8 characters
NS_IMETHODIMP
nsStandardURL::GetPathQueryRef(nsACString& result) {
result = Path();
return NS_OK;
}
// result is ASCII
NS_IMETHODIMP
nsStandardURL::GetAsciiSpec(nsACString& result) {
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 curent URL
nsStandardURL prevURL(false, false);
prevURL.CopyMembers(this, eHonorRef, EmptyCString());
Clear();
if (IsSpecialProtocol(filteredURI)) {
// Bug 652186: Replace all backslashes with slashes when parsing paths
// Stop when we reach the query or the hash.
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 (NS_SUCCEEDED(rv)) {
// finally, use the URLSegment member variables to build a normalized
// copy of |spec|
rv = BuildNormalizedSpec(spec, encoding);
}
// Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) {
rv = NS_ERROR_MALFORMED_URI;
}
if (NS_FAILED(rv)) {
Clear();
// If parsing the spec has failed, restore the old URL
// so we don't end up with an empty URL.
CopyMembers(&prevURL, eHonorRef, EmptyCString());
return rv;
}
if (LOG_ENABLED()) {
LOG((" spec = %s\n", mSpec.get()));
LOG((" port = %d\n", mPort));
LOG((" scheme = (%u,%d)\n", mScheme.mPos, mScheme.mLen));
LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen));
LOG((" username = (%u,%d)\n", mUsername.mPos, mUsername.mLen));
LOG((" password = (%u,%d)\n", mPassword.mPos, mPassword.mLen));
LOG((" hostname = (%u,%d)\n", mHost.mPos, mHost.mLen));
LOG((" path = (%u,%d)\n", mPath.mPos, mPath.mLen));
LOG((" filepath = (%u,%d)\n", mFilepath.mPos, mFilepath.mLen));
LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen));
LOG((" basename = (%u,%d)\n", mBasename.mPos, mBasename.mLen));
LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen));
LOG((" query = (%u,%d)\n", mQuery.mPos, mQuery.mLen));
LOG((" ref = (%u,%d)\n", mRef.mPos, mRef.mLen));
}
return rv;
}
nsresult nsStandardURL::SetScheme(const nsACString& input) {
const nsPromiseFlatCString& scheme = PromiseFlatCString(input);
LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
if (scheme.IsEmpty()) {
NS_WARNING("cannot remove the scheme from an url");
return NS_ERROR_UNEXPECTED;
}
if (mScheme.mLen < 0) {
NS_WARNING("uninitialized");
return NS_ERROR_NOT_INITIALIZED;
}
if (!net_IsValidScheme(scheme)) {
NS_WARNING("the given url scheme contains invalid characters");
return NS_ERROR_UNEXPECTED;
}
if (mSpec.Length() + input.Length() - Scheme().Length() >
StaticPrefs::network_standard_url_max_length()) {
return NS_ERROR_MALFORMED_URI;
}
InvalidateCache();
int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
if (shift) {
mScheme.mLen = scheme.Length();
ShiftFromAuthority(shift);
}
// ensure new scheme is lowercase
//
// XXX the string code unfortunately doesn't provide a ToLowerCase
// that operates on a substring.
net_ToLowerCase((char*)mSpec.get(), mScheme.mLen);
return NS_OK;
}
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;
}
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;
}
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_LITERAL_CSTRING("@"), 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;
}
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
});
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