diff --git a/content/base/src/CSPUtils.jsm b/content/base/src/CSPUtils.jsm index c7bd89fb1e1..d4e579fddb4 100644 --- a/content/base/src/CSPUtils.jsm +++ b/content/base/src/CSPUtils.jsm @@ -10,11 +10,19 @@ * 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"] @@ -222,7 +230,7 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { else if (opt === "eval-script") aCSPR._allowEval = true; else - CSPWarning("don't understand option '" + opt + "'. Ignoring it."); + CSPWarning(CSPLocalizer.getFormatStr("doNotUnderstandOption", [opt])); } continue directive; } @@ -275,18 +283,18 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { 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; } } @@ -295,14 +303,14 @@ 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("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; } } @@ -317,13 +325,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("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'"); } @@ -331,22 +339,22 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { 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'"); } } @@ -363,7 +371,7 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { 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'"); } @@ -373,7 +381,7 @@ CSPRep.fromString = function(aStr, self, docRequest, csp) { } // UNIDENTIFIED DIRECTIVE ///////////////////////////////////////////// - CSPWarning("Couldn't process unknown directive '" + dirname + "'"); + CSPWarning(CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective", [dirname])); } // end directive: loop @@ -516,7 +524,7 @@ CSPRep.prototype = { 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; } @@ -606,7 +614,7 @@ CSPSourceList.fromString = function(aStr, self, enforceSelfChecks) { 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); @@ -832,12 +840,12 @@ CSPSource.create = function(aData, self, enforceSelfChecks) { */ 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; } @@ -856,7 +864,7 @@ CSPSource.fromURI = function(aURI, self, enforceSelfChecks) { 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; } @@ -912,12 +920,12 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { 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; } @@ -931,7 +939,7 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { // 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(); @@ -950,7 +958,7 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { 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; } @@ -958,7 +966,7 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { 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; } } @@ -977,7 +985,7 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { 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; } } @@ -987,7 +995,7 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { 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; } } @@ -1009,7 +1017,7 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { // ... and parse sObj._host = CSPHost.fromString(cleanHost); if (!sObj._host) { - CSPError("Couldn't parse invalid host " + cleanHost); + CSPError(CSPLocalizer.getFormatStr("couldntParseInvalidHost",[cleanHost])); return null; } } @@ -1019,14 +1027,14 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { 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; } @@ -1035,12 +1043,12 @@ CSPSource.fromString = function(aStr, self, enforceSelfChecks) { // 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; } @@ -1202,8 +1210,7 @@ CSPSource.prototype = { 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; } @@ -1219,8 +1226,7 @@ CSPSource.prototype = { 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; } @@ -1236,14 +1242,13 @@ CSPSource.prototype = { 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("*"); } @@ -1486,3 +1491,55 @@ CSPViolationReportListener.prototype = { }; +////////////////////////////////////////////////////////////////////// + +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); +}); diff --git a/content/base/src/contentSecurityPolicy.js b/content/base/src/contentSecurityPolicy.js index f3059aa960e..12dc969e973 100644 --- a/content/base/src/contentSecurityPolicy.js +++ b/content/base/src/contentSecurityPolicy.js @@ -284,8 +284,13 @@ ContentSecurityPolicy.prototype = { 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, @@ -347,8 +352,8 @@ ContentSecurityPolicy.prototype = { } 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); } } } @@ -550,8 +555,7 @@ CSPReportRedirectSink.prototype = { // 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); diff --git a/dom/locales/en-US/chrome/security/csp.properties b/dom/locales/en-US/chrome/security/csp.properties new file mode 100644 index 00000000000..6216e067443 --- /dev/null +++ b/dom/locales/en-US/chrome/security/csp.properties @@ -0,0 +1,106 @@ +# 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/. + +# CSP Warnings: +# LOCALIZATION NOTE (directiveViolated): +# %1$S is the directive that has been violated. +directiveViolated = Directive %1$S violated +# LOCALIZATION NOTE (directiveViolatedWithURI): +# %1$S is the directive that has been violated. +# %2$S is the URI of the resource which violated the directive. +directiveViolatedWithURI = Directive %1$S violated by %2$S +# LOCALIZATION NOTE (triedToSendReport): +# %1$S is the URI we attempted to send a report to. +triedToSendReport = Tried to send report to invalid URI: "%1$S" +# LOCALIZATION NOTE (errorWas): +# %1$S is the error resulting from attempting to send the report +errorWas = error was: "%1$S" +# LOCALIZATION NOTE (couldNotParseReportURI): +# %1$S is the report URI that could not be parsed +couldNotParseReportURI = couldn't parse report URI: %1$S +# LOCALIZATION NOTE (couldNotProcessUnknownDirective): +# %1$S is the unknown directive +couldNotProcessUnknownDirective = Couldn't process unknown directive '%1$S' +# LOCALIZATION NOTE (doNotUnderstandOption): +# %1$S is the option that could not be understood +doNotUnderstandOption = don't understand option '%1$S'. Ignoring it. +# LOCALIZATION NOTE (notETLDPlus1): +# %1$S is the ETLD of the report URI that could not be used +notETLDPlus1 = can't use report URI from non-matching eTLD+1: %1$S +# LOCALIZATION NOTE (notSameScheme): +# %1$S is the report URI that could not be used +notSameScheme = can't use report URI with different scheme from originating document: %1$S +# LOCALIZATION NOTE (notSamePort): +# %1$S is the report URI that could not be used +notSamePort = can't use report URI with different port from originating document: %1$S +# LOCALIZATION NOTE (pageCannotSendReportsTo): +# %1$S is the URI of the page with the policy +# %2$S is the report URI that could not be used +pageCannotSendReportsTo = page on %1$S cannot send reports to %2$S +allowOrDefaultSrcRequired = 'allow' or 'default-src' directive required but not present. Reverting to "default-src 'none'" +# LOCALIZATION NOTE (failedToParseUnrecognizedSource): +# %1$S is the CSP Source that could not be parsed +failedToParseUnrecognizedSource = Failed to parse unrecoginzied source %1$S +# LOCALIZATION NOTE (reportPostRedirect): +# %1$S is the specified report URI before redirect +reportPostRedirect = Post of violation report to %1$S failed, as a redirect occurred + +# CSP Errors: +policyURINotAlone = policy-uri directive can only appear alone +noParentRequest = The policy-uri cannot be fetched without a parent request and a CSP. +# LOCALIZATION NOTE (policyURIParseError): +# %1$S is the URI that could not be parsed +policyURIParseError = could not parse URI in policy URI: %1$S +# LOCALIZATION NOTE (nonMatchingHost): +# %1$S is the URI host that does not match +nonMatchingHost = can't fetch policy uri from non-matching hostname: %1$S +# LOCALIZATION NOTE (nonMatchingPort): +# %1$S is the URI port that does not match +nonMatchingPort = can't fetch policy uri from non-matching port: %1$S +# LOCALIZATION NOTE (nonMatchingScheme): +# %1$S is the URI scheme that does not match +nonMatchingScheme = can't fetch policy uri from non-matching scheme: %1$S +# LOCALIZATION NOTE (errorFetchingPolicy): +# %1$S is the error that caused fetching to fail +errorFetchingPolicy = Error fetching policy-uri: %1$S +cspSourceNotURI = Provided argument is not an nsIURI +argumentIsNotString = Provided argument is not a string +selfDataNotProvided = Can't use 'self' if self data is not provided +# LOCALIZATION NOTE (uriWithoutScheme): +# %1$S is the URI without a scheme +uriWithoutScheme = can't parse a URI without a scheme: %1$S +selfKeywordNoSelfData = self keyword used, but no self data specified +# LOCALIZATION NOTE (couldntParseInvalidSource): +# %1$S is the source that could not be parsed +couldntParseInvalidSource = Couldn't parse invalid source %1$S +# LOCALIZATION NOTE (hostSourceWithoutData): +# %1$S is the source +hostSourceWithoutData = Can't create host-only source %1$S without 'self' data +# LOCALIZATION NOTE (sourceWithoutData): +# %1$S is the source +sourceWithoutData = Can't create source %1$S without 'self' data +# LOCALIZATION NOTE (couldntParseInvalidHost): +# %1$S is the host that's invalid +couldntParseInvalidHost = Couldn't parse invalid host %1$S +# LOCALIZATION NOTE (couldntParseScheme): +# %1$S is the string source +couldntParseScheme = Couldn't parse scheme in %1$S +# LOCALIZATION NOTE (couldntParsePort): +# %1$S is the string source +couldntParsePort = Couldn't parse port in %1$S +# LOCALIZATION NOTE (notIntersectPort): +# %1$S is one source we tried to intersect +# %2$S is the other +notIntersectPort = Could not intersect %1$S with %2$S due to port problems. +# LOCALIZATION NOTE (notIntersectScheme): +# %1$S is one source we tried to intersect +# %2$S is the other +notIntersectScheme = Could not intersect %1$S with %2$S due to scheme problems. +# LOCALIZATION NOTE (intersectingSourceWithUndefinedHost): +# %1$S is the source +intersectingSourceWithUndefinedHost = intersecting source with undefined host: %1$S +# LOCALIZATION NOTE (intersectingSourcesWithUndefinedHosts): +# %1$S is the first source +# %2$S is the second source +intersectingSourcesWithUndefinedHosts = intersecting two sources with undefined hosts: %1$S and %2$S diff --git a/dom/locales/jar.mn b/dom/locales/jar.mn index b0624810c3b..198bb956ab2 100644 --- a/dom/locales/jar.mn +++ b/dom/locales/jar.mn @@ -26,6 +26,7 @@ locale/@AB_CD@/global/layout/xmlparser.properties (%chrome/layout/xmlparser.properties) locale/@AB_CD@/global/layout/HtmlForm.properties (%chrome/layout/HtmlForm.properties) locale/@AB_CD@/global/security/caps.properties (%chrome/security/caps.properties) + locale/@AB_CD@/global/security/csp.properties (%chrome/security/csp.properties) locale/@AB_CD@/global/xml/prettyprint.dtd (%chrome/xml/prettyprint.dtd) locale/@AB_CD@/global-platform/win/accessible.properties (%chrome/accessibility/win/accessible.properties) locale/@AB_CD@/global-platform/mac/accessible.properties (%chrome/accessibility/mac/accessible.properties)