799 lines
24 KiB
C++
799 lines
24 KiB
C++
/* -*- Mode: C++; tab-width: 2; 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/. */
|
|
|
|
#include "mozilla/extensions/MatchPattern.h"
|
|
#include "mozilla/extensions/MatchGlob.h"
|
|
|
|
#include "js/RegExp.h" // JS::NewUCRegExpObject, JS::ExecuteRegExpNoStatics
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/ErrorResult.h"
|
|
#include "mozilla/HoldDropJSObjects.h"
|
|
#include "mozilla/Unused.h"
|
|
|
|
#include "nsGkAtoms.h"
|
|
#include "nsIProtocolHandler.h"
|
|
#include "nsIURL.h"
|
|
#include "nsNetUtil.h"
|
|
|
|
namespace mozilla {
|
|
namespace extensions {
|
|
|
|
using namespace mozilla::dom;
|
|
|
|
/*****************************************************************************
|
|
* AtomSet
|
|
*****************************************************************************/
|
|
|
|
template <typename Range, typename AsAtom>
|
|
static AtomSet::ArrayType AtomSetFromRange(Range&& aRange,
|
|
AsAtom&& aTransform) {
|
|
AtomSet::ArrayType atoms;
|
|
if (auto estimate = RangeSizeEstimate(aRange)) {
|
|
atoms.SetCapacity(estimate);
|
|
}
|
|
std::transform(aRange.begin(), aRange.end(), MakeBackInserter(atoms),
|
|
std::forward<AsAtom>(aTransform));
|
|
|
|
atoms.Sort();
|
|
|
|
nsAtom* prev = nullptr;
|
|
atoms.RemoveElementsBy([&prev](const RefPtr<nsAtom>& aAtom) {
|
|
bool remove = aAtom == prev;
|
|
prev = aAtom;
|
|
return remove;
|
|
});
|
|
|
|
atoms.Compact();
|
|
return atoms;
|
|
}
|
|
|
|
AtomSet::AtomSet(const nsTArray<nsString>& aElems)
|
|
: mElems(AtomSetFromRange(
|
|
aElems, [](const nsString& elem) { return NS_Atomize(elem); })) {}
|
|
|
|
AtomSet::AtomSet(std::initializer_list<nsAtom*> aIL)
|
|
: mElems(AtomSetFromRange(aIL, [](nsAtom* elem) { return elem; })) {}
|
|
|
|
bool AtomSet::Intersects(const AtomSet& aOther) const {
|
|
for (const auto& atom : *this) {
|
|
if (aOther.Contains(atom)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (const auto& atom : aOther) {
|
|
if (Contains(atom)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#define DEFINE_STATIC_ATOM_SET(name, ...) \
|
|
static already_AddRefed<AtomSet> name() { \
|
|
MOZ_ASSERT(NS_IsMainThread()); \
|
|
static StaticRefPtr<AtomSet> sAtomSet; \
|
|
RefPtr<AtomSet> atomSet = sAtomSet; \
|
|
if (!atomSet) { \
|
|
atomSet = sAtomSet = new AtomSet{__VA_ARGS__}; \
|
|
ClearOnShutdown(&sAtomSet); \
|
|
} \
|
|
return atomSet.forget(); \
|
|
}
|
|
|
|
DEFINE_STATIC_ATOM_SET(PermittedSchemes, nsGkAtoms::http, nsGkAtoms::https,
|
|
nsGkAtoms::ws, nsGkAtoms::wss, nsGkAtoms::file,
|
|
nsGkAtoms::ftp, nsGkAtoms::data);
|
|
|
|
// Known schemes that are followed by "://" instead of ":".
|
|
DEFINE_STATIC_ATOM_SET(HostLocatorSchemes, nsGkAtoms::http, nsGkAtoms::https,
|
|
nsGkAtoms::ws, nsGkAtoms::wss, nsGkAtoms::file,
|
|
nsGkAtoms::ftp, nsGkAtoms::moz_extension,
|
|
nsGkAtoms::chrome, nsGkAtoms::resource, nsGkAtoms::moz,
|
|
nsGkAtoms::moz_icon, nsGkAtoms::moz_gio);
|
|
|
|
DEFINE_STATIC_ATOM_SET(WildcardSchemes, nsGkAtoms::http, nsGkAtoms::https,
|
|
nsGkAtoms::ws, nsGkAtoms::wss);
|
|
|
|
// Schemes whose URLs are a meaningful representation of the document URL, even
|
|
// if the origin of that document is opaque (e.g. due to CSP sandbox).
|
|
// Such documents have a null principal, and their precursor (origin) is
|
|
// usually equal to the origin component of such URLs.
|
|
// Counter-examples: about: and data: do not have any embedded origin,
|
|
// blob:-URLs have an origin (becomes "null" when opaque) and no path.
|
|
//
|
|
// Note: view-source: is part of this set to make sure that we always look at
|
|
// the URL and never at the principal in these cases, because in case of null
|
|
// principals, the precursor could be a http(s) URL. We don't want to run
|
|
// scripts in view-source: because we have historically not allowed extensions
|
|
// to do so.
|
|
DEFINE_STATIC_ATOM_SET(NonOpaqueSchemes, nsGkAtoms::http, nsGkAtoms::https,
|
|
nsGkAtoms::file, nsGkAtoms::view_source);
|
|
|
|
#undef DEFINE_STATIC_ATOM_SET
|
|
|
|
/*****************************************************************************
|
|
* URLInfo
|
|
*****************************************************************************/
|
|
|
|
nsAtom* URLInfo::Scheme() const {
|
|
if (!mScheme) {
|
|
nsCString scheme;
|
|
if (NS_SUCCEEDED(mURI->GetScheme(scheme))) {
|
|
mScheme = NS_Atomize(scheme);
|
|
}
|
|
}
|
|
return mScheme;
|
|
}
|
|
|
|
const nsCString& URLInfo::Host() const {
|
|
if (mHost.IsVoid()) {
|
|
Unused << mURI->GetHost(mHost);
|
|
}
|
|
return mHost;
|
|
}
|
|
|
|
const nsAtom* URLInfo::HostAtom() const {
|
|
if (!mHostAtom) {
|
|
mHostAtom = NS_Atomize(Host());
|
|
}
|
|
return mHostAtom;
|
|
}
|
|
|
|
const nsCString& URLInfo::FilePath() const {
|
|
if (mFilePath.IsEmpty()) {
|
|
nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
|
|
if (!url || NS_FAILED(url->GetFilePath(mFilePath))) {
|
|
mFilePath = Path();
|
|
}
|
|
}
|
|
return mFilePath;
|
|
}
|
|
|
|
const nsCString& URLInfo::Path() const {
|
|
if (mPath.IsEmpty()) {
|
|
if (NS_FAILED(URINoRef()->GetPathQueryRef(mPath))) {
|
|
mPath.Truncate();
|
|
}
|
|
}
|
|
return mPath;
|
|
}
|
|
|
|
const nsCString& URLInfo::CSpec() const {
|
|
if (mCSpec.IsEmpty()) {
|
|
Unused << URINoRef()->GetSpec(mCSpec);
|
|
}
|
|
return mCSpec;
|
|
}
|
|
|
|
const nsString& URLInfo::Spec() const {
|
|
if (mSpec.IsEmpty()) {
|
|
AppendUTF8toUTF16(CSpec(), mSpec);
|
|
}
|
|
return mSpec;
|
|
}
|
|
|
|
nsIURI* URLInfo::URINoRef() const {
|
|
if (!mURINoRef) {
|
|
if (NS_FAILED(NS_GetURIWithoutRef(mURI, getter_AddRefs(mURINoRef)))) {
|
|
mURINoRef = mURI;
|
|
}
|
|
}
|
|
return mURINoRef;
|
|
}
|
|
|
|
bool URLInfo::InheritsPrincipal() const {
|
|
if (!mInheritsPrincipal.isSome()) {
|
|
// This logic here supports the match_about_blank flag of content scripts.
|
|
// For our purposes, about:blank and about:srcdoc are treated as URIs that
|
|
// inherit principals.
|
|
bool inherits =
|
|
Scheme() == nsGkAtoms::about && (Spec().EqualsLiteral("about:blank") ||
|
|
Spec().EqualsLiteral("about:srcdoc"));
|
|
|
|
mInheritsPrincipal.emplace(inherits);
|
|
}
|
|
return mInheritsPrincipal.ref();
|
|
}
|
|
|
|
bool URLInfo::IsNonOpaqueURL() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!mIsNonOpaqueURL.isSome()) {
|
|
RefPtr<AtomSet> nonOpaqueSchemes = NonOpaqueSchemes();
|
|
mIsNonOpaqueURL.emplace(nonOpaqueSchemes->Contains(Scheme()));
|
|
}
|
|
return mIsNonOpaqueURL.ref();
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* CookieInfo
|
|
*****************************************************************************/
|
|
|
|
bool CookieInfo::IsDomain() const {
|
|
if (mIsDomain.isNothing()) {
|
|
mIsDomain.emplace(false);
|
|
MOZ_ALWAYS_SUCCEEDS(mCookie->GetIsDomain(mIsDomain.ptr()));
|
|
}
|
|
return mIsDomain.ref();
|
|
}
|
|
|
|
bool CookieInfo::IsSecure() const {
|
|
if (mIsSecure.isNothing()) {
|
|
mIsSecure.emplace(false);
|
|
MOZ_ALWAYS_SUCCEEDS(mCookie->GetIsSecure(mIsSecure.ptr()));
|
|
}
|
|
return mIsSecure.ref();
|
|
}
|
|
|
|
const nsCString& CookieInfo::Host() const {
|
|
if (mHost.IsEmpty()) {
|
|
MOZ_ALWAYS_SUCCEEDS(mCookie->GetHost(mHost));
|
|
}
|
|
return mHost;
|
|
}
|
|
|
|
const nsCString& CookieInfo::RawHost() const {
|
|
if (mRawHost.IsEmpty()) {
|
|
MOZ_ALWAYS_SUCCEEDS(mCookie->GetRawHost(mRawHost));
|
|
}
|
|
return mRawHost;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* MatchPatternCore
|
|
*****************************************************************************/
|
|
|
|
MatchPatternCore::MatchPatternCore(const nsAString& aPattern, bool aIgnorePath,
|
|
bool aRestrictSchemes, ErrorResult& aRv) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
RefPtr<AtomSet> permittedSchemes = PermittedSchemes();
|
|
|
|
mPattern = aPattern;
|
|
|
|
if (aPattern.EqualsLiteral("<all_urls>")) {
|
|
mSchemes = permittedSchemes;
|
|
mMatchSubdomain = true;
|
|
return;
|
|
}
|
|
|
|
// The portion of the URL we're currently examining.
|
|
uint32_t offset = 0;
|
|
auto tail = Substring(aPattern, offset);
|
|
|
|
/***************************************************************************
|
|
* Scheme
|
|
***************************************************************************/
|
|
int32_t index = aPattern.FindChar(':');
|
|
if (index <= 0) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
RefPtr<nsAtom> scheme = NS_AtomizeMainThread(StringHead(aPattern, index));
|
|
bool requireHostLocatorScheme = true;
|
|
if (scheme == nsGkAtoms::_asterisk) {
|
|
mSchemes = WildcardSchemes();
|
|
} else if (!aRestrictSchemes || permittedSchemes->Contains(scheme) ||
|
|
scheme == nsGkAtoms::moz_extension) {
|
|
RefPtr<AtomSet> hostLocatorSchemes = HostLocatorSchemes();
|
|
mSchemes = new AtomSet({scheme});
|
|
requireHostLocatorScheme = hostLocatorSchemes->Contains(scheme);
|
|
} else {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Host
|
|
***************************************************************************/
|
|
offset = index + 1;
|
|
tail.Rebind(aPattern, offset);
|
|
|
|
if (!requireHostLocatorScheme) {
|
|
// Unrecognized schemes and some schemes such as about: and data: URIs
|
|
// don't have hosts, so just match on the path.
|
|
// And so, ignorePath doesn't make sense for these matchers.
|
|
aIgnorePath = false;
|
|
} else {
|
|
if (!StringHead(tail, 2).EqualsLiteral("//")) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
offset += 2;
|
|
tail.Rebind(aPattern, offset);
|
|
index = tail.FindChar('/');
|
|
if (index < 0) {
|
|
index = tail.Length();
|
|
}
|
|
|
|
auto host = StringHead(tail, index);
|
|
if (host.IsEmpty() && scheme != nsGkAtoms::file) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
offset += index;
|
|
tail.Rebind(aPattern, offset);
|
|
|
|
if (host.EqualsLiteral("*")) {
|
|
mMatchSubdomain = true;
|
|
} else if (StringHead(host, 2).EqualsLiteral("*.")) {
|
|
CopyUTF16toUTF8(Substring(host, 2), mDomain);
|
|
mMatchSubdomain = true;
|
|
} else if (host.Length() > 1 && host[0] == '[' &&
|
|
host[host.Length() - 1] == ']') {
|
|
// This is an IPv6 literal, we drop the enclosing `[]` to be
|
|
// consistent with nsIURI.
|
|
CopyUTF16toUTF8(Substring(host, 1, host.Length() - 2), mDomain);
|
|
} else {
|
|
CopyUTF16toUTF8(host, mDomain);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Path
|
|
***************************************************************************/
|
|
if (aIgnorePath) {
|
|
mPattern.Truncate(offset);
|
|
mPattern.AppendLiteral("/*");
|
|
return;
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 path(tail);
|
|
if (path.IsEmpty()) {
|
|
aRv.Throw(NS_ERROR_INVALID_ARG);
|
|
return;
|
|
}
|
|
|
|
// Anything matched against one of the hosts in hostLocatorSchemes is expected
|
|
// to have a path starting with "/". Pass isPathGlob=true in these cases to
|
|
// ensure that MatchGlobCore treats "/*" paths as a wildcard (IsWildcard()).
|
|
bool isPathGlob = requireHostLocatorScheme;
|
|
mPath = new MatchGlobCore(path, false, isPathGlob, aRv);
|
|
}
|
|
|
|
bool MatchPatternCore::MatchesAllWebUrls() const {
|
|
// Returns true if the match pattern matches any http(s) URL, i.e.:
|
|
// - ["<all_urls>"]
|
|
// - ["*://*/*"]
|
|
return (mSchemes->Contains(nsGkAtoms::http) &&
|
|
MatchesAllUrlsWithScheme(nsGkAtoms::https));
|
|
}
|
|
|
|
bool MatchPatternCore::MatchesAllUrlsWithScheme(const nsAtom* scheme) const {
|
|
return (mSchemes->Contains(scheme) && DomainIsWildcard() &&
|
|
(!mPath || mPath->IsWildcard()));
|
|
}
|
|
|
|
bool MatchPatternCore::MatchesDomain(const nsACString& aDomain) const {
|
|
if (DomainIsWildcard() || mDomain == aDomain) {
|
|
return true;
|
|
}
|
|
|
|
if (mMatchSubdomain) {
|
|
int64_t offset = (int64_t)aDomain.Length() - mDomain.Length();
|
|
if (offset > 0 && aDomain[offset - 1] == '.' &&
|
|
Substring(aDomain, offset) == mDomain) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternCore::Matches(const nsAString& aURL, bool aExplicit,
|
|
ErrorResult& aRv) const {
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return false;
|
|
}
|
|
|
|
return Matches(uri.get(), aExplicit);
|
|
}
|
|
|
|
bool MatchPatternCore::Matches(const URLInfo& aURL, bool aExplicit) const {
|
|
if (aExplicit && mMatchSubdomain) {
|
|
return false;
|
|
}
|
|
|
|
if (!mSchemes->Contains(aURL.Scheme())) {
|
|
return false;
|
|
}
|
|
|
|
if (!MatchesDomain(aURL.Host())) {
|
|
return false;
|
|
}
|
|
|
|
if (mPath && !mPath->IsWildcard() && !mPath->Matches(aURL.Path())) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MatchPatternCore::MatchesCookie(const CookieInfo& aCookie) const {
|
|
if (!mSchemes->Contains(nsGkAtoms::https) &&
|
|
(aCookie.IsSecure() || !mSchemes->Contains(nsGkAtoms::http))) {
|
|
return false;
|
|
}
|
|
|
|
if (MatchesDomain(aCookie.RawHost())) {
|
|
return true;
|
|
}
|
|
|
|
if (!aCookie.IsDomain()) {
|
|
return false;
|
|
}
|
|
|
|
// Things get tricker for domain cookies. The extension needs to be able
|
|
// to read any cookies that could be read by any host it has permissions
|
|
// for. This means that our normal host matching checks won't work,
|
|
// since the pattern "*://*.foo.example.com/" doesn't match ".example.com",
|
|
// but it does match "bar.foo.example.com", which can read cookies
|
|
// with the domain ".example.com".
|
|
//
|
|
// So, instead, we need to manually check our filters, and accept any
|
|
// with hosts that end with our cookie's host.
|
|
|
|
auto& host = aCookie.Host();
|
|
return StringTail(mDomain, host.Length()) == host;
|
|
}
|
|
|
|
bool MatchPatternCore::SubsumesDomain(const MatchPatternCore& aPattern) const {
|
|
if (!mMatchSubdomain && aPattern.mMatchSubdomain &&
|
|
aPattern.mDomain == mDomain) {
|
|
return false;
|
|
}
|
|
|
|
return MatchesDomain(aPattern.mDomain);
|
|
}
|
|
|
|
bool MatchPatternCore::Subsumes(const MatchPatternCore& aPattern) const {
|
|
for (auto& scheme : *aPattern.mSchemes) {
|
|
if (!mSchemes->Contains(scheme)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return SubsumesDomain(aPattern);
|
|
}
|
|
|
|
bool MatchPatternCore::Overlaps(const MatchPatternCore& aPattern) const {
|
|
if (!mSchemes->Intersects(*aPattern.mSchemes)) {
|
|
return false;
|
|
}
|
|
|
|
return SubsumesDomain(aPattern) || aPattern.SubsumesDomain(*this);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* MatchPattern
|
|
*****************************************************************************/
|
|
|
|
/* static */
|
|
already_AddRefed<MatchPattern> MatchPattern::Constructor(
|
|
dom::GlobalObject& aGlobal, const nsAString& aPattern,
|
|
const MatchPatternOptions& aOptions, ErrorResult& aRv) {
|
|
RefPtr<MatchPattern> pattern = new MatchPattern(
|
|
aGlobal.GetAsSupports(),
|
|
MakeAndAddRef<MatchPatternCore>(aPattern, aOptions.mIgnorePath,
|
|
aOptions.mRestrictSchemes, aRv));
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
return pattern.forget();
|
|
}
|
|
|
|
JSObject* MatchPattern::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return MatchPattern_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
/* static */
|
|
bool MatchPattern::MatchesAllURLs(const URLInfo& aURL) {
|
|
RefPtr<AtomSet> permittedSchemes = PermittedSchemes();
|
|
return permittedSchemes->Contains(aURL.Scheme());
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchPattern, mParent)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchPattern)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchPattern)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchPattern)
|
|
|
|
bool MatchPatternSetCore::Matches(const nsAString& aURL, bool aExplicit,
|
|
ErrorResult& aRv) const {
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return false;
|
|
}
|
|
|
|
return Matches(uri.get(), aExplicit);
|
|
}
|
|
|
|
bool MatchPatternSetCore::Matches(const URLInfo& aURL, bool aExplicit) const {
|
|
for (const auto& pattern : mPatterns) {
|
|
if (pattern->Matches(aURL, aExplicit)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternSetCore::MatchesAllWebUrls() const {
|
|
// Returns true if the match pattern matches any http(s) URL, i.e.:
|
|
// - ["<all_urls>"]
|
|
// - ["*://*/*"]
|
|
// - ["https://*/*", "http://*/*"]
|
|
bool hasHttp = false;
|
|
bool hasHttps = false;
|
|
for (const auto& pattern : mPatterns) {
|
|
if (!hasHttp && pattern->MatchesAllUrlsWithScheme(nsGkAtoms::http)) {
|
|
hasHttp = true;
|
|
}
|
|
if (!hasHttps && pattern->MatchesAllUrlsWithScheme(nsGkAtoms::https)) {
|
|
hasHttps = true;
|
|
}
|
|
if (hasHttp && hasHttps) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternSetCore::MatchesCookie(const CookieInfo& aCookie) const {
|
|
for (const auto& pattern : mPatterns) {
|
|
if (pattern->MatchesCookie(aCookie)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternSetCore::Subsumes(const MatchPatternCore& aPattern) const {
|
|
// Note: the implementation below assumes that a pattern can only be subsumed
|
|
// if it is fully contained within another pattern. Logically, this is an
|
|
// incorrect assumption: "*://example.com/" matches multiple schemes, and is
|
|
// equivalent to a MatchPatternSet that lists all schemes explicitly.
|
|
// TODO bug 1856380: account for all patterns if aPattern has a wildcard
|
|
// scheme (such as when aPattern.MatchesAllWebUrls() is true).
|
|
for (const auto& pattern : mPatterns) {
|
|
if (pattern->Subsumes(aPattern)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternSetCore::SubsumesDomain(
|
|
const MatchPatternCore& aPattern) const {
|
|
for (const auto& pattern : mPatterns) {
|
|
if (pattern->SubsumesDomain(aPattern)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternSetCore::Overlaps(
|
|
const MatchPatternSetCore& aPatternSet) const {
|
|
for (const auto& pattern : aPatternSet.mPatterns) {
|
|
if (Overlaps(*pattern)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternSetCore::Overlaps(const MatchPatternCore& aPattern) const {
|
|
for (const auto& pattern : mPatterns) {
|
|
if (pattern->Overlaps(aPattern)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MatchPatternSetCore::OverlapsAll(
|
|
const MatchPatternSetCore& aPatternSet) const {
|
|
for (const auto& pattern : aPatternSet.mPatterns) {
|
|
if (!Overlaps(*pattern)) {
|
|
return false;
|
|
}
|
|
}
|
|
return aPatternSet.mPatterns.Length() > 0;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* MatchPatternSet
|
|
*****************************************************************************/
|
|
|
|
/* static */
|
|
already_AddRefed<MatchPatternSet> MatchPatternSet::Constructor(
|
|
dom::GlobalObject& aGlobal,
|
|
const nsTArray<dom::OwningStringOrMatchPattern>& aPatterns,
|
|
const MatchPatternOptions& aOptions, ErrorResult& aRv) {
|
|
MatchPatternSetCore::ArrayType patterns;
|
|
|
|
for (auto& elem : aPatterns) {
|
|
if (elem.IsMatchPattern()) {
|
|
patterns.AppendElement(elem.GetAsMatchPattern()->Core());
|
|
} else {
|
|
RefPtr<MatchPatternCore> pattern =
|
|
new MatchPatternCore(elem.GetAsString(), aOptions.mIgnorePath,
|
|
aOptions.mRestrictSchemes, aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
patterns.AppendElement(std::move(pattern));
|
|
}
|
|
}
|
|
|
|
RefPtr<MatchPatternSet> patternSet = new MatchPatternSet(
|
|
aGlobal.GetAsSupports(),
|
|
do_AddRef(new MatchPatternSetCore(std::move(patterns))));
|
|
return patternSet.forget();
|
|
}
|
|
|
|
void MatchPatternSet::GetPatterns(ArrayType& aPatterns) {
|
|
if (!mPatternsCache) {
|
|
mPatternsCache.emplace(Core()->mPatterns.Length());
|
|
for (auto& elem : Core()->mPatterns) {
|
|
mPatternsCache->AppendElement(new MatchPattern(this, do_AddRef(elem)));
|
|
}
|
|
}
|
|
aPatterns.AppendElements(*mPatternsCache);
|
|
}
|
|
|
|
JSObject* MatchPatternSet::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return MatchPatternSet_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchPatternSet, mPatternsCache, mParent)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchPatternSet)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchPatternSet)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchPatternSet)
|
|
|
|
/*****************************************************************************
|
|
* MatchGlobCore
|
|
*****************************************************************************/
|
|
|
|
MatchGlobCore::MatchGlobCore(const nsACString& aGlob, bool aAllowQuestion,
|
|
bool aIsPathGlob, ErrorResult& aRv)
|
|
: mGlob(aGlob) {
|
|
// Check for a literal match with no glob metacharacters.
|
|
auto index = mGlob.FindCharInSet(aAllowQuestion ? "*?" : "*");
|
|
if (index < 0) {
|
|
mPathLiteral = mGlob;
|
|
return;
|
|
}
|
|
|
|
// Check for a prefix match, where the only glob metacharacter is a "*"
|
|
// at the end of the string (or a sequence of it).
|
|
for (int32_t i = mGlob.Length() - 1; i >= index && mGlob[i] == '*'; --i) {
|
|
if (i == index) {
|
|
mPathLiteral = StringHead(mGlob, index);
|
|
if (aIsPathGlob && mPathLiteral.EqualsLiteral("/")) {
|
|
// Ensure that IsWildcard() correctly treats us as a wildcard.
|
|
mPathLiteral.Truncate();
|
|
}
|
|
mIsPrefix = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fall back to the regexp slow path.
|
|
constexpr auto metaChars = ".+*?^${}()|[]\\"_ns;
|
|
|
|
nsAutoCString escaped;
|
|
escaped.Append('^');
|
|
|
|
// For any continuous string of * (and ? if aAllowQuestion) wildcards, only
|
|
// emit the first *, later ones are redundant, and can hang regex matching.
|
|
bool emittedFirstStar = false;
|
|
|
|
for (uint32_t i = 0; i < mGlob.Length(); i++) {
|
|
auto c = mGlob[i];
|
|
if (c == '*') {
|
|
if (!emittedFirstStar) {
|
|
escaped.AppendLiteral(".*");
|
|
emittedFirstStar = true;
|
|
}
|
|
} else if (c == '?' && aAllowQuestion) {
|
|
escaped.Append('.');
|
|
} else {
|
|
if (metaChars.Contains(c)) {
|
|
escaped.Append('\\');
|
|
}
|
|
escaped.Append(c);
|
|
|
|
// String of wildcards broken by a non-wildcard char, reset tracking flag.
|
|
emittedFirstStar = false;
|
|
}
|
|
}
|
|
|
|
escaped.Append('$');
|
|
|
|
mRegExp = RustRegex(escaped);
|
|
if (!mRegExp) {
|
|
aRv.ThrowTypeError("failed to compile regex for glob");
|
|
}
|
|
}
|
|
|
|
bool MatchGlobCore::Matches(const nsACString& aString) const {
|
|
if (mRegExp) {
|
|
return mRegExp.IsMatch(aString);
|
|
}
|
|
|
|
if (mIsPrefix) {
|
|
return mPathLiteral == StringHead(aString, mPathLiteral.Length());
|
|
}
|
|
|
|
return mPathLiteral == aString;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* MatchGlob
|
|
*****************************************************************************/
|
|
|
|
/* static */
|
|
already_AddRefed<MatchGlob> MatchGlob::Constructor(dom::GlobalObject& aGlobal,
|
|
const nsACString& aGlob,
|
|
bool aAllowQuestion,
|
|
ErrorResult& aRv) {
|
|
RefPtr<MatchGlob> glob = new MatchGlob(
|
|
aGlobal.GetAsSupports(),
|
|
MakeAndAddRef<MatchGlobCore>(aGlob, aAllowQuestion, false, aRv));
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
return glob.forget();
|
|
}
|
|
|
|
JSObject* MatchGlob::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return MatchGlob_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchGlob, mParent)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchGlob)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchGlob)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchGlob)
|
|
|
|
/*****************************************************************************
|
|
* MatchGlobSet
|
|
*****************************************************************************/
|
|
|
|
bool MatchGlobSet::Matches(const nsACString& aValue) const {
|
|
for (auto& glob : *this) {
|
|
if (glob->Matches(aValue)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace extensions
|
|
} // namespace mozilla
|