Bug 1693271 - Part 1: Use RustRegex for MatchGlob, r=kmag

This also involves making MatchGlob operate on UTF8String instead of DOMString,
as the rust `regex` crate operates on utf-8 strings. This should have no
functional impact on callers.

Differential Revision: https://phabricator.services.mozilla.com/D158877
This commit is contained in:
Nika Layzell
2022-10-12 15:39:52 +00:00
parent dfce9adfed
commit 9b90ca6ec6
9 changed files with 56 additions and 99 deletions

View File

@@ -11,16 +11,16 @@
[ChromeOnly, Exposed=Window] [ChromeOnly, Exposed=Window]
interface MatchGlob { interface MatchGlob {
[Throws] [Throws]
constructor(DOMString glob, optional boolean allowQuestion = true); constructor(UTF8String glob, optional boolean allowQuestion = true);
/** /**
* Returns true if the string matches the glob. * Returns true if the string matches the glob.
*/ */
boolean matches(DOMString string); boolean matches(UTF8String string);
/** /**
* The glob string this MatchGlob represents. * The glob string this MatchGlob represents.
*/ */
[Constant] [Constant]
readonly attribute DOMString glob; readonly attribute UTF8String glob;
}; };

View File

@@ -7,7 +7,7 @@ interface URI;
interface WindowProxy; interface WindowProxy;
typedef (MatchPatternSet or sequence<DOMString>) MatchPatternSetOrStringSequence; typedef (MatchPatternSet or sequence<DOMString>) MatchPatternSetOrStringSequence;
typedef (MatchGlob or DOMString) MatchGlobOrString; typedef (MatchGlob or UTF8String) MatchGlobOrString;
[ChromeOnly, Exposed=Window] [ChromeOnly, Exposed=Window]
interface MozDocumentMatcher { interface MozDocumentMatcher {

View File

@@ -176,14 +176,14 @@ interface WebExtensionPolicy {
* URL root is listed as a web accessible path. Access checks on a path, such * URL root is listed as a web accessible path. Access checks on a path, such
* as performed in nsScriptSecurityManager, use sourceMayAccessPath below. * as performed in nsScriptSecurityManager, use sourceMayAccessPath below.
*/ */
boolean isWebAccessiblePath(DOMString pathname); boolean isWebAccessiblePath(UTF8String pathname);
/** /**
* Returns true if the given path relative to the extension's moz-extension: * Returns true if the given path relative to the extension's moz-extension:
* URL root may be accessed by web content at sourceURI. For Manifest V2, * URL root may be accessed by web content at sourceURI. For Manifest V2,
* sourceURI is ignored and the path must merely be listed as web accessible. * sourceURI is ignored and the path must merely be listed as web accessible.
*/ */
boolean sourceMayAccessPath(URI sourceURI, DOMString pathname); boolean sourceMayAccessPath(URI sourceURI, UTF8String pathname);
/** /**
* Replaces localization placeholders in the given string with localized * Replaces localization placeholders in the given string with localized

View File

@@ -459,7 +459,7 @@ FilenameTypeAndDetails nsContentSecurityUtils::FilenameToFilenameType(
sanitizedPathAndScheme.Append(u"can't get addon off main thread]"_ns); sanitizedPathAndScheme.Append(u"can't get addon off main thread]"_ns);
} }
sanitizedPathAndScheme.Append(url.FilePath()); AppendUTF8toUTF16(url.FilePath(), sanitizedPathAndScheme);
return FilenameTypeAndDetails(kExtensionURI, Some(sanitizedPathAndScheme)); return FilenameTypeAndDetails(kExtensionURI, Some(sanitizedPathAndScheme));
} }

View File

@@ -12,6 +12,7 @@
#include "jspubtd.h" #include "jspubtd.h"
#include "js/RootingAPI.h" #include "js/RootingAPI.h"
#include "mozilla/RustRegex.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h" #include "nsCycleCollectionParticipant.h"
#include "nsISupports.h" #include "nsISupports.h"
@@ -25,18 +26,18 @@ class MatchPattern;
class MatchGlob final : public nsISupports, public nsWrapperCache { class MatchGlob final : public nsISupports, public nsWrapperCache {
public: public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MatchGlob) NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MatchGlob)
static already_AddRefed<MatchGlob> Constructor(dom::GlobalObject& aGlobal, static already_AddRefed<MatchGlob> Constructor(dom::GlobalObject& aGlobal,
const nsAString& aGlob, const nsACString& aGlob,
bool aAllowQuestion, bool aAllowQuestion,
ErrorResult& aRv); ErrorResult& aRv);
bool Matches(const nsAString& aString) const; bool Matches(const nsACString& aString) const;
bool IsWildcard() const { return mIsPrefix && mPathLiteral.IsEmpty(); } bool IsWildcard() const { return mIsPrefix && mPathLiteral.IsEmpty(); }
void GetGlob(nsAString& aGlob) const { aGlob = mGlob; } void GetGlob(nsACString& aGlob) const { aGlob = mGlob; }
nsISupports* GetParentObject() const { return mParent; } nsISupports* GetParentObject() const { return mParent; }
@@ -49,27 +50,25 @@ class MatchGlob final : public nsISupports, public nsWrapperCache {
private: private:
friend class MatchPattern; friend class MatchPattern;
explicit MatchGlob(nsISupports* aParent) : mParent(aParent) {} explicit MatchGlob(nsISupports* aParent, const nsACString& aGlob,
bool aAllowQuestion, ErrorResult& aRv);
void Init(JSContext* aCx, const nsAString& aGlob, bool aAllowQuestion,
ErrorResult& aRv);
nsCOMPtr<nsISupports> mParent; nsCOMPtr<nsISupports> mParent;
// The original glob string that this glob object represents. // The original glob string that this glob object represents.
nsString mGlob; const nsCString mGlob;
// The literal path string to match against. If this contains a non-void // The literal path string to match against. If this contains a non-void
// value, the glob matches against this exact literal string, rather than // value, the glob matches against this exact literal string, rather than
// performng a pattern match. If mIsPrefix is true, the literal must appear // performng a pattern match. If mIsPrefix is true, the literal must appear
// at the start of the matched string. If it is false, the the literal must // at the start of the matched string. If it is false, the the literal must
// be exactly equal to the matched string. // be exactly equal to the matched string.
nsString mPathLiteral; nsCString mPathLiteral;
bool mIsPrefix = false; bool mIsPrefix = false;
// The regular expression object which is equivalent to this glob pattern. // The regular expression object which is equivalent to this glob pattern.
// Used for matching if, and only if, mPathLiteral is non-void. // Used for matching if, and only if, mPathLiteral is non-void.
JS::Heap<JSObject*> mRegExp; RustRegex mRegExp;
}; };
class MatchGlobSet final : public CopyableTArray<RefPtr<MatchGlob>> { class MatchGlobSet final : public CopyableTArray<RefPtr<MatchGlob>> {
@@ -84,7 +83,7 @@ class MatchGlobSet final : public CopyableTArray<RefPtr<MatchGlob>> {
MOZ_IMPLICIT MatchGlobSet(std::initializer_list<RefPtr<MatchGlob>> aIL) MOZ_IMPLICIT MatchGlobSet(std::initializer_list<RefPtr<MatchGlob>> aIL)
: CopyableTArray(aIL) {} : CopyableTArray(aIL) {}
bool Matches(const nsAString& aValue) const; bool Matches(const nsACString& aValue) const;
}; };
} // namespace extensions } // namespace extensions

View File

@@ -123,24 +123,20 @@ const nsAtom* URLInfo::HostAtom() const {
return mHostAtom; return mHostAtom;
} }
const nsString& URLInfo::FilePath() const { const nsCString& URLInfo::FilePath() const {
if (mFilePath.IsEmpty()) { if (mFilePath.IsEmpty()) {
nsCString path;
nsCOMPtr<nsIURL> url = do_QueryInterface(mURI); nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
if (url && NS_SUCCEEDED(url->GetFilePath(path))) { if (!url || NS_FAILED(url->GetFilePath(mFilePath))) {
AppendUTF8toUTF16(path, mFilePath);
} else {
mFilePath = Path(); mFilePath = Path();
} }
} }
return mFilePath; return mFilePath;
} }
const nsString& URLInfo::Path() const { const nsCString& URLInfo::Path() const {
if (mPath.IsEmpty()) { if (mPath.IsEmpty()) {
nsCString path; if (NS_FAILED(URINoRef()->GetPathQueryRef(mPath))) {
if (NS_SUCCEEDED(URINoRef()->GetPathQueryRef(path))) { mPath.Truncate();
AppendUTF8toUTF16(path, mPath);
} }
} }
return mPath; return mPath;
@@ -359,14 +355,13 @@ void MatchPattern::Init(JSContext* aCx, const nsAString& aPattern,
return; return;
} }
auto path = tail; NS_ConvertUTF16toUTF8 path(tail);
if (path.IsEmpty()) { if (path.IsEmpty()) {
aRv.Throw(NS_ERROR_INVALID_ARG); aRv.Throw(NS_ERROR_INVALID_ARG);
return; return;
} }
mPath = new MatchGlob(this); mPath = new MatchGlob(this, path, false, aRv);
mPath->Init(aCx, path, false, aRv);
} }
bool MatchPattern::MatchesDomain(const nsACString& aDomain) const { bool MatchPattern::MatchesDomain(const nsACString& aDomain) const {
@@ -622,27 +617,26 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchPatternSet)
* MatchGlob * MatchGlob
*****************************************************************************/ *****************************************************************************/
MatchGlob::~MatchGlob() { mozilla::DropJSObjects(this); } MatchGlob::~MatchGlob() = default;
/* static */ /* static */
already_AddRefed<MatchGlob> MatchGlob::Constructor(dom::GlobalObject& aGlobal, already_AddRefed<MatchGlob> MatchGlob::Constructor(dom::GlobalObject& aGlobal,
const nsAString& aGlob, const nsACString& aGlob,
bool aAllowQuestion, bool aAllowQuestion,
ErrorResult& aRv) { ErrorResult& aRv) {
RefPtr<MatchGlob> glob = new MatchGlob(aGlobal.GetAsSupports()); RefPtr<MatchGlob> glob =
glob->Init(aGlobal.Context(), aGlob, aAllowQuestion, aRv); new MatchGlob(aGlobal.GetAsSupports(), aGlob, aAllowQuestion, aRv);
if (aRv.Failed()) { if (aRv.Failed()) {
return nullptr; return nullptr;
} }
return glob.forget(); return glob.forget();
} }
void MatchGlob::Init(JSContext* aCx, const nsAString& aGlob, MatchGlob::MatchGlob(nsISupports* aParent, const nsACString& aGlob,
bool aAllowQuestion, ErrorResult& aRv) { bool aAllowQuestion, ErrorResult& aRv)
mGlob = aGlob; : mParent(aParent), mGlob(aGlob) {
// Check for a literal match with no glob metacharacters. // Check for a literal match with no glob metacharacters.
auto index = mGlob.FindCharInSet(aAllowQuestion ? u"*?" : u"*"); auto index = mGlob.FindCharInSet(aAllowQuestion ? "*?" : "*");
if (index < 0) { if (index < 0) {
mPathLiteral = mGlob; mPathLiteral = mGlob;
return; return;
@@ -659,7 +653,7 @@ void MatchGlob::Init(JSContext* aCx, const nsAString& aGlob,
// Fall back to the regexp slow path. // Fall back to the regexp slow path.
constexpr auto metaChars = ".+*?^${}()|[]\\"_ns; constexpr auto metaChars = ".+*?^${}()|[]\\"_ns;
nsAutoString escaped; nsAutoCString escaped;
escaped.Append('^'); escaped.Append('^');
// For any continuous string of * (and ? if aAllowQuestion) wildcards, only // For any continuous string of * (and ? if aAllowQuestion) wildcards, only
@@ -688,37 +682,15 @@ void MatchGlob::Init(JSContext* aCx, const nsAString& aGlob,
escaped.Append('$'); escaped.Append('$');
// TODO: Switch to the Rust regexp crate, when Rust integration is easier. mRegExp = RustRegex(escaped);
// It uses a much more efficient, linear time matching algorithm, and if (!mRegExp) {
// doesn't require special casing for the literal and prefix cases. aRv.ThrowTypeError("failed to compile regex for glob");
mRegExp = JS::NewUCRegExpObject(aCx, escaped.get(), escaped.Length(), 0);
if (mRegExp) {
mozilla::HoldJSObjects(this);
} else {
aRv.NoteJSContextException(aCx);
} }
} }
bool MatchGlob::Matches(const nsAString& aString) const { bool MatchGlob::Matches(const nsACString& aString) const {
if (mRegExp) { if (mRegExp) {
AutoJSAPI jsapi; return mRegExp.IsMatch(aString);
jsapi.Init();
JSContext* cx = jsapi.cx();
JSAutoRealm ar(cx, mRegExp);
JS::Rooted<JSObject*> regexp(cx, mRegExp);
JS::Rooted<JS::Value> result(cx);
nsString input(aString);
size_t index = 0;
if (!JS::ExecuteRegExpNoStatics(cx, regexp, input.BeginWriting(),
aString.Length(), &index, true, &result)) {
return false;
}
return result.isBoolean() && result.toBoolean();
} }
if (mIsPrefix) { if (mIsPrefix) {
@@ -733,22 +705,7 @@ JSObject* MatchGlob::WrapObject(JSContext* aCx,
return MatchGlob_Binding::Wrap(aCx, this, aGivenProto); return MatchGlob_Binding::Wrap(aCx, this, aGivenProto);
} }
NS_IMPL_CYCLE_COLLECTION_CLASS(MatchGlob) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchGlob, mParent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MatchGlob)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
tmp->mRegExp = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MatchGlob)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MatchGlob)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRegExp)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchGlob) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchGlob)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@@ -762,7 +719,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchGlob)
* MatchGlobSet * MatchGlobSet
*****************************************************************************/ *****************************************************************************/
bool MatchGlobSet::Matches(const nsAString& aValue) const { bool MatchGlobSet::Matches(const nsACString& aValue) const {
for (auto& glob : *this) { for (auto& glob : *this) {
if (glob->Matches(aValue)) { if (glob->Matches(aValue)) {
return true; return true;

View File

@@ -143,8 +143,8 @@ class URLInfo final {
nsAtom* Scheme() const; nsAtom* Scheme() const;
const nsCString& Host() const; const nsCString& Host() const;
const nsAtom* HostAtom() const; const nsAtom* HostAtom() const;
const nsString& Path() const; const nsCString& Path() const;
const nsString& FilePath() const; const nsCString& FilePath() const;
const nsString& Spec() const; const nsString& Spec() const;
const nsCString& CSpec() const; const nsCString& CSpec() const;
@@ -160,8 +160,8 @@ class URLInfo final {
mutable nsCString mHost; mutable nsCString mHost;
mutable RefPtr<nsAtom> mHostAtom; mutable RefPtr<nsAtom> mHostAtom;
mutable nsString mPath; mutable nsCString mPath;
mutable nsString mFilePath; mutable nsCString mFilePath;
mutable nsString mSpec; mutable nsString mSpec;
mutable nsCString mCSpec; mutable nsCString mCSpec;

View File

@@ -79,14 +79,15 @@ static nsISubstitutingProtocolHandler* Proto() {
return sHandler; return sHandler;
} }
bool ParseGlobs(GlobalObject& aGlobal, Sequence<OwningMatchGlobOrString> aGlobs, bool ParseGlobs(GlobalObject& aGlobal,
Sequence<OwningMatchGlobOrUTF8String> aGlobs,
nsTArray<RefPtr<MatchGlob>>& aResult, ErrorResult& aRv) { nsTArray<RefPtr<MatchGlob>>& aResult, ErrorResult& aRv) {
for (auto& elem : aGlobs) { for (auto& elem : aGlobs) {
if (elem.IsMatchGlob()) { if (elem.IsMatchGlob()) {
aResult.AppendElement(elem.GetAsMatchGlob()); aResult.AppendElement(elem.GetAsMatchGlob());
} else { } else {
RefPtr<MatchGlob> glob = RefPtr<MatchGlob> glob =
MatchGlob::Constructor(aGlobal, elem.GetAsString(), true, aRv); MatchGlob::Constructor(aGlobal, elem.GetAsUTF8String(), true, aRv);
if (aRv.Failed()) { if (aRv.Failed()) {
return false; return false;
} }
@@ -441,7 +442,7 @@ bool WebExtensionPolicy::BackgroundServiceWorkerEnabled(GlobalObject& aGlobal) {
} }
bool WebExtensionPolicy::SourceMayAccessPath(const URLInfo& aURI, bool WebExtensionPolicy::SourceMayAccessPath(const URLInfo& aURI,
const nsAString& aPath) const { const nsACString& aPath) const {
if (aURI.Scheme() == nsGkAtoms::moz_extension && if (aURI.Scheme() == nsGkAtoms::moz_extension &&
mHostname.Equals(aURI.Host())) { mHostname.Equals(aURI.Host())) {
// An extension can always access it's own paths. // An extension can always access it's own paths.
@@ -824,11 +825,11 @@ bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL,
return false; return false;
} }
if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) { if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.CSpec())) {
return false; return false;
} }
if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) { if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.CSpec())) {
return false; return false;
} }

View File

@@ -46,11 +46,11 @@ class WebAccessibleResource final : public nsISupports {
const WebAccessibleResourceInit& aInit, const WebAccessibleResourceInit& aInit,
ErrorResult& aRv); ErrorResult& aRv);
bool IsWebAccessiblePath(const nsAString& aPath) const { bool IsWebAccessiblePath(const nsACString& aPath) const {
return mWebAccessiblePaths.Matches(aPath); return mWebAccessiblePaths.Matches(aPath);
} }
bool SourceMayAccessPath(const URLInfo& aURI, const nsAString& aPath) { bool SourceMayAccessPath(const URLInfo& aURI, const nsACString& aPath) {
return mWebAccessiblePaths.Matches(aPath) && return mWebAccessiblePaths.Matches(aPath) &&
(IsHostMatch(aURI) || IsExtensionMatch(aURI)); (IsHostMatch(aURI) || IsExtensionMatch(aURI));
} }
@@ -115,7 +115,7 @@ class WebExtensionPolicy final : public nsISupports,
bool aCheckRestricted = true, bool aCheckRestricted = true,
bool aAllowFilePermission = false) const; bool aAllowFilePermission = false) const;
bool IsWebAccessiblePath(const nsAString& aPath) const { bool IsWebAccessiblePath(const nsACString& aPath) const {
for (const auto& resource : mWebAccessibleResources) { for (const auto& resource : mWebAccessibleResources) {
if (resource->IsWebAccessiblePath(aPath)) { if (resource->IsWebAccessiblePath(aPath)) {
return true; return true;
@@ -124,7 +124,7 @@ class WebExtensionPolicy final : public nsISupports,
return false; return false;
} }
bool SourceMayAccessPath(const URLInfo& aURI, const nsAString& aPath) const; bool SourceMayAccessPath(const URLInfo& aURI, const nsACString& aPath) const;
bool HasPermission(const nsAtom* aPermission) const { bool HasPermission(const nsAtom* aPermission) const {
return mPermissions->Contains(aPermission); return mPermissions->Contains(aPermission);