Bug 770099 - Send CSP policy and report information to Web Console (Part 1); r=dveditz,msucan,jwalker

This commit is contained in:
Mark Goodwin 2012-10-01 10:10:29 +01:00
parent 2561b4b1e6
commit 344f390f55
3 changed files with 277 additions and 177 deletions

View File

@ -11,6 +11,7 @@
*/
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -20,17 +21,16 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
// Module stuff
var EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", "CSPHost",
"CSPWarning", "CSPError", "CSPdebug",
"CSPViolationReportListener", "CSPLocalizer"];
"CSPdebug", "CSPViolationReportListener", "CSPLocalizer"];
var STRINGS_URI = "chrome://global/locale/security/csp.properties";
// these are not exported
var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
.getService(Ci.nsIIOService);
var gETLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
.getService(Components.interfaces.nsIEffectiveTLDService);
.getService(Ci.nsIEffectiveTLDService);
// These regexps represent the concrete syntax on the w3 spec as of 7-5-2012
// scheme = <scheme production from RFC 3986>
@ -76,58 +76,30 @@ var gPrefObserver = {
_initialize: function() {
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
.getService(Ci.nsIPrefService);
this._branch = prefSvc.getBranch("security.csp.");
this._branch.addObserver("", this, false);
this._debugEnabled = this._branch.getBoolPref("debug");
},
unregister: function() {
if(!this._branch) return;
if (!this._branch) return;
this._branch.removeObserver("", this);
},
observe: function(aSubject, aTopic, aData) {
if(aTopic != "nsPref:changed") return;
if(aData === "debug")
if (aTopic != "nsPref:changed") return;
if (aData === "debug")
this._debugEnabled = this._branch.getBoolPref("debug");
},
};
function CSPWarning(aMsg, aWindowID, aSource, aScriptSample, aLineNum) {
var textMessage = 'CSP WARN: ' + aMsg + "\n";
var consoleMsg = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Components.interfaces.nsIScriptError);
consoleMsg.initWithWindowID(textMessage, aSource, aScriptSample, aLineNum, 0,
Components.interfaces.nsIScriptError.warningFlag,
"Content Security Policy", aWindowID);
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService)
.logMessage(consoleMsg);
}
function CSPError(aMsg, aWindowID) {
var textMessage = 'CSP ERROR: ' + aMsg + "\n";
var consoleMsg = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Components.interfaces.nsIScriptError);
consoleMsg.initWithWindowID(textMessage, null, null, 0, 0,
Components.interfaces.nsIScriptError.errorFlag,
"Content Security Policy", aWindowID);
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService)
.logMessage(consoleMsg);
}
function CSPdebug(aMsg) {
if (!gPrefObserver.debugEnabled) return;
aMsg = 'CSP debug: ' + aMsg + "\n";
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService)
.getService(Ci.nsIConsoleService)
.logStringMessage(aMsg);
}
@ -138,16 +110,16 @@ function CSPPolicyURIListener(policyURI, docRequest, csp) {
this._csp = csp; // parent document's CSP
this._policy = ""; // contents fetched from policyURI
this._wrapper = null; // nsIScriptableInputStream
this._docURI = docRequest.QueryInterface(Components.interfaces.nsIChannel)
this._docURI = docRequest.QueryInterface(Ci.nsIChannel)
.URI; // parent document URI (to be used as 'self')
}
CSPPolicyURIListener.prototype = {
QueryInterface: function(iid) {
if (iid.equals(Components.interfaces.nsIStreamListener) ||
iid.equals(Components.interfaces.nsIRequestObserver) ||
iid.equals(Components.interfaces.nsISupports))
if (iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsIRequestObserver) ||
iid.equals(Ci.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
@ -159,7 +131,7 @@ CSPPolicyURIListener.prototype = {
function(request, context, inputStream, offset, count) {
if (this._wrapper == null) {
this._wrapper = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
.createInstance(Ci.nsIScriptableInputStream);
this._wrapper.init(inputStream);
}
// store the remote policy as it becomes available
@ -241,9 +213,10 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
var UD = CSPRep.URI_DIRECTIVES;
var aCSPR = new CSPRep();
aCSPR._originalText = aStr;
aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
var selfUri = null;
if (self instanceof Components.interfaces.nsIURI)
if (self instanceof Ci.nsIURI)
selfUri = self.clone();
var dirs = aStr.split(";");
@ -258,7 +231,8 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
if (aCSPR._directives.hasOwnProperty(dirname)) {
// Check for (most) duplicate directives
CSPError(CSPLocalizer.getFormatStr("duplicateDirective", [dirname]));
cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
[dirname]));
CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
continue directive;
}
@ -267,7 +241,8 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
if (dirname === CSPRep.OPTIONS_DIRECTIVE) {
if (aCSPR._allowInlineScripts || aCSPR._allowEval) {
// Check for duplicate options directives
CSPError(CSPLocalizer.getFormatStr("duplicateDirective", [dirname]));
cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
[dirname]));
CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
continue directive;
}
@ -280,7 +255,8 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
else if (opt === "eval-script")
aCSPR._allowEval = true;
else
CSPWarning(CSPLocalizer.getFormatStr("doNotUnderstandOption", [opt]));
cspWarn(aCSPR, CSPLocalizer.getFormatStr("doNotUnderstandOption",
[opt]));
}
continue directive;
}
@ -289,14 +265,15 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
// parse "allow" as equivalent to "default-src", at least until the spec
// stabilizes, at which time we can stop parsing "allow"
if (dirname === CSPRep.ALLOW_DIRECTIVE) {
CSPWarning(CSPLocalizer.getStr("allowDirectiveDeprecated"));
cspWarn(aCSPR, CSPLocalizer.getStr("allowDirectiveDeprecated"));
if (aCSPR._directives.hasOwnProperty(SD.DEFAULT_SRC)) {
// Check for duplicate default-src and allow directives
CSPError(CSPLocalizer.getFormatStr("duplicateDirective", [dirname]));
cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
[dirname]));
CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
continue directive;
}
var dv = CSPSourceList.fromString(dirvalue, self, true);
var dv = CSPSourceList.fromString(dirvalue, aCSPR, self, true);
if (dv) {
aCSPR._directives[SD.DEFAULT_SRC] = dv;
continue directive;
@ -307,7 +284,7 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
for each(var sdi in SD) {
if (dirname === sdi) {
// process dirs, and enforce that 'self' is defined.
var dv = CSPSourceList.fromString(dirvalue, self, true);
var dv = CSPSourceList.fromString(dirvalue, aCSPR, self, true);
if (dv) {
aCSPR._directives[sdi] = dv;
continue directive;
@ -340,18 +317,19 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
if (self) {
if (gETLDService.getBaseDomain(uri) !==
gETLDService.getBaseDomain(selfUri)) {
CSPWarning(CSPLocalizer.getFormatStr("notETLDPlus1",
[gETLDService.getBaseDomain(uri)]));
cspWarn(aCSPR,
CSPLocalizer.getFormatStr("notETLDPlus1",
[gETLDService.getBaseDomain(uri)]));
continue;
}
if (!uri.schemeIs(selfUri.scheme)) {
CSPWarning(CSPLocalizer.getFormatStr("notSameScheme",
[uri.asciiSpec]));
cspWarn(aCSPR, CSPLocalizer.getFormatStr("notSameScheme",
[uri.asciiSpec]));
continue;
}
if (uri.port && uri.port !== selfUri.port) {
CSPWarning(CSPLocalizer.getFormatStr("notSamePort",
[uri.asciiSpec]));
cspWarn(aCSPR, CSPLocalizer.getFormatStr("notSamePort",
[uri.asciiSpec]));
continue;
}
}
@ -360,15 +338,16 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
case Components.results.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS:
case Components.results.NS_ERROR_HOST_IS_IP_ADDRESS:
if (uri.host !== selfUri.host) {
CSPWarning(CSPLocalizer.getFormatStr("pageCannotSendReportsTo",
[selfUri.host, uri.host]));
cspWarn(aCSPR,
CSPLocalizer.getFormatStr("pageCannotSendReportsTo",
[selfUri.host, uri.host]));
continue;
}
break;
default:
CSPWarning(CSPLocalizer.getFormatStr("couldNotParseReportURI",
[uriStrings[i]]));
cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotParseReportURI",
[uriStrings[i]]));
continue;
}
}
@ -383,13 +362,13 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
if (dirname === UD.POLICY_URI) {
// POLICY_URI can only be alone
if (aCSPR._directives.length > 0 || dirs.length > 1) {
CSPError(CSPLocalizer.getStr("policyURINotAlone"));
cspError(aCSPR, CSPLocalizer.getStr("policyURINotAlone"));
return CSPRep.fromString("default-src 'none'");
}
// if we were called without a reference to the parent document request
// we won't be able to suspend it while we fetch the policy -> fail closed
if (!docRequest || !csp) {
CSPError(CSPLocalizer.getStr("noParentRequest"));
cspError(aCSPR, CSPLocalizer.getStr("noParentRequest"));
return CSPRep.fromString("default-src 'none'");
}
@ -397,22 +376,26 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
try {
uri = gIoService.newURI(dirvalue, null, selfUri);
} catch(e) {
CSPError(CSPLocalizer.getFormatStr("policyURIParseError", [dirvalue]));
cspError(aCSPR, CSPLocalizer.getFormatStr("policyURIParseError",
[dirvalue]));
return CSPRep.fromString("default-src 'none'");
}
// Verify that policy URI comes from the same origin
if (selfUri) {
if (selfUri.host !== uri.host){
CSPError(CSPLocalizer.getFormatStr("nonMatchingHost", [uri.host]));
if (selfUri.host !== uri.host) {
cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingHost",
[uri.host]));
return CSPRep.fromString("default-src 'none'");
}
if (selfUri.port !== uri.port){
CSPError(CSPLocalizer.getFormatStr("nonMatchingPort", [uri.port.toString()]));
if (selfUri.port !== uri.port) {
cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingPort",
[uri.port.toString()]));
return CSPRep.fromString("default-src 'none'");
}
if (selfUri.scheme !== uri.scheme){
CSPError(CSPLocalizer.getFormatStr("nonMatchingScheme", [uri.scheme]));
if (selfUri.scheme !== uri.scheme) {
cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingScheme",
[uri.scheme]));
return CSPRep.fromString("default-src 'none'");
}
}
@ -423,13 +406,14 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
var chan = gIoService.newChannel(uri.asciiSpec, null, null);
// make request anonymous (no cookies, etc.) so the request for the
// policy-uri can't be abused for CSRF
chan.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS;
chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp), null);
}
catch (e) {
// resume the document request and apply most restrictive policy
docRequest.resume();
CSPError(CSPLocalizer.getFormatStr("errorFetchingPolicy", [e.toString()]));
cspError(aCSPR, CSPLocalizer.getFormatStr("errorFetchingPolicy",
[e.toString()]));
return CSPRep.fromString("default-src 'none'");
}
@ -439,7 +423,8 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) {
}
// UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
CSPWarning(CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective", [dirname]));
cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective",
[dirname]));
} // end directive: loop
@ -511,7 +496,7 @@ CSPRep.prototype = {
// GLOBALLY ALLOW "about:" SCHEME
if (aURI instanceof String && aURI.substring(0,6) === "about:")
return true;
if (aURI instanceof Components.interfaces.nsIURI && aURI.scheme === "about")
if (aURI instanceof Ci.nsIURI && aURI.scheme === "about")
return true;
// make sure the context is valid
@ -563,6 +548,9 @@ CSPRep.prototype = {
newRep._allowInlineScripts = this.allowsInlineScripts
&& aCSPRep.allowsInlineScripts;
newRep._innerWindowID = this._innerWindowID ?
this._innerWindowID : aCSPRep._innerWindowID;
return newRep;
},
@ -578,7 +566,7 @@ CSPRep.prototype = {
var SD = CSPRep.SRC_DIRECTIVES;
var defaultSrcDir = this._directives[SD.DEFAULT_SRC];
if (!defaultSrcDir) {
CSPWarning(CSPLocalizer.getStr("allowOrDefaultSrcRequired"));
this.warn(CSPLocalizer.getStr("allowOrDefaultSrcRequired"));
return false;
}
@ -589,7 +577,7 @@ CSPRep.prototype = {
// implicit directive, make explicit.
// All but frame-ancestors directive inherit from 'allow' (bug 555068)
if (dirv === SD.FRAME_ANCESTORS)
this._directives[dirv] = CSPSourceList.fromString("*");
this._directives[dirv] = CSPSourceList.fromString("*",this);
else
this._directives[dirv] = defaultSrcDir.clone();
this._directives[dirv]._isImplicit = true;
@ -613,6 +601,62 @@ CSPRep.prototype = {
get allowsInlineScripts () {
return this._allowInlineScripts;
},
/**
* Sends a warning message to the error console and web developer console.
* @param aMsg
* The message to send
* @param aSource (optional)
* The URL of the file in which the error occurred
* @param aScriptLine (optional)
* The line in the source file which the error occurred
* @param aLineNum (optional)
* The number of the line where the error occurred
*/
warn:
function cspd_warn(aMsg, aSource, aScriptLine, aLineNum) {
var textMessage = 'CSP WARN: ' + aMsg + "\n";
var consoleMsg = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Ci.nsIScriptError);
if (this._innerWindowID) {
consoleMsg.initWithWindowID(textMessage, aSource, aScriptLine, aLineNum,
0, Ci.nsIScriptError.warningFlag,
"Content Security Policy",
this._innerWindowID);
} else {
consoleMsg.init(textMessage, aSource, aScriptLine, aLineNum, 0,
Ci.nsIScriptError.warningFlag,
"Content Security Policy");
}
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService).logMessage(consoleMsg);
},
/**
* Sends an error message to the error console and web developer console.
* @param aMsg
* The message to send
*/
error:
function cspsd_error(aMsg) {
var textMessage = 'CSP ERROR: ' + aMsg + "\n";
var consoleMsg = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Ci.nsIScriptError);
if (this._innerWindowID) {
consoleMsg.initWithWindowID(textMessage, null, null, 0, 0,
Ci.nsIScriptError.errorFlag,
"Content Security Policy",
this._innerWindowID);
}
else {
consoleMsg.init(textMessage, null, null, 0, 0,
Ci.nsIScriptError.errorFlag, "Content Security Policy");
}
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService).logMessage(consoleMsg);
},
};
//////////////////////////////////////////////////////////////////////
@ -633,6 +677,9 @@ function CSPSourceList() {
*
* @param aStr
* string rep of a CSP Source List
* @param aCSPRep
* the CSPRep to which this souce list belongs. If null, CSP errors and
* warnings will not be sent to the web console.
* @param self (optional)
* URI or CSPSource representing the "self" source
* @param enforceSelfChecks (optional)
@ -641,39 +688,42 @@ function CSPSourceList() {
* @returns
* an instance of CSPSourceList
*/
CSPSourceList.fromString = function(aStr, self, enforceSelfChecks) {
CSPSourceList.fromString = function(aStr, aCSPRep, self, enforceSelfChecks) {
// source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
// / *WSP "'none'" *WSP
/* If self parameter is passed, convert to CSPSource,
unless it is already a CSPSource. */
if(self && !(self instanceof CSPSource)) {
self = CSPSource.create(self);
if (self && !(self instanceof CSPSource)) {
self = CSPSource.create(self, aCSPRep);
}
var slObj = new CSPSourceList();
slObj._CSPRep = aCSPRep;
aStr = aStr.trim();
// w3 specifies case insensitive equality
if (aStr.toUpperCase() === "'NONE'"){
if (aStr.toUpperCase() === "'NONE'") {
slObj._permitAllSources = false;
return slObj;
}
var tokens = aStr.split(/\s+/);
for (var i in tokens) {
if (!R_SOURCEEXP.test(tokens[i])){
CSPWarning(CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource",
[tokens[i]]));
if (!R_SOURCEEXP.test(tokens[i])) {
cspWarn(aCSPRep,
CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource",
[tokens[i]]));
continue;
}
var src = CSPSource.create(tokens[i], self, enforceSelfChecks);
var src = CSPSource.create(tokens[i], aCSPRep, self, enforceSelfChecks);
if (!src) {
CSPWarning(CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource",
[tokens[i]]));
cspWarn(aCSPRep,
CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource",
[tokens[i]]));
continue;
}
// if a source is a *, then we can permit all sources
if (src.permitAll){
if (src.permitAll) {
slObj._permitAllSources = true;
return slObj;
} else {
@ -758,6 +808,7 @@ CSPSourceList.prototype = {
function() {
var aSL = new CSPSourceList();
aSL._permitAllSources = this._permitAllSources;
aSL._CSPRep = this._CSPRep;
for (var i in this._sources) {
aSL._sources[i] = this._sources[i].clone();
}
@ -800,7 +851,7 @@ CSPSourceList.prototype = {
if (!that) return this.clone();
if (this.isNone() || that.isNone())
newCSPSrcList = CSPSourceList.fromString("'none'");
newCSPSrcList = CSPSourceList.fromString("'none'", this._CSPRep);
if (this.isAll()) newCSPSrcList = that.clone();
if (that.isAll()) newCSPSrcList = this.clone();
@ -837,6 +888,9 @@ CSPSourceList.prototype = {
// if either was explicit, so is this.
newCSPSrcList._isImplicit = this._isImplicit && that._isImplicit;
if ((!newCSPSrcList._CSPRep) && that._CSPRep) {
newCSPSrcList._CSPRep = that._CSPRep;
}
return newCSPSrcList;
}
}
@ -865,6 +919,9 @@ function CSPSource() {
* - CSPSource (clone)
* @param aData
* string, nsURI, or CSPSource
* @param aCSPRep
* The CSPRep this source belongs to. If null, CSP errors and warnings
* will not be sent to the web console.
* @param self (optional)
* if present, string, URI, or CSPSource representing the "self" resource
* @param enforceSelfChecks (optional)
@ -873,12 +930,12 @@ function CSPSource() {
* @returns
* an instance of CSPSource
*/
CSPSource.create = function(aData, self, enforceSelfChecks) {
CSPSource.create = function(aData, aCSPRep, self, enforceSelfChecks) {
if (typeof aData === 'string')
return CSPSource.fromString(aData, self, enforceSelfChecks);
return CSPSource.fromString(aData, aCSPRep, self, enforceSelfChecks);
if (aData instanceof Components.interfaces.nsIURI)
return CSPSource.fromURI(aData, self, enforceSelfChecks);
if (aData instanceof Ci.nsIURI)
return CSPSource.fromURI(aData, aCSPRep, self, enforceSelfChecks);
if (aData instanceof CSPSource) {
var ns = aData.clone();
@ -896,6 +953,9 @@ CSPSource.create = function(aData, self, enforceSelfChecks) {
*
* @param aURI
* nsIURI rep of a URI
* @param aCSPRep
* The policy this source belongs to. If null, CSP errors and warnings
* will not be sent to the web console.
* @param self (optional)
* string or CSPSource representing the "self" source
* @param enforceSelfChecks (optional)
@ -904,23 +964,24 @@ CSPSource.create = function(aData, self, enforceSelfChecks) {
* @returns
* an instance of CSPSource
*/
CSPSource.fromURI = function(aURI, self, enforceSelfChecks) {
if (!(aURI instanceof Components.interfaces.nsIURI)){
CSPError(CSPLocalizer.getStr("cspSourceNotURI"));
CSPSource.fromURI = function(aURI, aCSPRep, self, enforceSelfChecks) {
if (!(aURI instanceof Ci.nsIURI)) {
cspError(aCSPRep, CSPLocalizer.getStr("cspSourceNotURI"));
return null;
}
if (!self && enforceSelfChecks) {
CSPError(CSPLocalizer.getStr("selfDataNotProvided"));
cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided"));
return null;
}
if (self && !(self instanceof CSPSource)) {
self = CSPSource.create(self, undefined, false);
self = CSPSource.create(self, aCSPRep, undefined, false);
}
var sObj = new CSPSource();
sObj._self = self;
sObj._CSPRep = aCSPRep;
// PARSE
// If 'self' is undefined, then use default port for scheme if there is one.
@ -930,7 +991,8 @@ CSPSource.fromURI = function(aURI, self, enforceSelfChecks) {
sObj._scheme = aURI.scheme;
} catch(e) {
sObj._scheme = undefined;
CSPError(CSPLocalizer.getFormatStr("uriWithoutScheme", [aURI.asciiSpec]));
cspError(aCSPRep, CSPLocalizer.getFormatStr("uriWithoutScheme",
[aURI.asciiSpec]));
return null;
}
@ -973,6 +1035,8 @@ CSPSource.fromURI = function(aURI, self, enforceSelfChecks) {
*
* @param aStr
* string rep of a CSP Source
* @param aCSPRep
* the CSPRep this CSPSource belongs to
* @param self (optional)
* string, URI, or CSPSource representing the "self" source
* @param enforceSelfChecks (optional)
@ -981,35 +1045,37 @@ CSPSource.fromURI = function(aURI, self, enforceSelfChecks) {
* @returns
* an instance of CSPSource
*/
CSPSource.fromString = function(aStr, self, enforceSelfChecks) {
CSPSource.fromString = function(aStr, aCSPRep, self, enforceSelfChecks) {
if (!aStr)
return null;
if (!(typeof aStr === 'string')) {
CSPError(CSPLocalizer.getStr("argumentIsNotString"));
cspError(aCSPRep, CSPLocalizer.getStr("argumentIsNotString"));
return null;
}
var sObj = new CSPSource();
sObj._self = self;
sObj._CSPRep = aCSPRep;
// if equal, return does match
if (aStr === "*"){
if (aStr === "*") {
sObj._permitAll = true;
return sObj;
}
if (!self && enforceSelfChecks) {
CSPError(CSPLocalizer.getStr("selfDataNotProvided"));
cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided"));
return null;
}
if (self && !(self instanceof CSPSource)) {
self = CSPSource.create(self, undefined, false);
self = CSPSource.create(self, aCSPRep, undefined, false);
}
// check for scheme-source match
if (R_SCHEMESRC.test(aStr)){
if (R_SCHEMESRC.test(aStr)) {
var schemeSrcMatch = R_GETSCHEME.exec(aStr);
sObj._scheme = schemeSrcMatch[0];
if (!sObj._host) sObj._host = CSPHost.fromString("*");
@ -1031,8 +1097,9 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) {
// get array of matches to the R_HOST regular expression
var hostMatch = R_HOST.exec(aStr);
if (!hostMatch){
CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource", [aStr]));
if (!hostMatch) {
cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource",
[aStr]));
return null;
}
// host regex gets scheme, so remove scheme from aStr. Add 3 for '://'
@ -1044,7 +1111,9 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) {
// gets the default port for the given scheme
defPort = Services.io.getProtocolHandler(sObj._scheme).defaultPort;
if (!defPort) {
CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource", [aStr]));
cspError(aCSPRep,
CSPLocalizer.getFormatStr("couldntParseInvalidSource",
[aStr]));
return null;
}
sObj._port = defPort;
@ -1057,16 +1126,17 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) {
}
// check for 'self' (case insensitive)
if (aStr.toUpperCase() === "'SELF'"){
if (!self){
CSPError(CSPLocalizer.getStr("selfKeywordNoSelfData"));
if (aStr.toUpperCase() === "'SELF'") {
if (!self) {
cspError(aCSPRep, CSPLocalizer.getStr("selfKeywordNoSelfData"));
return null;
}
sObj._self = self.clone();
sObj._isSelf = true;
return sObj;
}
CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource",[aStr]));
cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource",
[aStr]));
return null;
};
@ -1156,6 +1226,7 @@ CSPSource.prototype = {
aClone._port = this._port;
aClone._host = this._host ? this._host.clone() : undefined;
aClone._isSelf = this._isSelf;
aClone._CSPRep = this._CSPRep;
return aClone;
},
@ -1171,7 +1242,7 @@ CSPSource.prototype = {
if (!aSource) return false;
if (!(aSource instanceof CSPSource))
return this.permits(CSPSource.create(aSource));
return this.permits(CSPSource.create(aSource, this._CSPRep));
// verify scheme
if (this.scheme != aSource.scheme)
@ -1225,7 +1296,9 @@ CSPSource.prototype = {
else if (that.port === this.port)
newSource._port = this.port;
else {
CSPError(CSPLocalizer.getFormatStr("notIntersectPort", [this.toString(), that.toString()]));
let msg = CSPLocalizer.getFormatStr("notIntersectPort",
[this.toString(), that.toString()]);
cspError(this._CSPRep, msg);
return null;
}
@ -1241,7 +1314,9 @@ CSPSource.prototype = {
else if (that.scheme === this.scheme)
newSource._scheme = this.scheme;
else {
CSPError(CSPLocalizer.getFormatStr("notIntersectScheme", [this.toString(), that.toString()]));
var msg = CSPLocalizer.getFormatStr("notIntersectScheme",
[this.toString(), that.toString()]);
cspError(this._CSPRep, msg);
return null;
}
@ -1257,13 +1332,19 @@ CSPSource.prototype = {
if (this.host && that.host) {
newSource._host = this.host.intersectWith(that.host);
} else if (this.host) {
CSPError(CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost", [that.toString()]));
let msg = CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost",
[that.toString()]);
cspError(this._CSPRep, msg);
newSource._host = this.host.clone();
} else if (that.host) {
CSPError(CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost", [this.toString()]));
let msg = CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost",
[this.toString()]);
cspError(this._CSPRep, msg);
newSource._host = that.host.clone();
} else {
CSPError(CSPLocalizer.getFormatStr("intersectingSourcesWithUndefinedHosts", [this.toString(), that.toString()]));
let msg = CSPLocalizer.getFormatStr("intersectingSourcesWithUndefinedHosts",
[this.toString(), that.toString()]);
cspError(this._CSPRep, msg);
newSource._host = CSPHost.fromString("*");
}
@ -1482,7 +1563,7 @@ CSPViolationReportListener.prototype = {
_reportURI: null,
QueryInterface: function(iid) {
if(iid.equals(Ci.nsIStreamListener) ||
if (iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsIRequestObserver) ||
iid.equals(Ci.nsISupports))
return this;
@ -1508,6 +1589,52 @@ CSPViolationReportListener.prototype = {
//////////////////////////////////////////////////////////////////////
function innerWindowFromRequest(docRequest) {
let win = null;
let loadContext = null;
try {
loadContext = docRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) {
try {
loadContext = docRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) {
return null;
}
}
if (loadContext) {
win = loadContext.associatedWindow;
}
if (win) {
try {
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
return winUtils.currentInnerWindowID;
} catch (ex) {
return null;
}
}
return null;
}
function cspError(aCSPRep, aMessage) {
if (aCSPRep) {
aCSPRep.error(aMessage);
} else {
(new CSPRep()).error(aMessage);
}
}
function cspWarn(aCSPRep, aMessage) {
if (aCSPRep) {
aCSPRep.warn(aMessage);
} else {
(new CSPRep()).warn(aMessage);
}
}
//////////////////////////////////////////////////////////////////////
CSPLocalizer = {
/**
* Retrieve a localized string.

View File

@ -104,34 +104,6 @@ ContentSecurityPolicy.prototype = {
return this._reportOnlyMode || this._policy.allowsEvalInScripts;
},
get innerWindowID() {
let win = null;
let loadContext = null;
try {
loadContext = this._docRequest
.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) {
try {
loadContext = this._docRequest.loadGroup
.notificationCallbacks.getInterface(Ci.nsILoadContext);
} catch (ex) {
}
}
if (loadContext) {
win = loadContext.associatedWindow;
}
if (win) {
try {
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
return winUtils.currentInnerWindowID;
} catch (ex) {
}
}
return null;
},
/**
* Log policy violation on the Error Console and send a report if a report-uri
* is present in the policy
@ -302,16 +274,15 @@ ContentSecurityPolicy.prototype = {
CSPdebug("Constructed violation report:\n" + reportString);
var violationMessage = null;
if(blockedUri["asciiSpec"]){
if (blockedUri["asciiSpec"]) {
violationMessage = CSPLocalizer.getFormatStr("directiveViolatedWithURI", [violatedDirective, blockedUri.asciiSpec]);
} else {
violationMessage = CSPLocalizer.getFormatStr("directiveViolated", [violatedDirective]);
}
CSPWarning(violationMessage,
this.innerWindowID,
(aSourceFile) ? aSourceFile : null,
(aScriptSample) ? decodeURIComponent(aScriptSample) : null,
(aLineNum) ? aLineNum : null);
this._policy.warn(violationMessage,
(aSourceFile) ? aSourceFile : null,
(aScriptSample) ? decodeURIComponent(aScriptSample) : null,
(aLineNum) ? aLineNum : null);
// For each URI in the report list, send out a report.
// We make the assumption that all of the URIs are absolute URIs; this
@ -323,7 +294,7 @@ ContentSecurityPolicy.prototype = {
try {
var chan = Services.io.newChannel(uris[i], null, null);
if(!chan) {
if (!chan) {
CSPdebug("Error creating channel for " + uris[i]);
continue;
}
@ -338,7 +309,7 @@ ContentSecurityPolicy.prototype = {
// we need to set an nsIChannelEventSink on the channel object
// so we can tell it to not follow redirects when posting the reports
chan.notificationCallbacks = new CSPReportRedirectSink();
chan.notificationCallbacks = new CSPReportRedirectSink(this._policy);
chan.QueryInterface(Ci.nsIUploadChannel)
.setUploadStream(content, "application/json", content.available());
@ -369,8 +340,8 @@ ContentSecurityPolicy.prototype = {
} catch(e) {
// it's possible that the URI was invalid, just log a
// warning and skip over that.
CSPWarning(CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]), this.innerWindowID);
CSPWarning(CSPLocalizer.getFormatStr("errorWas", [e.toString()]), this.innerWindowID);
this._policy.warn(CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
this._policy.warn(CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
}
}
}
@ -552,7 +523,8 @@ ContentSecurityPolicy.prototype = {
// The POST of the violation report (if it happens) should not follow
// redirects, per the spec. hence, we implement an nsIChannelEventSink
// with an object so we can tell XHR to abort if a redirect happens.
function CSPReportRedirectSink() {
function CSPReportRedirectSink(policy) {
this._policy = policy;
}
CSPReportRedirectSink.prototype = {
@ -575,7 +547,7 @@ CSPReportRedirectSink.prototype = {
// nsIChannelEventSink
asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel,
flags, callback) {
CSPWarning(CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec]));
this._policy.warn(CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec]));
// cancel the old channel so XHR failure callback happens
oldChannel.cancel(Cr.NS_ERROR_ABORT);

View File

@ -159,29 +159,29 @@ test(
function test_CSPSource_fromString() {
// can't do these tests because "self" is not defined.
//"basic source should not be null.");
do_check_neq(null, CSPSource.fromString("a.com", "http://abc.com"));
do_check_neq(null, CSPSource.fromString("a.com", undefined, "http://abc.com"));
//"ldh characters should all work for host.");
do_check_neq(null, CSPSource.fromString("a2-c.com", "https://a.com"));
do_check_neq(null, CSPSource.fromString("a2-c.com", undefined, "https://a.com"));
//"wildcard should work in first token for host.");
do_check_neq(null, CSPSource.fromString("*.a.com", "http://abc.com"));
do_check_neq(null, CSPSource.fromString("*.a.com", undefined, "http://abc.com"));
//print(" --- Ignore the following two errors if they print ---");
//"wildcard should not work in non-first token for host.");
do_check_eq(null, CSPSource.fromString("x.*.a.com", "http://a.com"));
do_check_eq(null, CSPSource.fromString("x.*.a.com", undefined, "http://a.com"));
//"funny characters (#) should not work for host.");
do_check_eq(null, CSPSource.fromString("a#2-c.com", "http://a.com"));
do_check_eq(null, CSPSource.fromString("a#2-c.com", undefined, "http://a.com"));
//print(" --- Stop ignoring errors that print ---\n");
//"failed to parse host with port.");
do_check_neq(null, CSPSource.create("a.com:23", "http://a.com"));
do_check_neq(null, CSPSource.create("a.com:23", undefined, "http://a.com"));
//"failed to parse host with scheme.");
do_check_neq(null, CSPSource.create("https://a.com", "http://a.com"));
do_check_neq(null, CSPSource.create("https://a.com", undefined, "http://a.com"));
//"failed to parse host with scheme and port.");
do_check_neq(null, CSPSource.create("https://a.com:200", "http://a.com"));
do_check_neq(null, CSPSource.create("https://a.com:200", undefined, "http://a.com"));
//Check to make sure we don't match multiple instances with regex
do_check_eq(null, CSPSource.create("http://foo.com:bar.com:23"));
@ -193,7 +193,7 @@ test(
test(
function test_CSPSource_fromString_withSelf() {
var src;
src = CSPSource.create("a.com", "https://foobar.com:443");
src = CSPSource.create("a.com", undefined, "https://foobar.com:443");
//"src should inherit port *
do_check_true(src.permits("https://a.com:443"));
//"src should inherit and require https scheme
@ -201,7 +201,7 @@ test(
//"src should inherit scheme 'https'"
do_check_true(src.permits("https://a.com"));
src = CSPSource.create("http://a.com", "https://foobar.com:443");
src = CSPSource.create("http://a.com", undefined, "https://foobar.com:443");
//"src should inherit and require http scheme"
do_check_false(src.permits("https://a.com"));
//"src should inherit scheme 'http'"
@ -210,7 +210,7 @@ test(
//"src should inherit default port for 'http'"
do_check_true(src.permits("http://a.com:80"));
src = CSPSource.create("'self'", "https://foobar.com:443");
src = CSPSource.create("'self'", undefined, "https://foobar.com:443");
//"src should inherit port *
do_check_true(src.permits("https://foobar.com:443"));
//"src should inherit and require https scheme
@ -220,7 +220,7 @@ test(
//"src should reject other hosts"
do_check_false(src.permits("https://a.com"));
src = CSPSource.create("javascript:", "https://foobar.com:443");
src = CSPSource.create("javascript:", undefined, "https://foobar.com:443");
//"hostless schemes should be parseable."
var aUri = NetUtil.newURI("javascript:alert('foo');");
do_check_true(src.permits(aUri));
@ -260,7 +260,7 @@ test(
function test_CSPSourceList_fromString_twohost() {
var str = "foo.bar:21 https://ras.bar";
var parsed = "http://foo.bar:21 https://ras.bar:443";
var sd = CSPSourceList.fromString(str, URI("http://self.com:80"));
var sd = CSPSourceList.fromString(str, undefined, URI("http://self.com:80"));
//"two-host list should parse"
do_check_neq(null,sd);
//"two-host list should parse to two hosts"
@ -272,8 +272,9 @@ test(
test(
function test_CSPSourceList_permits() {
var nullSourceList = CSPSourceList.fromString("'none'");
var simpleSourceList = CSPSourceList.fromString("a.com", URI("http://self.com"));
var simpleSourceList = CSPSourceList.fromString("a.com", undefined, URI("http://self.com"));
var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88",
undefined,
URI("http://self.com:88"));
var allSourceList = CSPSourceList.fromString("*");
var allAndMoreSourceList = CSPSourceList.fromString("* https://bar.com 'none'");
@ -680,7 +681,7 @@ test(
*/
var src;
src = CSPSource.create("a.com", "https://foobar.com:4443");
src = CSPSource.create("a.com", undefined, "https://foobar.com:4443");
//"src should inherit and require https scheme
do_check_false(src.permits("http://a.com"));
//"src should inherit scheme 'https'"
@ -688,7 +689,7 @@ test(
//"src should get default port
do_check_true(src.permits("https://a.com:443"));
src = CSPSource.create("http://a.com", "https://foobar.com:4443");
src = CSPSource.create("http://a.com", undefined, "https://foobar.com:4443");
//"src should require http scheme"
do_check_false(src.permits("https://a.com"));
//"src should keep scheme 'http'"
@ -696,7 +697,7 @@ test(
//"src should inherit default port for 'http'"
do_check_true(src.permits("http://a.com:80"));
src = CSPSource.create("'self'", "https://foobar.com:4443");
src = CSPSource.create("'self'", undefined, "https://foobar.com:4443");
//"src should inherit nonstandard port from self
do_check_true(src.permits("https://foobar.com:4443"));
do_check_false(src.permits("https://foobar.com"));
@ -716,9 +717,9 @@ test(
* doesn't happen.
*/
var p_none = CSPSourceList.fromString("'none'", "http://foo.com", false);
var p_all = CSPSourceList.fromString("*", "http://foo.com", false);
var p_one = CSPSourceList.fromString("bar.com", "http://foo.com", false);
var p_none = CSPSourceList.fromString("'none'", undefined, "http://foo.com", false);
var p_all = CSPSourceList.fromString("*", undefined, "http://foo.com", false);
var p_one = CSPSourceList.fromString("bar.com", undefined, "http://foo.com", false);
do_check_false(p_none.equals(p_all));
do_check_false(p_none.equals(p_one));