--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -5,21 +5,29 @@
/**
* Content Security Policy Utilities
*
* Overview
* This contains a set of classes and utilities for CSP. It is in this
* separate file for testing purposes.
*/
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
// Module stuff
var EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", "CSPHost",
"CSPWarning", "CSPError", "CSPdebug",
- "CSPViolationReportListener"];
+ "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);
var gETLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
.getService(Components.interfaces.nsIEffectiveTLDService);
@@ -217,17 +225,17 @@ CSPRep.fromString = function(aStr, self,
// grab value tokens and interpret them
var options = dirvalue.split(/\s+/);
for each (var opt in options) {
if (opt === "inline-script")
aCSPR._allowInlineScripts = true;
else if (opt === "eval-script")
aCSPR._allowEval = true;
else
- CSPWarning("don't understand option '" + opt + "'. Ignoring it.");
+ CSPWarning(CSPLocalizer.getFormatStr("doNotUnderstandOption", [opt]));
}
continue directive;
}
// ALLOW DIRECTIVE //////////////////////////////////////////////////
// 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) {
@@ -270,115 +278,115 @@ CSPRep.fromString = function(aStr, self,
uri.host;
// Verify that each report URI is in the same etld + 1 and that the
// scheme and port match "self" if "self" is defined, and just that
// it's valid otherwise.
if (self) {
if (gETLDService.getBaseDomain(uri) !==
gETLDService.getBaseDomain(selfUri)) {
- CSPWarning("can't use report URI from non-matching eTLD+1: "
- + gETLDService.getBaseDomain(uri));
+ CSPWarning(CSPLocalizer.getFormatStr("notETLDPlus1",
+ [gETLDService.getBaseDomain(uri)]));
continue;
}
if (!uri.schemeIs(selfUri.scheme)) {
- CSPWarning("can't use report URI with different scheme from "
- + "originating document: " + uri.asciiSpec);
+ CSPWarning(CSPLocalizer.getFormatStr("notSameScheme",
+ [uri.asciiSpec]));
continue;
}
if (uri.port && uri.port !== selfUri.port) {
- CSPWarning("can't use report URI with different port from "
- + "originating document: " + uri.asciiSpec);
+ CSPWarning(CSPLocalizer.getFormatStr("notSamePort",
+ [uri.asciiSpec]));
continue;
}
}
} catch(e) {
switch (e.result) {
case Components.results.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS:
case Components.results.NS_ERROR_HOST_IS_IP_ADDRESS:
if (uri.host !== selfUri.host) {
- CSPWarning("page on " + selfUri.host
- + " cannot send reports to " + uri.host);
+ CSPWarning(CSPLocalizer.getFormatStr("pageCannotSendReportsTo",
+ [selfUri.host, uri.host]));
continue;
}
break;
default:
- CSPWarning("couldn't parse report URI: " + uriStrings[i]);
+ CSPWarning(CSPLocalizer.getFormatStr("couldNotParseReportURI", [uriStrings[i]]));
continue;
}
}
// all verification passed: same ETLD+1, scheme, and port.
okUriStrings.push(uri.asciiSpec);
}
aCSPR._directives[UD.REPORT_URI] = okUriStrings.join(' ');
continue directive;
}
// POLICY URI //////////////////////////////////////////////////////////
if (dirname === UD.POLICY_URI) {
// POLICY_URI can only be alone
if (aCSPR._directives.length > 0 || dirs.length > 1) {
- CSPError("policy-uri directive can only appear alone");
+ CSPError(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("The policy-uri cannot be fetched without a parent request and a CSP.");
+ CSPError(CSPLocalizer.getStr("noParentRequest"));
return CSPRep.fromString("default-src 'none'");
}
var uri = '';
try {
uri = gIoService.newURI(dirvalue, null, selfUri);
} catch(e) {
- CSPError("could not parse URI in policy URI: " + dirvalue);
+ CSPError(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("can't fetch policy uri from non-matching hostname: " + uri.host);
+ CSPError(CSPLocalizer.getFormatStr("nonMatchingHost", [uri.host]));
return CSPRep.fromString("default-src 'none'");
}
if (selfUri.port !== uri.port){
- CSPError("can't fetch policy uri from non-matching port: " + uri.port);
+ CSPError(CSPLocalizer.getFormatStr("nonMatchingPort", [uri.port.toString()]));
return CSPRep.fromString("default-src 'none'");
}
if (selfUri.scheme !== uri.scheme){
- CSPError("can't fetch policy uri from non-matching scheme: " + uri.scheme);
+ CSPError(CSPLocalizer.getFormatStr("nonMatchingScheme", [uri.scheme]));
return CSPRep.fromString("default-src 'none'");
}
}
// suspend the parent document request while we fetch the policy-uri
try {
docRequest.suspend();
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.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp), null);
}
catch (e) {
// resume the document request and apply most restrictive policy
docRequest.resume();
- CSPError("Error fetching policy-uri: " + e);
+ CSPError(CSPLocalizer.getFormatStr("errorFetchingPolicy", [e.toString()]));
return CSPRep.fromString("default-src 'none'");
}
// return a fully-open policy to be intersected with the contents of the
// policy-uri when it returns
return CSPRep.fromString("default-src *");
}
// UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
- CSPWarning("Couldn't process unknown directive '" + dirname + "'");
+ CSPWarning(CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective", [dirname]));
} // end directive: loop
// if makeExplicit fails for any reason, default to default-src 'none'. This
// includes the case where "default-src" is not present.
if (aCSPR.makeExplicit())
return aCSPR;
return CSPRep.fromString("default-src 'none'", self);
@@ -511,17 +519,17 @@ CSPRep.prototype = {
* true if the makeExplicit succeeds
* false if it fails (for some weird reason)
*/
makeExplicit:
function cspsd_makeExplicit() {
var SD = CSPRep.SRC_DIRECTIVES;
var defaultSrcDir = this._directives[SD.DEFAULT_SRC];
if (!defaultSrcDir) {
- CSPWarning("'allow' or 'default-src' directive required but not present. Reverting to \"default-src 'none'\"");
+ CSPWarning(CSPLocalizer.getStr("allowOrDefaultSrcRequired"));
return false;
}
for (var dir in SD) {
var dirv = SD[dir];
if (dirv === SD.DEFAULT_SRC) continue;
if (!this._directives[dirv]) {
// implicit directive, make explicit.
@@ -601,17 +609,17 @@ CSPSourceList.fromString = function(aStr
return slObj;
}
var tokens = aStr.split(/\s+/);
for (var i in tokens) {
if (tokens[i] === "") continue;
var src = CSPSource.create(tokens[i], self, enforceSelfChecks);
if (!src) {
- CSPWarning("Failed to parse unrecoginzied source " + tokens[i]);
+ CSPWarning(CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource", [tokens[i]]));
continue;
}
slObj._sources.push(src);
}
return slObj;
};
@@ -827,22 +835,22 @@ CSPSource.create = function(aData, self,
* @param enforceSelfChecks (optional)
* if present, and "true", will check to be sure "self" has the
* appropriate values to inherit when they are omitted from aURI.
* @returns
* an instance of CSPSource
*/
CSPSource.fromURI = function(aURI, self, enforceSelfChecks) {
if (!(aURI instanceof Components.interfaces.nsIURI)){
- CSPError("Provided argument is not an nsIURI");
+ CSPError(CSPLocalizer.getStr("cspSourceNotURI"));
return null;
}
if (!self && enforceSelfChecks) {
- CSPError("Can't use 'self' if self data is not provided");
+ CSPError(CSPLocalizer.getStr("selfDataNotProvided"));
return null;
}
if (self && !(self instanceof CSPSource)) {
self = CSPSource.create(self, undefined, false);
}
var sObj = new CSPSource();
@@ -851,17 +859,17 @@ CSPSource.fromURI = function(aURI, self,
// PARSE
// If 'self' is undefined, then use default port for scheme if there is one.
// grab scheme (if there is one)
try {
sObj._scheme = aURI.scheme;
} catch(e) {
sObj._scheme = undefined;
- CSPError("can't parse a URI without a scheme: " + aURI.asciiSpec);
+ CSPError(CSPLocalizer.getFormatStr("uriWithoutScheme", [aURI.asciiSpec]));
return null;
}
// grab host (if there is one)
try {
// if there's no host, an exception will get thrown
// (NS_ERROR_FAILURE)
sObj._host = CSPHost.fromString(aURI.host);
@@ -907,36 +915,36 @@ CSPSource.fromURI = function(aURI, self,
* @returns
* an instance of CSPSource
*/
CSPSource.fromString = function(aStr, self, enforceSelfChecks) {
if (!aStr)
return null;
if (!(typeof aStr === 'string')) {
- CSPError("Provided argument is not a string");
+ CSPError(CSPLocalizer.getStr("argumentIsNotString"));
return null;
}
if (!self && enforceSelfChecks) {
- CSPError("Can't use 'self' if self data is not provided");
+ CSPError(CSPLocalizer.getStr("selfDataNotProvided"));
return null;
}
if (self && !(self instanceof CSPSource)) {
self = CSPSource.create(self, undefined, false);
}
var sObj = new CSPSource();
sObj._self = self;
// take care of 'self' keyword
if (aStr === "'self'") {
if (!self) {
- CSPError("self keyword used, but no self data specified");
+ CSPError(CSPLocalizer.getStr("selfKeywordNoSelfData"));
return null;
}
sObj._self = self.clone();
sObj._isSelf = true;
return sObj;
}
// We could just create a URI and then send this off to fromURI, but
@@ -945,25 +953,25 @@ CSPSource.fromString = function(aStr, se
// split it up
var chunks = aStr.split(":");
// If there is only one chunk, it's gotta be a host.
if (chunks.length == 1) {
sObj._host = CSPHost.fromString(chunks[0]);
if (!sObj._host) {
- CSPError("Couldn't parse invalid source " + aStr);
+ CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource",[aStr]));
return null;
}
// enforce 'self' inheritance
if (enforceSelfChecks) {
// note: the non _scheme accessor checks sObj._self
if (!sObj.scheme || !sObj.port) {
- CSPError("Can't create host-only source " + aStr + " without 'self' data");
+ CSPError(CSPLocalizer.getFormatStr("hostSourceWithoutData",[aStr]));
return null;
}
}
return sObj;
}
// If there are two chunks, it's either scheme://host or host:port
// ... but scheme://host can have an empty host.
@@ -972,27 +980,27 @@ CSPSource.fromString = function(aStr, se
// is the last bit a port?
if (chunks[1] === "*" || chunks[1].match(/^\d+$/)) {
sObj._port = chunks[1];
// then the previous chunk *must* be a host or empty.
if (chunks[0] !== "") {
sObj._host = CSPHost.fromString(chunks[0]);
if (!sObj._host) {
- CSPError("Couldn't parse invalid source " + aStr);
+ CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource",[aStr]));
return null;
}
}
// enforce 'self' inheritance
// (scheme:host requires port, host:port does too. Wildcard support is
// only available if the scheme and host are wildcarded)
if (enforceSelfChecks) {
// note: the non _scheme accessor checks sObj._self
if (!sObj.scheme || !sObj.host || !sObj.port) {
- CSPError("Can't create source " + aStr + " without 'self' data");
+ CSPError(CSPLocalizer.getFormatStr("sourceWithoutData",[aStr]));
return null;
}
}
}
// is the first bit a scheme?
else if (CSPSource.validSchemeName(chunks[0])) {
sObj._scheme = chunks[0];
// then the second bit *must* be a host or empty
@@ -1004,48 +1012,48 @@ CSPSource.fromString = function(aStr, se
if (!sObj._port) sObj._port = "*";
} else {
// some host was defined.
// ... remove <= 3 leading slashes (from the scheme) and parse
var cleanHost = chunks[1].replace(/^\/{0,3}/,"");
// ... and parse
sObj._host = CSPHost.fromString(cleanHost);
if (!sObj._host) {
- CSPError("Couldn't parse invalid host " + cleanHost);
+ CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidHost",[cleanHost]));
return null;
}
}
// enforce 'self' inheritance (scheme-only should be scheme:*:* now, and
// if there was a host provided it should be scheme:host:selfport
if (enforceSelfChecks) {
// note: the non _scheme accessor checks sObj._self
if (!sObj.scheme || !sObj.host || !sObj.port) {
- CSPError("Can't create source " + aStr + " without 'self' data");
+ CSPError(CSPLocalizer.getFormatStr("sourceWithoutData",[aStr]));
return null;
}
}
}
else {
// AAAH! Don't know what to do! No valid scheme or port!
- CSPError("Couldn't parse invalid source " + aStr);
+ CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidSource",[aStr]));
return null;
}
return sObj;
}
// If there are three chunks, we got 'em all!
if (!CSPSource.validSchemeName(chunks[0])) {
- CSPError("Couldn't parse scheme in " + aStr);
+ CSPError(CSPLocalizer.getFormatStr("couldntParseScheme",[aStr]));
return null;
}
sObj._scheme = chunks[0];
if (!(chunks[2] === "*" || chunks[2].match(/^\d+$/))) {
- CSPError("Couldn't parse port in " + aStr);
+ CSPError(CSPLocalizer.getFormatStr("couldntParsePort",[aStr]));
return null;
}
sObj._port = chunks[2];
// ... remove <= 3 leading slashes (from the scheme) and parse
var cleanHost = chunks[1].replace(/^\/{0,3}/,"");
sObj._host = CSPHost.fromString(cleanHost);
@@ -1197,58 +1205,55 @@ CSPSource.prototype = {
newSource._port = this._port;
else if (this._port === "*")
newSource._port = that._port;
else if (that._port === "*")
newSource._port = this._port;
else if (that._port === this._port)
newSource._port = this._port;
else {
- CSPError("Could not intersect " + this + " with " + that
- + " due to port problems.");
+ CSPError(CSPLocalizer.getFormatStr("notIntersectPort", [this.toString(), that.toString()]));
return null;
}
// scheme
if (!this._scheme)
newSource._scheme = that._scheme;
else if (!that._scheme)
newSource._scheme = this._scheme;
if (this._scheme === "*")
newSource._scheme = that._scheme;
else if (that._scheme === "*")
newSource._scheme = this._scheme;
else if (that._scheme === this._scheme)
newSource._scheme = this._scheme;
else {
- CSPError("Could not intersect " + this + " with " + that
- + " due to scheme problems.");
+ CSPError(CSPLocalizer.getFormatStr("notIntersectScheme", [this.toString(), that.toString()]));
return null;
}
// NOTE: Both sources must have a host, if they don't, something funny is
// going on. The fromString() factory method should have set the host to
// * if there's no host specified in the input. Regardless, if a host is
// not present either the scheme is hostless or any host should be allowed.
// This means we can use the other source's host as the more restrictive
// host expression, or if neither are present, we can use "*", but the
// error should still be reported.
// host
if (this._host && that._host) {
newSource._host = this._host.intersectWith(that._host);
} else if (this._host) {
- CSPError("intersecting source with undefined host: " + that.toString());
+ CSPError(CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost", [that.toString()]));
newSource._host = this._host.clone();
} else if (that._host) {
- CSPError("intersecting source with undefined host: " + this.toString());
+ CSPError(CSPLocalizer.getFormatStr("intersectingSourceWithUndefinedHost", [this.toString()]));
newSource._host = that._host.clone();
} else {
- CSPError("intersecting two sources with undefined hosts: " +
- this.toString() + " and " + that.toString());
+ CSPError(CSPLocalizer.getFormatStr("intersectingSourcesWithUndefinedHosts", [this.toString(), that.toString()]));
newSource._host = CSPHost.fromString("*");
}
return newSource;
},
/**
* Compares one CSPSource to another.
@@ -1481,8 +1486,60 @@ CSPViolationReportListener.prototype = {
onStartRequest:
function(request, context) { },
onDataAvailable:
function(request, context, inputStream, offset, count) { },
};
+//////////////////////////////////////////////////////////////////////
+
+CSPLocalizer = {
+ /**
+ * Retrieve a localized string.
+ *
+ * @param string aName
+ * The string name you want from the CSP string bundle.
+ * @return string
+ * The localized string.
+ */
+ getStr: function CSPLoc_getStr(aName)
+ {
+ let result;
+ try {
+ result = this.stringBundle.GetStringFromName(aName);
+ }
+ catch (ex) {
+ Cu.reportError("Failed to get string: " + aName);
+ throw ex;
+ }
+ return result;
+ },
+
+ /**
+ * Retrieve a localized string formatted with values coming from the given
+ * array.
+ *
+ * @param string aName
+ * The string name you want from the CSP string bundle.
+ * @param array aArray
+ * The array of values you want in the formatted string.
+ * @return string
+ * The formatted local string.
+ */
+ getFormatStr: function CSPLoc_getFormatStr(aName, aArray)
+ {
+ let result;
+ try {
+ result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
+ }
+ catch (ex) {
+ Cu.reportError("Failed to format string: " + aName);
+ throw ex;
+ }
+ return result;
+ },
+};
+
+XPCOMUtils.defineLazyGetter(CSPLocalizer, "stringBundle", function() {
+ return Services.strings.createBundle(STRINGS_URI);
+});
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -279,18 +279,23 @@ ContentSecurityPolicy.prototype = {
if (aScriptSample)
report["csp-report"]["script-sample"] = aScriptSample;
if (aLineNum)
report["csp-report"]["line-number"] = aLineNum;
var reportString = JSON.stringify(report);
CSPdebug("Constructed violation report:\n" + reportString);
- CSPWarning("Directive \"" + violatedDirective + "\" violated"
- + (blockedUri['asciiSpec'] ? " by " + blockedUri.asciiSpec : ""),
+ var violationMessage = null;
+ 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);
// For each URI in the report list, send out a report.
// We make the assumption that all of the URIs are absolute URIs; this
// should be taken care of in CSPRep.fromString (where it converts any
@@ -342,18 +347,18 @@ ContentSecurityPolicy.prototype = {
}
//send data (and set up error notifications)
chan.asyncOpen(new CSPViolationReportListener(uris[i]), null);
CSPdebug("Sent violation report to " + uris[i]);
} catch(e) {
// it's possible that the URI was invalid, just log a
// warning and skip over that.
- CSPWarning("Tried to send report to invalid URI: \"" + uris[i] + "\"", this.innerWindowID);
- CSPWarning("error was: \"" + e + "\"", this.innerWindowID);
+ CSPWarning(CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]), this.innerWindowID);
+ CSPWarning(CSPLocalizer.getFormatStr("errorWas", [e.toString()]), this.innerWindowID);
}
}
}
},
/**
* Exposed Method to analyze docShell for approved frame ancestry.
* Also sends violation reports if necessary.
@@ -545,18 +550,17 @@ CSPReportRedirectSink.prototype = {
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
// nsIChannelEventSink
asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel,
flags, callback) {
- CSPWarning("Post of violation report to " + oldChannel.URI.asciiSpec +
- " failed, as a redirect occurred", this.innerWindowID);
+ CSPWarning(CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec]));
// cancel the old channel so XHR failure callback happens
oldChannel.cancel(Cr.NS_ERROR_ABORT);
// notify an observer that we have blocked the report POST due to a redirect,
// used in testing, do this async since we're in an async call now to begin with
Services.tm.mainThread.dispatch(
function() {