Files
tubestation/toolkit/modules/addons/WebRequestContent.js
Kris Maglione 7b017c97d6 Bug 1407056: Part 1 - Provide more consistent principal/origin URL to content policies. r=bz,ckerschb
We're currently fairly vague and inconsistent about the values we provide to
content policy implementations for requestOrigin and requestPrincipal. In some
cases they're the triggering principal, sometimes the loading principal,
sometimes the channel principal.

Our existing content policy implementations which require or expect a loading
principal currently retrieve it from the context node. Since no current
callers require the principal to be the loading principal, and some already
expect it to be the triggering principal (which there's currently no other way
to retrieve), I chose to pass the triggering principal whenever possible, but
use the loading principal to determine the origin URL.

As a follow-up, I'd like to change the nsIContentPolicy interface to
explicitly receive loading and triggering principals, or possibly just
LoadInfo instances, rather than poorly-defined request
origin/principal/context args. But since that may cause trouble for
comm-central, I'd rather not do it as part of this bug.

MozReview-Commit-ID: LqD9GxdzMte
2017-10-12 15:43:55 -07:00

213 lines
6.7 KiB
JavaScript

/* 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/. */
// Managed via the message managers.
/* globals MatchPatternSet, initialProcessData */
"use strict";
var Ci = Components.interfaces;
var Cc = Components.classes;
var Cu = Components.utils;
var Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequestCommon",
"resource://gre/modules/WebRequestCommon.jsm");
// Websockets will get handled via httpchannel notifications same as http
// requests, treat them the same as http in ContentPolicy.
const IS_HTTP = /^https?:|wss?:/;
var ContentPolicy = {
_classDescription: "WebRequest content policy",
_classID: Components.ID("938e5d24-9ccc-4b55-883e-c252a41f7ce9"),
_contractID: "@mozilla.org/webrequest/policy;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy,
Ci.nsIFactory,
Ci.nsISupportsWeakReference]),
init() {
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);
this.contentPolicies = new Map();
Services.cpmm.addMessageListener("WebRequest:AddContentPolicy", this);
Services.cpmm.addMessageListener("WebRequest:RemoveContentPolicy", this);
if (initialProcessData && initialProcessData.webRequestContentPolicies) {
for (let data of initialProcessData.webRequestContentPolicies.values()) {
this.addContentPolicy(data);
}
}
},
addContentPolicy({id, blocking, filter}) {
if (this.contentPolicies.size == 0) {
this.register();
}
if (filter.urls) {
filter.urls = new MatchPatternSet(filter.urls);
}
this.contentPolicies.set(id, {blocking, filter});
},
receiveMessage(msg) {
switch (msg.name) {
case "WebRequest:AddContentPolicy":
this.addContentPolicy(msg.data);
break;
case "WebRequest:RemoveContentPolicy":
this.contentPolicies.delete(msg.data.id);
if (this.contentPolicies.size == 0) {
this.unregister();
}
break;
}
},
register() {
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
},
unregister() {
let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
catMan.deleteCategoryEntry("content-policy", this._contractID, false);
},
shouldLoad(policyType, contentLocation, requestOrigin,
node, mimeTypeGuess, extra, requestPrincipal) {
// Loads of TYPE_DOCUMENT and TYPE_SUBDOCUMENT perform a ConPol check
// within docshell as well as within the ContentSecurityManager. To avoid
// duplicate evaluations we ignore ConPol checks performed within docShell.
if (extra instanceof Ci.nsISupportsString) {
if (extra.data === "conPolCheckFromDocShell") {
return Ci.nsIContentPolicy.ACCEPT;
}
}
if (requestPrincipal &&
Services.scriptSecurityManager.isSystemPrincipal(requestPrincipal)) {
return Ci.nsIContentPolicy.ACCEPT;
}
let url = contentLocation.spec;
if (IS_HTTP.test(url)) {
// We'll handle this in our parent process HTTP observer.
return Ci.nsIContentPolicy.ACCEPT;
}
let ids = [];
for (let [id, {filter}] of this.contentPolicies.entries()) {
if (WebRequestCommon.typeMatches(policyType, filter.types) &&
WebRequestCommon.urlMatches(contentLocation, filter.urls)) {
ids.push(id);
}
}
if (!ids.length) {
return Ci.nsIContentPolicy.ACCEPT;
}
let windowId = 0;
let parentWindowId = -1;
let frameAncestors = [];
let mm = Services.cpmm;
function getWindowId(window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
}
if (policyType == Ci.nsIContentPolicy.TYPE_SUBDOCUMENT ||
(node instanceof Ci.nsIDOMXULElement && node.localName == "browser")) {
// Chrome sets frameId to the ID of the sub-window. But when
// Firefox loads an iframe, it sets |node| to the <iframe>
// element, whose window is the parent window. We adopt the
// Chrome behavior here.
node = node.contentWindow;
}
if (node) {
let window;
if (node instanceof Ci.nsIDOMWindow) {
window = node;
} else {
let doc;
if (node.ownerDocument) {
doc = node.ownerDocument;
} else {
doc = node;
}
window = doc.defaultView;
}
windowId = getWindowId(window);
if (window.parent !== window) {
parentWindowId = getWindowId(window.parent);
for (let frame = window.parent; ; frame = frame.parent) {
frameAncestors.push({
url: frame.document.documentURIObject.spec,
frameId: getWindowId(frame),
});
if (frame === frame.parent) {
// Set the last frameId to zero for top level frame.
frameAncestors[frameAncestors.length - 1].frameId = 0;
break;
}
}
}
let ir = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor);
try {
// If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
mm = ir.getInterface(Ci.nsIContentFrameMessageManager);
} catch (e) {
if (e.result != Cr.NS_NOINTERFACE) {
throw e;
}
}
}
let data = {ids,
url,
type: WebRequestCommon.typeForPolicyType(policyType),
windowId,
parentWindowId};
if (frameAncestors.length > 0) {
data.frameAncestors = frameAncestors;
}
if (requestOrigin) {
data.documentUrl = requestOrigin.spec;
}
if (requestPrincipal && requestPrincipal.URI) {
data.originUrl = requestPrincipal.URI.spec;
}
mm.sendAsyncMessage("WebRequest:ShouldLoad", data);
return Ci.nsIContentPolicy.ACCEPT;
},
shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
return Ci.nsIContentPolicy.ACCEPT;
},
createInstance: function(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
};
ContentPolicy.init();