forked from mirrors/gecko-dev
		
	 046aaf98cc
			
		
	
	
		046aaf98cc
		
	
	
	
	
		
			
			Introduce `MatchPatternSet::MatchesAllWebUrls()` and `MatchPattern::MatchesAllWebUrls()` to serve the purpose of determining whether a match pattern is going to match any http(s) URL. Differential Revision: https://phabricator.services.mozilla.com/D189491
		
			
				
	
	
		
			777 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			777 lines
		
	
	
	
		
			23 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;
 | |
|   atoms.SetCapacity(RangeSize(aRange));
 | |
|   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;
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * URLInfo
 | |
|  *****************************************************************************/
 | |
| 
 | |
| nsAtom* URLInfo::Scheme() const {
 | |
|   if (!mScheme) {
 | |
|     nsCString scheme;
 | |
|     if (NS_SUCCEEDED(mURI->GetScheme(scheme))) {
 | |
|       mScheme = NS_AtomizeMainThread(NS_ConvertASCIItoUTF16(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()) {
 | |
|     // For our purposes, about:blank and about:srcdoc are treated as URIs that
 | |
|     // inherit principals.
 | |
|     bool inherits = Spec().EqualsLiteral("about:blank") ||
 | |
|                     Spec().EqualsLiteral("about:srcdoc");
 | |
| 
 | |
|     if (!inherits) {
 | |
|       nsresult rv = NS_URIChainHasFlags(
 | |
|           mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &inherits);
 | |
|       Unused << NS_WARN_IF(NS_FAILED(rv));
 | |
|     }
 | |
| 
 | |
|     mInheritsPrincipal.emplace(inherits);
 | |
|   }
 | |
|   return mInheritsPrincipal.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
 | |
|  *****************************************************************************/
 | |
| 
 | |
| #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);
 | |
| 
 | |
| #undef DEFINE_STATIC_ATOM_SET
 | |
| 
 | |
| 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
 |