зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1402944: Part 5 - Move request filtering and permission matching into ChannelWrapper. r=mixedpuppy,ehsan
This allows us to reuse the same URLInfo objects for each permission or extension that we match, and also avoids a lot of XPConnect overhead we wind up incurring when we access URI objects from the JS side. MozReview-Commit-ID: GqgVRjQ3wYQ --HG-- extra : rebase_source : 71c19fd8b432c16a3f13f7d0bd0424064f3e5661
This commit is contained in:
Родитель
e3089ef89e
Коммит
89ae1721a0
|
@ -2077,6 +2077,7 @@ GK_ATOM(tabs, "tabs")
|
|||
GK_ATOM(webRequestBlocking, "webRequestBlocking")
|
||||
GK_ATOM(http, "http")
|
||||
GK_ATOM(https, "https")
|
||||
GK_ATOM(proxy, "proxy")
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Special atoms
|
||||
|
|
|
@ -113,15 +113,24 @@ interface ChannelWrapper : EventTarget {
|
|||
* The final URI of the channel (as returned by NS_GetFinalChannelURI) after
|
||||
* any redirects have been processed.
|
||||
*/
|
||||
[Cached, GetterThrows, Pure]
|
||||
[Cached, Pure]
|
||||
readonly attribute URI finalURI;
|
||||
|
||||
/**
|
||||
* The string version of finalURI (but cheaper to access than
|
||||
* finalURI.spec).
|
||||
*/
|
||||
[Cached, GetterThrows, Pure]
|
||||
readonly attribute ByteString finalURL;
|
||||
[Cached, Pure]
|
||||
readonly attribute DOMString finalURL;
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the request matches the given request filter, and the
|
||||
* given extension has permission to access it.
|
||||
*/
|
||||
boolean matches(optional MozRequestFilter filter,
|
||||
optional WebExtensionPolicy? extension = null,
|
||||
optional MozRequestMatchOptions options);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -372,3 +381,27 @@ dictionary MozFrameAncestorInfo {
|
|||
required ByteString url;
|
||||
required unsigned long long frameId;
|
||||
};
|
||||
|
||||
/**
|
||||
* An object used for filtering requests.
|
||||
*/
|
||||
dictionary MozRequestFilter {
|
||||
/**
|
||||
* If present, the request only matches if its `type` attribute matches one
|
||||
* of the given types.
|
||||
*/
|
||||
sequence<MozContentPolicyType>? types = null;
|
||||
|
||||
/**
|
||||
* If present, the request only matches if its finalURI matches the given
|
||||
* match pattern set.
|
||||
*/
|
||||
MatchPatternSet? urls = null;
|
||||
};
|
||||
|
||||
dictionary MozRequestMatchOptions {
|
||||
/**
|
||||
* True if we're matching for the proxy portion of a proxied request.
|
||||
*/
|
||||
boolean isProxy = false;
|
||||
};
|
||||
|
|
|
@ -158,14 +158,20 @@ URLInfo::Path() const
|
|||
return mPath;
|
||||
}
|
||||
|
||||
const nsCString&
|
||||
URLInfo::CSpec() const
|
||||
{
|
||||
if (mCSpec.IsEmpty()) {
|
||||
Unused << URINoRef()->GetSpec(mCSpec);
|
||||
}
|
||||
return mCSpec;
|
||||
}
|
||||
|
||||
const nsString&
|
||||
URLInfo::Spec() const
|
||||
{
|
||||
if (mSpec.IsEmpty()) {
|
||||
nsCString spec;
|
||||
if (NS_SUCCEEDED(URINoRef()->GetSpec(spec))) {
|
||||
AppendUTF8toUTF16(spec, mSpec);
|
||||
}
|
||||
AppendUTF8toUTF16(CSpec(), mSpec);
|
||||
}
|
||||
return mSpec;
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ private:
|
|||
// A helper class to lazily retrieve, transcode, and atomize certain URI
|
||||
// properties the first time they're used, and cache the results, so that they
|
||||
// can be used across multiple match operations.
|
||||
class MOZ_STACK_CLASS URLInfo final
|
||||
class URLInfo final
|
||||
{
|
||||
public:
|
||||
MOZ_IMPLICIT URLInfo(nsIURI* aURI)
|
||||
|
@ -139,6 +139,14 @@ public:
|
|||
mHost.SetIsVoid(true);
|
||||
}
|
||||
|
||||
URLInfo(nsIURI* aURI, bool aNoRef)
|
||||
: URLInfo(aURI)
|
||||
{
|
||||
if (aNoRef) {
|
||||
mURINoRef = mURI;
|
||||
}
|
||||
}
|
||||
|
||||
URLInfo(const URLInfo& aOther)
|
||||
: URLInfo(aOther.mURI.get())
|
||||
{}
|
||||
|
@ -150,6 +158,7 @@ public:
|
|||
const nsString& Path() const;
|
||||
const nsString& FilePath() const;
|
||||
const nsString& Spec() const;
|
||||
const nsCString& CSpec() const;
|
||||
|
||||
bool InheritsPrincipal() const;
|
||||
|
||||
|
@ -162,9 +171,10 @@ private:
|
|||
mutable RefPtr<nsIAtom> mScheme;
|
||||
mutable nsCString mHost;
|
||||
|
||||
mutable nsAutoString mPath;
|
||||
mutable nsAutoString mFilePath;
|
||||
mutable nsAutoString mSpec;
|
||||
mutable nsString mPath;
|
||||
mutable nsString mFilePath;
|
||||
mutable nsString mSpec;
|
||||
mutable nsCString mCSpec;
|
||||
|
||||
mutable Maybe<bool> mInheritsPrincipal;
|
||||
};
|
||||
|
|
|
@ -59,7 +59,7 @@ public:
|
|||
|
||||
Result<nsString, nsresult> GetURL(const nsAString& aPath) const;
|
||||
|
||||
bool CanAccessURI(nsIURI* aURI, bool aExplicit = false) const
|
||||
bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false) const
|
||||
{
|
||||
return mHostPermissions && mHostPermissions->Matches(aURI, aExplicit);
|
||||
}
|
||||
|
|
|
@ -17,32 +17,6 @@ function WebRequestEventManager(context, eventName) {
|
|||
let name = `webRequest.${eventName}`;
|
||||
let register = (fire, filter, info) => {
|
||||
let listener = data => {
|
||||
// data.isProxy is only set from the WebRequest AuthRequestor handler,
|
||||
// in which case we know that this is onAuthRequired. If this is proxy
|
||||
// authorization, we allow without any additional matching/filtering.
|
||||
let isProxyAuth = data.isProxy && context.extension.hasPermission("proxy");
|
||||
|
||||
// Prevent listening in on requests originating from system principal to
|
||||
// prevent tinkering with OCSP, app and addon updates, etc. However,
|
||||
// proxy addons need to be able to provide auth for any request so we
|
||||
// allow those through. The exception is for proxy extensions handling
|
||||
// proxy authentication.
|
||||
if (data.isSystemPrincipal && !isProxyAuth) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check hosts permissions for both the resource being requested,
|
||||
const hosts = context.extension.whiteListedHosts;
|
||||
if (!hosts.matches(data.URI)) {
|
||||
return;
|
||||
}
|
||||
// and the origin that is loading the resource.
|
||||
const origin = data.documentUrl;
|
||||
const own = origin && origin.startsWith(context.extension.getURL());
|
||||
if (origin && !own && !isProxyAuth && !hosts.matches(data.documentURI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let browserData = {tabId: -1, windowId: -1};
|
||||
if (data.browser) {
|
||||
browserData = tabTracker.getBrowserData(data.browser);
|
||||
|
@ -97,6 +71,7 @@ function WebRequestEventManager(context, eventName) {
|
|||
|
||||
let listenerDetails = {
|
||||
addonId: context.extension.id,
|
||||
extension: context.extension.policy,
|
||||
blockingAllowed,
|
||||
tabParent: context.xulBrowser.frameLoader.tabParent,
|
||||
};
|
||||
|
|
|
@ -82,14 +82,15 @@ ChannelWrapper::SetChannel(nsIChannel* aChannel)
|
|||
{
|
||||
detail::ChannelHolder::SetChannel(aChannel);
|
||||
ClearCachedAttributes();
|
||||
ChannelWrapperBinding::ClearCachedFinalURIValue(this);
|
||||
ChannelWrapperBinding::ClearCachedFinalURLValue(this);
|
||||
mFinalURLInfo.reset();
|
||||
ChannelWrapperBinding::ClearCachedProxyInfoValue(this);
|
||||
}
|
||||
|
||||
void
|
||||
ChannelWrapper::ClearCachedAttributes()
|
||||
{
|
||||
ChannelWrapperBinding::ClearCachedFinalURIValue(this);
|
||||
ChannelWrapperBinding::ClearCachedFinalURLValue(this);
|
||||
ChannelWrapperBinding::ClearCachedProxyInfoValue(this);
|
||||
ChannelWrapperBinding::ClearCachedRemoteAddressValue(this);
|
||||
ChannelWrapperBinding::ClearCachedStatusCodeValue(this);
|
||||
ChannelWrapperBinding::ClearCachedStatusLineValue(this);
|
||||
|
@ -370,7 +371,7 @@ ChannelWrapper::IsSystemLoad() const
|
|||
bool
|
||||
ChannelWrapper::GetCanModify(ErrorResult& aRv) const
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri = GetFinalURI(aRv);
|
||||
nsCOMPtr<nsIURI> uri = FinalURI();
|
||||
nsAutoCString spec;
|
||||
if (uri) {
|
||||
uri->GetSpec(spec);
|
||||
|
@ -440,6 +441,93 @@ ChannelWrapper::GetDocumentURL(nsCString& aRetVal) const
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const URLInfo&
|
||||
ChannelWrapper::FinalURLInfo() const
|
||||
{
|
||||
if (mFinalURLInfo.isNothing()) {
|
||||
ErrorResult rv;
|
||||
nsCOMPtr<nsIURI> uri = FinalURI();
|
||||
MOZ_ASSERT(uri);
|
||||
mFinalURLInfo.emplace(uri.get(), true);
|
||||
|
||||
// If this is a WebSocket request, mangle the URL so that the scheme is
|
||||
// ws: or wss:, as appropriate.
|
||||
auto& url = mFinalURLInfo.ref();
|
||||
if (Type() == MozContentPolicyType::Websocket &&
|
||||
(url.Scheme() == nsGkAtoms::http ||
|
||||
url.Scheme() == nsGkAtoms::https)) {
|
||||
nsAutoCString spec(url.CSpec());
|
||||
spec.Replace(0, 4, NS_LITERAL_CSTRING("ws"));
|
||||
|
||||
Unused << NS_NewURI(getter_AddRefs(uri), spec);
|
||||
MOZ_RELEASE_ASSERT(uri);
|
||||
mFinalURLInfo.reset();
|
||||
mFinalURLInfo.emplace(uri.get(), true);
|
||||
}
|
||||
}
|
||||
return mFinalURLInfo.ref();
|
||||
}
|
||||
|
||||
const URLInfo*
|
||||
ChannelWrapper::DocumentURLInfo() const
|
||||
{
|
||||
if (mDocumentURLInfo.isNothing()) {
|
||||
nsCOMPtr<nsIURI> uri = GetDocumentURI();
|
||||
if (!uri) {
|
||||
return nullptr;
|
||||
}
|
||||
mDocumentURLInfo.emplace(uri.get(), true);
|
||||
}
|
||||
return &mDocumentURLInfo.ref();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ChannelWrapper::Matches(const dom::MozRequestFilter& aFilter,
|
||||
const WebExtensionPolicy* aExtension,
|
||||
const dom::MozRequestMatchOptions& aOptions) const
|
||||
{
|
||||
if (!HaveChannel()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!aFilter.mTypes.IsNull() && !aFilter.mTypes.Value().Contains(Type())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& urlInfo = FinalURLInfo();
|
||||
if (aFilter.mUrls && !aFilter.mUrls->Matches(urlInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aExtension) {
|
||||
if (!aExtension->CanAccessURI(urlInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isProxy = aOptions.mIsProxy && aExtension->HasPermission(nsGkAtoms::proxy);
|
||||
if (!isProxy && IsSystemLoad()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto origin = DocumentURLInfo()) {
|
||||
nsAutoCString baseURL;
|
||||
aExtension->GetBaseURL(baseURL);
|
||||
|
||||
if (!isProxy &&
|
||||
!StringBeginsWith(origin->CSpec(), baseURL) &&
|
||||
!aExtension->CanAccessURI(*origin)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int64_t
|
||||
NormalizeWindowID(nsILoadInfo* aLoadInfo, uint64_t windowID)
|
||||
{
|
||||
|
@ -654,25 +742,20 @@ ChannelWrapper::GetStatusLine(nsCString& aRetVal) const
|
|||
*****************************************************************************/
|
||||
|
||||
already_AddRefed<nsIURI>
|
||||
ChannelWrapper::GetFinalURI(ErrorResult& aRv) const
|
||||
ChannelWrapper::FinalURI() const
|
||||
{
|
||||
nsresult rv = NS_ERROR_UNEXPECTED;
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
|
||||
rv = NS_GetFinalChannelURI(chan, getter_AddRefs(uri));
|
||||
}
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
NS_GetFinalChannelURI(chan, getter_AddRefs(uri));
|
||||
}
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
void
|
||||
ChannelWrapper::GetFinalURL(nsCString& aRetVal, ErrorResult& aRv) const
|
||||
ChannelWrapper::GetFinalURL(nsString& aRetVal) const
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri = GetFinalURI(aRv);
|
||||
if (uri) {
|
||||
Unused << uri->GetSpec(aRetVal);
|
||||
if (HaveChannel()) {
|
||||
aRetVal = FinalURLInfo().Spec();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/ChannelWrapperBinding.h"
|
||||
|
||||
#include "mozilla/extensions/MatchPattern.h"
|
||||
#include "mozilla/extensions/WebExtensionPolicy.h"
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
|
@ -145,9 +148,14 @@ public:
|
|||
IMPL_EVENT_HANDLER(stop);
|
||||
|
||||
|
||||
already_AddRefed<nsIURI> GetFinalURI(ErrorResult& aRv) const;
|
||||
already_AddRefed<nsIURI> FinalURI() const;
|
||||
|
||||
void GetFinalURL(nsCString& aRetVal, ErrorResult& aRv) const;
|
||||
void GetFinalURL(nsString& aRetVal) const;
|
||||
|
||||
|
||||
bool Matches(const dom::MozRequestFilter& aFilter,
|
||||
const WebExtensionPolicy* aExtension,
|
||||
const dom::MozRequestMatchOptions& aOptions) const;
|
||||
|
||||
|
||||
already_AddRefed<nsILoadInfo> GetLoadInfo() const
|
||||
|
@ -229,6 +237,11 @@ private:
|
|||
|
||||
void FireEvent(const nsAString& aType);
|
||||
|
||||
|
||||
const URLInfo& FinalURLInfo() const;
|
||||
const URLInfo* DocumentURLInfo() const;
|
||||
|
||||
|
||||
uint64_t WindowId(nsILoadInfo* aLoadInfo) const;
|
||||
|
||||
nsresult GetFrameAncestors(nsILoadInfo* aLoadInfo, nsTArray<dom::MozFrameAncestorInfo>& aFrameAncestors) const;
|
||||
|
|
|
@ -240,7 +240,6 @@ var ContentPolicyManager = {
|
|||
let response = null;
|
||||
let listenerKind = "onStop";
|
||||
let data = Object.assign({requestId, browser, serialize: serializeRequestData}, msg.data);
|
||||
data.URI = data.url;
|
||||
|
||||
delete data.ids;
|
||||
try {
|
||||
|
@ -263,15 +262,31 @@ var ContentPolicyManager = {
|
|||
return {};
|
||||
},
|
||||
|
||||
shouldRunListener(policyType, url, opts) {
|
||||
let {filter} = opts;
|
||||
|
||||
if (filter.types && !filter.types.includes(policyType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter.urls && !filter.urls.matches(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let {extension} = opts;
|
||||
if (extension && !extension.allowedOrigins.matches(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
runChannelListener(kind, data) {
|
||||
let listeners = HttpObserverManager.listeners[kind];
|
||||
let uri = Services.io.newURI(data.url);
|
||||
let policyType = data.type;
|
||||
for (let [callback, opts] of listeners.entries()) {
|
||||
if (!HttpObserverManager.shouldRunListener(policyType, uri, opts.filter)) {
|
||||
continue;
|
||||
if (this.shouldRunListener(data.type, data.url, opts)) {
|
||||
callback(data);
|
||||
}
|
||||
callback(data);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -661,24 +676,10 @@ HttpObserverManager = {
|
|||
}
|
||||
},
|
||||
|
||||
shouldRunListener(policyType, uri, filter) {
|
||||
// force the protocol to be ws again.
|
||||
if (policyType == "websocket" && ["http", "https"].includes(uri.scheme)) {
|
||||
uri = Services.io.newURI(`ws${uri.spec.substring(4)}`);
|
||||
}
|
||||
|
||||
if (filter.types && !filter.types.includes(policyType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !filter.urls || filter.urls.matches(uri);
|
||||
},
|
||||
|
||||
getRequestData(channel, extraData) {
|
||||
let data = {
|
||||
requestId: String(channel.id),
|
||||
url: channel.finalURL,
|
||||
URI: channel.finalURI,
|
||||
method: channel.method,
|
||||
browser: channel.browserElement,
|
||||
type: channel.type,
|
||||
|
@ -686,13 +687,12 @@ HttpObserverManager = {
|
|||
|
||||
originUrl: channel.originURL || undefined,
|
||||
documentUrl: channel.documentURL || undefined,
|
||||
originURI: channel.originURI,
|
||||
documentURI: channel.documentURI,
|
||||
isSystemPrincipal: channel.isSystemLoad,
|
||||
|
||||
windowId: channel.windowId,
|
||||
parentWindowId: channel.parentWindowId,
|
||||
|
||||
frameAncestors: channel.frameAncestors || undefined,
|
||||
|
||||
ip: channel.remoteAddress,
|
||||
|
||||
proxyInfo: channel.proxyInfo,
|
||||
|
@ -700,18 +700,6 @@ HttpObserverManager = {
|
|||
serialize: serializeRequestData,
|
||||
};
|
||||
|
||||
try {
|
||||
let {frameAncestors} = channel;
|
||||
if (frameAncestors !== null) {
|
||||
data.frameAncestors = frameAncestors;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// force the protocol to be ws again.
|
||||
if (data.type == "websocket" && data.url.startsWith("http")) {
|
||||
data.url = `ws${data.url.substring(4)}`;
|
||||
}
|
||||
|
||||
return Object.assign(data, extraData);
|
||||
},
|
||||
|
||||
|
@ -774,10 +762,9 @@ HttpObserverManager = {
|
|||
let registerFilter = ["opening", "modify", "afterModify", "headersReceived", "authRequired", "onRedirect"].includes(kind);
|
||||
|
||||
let commonData = null;
|
||||
let uri = channel.finalURI;
|
||||
let requestBody;
|
||||
for (let [callback, opts] of this.listeners[kind].entries()) {
|
||||
if (!this.shouldRunListener(channel.type, uri, opts.filter)) {
|
||||
if (!channel.matches(opts.filter, opts.extension, extraData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -902,13 +889,13 @@ HttpObserverManager = {
|
|||
}
|
||||
},
|
||||
|
||||
shouldHookListener(listener, channel) {
|
||||
shouldHookListener(listener, channel, extraData) {
|
||||
if (listener.size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let opts of listener.values()) {
|
||||
if (this.shouldRunListener(channel.type, channel.finalURI, opts.filter)) {
|
||||
if (channel.matches(opts.filter, opts.extension, extraData)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -920,7 +907,8 @@ HttpObserverManager = {
|
|||
this.runChannelListener(channel, "headersReceived");
|
||||
}
|
||||
|
||||
if (!channel.hasAuthRequestor && this.shouldHookListener(this.listeners.authRequired, channel)) {
|
||||
if (!channel.hasAuthRequestor &&
|
||||
this.shouldHookListener(this.listeners.authRequired, channel, {isProxy: true})) {
|
||||
channel.channel.notificationCallbacks = new AuthRequestor(channel.channel, this);
|
||||
channel.hasAuthRequestor = true;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
var { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
|
||||
|
||||
var {WebRequest} = Cu.import("resource://gre/modules/WebRequest.jsm", {});
|
||||
var {MatchPattern} = Cu.import("resource://gre/modules/MatchPattern.jsm", {});
|
||||
|
||||
const BASE = "http://example.com/browser/toolkit/modules/tests/browser";
|
||||
const URL = BASE + "/file_WebRequest_page2.html";
|
||||
|
@ -65,7 +64,7 @@ add_task(async function setup() {
|
|||
});
|
||||
|
||||
add_task(async function filter_urls() {
|
||||
let filter = {urls: new MatchPattern("*://*/*_style_*")};
|
||||
let filter = {urls: new MatchPatternSet(["*://*/*_style_*"])};
|
||||
|
||||
WebRequest.onBeforeRequest.addListener(onBeforeRequest, filter, ["blocking"]);
|
||||
WebRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, filter, ["blocking"]);
|
||||
|
|
Загрузка…
Ссылка в новой задаче