/* -*- 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/. */
// 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)
usingnamespace mozilla::ipc;
/** * The UTS #46 ToUnicode operation as parametrized by the WHATWG URL Standard, * except potentially misleading labels are treated according to ToASCII * instead. Combined with the ToASCII operation without rerunning the expensive * part. * * NOTE: This function performs percent-decoding on the argument unlike * the other `NS_DomainTo` functions! * * If upon successfull return `aASCII` is empty, it is the caller's * responsibility to treat the value of `aDisplay` also as the value of * `aASCII`. (The weird semantics avoid useless allocation / copying.) * * Rust callers that don't happen to be using XPCOM strings are better * off using the `idna` crate directly. (See `idna_glue` for what policy * closure to use.)
*/ inline nsresult NS_DomainToDisplayAndASCII(const nsACString& aDomain,
nsACString& aDisplay,
nsACString& aASCII) { return mozilla_net_domain_to_display_and_ascii_impl(&aDomain, &aDisplay,
&aASCII);
}
// This will always be initialized and destroyed on the main thread, but // can be safely used on other threads.
StaticRefPtr<nsIIDNService> nsStandardURL::gIDN;
int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount( constchar* 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;
}
// 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;
}
// 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();
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); returntrue;
} #endif // Bad value if (NS_WARN_IF(aSeg.mLen < -1)) { returnfalse;
} if (aSeg.mLen == -1) { returntrue;
}
// Points out of string if (NS_WARN_IF(aSeg.mPos + aSeg.mLen > mSpec.Length())) { returnfalse;
}
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;
}
#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
}
nsresult nsStandardURL::NormalizeIDN(const nsACString& aHost,
nsACString& aResult) {
mDisplayHost.Truncate();
mCheckedIfHostA = true;
nsCString displayHost; // Intentionally not nsAutoCString to avoid copy when // assigning to field
nsresult rv = NS_DomainToDisplayAndASCII(aHost, displayHost, aResult); if (NS_FAILED(rv)) { return rv;
} if (aResult.IsEmpty()) {
aResult.Assign(displayHost);
} else {
mDisplayHost = displayHost;
} return NS_OK;
}
void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char* path) { auto resultCoalesceDirs = net_CoalesceDirs(coalesceFlag, path);
int32_t newLen = strlen(path); if (newLen < mPath.mLen && resultCoalesceDirs) { // Obtain indices for the last slash and the end of the basename
uint32_t lastSlash = resultCoalesceDirs->first();
uint32_t endOfBasename = resultCoalesceDirs->second();
// The directory length includes all characters up to and // including the last slash
mDirectory.mLen = static_cast<int32_t>(lastSlash) + 1;
// basename length includes everything after the last slash // until hash, query, or the null char. However, if there was an extension // we must make sure to update the length.
mBasename.mLen = static_cast<int32_t>(endOfBasename - mDirectory.mLen); if (mExtension.mLen >= 0) {
mBasename.mLen -= 1; // Length of the . character
mBasename.mLen -= mExtension.mLen;
}
mBasename.mPos = mDirectory.mPos + mDirectory.mLen;
// Adjust the positions of extension, query, and ref as needed // This is possible because net_CoalesceDirs does not modify their lengths
ShiftFromExtension(diff);
// // 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
// 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) {
nsDependentCSubstring tempHost(spec + mHost.mPos, mHost.mLen);
nsresult rv; bool allowIp = !SegmentIs(spec, mScheme, "resource") &&
!SegmentIs(spec, mScheme, "chrome"); if (tempHost.First() == '[' && allowIp) {
mCheckedIfHostA = true;
rv = (nsresult)rusturl_parse_ipv6addr(&tempHost, &encHost); if (NS_FAILED(rv)) { return rv;
}
} else {
rv = NormalizeIDN(tempHost, encHost); if (NS_FAILED(rv)) { return rv;
} if (IPv4Parser::EndsInANumber(encHost) && allowIp) {
nsAutoCString ipString;
rv = IPv4Parser::NormalizeIPv4(encHost, ipString); if (NS_FAILED(rv)) { return rv;
}
encHost = ipString;
}
}
// NormalizeIDN always copies, if the call was successful.
useEncHost = true;
approxLen += encHost.Length();
} 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);
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]; // To account for cases like file:///w|/m and file:///c| if (mPath.mLen >= 3 && path[0] == '/' && IsAsciiAlpha(path[1]) &&
path[2] == '|' && (mPath.mLen == 3 || path[3] == '/')) {
buf[mPath.mPos + 2] = ':';
}
}
if (mDirectory.mLen > 0) {
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.");
bool nsStandardURL::SegmentIs(const URLSegment& seg, constchar* val, bool ignoreCase) { // one or both may be null if (!val || mSpec.IsEmpty()) { return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
} if (seg.mLen < 0) { returnfalse;
} // 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');
}
bool nsStandardURL::SegmentIs(constchar* spec, const URLSegment& seg, constchar* val, bool ignoreCase) { // one or both may be null if (!val || !spec) { return (!val && (!spec || seg.mLen < 0));
} if (seg.mLen < 0) { returnfalse;
} // 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');
}
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)
// 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);
}
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;
}
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++;
}
}
// 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 <= 0) {
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 (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 (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;
} elseif (Scheme() == "https"_ns || Scheme() == "wss"_ns) {
mDefaultPort = 443;
} if (mPort == mDefaultPort) {
MOZ_ALWAYS_SUCCEEDS(SetPort(-1));
}
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 (mAuthority.mLen == 0) { // If the URL doesn't have a hostname then setting the userpass to // empty string is a no-op. But setting it to anything else should // return an error. if (input.Length() == 0) { return NS_OK;
} else { return NS_ERROR_UNEXPECTED;
}
}
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 (mAuthority.mLen == 0) { // If the URL doesn't have a hostname then setting the username to // empty string is a no-op. But setting it to anything else should // return an error. if (input.Length() == 0) { return NS_OK;
} else { return NS_ERROR_UNEXPECTED;
}
}
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(); });
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 (mAuthority.mLen == 0) { // If the URL doesn't have a hostname then setting the password to // empty string is a no-op. But setting it to anything else should // return an error. if (input.Length() == 0) { return NS_OK;
} else { return NS_ERROR_UNEXPECTED;
}
}
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.
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(); });
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;
}
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 (mURLType == URLTYPE_AUTHORITY && flat.IsEmpty()) { // Setting an empty hostname is not allowed for URLTYPE_AUTHORITY. return NS_ERROR_UNEXPECTED;
}
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;
} if (mAuthority.mLen == 0) { // If the URL doesn't have a hostname then setting the port to // -1 is a no-op. But setting it to anything else should // return an error. if (port == -1) { return NS_OK;
} else { 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);
}
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)
// 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;
}
// 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;
// Special case for resource:// urls that don't resolve to files, // and for moz-extension://UUID/_generated_background_page.html // because it doesn't resolve to a file (instead it resolves to a data: URI, // see ExtensionProtocolHandler::ResolveSpecialCases, see Bug 1926106). if (rv == NS_ERROR_NO_INTERFACE || rv2 == NS_ERROR_NO_INTERFACE) { 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;
// 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;
}
// 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;
// Bug 1873976: For cases involving file:c: against file: if (NS_SUCCEEDED(rv) && protocol == "file"_ns && baseProtocol == "file"_ns) { constchar* path = buf.get() + scheme.mPos + scheme.mLen; // For instance: file:c:\foo\bar.html against file:///tmp/mock/path if (path[0] == ':' && IsAsciiAlpha(path[1]) &&
(path[2] == ':' || path[2] == '|')) {
out = buf; return NS_OK;
}
}
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; constchar* 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;
} elseif (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 (protocol.IsEmpty() && Scheme() == "file" &&
IsAsciiAlpha(realrelpath[0]) && realrelpath[1] == '|') { // For instance, <C|/foo/bar> against <file:///tmp/mock/path> // Treat tmp/mock/C|/foo/bar as /C|/foo/bar // + 1 should account for '/' at the beginning
len = mAuthority.mPos + mAuthority.mLen + 1;
} elseif (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) {
constexpr uint32_t slashDriveSpecifierLength = sizeof("/C:") - 1; // starting with file:C:/* // We need to ignore file:C: and begin from / // Note that file:C://* is already handled if (protocol.IsEmpty() && Scheme() == "file") { if (resultPath[0] == '/' && IsAsciiAlpha(resultPath[1]) &&
(resultPath[2] == ':' || resultPath[2] == '|')) {
resultPath += slashDriveSpecifierLength;
}
}
// Edge case: <C|> against <file:///tmp/mock/path> if (resultPath && resultPath[0] == '/') {
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);
}
// scan for first mismatched character constchar *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());
// scan for first mismatched character constchar *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--;
}
// 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));
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 (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++;
}
}
// 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;
}
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();
}
// 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);
}
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;
}
constauto* 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);
}
// 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;
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(); });
// 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.
[[nodiscard]] inlinebool 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(); returntrue;
}
// A value of -1 means an empty segment, but < -1 is undefined. if (NS_WARN_IF(aSegment.length() < -1)) { returnfalse;
}
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())) { returnfalse;
}
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!"); returnfalse;
}
// 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(); });
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.