/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef nsStandardURL_h__
#define nsStandardURL_h__
#include <bitset>
#include "nsString.h"
#include "nsISerializable.h"
#include "nsIFileURL.h"
#include "nsIStandardURL.h"
#include "mozilla/Encoding.h"
#include "nsCOMPtr.h"
#include "nsURLHelper.h"
#include "nsISizeOf.h"
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReporting.h"
#include "nsISensitiveInfoHiddenURI.h"
#include "nsIURIMutator.h"
#ifdef NS_BUILD_REFCNT_LOGGING
# define DEBUG_DUMP_URLS_AT_SHUTDOWN
#endif
class nsIBinaryInputStream;
class nsIBinaryOutputStream;
class nsIIDNService;
class nsIPrefBranch;
class nsIFile;
class nsIURLParser;
namespace mozilla {
class Encoding;
namespace net {
template <
typename T>
class URLSegmentNumber {
T mData{0};
bool mParity{
false};
public:
URLSegmentNumber() =
default;
explicit URLSegmentNumber(T data) : mData(data) {
mParity = CalculateParity();
}
bool operator==(URLSegmentNumber value)
const {
return mData == value.mData; }
bool operator!=(URLSegmentNumber value)
const {
return mData != value.mData; }
bool operator>(URLSegmentNumber value)
const {
return mData > value.mData; }
URLSegmentNumber
operator+(int32_t value)
const {
return URLSegmentNumber(mData + value);
}
URLSegmentNumber
operator+(uint32_t value)
const {
return URLSegmentNumber(mData + value);
}
URLSegmentNumber operator-(int32_t value)
const {
return URLSegmentNumber(mData - value);
}
URLSegmentNumber operator-(uint32_t value)
const {
return URLSegmentNumber(mData - value);
}
URLSegmentNumber
operator+=(URLSegmentNumber value) {
mData += value.mData;
mParity = CalculateParity();
return *
this;
}
URLSegmentNumber
operator+=(T value) {
mData += value;
mParity = CalculateParity();
return *
this;
}
URLSegmentNumber operator-=(URLSegmentNumber value) {
mData -= value.mData;
mParity = CalculateParity();
return *
this;
}
URLSegmentNumber operator-=(T value) {
mData -= value;
mParity = CalculateParity();
return *
this;
}
operator T()
const {
return mData; }
URLSegmentNumber&
operator=(T value) {
mData = value;
mParity = CalculateParity();
return *
this;
}
URLSegmentNumber&
operator++() {
++mData;
mParity = CalculateParity();
return *
this;
}
URLSegmentNumber
operator++(
int) {
URLSegmentNumber value = *
this;
*
this += 1;
return value;
}
bool CalculateParity()
const {
std::bitset<32> bits((uint32_t)mData);
return bits.count() % 2 == 0 ?
false :
true;
}
bool Parity()
const {
return mParity; }
};
//-----------------------------------------------------------------------------
// standard URL implementation
//-----------------------------------------------------------------------------
class nsStandardURL :
public nsIFileURL,
public nsIStandardURL,
public nsISerializable,
public nsISizeOf,
public nsISensitiveInfoHiddenURI
#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
,
public LinkedListElement<nsStandardURL>
#endif
{
protected:
virtual ~nsStandardURL();
explicit nsStandardURL(
bool aSupportsFileURL =
false,
bool aTrackURL =
true);
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURI
NS_DECL_NSIURL
NS_DECL_NSIFILEURL
NS_DECL_NSISTANDARDURL
NS_DECL_NSISERIALIZABLE
NS_DECL_NSISENSITIVEINFOHIDDENURI
// nsISizeOf
virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
const override;
virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
const override;
static void InitGlobalObjects();
static void ShutdownGlobalObjects();
//
// location and length of an url segment relative to mSpec
//
struct URLSegment {
#ifdef EARLY_BETA_OR_EARLIER
URLSegmentNumber<uint32_t> mPos{0};
URLSegmentNumber<int32_t> mLen{-1};
#else
uint32_t mPos{0};
int32_t mLen{-1};
#endif
URLSegment() =
default;
URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {}
URLSegment(
const URLSegment& aCopy) =
default;
void Reset() {
mPos = 0;
mLen = -1;
}
// Merge another segment following this one to it if they're contiguous
// Assumes we have something like "foo;bar" where this object is 'foo' and
// right is 'bar'.
void Merge(
const nsCString& spec,
const char separator,
const URLSegment& right) {
if (mLen >= 0 && *(spec.get() + mPos + mLen) == separator &&
mPos + mLen + 1 == right.mPos) {
mLen += 1 + right.mLen;
}
}
};
public:
//
// URL segment encoder : performs charset conversion and URL escaping.
//
class nsSegmentEncoder {
public:
explicit nsSegmentEncoder(
const Encoding* encoding = nullptr);
// Encode the given segment if necessary, and return the length of
// the encoded segment. The encoded segment is appended to |aOut|
// if and only if encoding is required.
int32_t EncodeSegmentCount(
const char* str,
const URLSegment& aSeg,
int16_t mask, nsCString& aOut,
bool& appended,
uint32_t extraLen = 0);
// Encode the given string if necessary, and return a reference to
// the encoded string. Returns a reference to |result| if encoding
// is required. Otherwise, a reference to |str| is returned.
const nsACString& EncodeSegment(
const nsACString& str, int16_t mask,
nsCString& result);
private:
const Encoding* mEncoding;
};
friend class nsSegmentEncoder;
static nsIIDNService* GetIDNService();
protected:
// enum used in a few places to specify how .ref attribute should be handled
enum RefHandlingEnum { eIgnoreRef, eHonorRef, eReplaceRef };
// Helper to share code between Equals and EqualsExceptRef
// NOTE: *not* virtual, because no one needs to override this so far...
nsresult EqualsInternal(nsIURI* unknownOther, RefHandlingEnum refHandlingMode,
bool* result);
virtual nsStandardURL* StartClone();
// Helper to share code between Clone methods.
nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
const nsACString& aNewRef, nsIURI** aClone);
// Helper method that copies member variables from the source StandardURL
// if copyCached = true, it will also copy mFile and mDisplayHost
nsresult CopyMembers(nsStandardURL* source, RefHandlingEnum mode,
const nsACString& newRef,
bool copyCached =
false);
// Helper for subclass implementation of GetFile(). Subclasses that map
// URIs to files in a special way should implement this method. It should
// ensure that our mFile is initialized, if it's possible.
// returns NS_ERROR_NO_INTERFACE if the url does not map to a file
virtual nsresult EnsureFile();
virtual nsresult Clone(nsIURI** aURI);
virtual nsresult SetSpecInternal(
const nsACString& input);
virtual nsresult SetScheme(
const nsACString& input);
virtual nsresult SetUserPass(
const nsACString& input);
virtual nsresult SetUsername(
const nsACString& input);
virtual nsresult SetPassword(
const nsACString& input);
virtual nsresult SetHostPort(
const nsACString& aValue);
virtual nsresult SetHost(
const nsACString& input);
virtual nsresult SetPort(int32_t port);
virtual nsresult SetPathQueryRef(
const nsACString& input);
virtual nsresult SetRef(
const nsACString& input);
virtual nsresult SetFilePath(
const nsACString& input);
virtual nsresult SetQuery(
const nsACString& input);
virtual nsresult SetQueryWithEncoding(
const nsACString& input,
const Encoding* encoding);
bool Deserialize(
const mozilla::ipc::URIParams&);
nsresult ReadPrivate(nsIObjectInputStream* stream);
private:
nsresult Init(uint32_t urlType, int32_t defaultPort,
const nsACString& spec,
const char* charset, nsIURI* baseURI);
nsresult SetDefaultPort(int32_t aNewDefaultPort);
nsresult SetFile(nsIFile* file);
nsresult SetFileNameInternal(
const nsACString& input);
nsresult SetFileBaseNameInternal(
const nsACString& input);
nsresult SetFileExtensionInternal(
const nsACString& input);
int32_t Port() {
return mPort == -1 ? mDefaultPort : mPort; }
void ReplacePortInSpec(int32_t aNewPort);
void Clear();
void InvalidateCache(
bool invalidateCachedFile =
true);
static bool IsValidOfBase(
unsigned char c,
const uint32_t base);
nsresult NormalizeIDN(
const nsACString& aHost, nsACString& aResult);
nsresult CheckIfHostIsAscii();
void CoalescePath(netCoalesceFlags coalesceFlag,
char* path);
uint32_t AppendSegmentToBuf(
char*, uint32_t,
const char*,
const URLSegment& input, URLSegment& output,
const nsCString* esc = nullptr,
bool useEsc =
false, int32_t* diff = nullptr);
uint32_t AppendToBuf(
char*, uint32_t,
const char*, uint32_t);
nsresult BuildNormalizedSpec(
const char* spec,
const Encoding* encoding);
nsresult SetSpecWithEncoding(
const nsACString& input,
const Encoding* encoding);
bool SegmentIs(
const URLSegment& seg,
const char* val,
bool ignoreCase =
false);
bool SegmentIs(
const char* spec,
const URLSegment& seg,
const char* val,
bool ignoreCase =
false);
bool SegmentIs(
const URLSegment& seg1,
const char* val,
const URLSegment& seg2,
bool ignoreCase =
false);
int32_t ReplaceSegment(uint32_t pos, uint32_t len,
const char* val,
uint32_t valLen);
int32_t ReplaceSegment(uint32_t pos, uint32_t len,
const nsACString& val);
nsresult ParseURL(
const char* spec, int32_t specLen);
nsresult ParsePath(
const char* spec, uint32_t pathPos, int32_t pathLen = -1);
char* AppendToSubstring(uint32_t pos, int32_t len,
const char* tail);
// dependent substring helpers
nsDependentCSubstring Segment(uint32_t pos, int32_t len);
// see below
nsDependentCSubstring Segment(
const URLSegment& s) {
return Segment(s.mPos, s.mLen);
}
// dependent substring getters
nsDependentCSubstring Prepath();
// see below
nsDependentCSubstring Scheme() {
return Segment(mScheme); }
nsDependentCSubstring Userpass(
bool includeDelim =
false);
// see below
nsDependentCSubstring Username() {
return Segment(mUsername); }
nsDependentCSubstring Password() {
return Segment(mPassword); }
nsDependentCSubstring Hostport();
// see below
nsDependentCSubstring Host();
// see below
nsDependentCSubstring Path() {
return Segment(mPath); }
nsDependentCSubstring Filepath() {
return Segment(mFilepath); }
nsDependentCSubstring Directory() {
return Segment(mDirectory); }
nsDependentCSubstring Filename();
// see below
nsDependentCSubstring Basename() {
return Segment(mBasename); }
nsDependentCSubstring Extension() {
return Segment(mExtension); }
nsDependentCSubstring Query() {
return Segment(mQuery); }
nsDependentCSubstring Ref() {
return Segment(mRef); }
// shift the URLSegments to the right by diff
void ShiftFromAuthority(int32_t diff);
void ShiftFromUsername(int32_t diff);
void ShiftFromPassword(int32_t diff);
void ShiftFromHost(int32_t diff);
void ShiftFromPath(int32_t diff);
void ShiftFromFilepath(int32_t diff);
void ShiftFromDirectory(int32_t diff);
void ShiftFromBasename(int32_t diff);
void ShiftFromExtension(int32_t diff);
void ShiftFromQuery(int32_t diff);
void ShiftFromRef(int32_t diff);
// fastload helper functions
nsresult ReadSegment(nsIBinaryInputStream*, URLSegment&);
nsresult WriteSegment(nsIBinaryOutputStream*,
const URLSegment&);
void FindHostLimit(nsACString::const_iterator& aStart,
nsACString::const_iterator& aEnd);
// Asserts that the URL has sane values
void SanityCheck();
// Checks if the URL has a valid representation.
bool IsValid();
// This value will only be updated on the main thread once.
static Atomic<
bool, Relaxed> gInitialized;
// mSpec contains the normalized version of the URL spec (UTF-8 encoded).
nsCString mSpec;
int32_t mDefaultPort{-1};
int32_t mPort{-1};
// url parts (relative to mSpec)
URLSegment mScheme;
URLSegment mAuthority;
URLSegment mUsername;
URLSegment mPassword;
URLSegment mHost;
URLSegment mPath;
URLSegment mFilepath;
URLSegment mDirectory;
URLSegment mBasename;
URLSegment mExtension;
URLSegment mQuery;
URLSegment mRef;
nsCOMPtr<nsIURLParser> mParser;
// mFile is protected so subclasses can access it directly
protected:
nsCOMPtr<nsIFile> mFile;
// cached result for nsIFileURL::GetFile
private:
// cached result for nsIURI::GetDisplayHost
nsCString mDisplayHost;
enum { eEncoding_Unknown, eEncoding_ASCII, eEncoding_UTF8 };
uint32_t mURLType : 2;
// nsIStandardURL::URLTYPE_xxx
uint32_t mSupportsFileURL : 1;
// QI to nsIFileURL?
uint32_t mCheckedIfHostA : 1;
// If set to true, it means either that
// mDisplayHost has a been initialized, or
// that the hostname is not punycode
// global objects.
static StaticRefPtr<nsIIDNService> gIDN;
static const char gHostLimitDigits[];
public:
#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
void PrintSpec()
const { printf(
" %s\n", mSpec.get()); }
#endif
public:
// We make this implementation a template so that we can avoid writing
// the same code for SubstitutingURL (which extends nsStandardURL)
template <
class T>
class TemplatedMutator :
public nsIURIMutator,
public BaseURIMutator<T>,
public nsIStandardURLMutator,
public nsIURLMutator,
public nsIFileURLMutator,
public nsISerializable {
NS_FORWARD_SAFE_NSIURISETTERS_RET(BaseURIMutator<T>::mURI)
[[nodiscard]] NS_IMETHOD Deserialize(
const mozilla::ipc::URIParams& aParams) override {
return BaseURIMutator<T>::InitFromIPCParams(aParams);
}
NS_IMETHOD
Write(nsIObjectOutputStream* aOutputStream) override {
MOZ_ASSERT_UNREACHABLE(
"Use nsIURIMutator.read() instead");
return NS_ERROR_NOT_IMPLEMENTED;
}
[[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
return BaseURIMutator<T>::InitFromInputStream(aStream);
}
[[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override {
BaseURIMutator<T>::mURI.forget(aURI);
return NS_OK;
}
[[nodiscard]] NS_IMETHOD SetSpec(
const nsACString& aSpec,
nsIURIMutator** aMutator) override {
if (aMutator) {
nsCOMPtr<nsIURIMutator> mutator =
this;
mutator.forget(aMutator);
}
return BaseURIMutator<T>::InitFromSpec(aSpec);
}
[[nodiscard]] NS_IMETHOD Init(uint32_t aURLType, int32_t aDefaultPort,
const nsACString& aSpec,
const char* aCharset,
nsIURI* aBaseURI,
nsIURIMutator** aMutator) override {
if (aMutator) {
nsCOMPtr<nsIURIMutator> mutator =
this;
mutator.forget(aMutator);
}
RefPtr<T> uri;
if (BaseURIMutator<T>::mURI) {
// We don't need a new URI object if we already have one
BaseURIMutator<T>::mURI.swap(uri);
}
else {
uri = Create();
}
nsresult rv =
uri->Init(aURLType, aDefaultPort, aSpec, aCharset, aBaseURI);
if (NS_FAILED(rv)) {
return rv;
}
BaseURIMutator<T>::mURI = std::move(uri);
return NS_OK;
}
[[nodiscard]] NS_IMETHODIMP SetDefaultPort(
int32_t aNewDefaultPort, nsIURIMutator** aMutator) override {
if (!BaseURIMutator<T>::mURI) {
return NS_ERROR_NULL_POINTER;
}
if (aMutator) {
nsCOMPtr<nsIURIMutator> mutator =
this;
mutator.forget(aMutator);
}
return BaseURIMutator<T>::mURI->SetDefaultPort(aNewDefaultPort);
}
[[nodiscard]] NS_IMETHOD SetFileName(
const nsACString& aFileName,
nsIURIMutator** aMutator) override {
if (!BaseURIMutator<T>::mURI) {
return NS_ERROR_NULL_POINTER;
}
if (aMutator) {
nsCOMPtr<nsIURIMutator> mutator =
this;
mutator.forget(aMutator);
}
return BaseURIMutator<T>::mURI->SetFileNameInternal(aFileName);
}
[[nodiscard]] NS_IMETHOD SetFileBaseName(
const nsACString& aFileBaseName, nsIURIMutator** aMutator) override {
if (!BaseURIMutator<T>::mURI) {
return NS_ERROR_NULL_POINTER;
}
if (aMutator) {
nsCOMPtr<nsIURIMutator> mutator =
this;
mutator.forget(aMutator);
}
return BaseURIMutator<T>::mURI->SetFileBaseNameInternal(aFileBaseName);
}
[[nodiscard]] NS_IMETHOD SetFileExtension(
const nsACString& aFileExtension, nsIURIMutator** aMutator) override {
if (!BaseURIMutator<T>::mURI) {
return NS_ERROR_NULL_POINTER;
}
if (aMutator) {
nsCOMPtr<nsIURIMutator> mutator =
this;
mutator.forget(aMutator);
}
return BaseURIMutator<T>::mURI->SetFileExtensionInternal(aFileExtension);
}
T* Create() override {
return new T(mMarkedFileURL); }
[[nodiscard]] NS_IMETHOD MarkFileURL() override {
mMarkedFileURL =
true;
return NS_OK;
}
[[nodiscard]] NS_IMETHOD SetFile(nsIFile* aFile) override {
RefPtr<T> uri;
if (BaseURIMutator<T>::mURI) {
// We don't need a new URI object if we already have one
BaseURIMutator<T>::mURI.swap(uri);
}
else {
uri =
new T(
/* aSupportsFileURL = */ true);
}
nsresult rv = uri->SetFile(aFile);
if (NS_FAILED(rv)) {
return rv;
}
BaseURIMutator<T>::mURI.swap(uri);
return NS_OK;
}
explicit TemplatedMutator() =
default;
private:
virtual ~TemplatedMutator() =
default;
bool mMarkedFileURL =
false;
friend T;
};
class Mutator final :
public TemplatedMutator<nsStandardURL> {
NS_DECL_ISUPPORTS
public:
explicit Mutator() =
default;
private:
virtual ~Mutator() =
default;
};
friend BaseURIMutator<nsStandardURL>;
};
#define NS_THIS_STANDARDURL_IMPL_CID \
{
/* b8e3e97b-1ccd-4b45-af5a-79596770f5d7 */ \
0xb8e3e97b, 0x1ccd, 0x4b45, { \
0xaf, 0x5a, 0x79, 0x59, 0x67, 0x70, 0xf5, 0xd7 \
} \
}
//-----------------------------------------------------------------------------
// Dependent substring getters
//-----------------------------------------------------------------------------
inline nsDependentCSubstring nsStandardURL::Segment(uint32_t pos, int32_t len) {
if (len < 0) {
pos = 0;
len = 0;
}
return Substring(mSpec, pos, uint32_t(len));
}
inline nsDependentCSubstring nsStandardURL::Prepath() {
uint32_t len = 0;
if (mAuthority.mLen >= 0) len = mAuthority.mPos + mAuthority.mLen;
return Substring(mSpec, 0, len);
}
inline nsDependentCSubstring nsStandardURL::Userpass(
bool includeDelim) {
uint32_t pos = 0, len = 0;
if (mUsername.mLen > 0 || mPassword.mLen > 0) {
if (mUsername.mLen > 0) {
pos = mUsername.mPos;
len = mUsername.mLen;
if (mPassword.mLen >= 0) {
len += (mPassword.mLen + 1);
}
}
else {
pos = mPassword.mPos - 1;
len = mPassword.mLen + 1;
}
if (includeDelim) len++;
}
return Substring(mSpec, pos, len);
}
inline nsDependentCSubstring nsStandardURL::Hostport() {
uint32_t pos = 0, len = 0;
if (mAuthority.mLen > 0) {
pos = mHost.mPos;
len = mAuthority.mPos + mAuthority.mLen - pos;
}
return Substring(mSpec, pos, len);
}
inline nsDependentCSubstring nsStandardURL::Host() {
uint32_t pos = 0, len = 0;
if (mHost.mLen > 0) {
pos = mHost.mPos;
len = mHost.mLen;
if (mSpec.CharAt(pos) ==
'[' && mSpec.CharAt(pos + len - 1) ==
']') {
pos++;
len -= 2;
}
}
return Substring(mSpec, pos, len);
}
inline nsDependentCSubstring nsStandardURL::Filename() {
uint32_t pos = 0, len = 0;
// if there is no basename, then there can be no extension
if (mBasename.mLen > 0) {
pos = mBasename.mPos;
len = mBasename.mLen;
if (mExtension.mLen >= 0) len += (mExtension.mLen + 1);
}
return Substring(mSpec, pos, len);
}
}
// namespace net
}
// namespace mozilla
#endif // nsStandardURL_h__