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-13 21:46:57 +00:00
Родитель 5af90d1cbe
Коммит a525f604ba
9 изменённых файлов: 56 добавлений и 99 удалений

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -176,14 +176,14 @@ interface WebExtensionPolicy {
* URL root is listed as a web accessible path. Access checks on a path, such
* 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:
* 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.
*/
boolean sourceMayAccessPath(URI sourceURI, DOMString pathname);
boolean sourceMayAccessPath(URI sourceURI, UTF8String pathname);
/**
* Replaces localization placeholders in the given string with localized

Просмотреть файл

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

Просмотреть файл

@ -12,6 +12,7 @@
#include "jspubtd.h"
#include "js/RootingAPI.h"
#include "mozilla/RustRegex.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupports.h"
@ -25,18 +26,18 @@ class MatchPattern;
class MatchGlob final : public nsISupports, public nsWrapperCache {
public:
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,
const nsAString& aGlob,
const nsACString& aGlob,
bool aAllowQuestion,
ErrorResult& aRv);
bool Matches(const nsAString& aString) const;
bool Matches(const nsACString& aString) const;
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; }
@ -49,27 +50,25 @@ class MatchGlob final : public nsISupports, public nsWrapperCache {
private:
friend class MatchPattern;
explicit MatchGlob(nsISupports* aParent) : mParent(aParent) {}
void Init(JSContext* aCx, const nsAString& aGlob, bool aAllowQuestion,
ErrorResult& aRv);
explicit MatchGlob(nsISupports* aParent, const nsACString& aGlob,
bool aAllowQuestion, ErrorResult& aRv);
nsCOMPtr<nsISupports> mParent;
// 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
// value, the glob matches against this exact literal string, rather than
// 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
// be exactly equal to the matched string.
nsString mPathLiteral;
nsCString mPathLiteral;
bool mIsPrefix = false;
// The regular expression object which is equivalent to this glob pattern.
// Used for matching if, and only if, mPathLiteral is non-void.
JS::Heap<JSObject*> mRegExp;
RustRegex mRegExp;
};
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)
: CopyableTArray(aIL) {}
bool Matches(const nsAString& aValue) const;
bool Matches(const nsACString& aValue) const;
};
} // namespace extensions

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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