1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/base/src/CSPUtils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2026 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * Content Security Policy Utilities 1.10 + * 1.11 + * Overview 1.12 + * This contains a set of classes and utilities for CSP. It is in this 1.13 + * separate file for testing purposes. 1.14 + */ 1.15 + 1.16 +const Cu = Components.utils; 1.17 +const Ci = Components.interfaces; 1.18 + 1.19 +const WARN_FLAG = Ci.nsIScriptError.warningFlag; 1.20 +const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG; 1.21 + 1.22 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.23 +Cu.import("resource://gre/modules/Services.jsm"); 1.24 + 1.25 +XPCOMUtils.defineLazyModuleGetter(this, "Services", 1.26 + "resource://gre/modules/Services.jsm"); 1.27 + 1.28 +// Module stuff 1.29 +this.EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", "CSPHost", 1.30 + "CSPdebug", "CSPViolationReportListener", "CSPLocalizer", 1.31 + "CSPPrefObserver"]; 1.32 + 1.33 +var STRINGS_URI = "chrome://global/locale/security/csp.properties"; 1.34 + 1.35 +// these are not exported 1.36 +var gIoService = Components.classes["@mozilla.org/network/io-service;1"] 1.37 + .getService(Ci.nsIIOService); 1.38 + 1.39 +var gETLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"] 1.40 + .getService(Ci.nsIEffectiveTLDService); 1.41 + 1.42 +// These regexps represent the concrete syntax on the w3 spec as of 7-5-2012 1.43 +// scheme = <scheme production from RFC 3986> 1.44 +const R_SCHEME = new RegExp ("([a-zA-Z0-9\\-]+)", 'i'); 1.45 +const R_GETSCHEME = new RegExp ("^" + R_SCHEME.source + "(?=\\:)", 'i'); 1.46 + 1.47 +// scheme-source = scheme ":" 1.48 +const R_SCHEMESRC = new RegExp ("^" + R_SCHEME.source + "\\:$", 'i'); 1.49 + 1.50 +// host-char = ALPHA / DIGIT / "-" 1.51 +// For the app: protocol, we need to add {} to the valid character set 1.52 +const HOSTCHAR = "{}a-zA-Z0-9\\-"; 1.53 +const R_HOSTCHAR = new RegExp ("[" + HOSTCHAR + "]", 'i'); 1.54 + 1.55 +// Complementary character set of HOSTCHAR (characters that can't appear) 1.56 +const R_COMP_HCHAR = new RegExp ("[^" + HOSTCHAR + "]", "i"); 1.57 + 1.58 +// Invalid character set for host strings (which can include dots and star) 1.59 +const R_INV_HCHAR = new RegExp ("[^" + HOSTCHAR + "\\.\\*]", 'i'); 1.60 + 1.61 + 1.62 +// host = "*" / [ "*." ] 1*host-char *( "." 1*host-char ) 1.63 +const R_HOST = new RegExp ("\\*|(((\\*\\.)?" + R_HOSTCHAR.source + 1.64 + "+)" + "(\\." + R_HOSTCHAR.source + "+)*)", 'i'); 1.65 + 1.66 +// port = ":" ( 1*DIGIT / "*" ) 1.67 +const R_PORT = new RegExp ("(\\:([0-9]+|\\*))", 'i'); 1.68 + 1.69 +// host-source = [ scheme "://" ] host [ port path file ] 1.70 +const R_HOSTSRC = new RegExp ("^((" + R_SCHEME.source + "\\:\\/\\/)?(" 1.71 + + R_HOST.source + ")" 1.72 + + R_PORT.source + "?)$", 'i'); 1.73 + 1.74 +function STRIP_INPUTDELIM(re) { 1.75 + return re.replace(/(^\^)|(\$$)/g, ""); 1.76 +} 1.77 + 1.78 +// ext-host-source = host-source "/" *( <VCHAR except ";" and ","> ) 1.79 +// ; ext-host-source is reserved for future use. 1.80 +const R_VCHAR_EXCEPT = new RegExp("[!-+--:<-~]"); // ranges exclude , and ; 1.81 +const R_EXTHOSTSRC = new RegExp ("^" + STRIP_INPUTDELIM(R_HOSTSRC.source) 1.82 + + "\\/" 1.83 + + R_VCHAR_EXCEPT.source + "*$", 'i'); 1.84 + 1.85 +// keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'" 1.86 +const R_KEYWORDSRC = new RegExp ("^('self'|'unsafe-inline'|'unsafe-eval')$", 'i'); 1.87 + 1.88 +const R_BASE64 = new RegExp ("([a-zA-Z0-9+/]+={0,2})"); 1.89 + 1.90 +// nonce-source = "'nonce-" nonce-value "'" 1.91 +// nonce-value = 1*( ALPHA / DIGIT / "+" / "/" ) 1.92 +const R_NONCESRC = new RegExp ("^'nonce-" + R_BASE64.source + "'$"); 1.93 + 1.94 +// hash-source = "'" hash-algo "-" hash-value "'" 1.95 +// hash-algo = "sha256" / "sha384" / "sha512" 1.96 +// hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) 1.97 +// Each algo must be a valid argument to nsICryptoHash.init 1.98 +const R_HASH_ALGOS = new RegExp ("(sha256|sha384|sha512)"); 1.99 +const R_HASHSRC = new RegExp ("^'" + R_HASH_ALGOS.source + "-" + R_BASE64.source + "'$"); 1.100 + 1.101 +// source-exp = scheme-source / host-source / keyword-source 1.102 +const R_SOURCEEXP = new RegExp (R_SCHEMESRC.source + "|" + 1.103 + R_HOSTSRC.source + "|" + 1.104 + R_EXTHOSTSRC.source + "|" + 1.105 + R_KEYWORDSRC.source + "|" + 1.106 + R_NONCESRC.source + "|" + 1.107 + R_HASHSRC.source, 'i'); 1.108 + 1.109 +const R_QUOTELESS_KEYWORDS = new RegExp ("^(self|unsafe-inline|unsafe-eval|" + 1.110 + "inline-script|eval-script|none)$", 'i'); 1.111 + 1.112 +this.CSPPrefObserver = { 1.113 + get debugEnabled () { 1.114 + if (!this._branch) 1.115 + this._initialize(); 1.116 + return this._debugEnabled; 1.117 + }, 1.118 + 1.119 + get experimentalEnabled () { 1.120 + if (!this._branch) 1.121 + this._initialize(); 1.122 + return this._experimentalEnabled; 1.123 + }, 1.124 + 1.125 + _initialize: function() { 1.126 + var prefSvc = Components.classes["@mozilla.org/preferences-service;1"] 1.127 + .getService(Ci.nsIPrefService); 1.128 + this._branch = prefSvc.getBranch("security.csp."); 1.129 + this._branch.addObserver("", this, false); 1.130 + this._debugEnabled = this._branch.getBoolPref("debug"); 1.131 + this._experimentalEnabled = this._branch.getBoolPref("experimentalEnabled"); 1.132 + }, 1.133 + 1.134 + unregister: function() { 1.135 + if (!this._branch) return; 1.136 + this._branch.removeObserver("", this); 1.137 + }, 1.138 + 1.139 + observe: function(aSubject, aTopic, aData) { 1.140 + if (aTopic != "nsPref:changed") return; 1.141 + if (aData === "debug") 1.142 + this._debugEnabled = this._branch.getBoolPref("debug"); 1.143 + if (aData === "experimentalEnabled") 1.144 + this._experimentalEnabled = this._branch.getBoolPref("experimentalEnabled"); 1.145 + }, 1.146 +}; 1.147 + 1.148 +this.CSPdebug = function CSPdebug(aMsg) { 1.149 + if (!CSPPrefObserver.debugEnabled) return; 1.150 + 1.151 + aMsg = 'CSP debug: ' + aMsg + "\n"; 1.152 + Components.classes["@mozilla.org/consoleservice;1"] 1.153 + .getService(Ci.nsIConsoleService) 1.154 + .logStringMessage(aMsg); 1.155 +} 1.156 + 1.157 +// Callback to resume a request once the policy-uri has been fetched 1.158 +function CSPPolicyURIListener(policyURI, docRequest, csp, reportOnly) { 1.159 + this._policyURI = policyURI; // location of remote policy 1.160 + this._docRequest = docRequest; // the parent document request 1.161 + this._csp = csp; // parent document's CSP 1.162 + this._policy = ""; // contents fetched from policyURI 1.163 + this._wrapper = null; // nsIScriptableInputStream 1.164 + this._docURI = docRequest.QueryInterface(Ci.nsIChannel) 1.165 + .URI; // parent document URI (to be used as 'self') 1.166 + this._reportOnly = reportOnly; 1.167 +} 1.168 + 1.169 +CSPPolicyURIListener.prototype = { 1.170 + 1.171 + QueryInterface: function(iid) { 1.172 + if (iid.equals(Ci.nsIStreamListener) || 1.173 + iid.equals(Ci.nsIRequestObserver) || 1.174 + iid.equals(Ci.nsISupports)) 1.175 + return this; 1.176 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.177 + }, 1.178 + 1.179 + onStartRequest: 1.180 + function(request, context) {}, 1.181 + 1.182 + onDataAvailable: 1.183 + function(request, context, inputStream, offset, count) { 1.184 + if (this._wrapper == null) { 1.185 + this._wrapper = Components.classes["@mozilla.org/scriptableinputstream;1"] 1.186 + .createInstance(Ci.nsIScriptableInputStream); 1.187 + this._wrapper.init(inputStream); 1.188 + } 1.189 + // store the remote policy as it becomes available 1.190 + this._policy += this._wrapper.read(count); 1.191 + }, 1.192 + 1.193 + onStopRequest: 1.194 + function(request, context, status) { 1.195 + if (Components.isSuccessCode(status)) { 1.196 + // send the policy we received back to the parent document's CSP 1.197 + // for parsing 1.198 + this._csp.appendPolicy(this._policy, this._docURI, 1.199 + this._reportOnly, this._csp._specCompliant); 1.200 + } 1.201 + else { 1.202 + // problem fetching policy so fail closed by appending a "block it all" 1.203 + // policy. Also toss an error into the console so developers can see why 1.204 + // this policy is used. 1.205 + this._csp.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorFetchingPolicy", 1.206 + [status])); 1.207 + this._csp.appendPolicy("default-src 'none'", this._docURI, 1.208 + this._reportOnly, this._csp._specCompliant); 1.209 + } 1.210 + // resume the parent document request 1.211 + this._docRequest.resume(); 1.212 + } 1.213 +}; 1.214 + 1.215 +//:::::::::::::::::::::::: CLASSES ::::::::::::::::::::::::::// 1.216 + 1.217 +/** 1.218 + * Class that represents a parsed policy structure. 1.219 + * 1.220 + * @param aSpecCompliant: true: this policy is a CSP 1.0 spec 1.221 + * compliant policy and should be parsed as such. 1.222 + * false or undefined: this is a policy using 1.223 + * our original implementation's CSP syntax. 1.224 + */ 1.225 +this.CSPRep = function CSPRep(aSpecCompliant) { 1.226 + // this gets set to true when the policy is done parsing, or when a 1.227 + // URI-borne policy has finished loading. 1.228 + this._isInitialized = false; 1.229 + 1.230 + this._allowEval = false; 1.231 + this._allowInlineScripts = false; 1.232 + this._reportOnlyMode = false; 1.233 + 1.234 + // don't auto-populate _directives, so it is easier to find bugs 1.235 + this._directives = {}; 1.236 + 1.237 + // Is this a 1.0 spec compliant CSPRep ? 1.238 + // Default to false if not specified. 1.239 + this._specCompliant = (aSpecCompliant !== undefined) ? aSpecCompliant : false; 1.240 + 1.241 + // Only CSP 1.0 spec compliant policies block inline styles by default. 1.242 + this._allowInlineStyles = !aSpecCompliant; 1.243 +} 1.244 + 1.245 +// Source directives for our original CSP implementation. 1.246 +// These can be removed when the original implementation is deprecated. 1.247 +CSPRep.SRC_DIRECTIVES_OLD = { 1.248 + DEFAULT_SRC: "default-src", 1.249 + SCRIPT_SRC: "script-src", 1.250 + STYLE_SRC: "style-src", 1.251 + MEDIA_SRC: "media-src", 1.252 + IMG_SRC: "img-src", 1.253 + OBJECT_SRC: "object-src", 1.254 + FRAME_SRC: "frame-src", 1.255 + FRAME_ANCESTORS: "frame-ancestors", 1.256 + FONT_SRC: "font-src", 1.257 + XHR_SRC: "xhr-src" 1.258 +}; 1.259 + 1.260 +// Source directives for our CSP 1.0 spec compliant implementation. 1.261 +CSPRep.SRC_DIRECTIVES_NEW = { 1.262 + DEFAULT_SRC: "default-src", 1.263 + SCRIPT_SRC: "script-src", 1.264 + STYLE_SRC: "style-src", 1.265 + MEDIA_SRC: "media-src", 1.266 + IMG_SRC: "img-src", 1.267 + OBJECT_SRC: "object-src", 1.268 + FRAME_SRC: "frame-src", 1.269 + FRAME_ANCESTORS: "frame-ancestors", 1.270 + FONT_SRC: "font-src", 1.271 + CONNECT_SRC: "connect-src" 1.272 +}; 1.273 + 1.274 +CSPRep.URI_DIRECTIVES = { 1.275 + REPORT_URI: "report-uri", /* list of URIs */ 1.276 + POLICY_URI: "policy-uri" /* single URI */ 1.277 +}; 1.278 + 1.279 +// These directives no longer exist in CSP 1.0 and 1.280 +// later and will eventually be removed when we no longer 1.281 +// support our original implementation's syntax. 1.282 +CSPRep.OPTIONS_DIRECTIVE = "options"; 1.283 +CSPRep.ALLOW_DIRECTIVE = "allow"; 1.284 + 1.285 +/** 1.286 + * Factory to create a new CSPRep, parsed from a string. 1.287 + * 1.288 + * @param aStr 1.289 + * string rep of a CSP 1.290 + * @param self (optional) 1.291 + * URI representing the "self" source 1.292 + * @param reportOnly (optional) 1.293 + * whether or not this CSP is report-only (defaults to false) 1.294 + * @param docRequest (optional) 1.295 + * request for the parent document which may need to be suspended 1.296 + * while the policy-uri is asynchronously fetched 1.297 + * @param csp (optional) 1.298 + * the CSP object to update once the policy has been fetched 1.299 + * @param enforceSelfChecks (optional) 1.300 + * if present, and "true", will check to be sure "self" has the 1.301 + * appropriate values to inherit when they are omitted from the source. 1.302 + * @returns 1.303 + * an instance of CSPRep 1.304 + */ 1.305 +CSPRep.fromString = function(aStr, self, reportOnly, docRequest, csp, 1.306 + enforceSelfChecks) { 1.307 + var SD = CSPRep.SRC_DIRECTIVES_OLD; 1.308 + var UD = CSPRep.URI_DIRECTIVES; 1.309 + var aCSPR = new CSPRep(); 1.310 + aCSPR._originalText = aStr; 1.311 + aCSPR._innerWindowID = innerWindowFromRequest(docRequest); 1.312 + if (typeof reportOnly === 'undefined') reportOnly = false; 1.313 + aCSPR._reportOnlyMode = reportOnly; 1.314 + 1.315 + var selfUri = null; 1.316 + if (self instanceof Ci.nsIURI) { 1.317 + selfUri = self.cloneIgnoringRef(); 1.318 + // clean userpass out of the URI (not used for CSP origin checking, but 1.319 + // shows up in prePath). 1.320 + try { 1.321 + // GetUserPass throws for some protocols without userPass 1.322 + selfUri.userPass = ''; 1.323 + } catch (ex) {} 1.324 + } 1.325 + 1.326 + var dirs = aStr.split(";"); 1.327 + 1.328 + directive: 1.329 + for each(var dir in dirs) { 1.330 + dir = dir.trim(); 1.331 + if (dir.length < 1) continue; 1.332 + 1.333 + var dirname = dir.split(/\s+/)[0].toLowerCase(); 1.334 + var dirvalue = dir.substring(dirname.length).trim(); 1.335 + 1.336 + if (aCSPR._directives.hasOwnProperty(dirname)) { 1.337 + // Check for (most) duplicate directives 1.338 + cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective", 1.339 + [dirname])); 1.340 + CSPdebug("Skipping duplicate directive: \"" + dir + "\""); 1.341 + continue directive; 1.342 + } 1.343 + 1.344 + // OPTIONS DIRECTIVE //////////////////////////////////////////////// 1.345 + if (dirname === CSPRep.OPTIONS_DIRECTIVE) { 1.346 + if (aCSPR._allowInlineScripts || aCSPR._allowEval) { 1.347 + // Check for duplicate options directives 1.348 + cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective", 1.349 + [dirname])); 1.350 + CSPdebug("Skipping duplicate directive: \"" + dir + "\""); 1.351 + continue directive; 1.352 + } 1.353 + 1.354 + // grab value tokens and interpret them 1.355 + var options = dirvalue.split(/\s+/); 1.356 + for each (var opt in options) { 1.357 + if (opt === "inline-script") 1.358 + aCSPR._allowInlineScripts = true; 1.359 + else if (opt === "eval-script") 1.360 + aCSPR._allowEval = true; 1.361 + else 1.362 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("ignoringUnknownOption", 1.363 + [opt])); 1.364 + } 1.365 + continue directive; 1.366 + } 1.367 + 1.368 + // ALLOW DIRECTIVE ////////////////////////////////////////////////// 1.369 + // parse "allow" as equivalent to "default-src", at least until the spec 1.370 + // stabilizes, at which time we can stop parsing "allow" 1.371 + if (dirname === CSPRep.ALLOW_DIRECTIVE) { 1.372 + cspWarn(aCSPR, CSPLocalizer.getStr("allowDirectiveIsDeprecated")); 1.373 + if (aCSPR._directives.hasOwnProperty(SD.DEFAULT_SRC)) { 1.374 + // Check for duplicate default-src and allow directives 1.375 + cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective", 1.376 + [dirname])); 1.377 + CSPdebug("Skipping duplicate directive: \"" + dir + "\""); 1.378 + continue directive; 1.379 + } 1.380 + var dv = CSPSourceList.fromString(dirvalue, aCSPR, selfUri, 1.381 + enforceSelfChecks); 1.382 + if (dv) { 1.383 + aCSPR._directives[SD.DEFAULT_SRC] = dv; 1.384 + continue directive; 1.385 + } 1.386 + } 1.387 + 1.388 + // SOURCE DIRECTIVES //////////////////////////////////////////////// 1.389 + for each(var sdi in SD) { 1.390 + if (dirname === sdi) { 1.391 + // process dirs, and enforce that 'self' is defined. 1.392 + var dv = CSPSourceList.fromString(dirvalue, aCSPR, selfUri, 1.393 + enforceSelfChecks); 1.394 + if (dv) { 1.395 + aCSPR._directives[sdi] = dv; 1.396 + continue directive; 1.397 + } 1.398 + } 1.399 + } 1.400 + 1.401 + // REPORT URI /////////////////////////////////////////////////////// 1.402 + if (dirname === UD.REPORT_URI) { 1.403 + // might be space-separated list of URIs 1.404 + var uriStrings = dirvalue.split(/\s+/); 1.405 + var okUriStrings = []; 1.406 + 1.407 + for (let i in uriStrings) { 1.408 + var uri = null; 1.409 + try { 1.410 + // Relative URIs are okay, but to ensure we send the reports to the 1.411 + // right spot, the relative URIs are expanded here during parsing. 1.412 + // The resulting CSPRep instance will have only absolute URIs. 1.413 + uri = gIoService.newURI(uriStrings[i],null,selfUri); 1.414 + 1.415 + // if there's no host, this will throw NS_ERROR_FAILURE, causing a 1.416 + // parse failure. 1.417 + uri.host; 1.418 + 1.419 + // warn about, but do not prohibit non-http and non-https schemes for 1.420 + // reporting URIs. The spec allows unrestricted URIs resolved 1.421 + // relative to "self", but we should let devs know if the scheme is 1.422 + // abnormal and may fail a POST. 1.423 + if (!uri.schemeIs("http") && !uri.schemeIs("https")) { 1.424 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotHttpsOrHttp2", 1.425 + [uri.asciiSpec])); 1.426 + } 1.427 + } catch(e) { 1.428 + switch (e.result) { 1.429 + case Components.results.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS: 1.430 + case Components.results.NS_ERROR_HOST_IS_IP_ADDRESS: 1.431 + if (uri.host !== selfUri.host) { 1.432 + cspWarn(aCSPR, 1.433 + CSPLocalizer.getFormatStr("pageCannotSendReportsTo", 1.434 + [selfUri.host, uri.host])); 1.435 + continue; 1.436 + } 1.437 + break; 1.438 + 1.439 + default: 1.440 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotParseReportURI", 1.441 + [uriStrings[i]])); 1.442 + continue; 1.443 + } 1.444 + } 1.445 + // all verification passed 1.446 + okUriStrings.push(uri.asciiSpec); 1.447 + } 1.448 + aCSPR._directives[UD.REPORT_URI] = okUriStrings.join(' '); 1.449 + continue directive; 1.450 + } 1.451 + 1.452 + // POLICY URI ////////////////////////////////////////////////////////// 1.453 + if (dirname === UD.POLICY_URI) { 1.454 + // POLICY_URI can only be alone 1.455 + if (aCSPR._directives.length > 0 || dirs.length > 1) { 1.456 + cspError(aCSPR, CSPLocalizer.getStr("policyURINotAlone")); 1.457 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.458 + } 1.459 + // if we were called without a reference to the parent document request 1.460 + // we won't be able to suspend it while we fetch the policy -> fail closed 1.461 + if (!docRequest || !csp) { 1.462 + cspError(aCSPR, CSPLocalizer.getStr("noParentRequest")); 1.463 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.464 + } 1.465 + 1.466 + var uri = ''; 1.467 + try { 1.468 + uri = gIoService.newURI(dirvalue, null, selfUri); 1.469 + } catch(e) { 1.470 + cspError(aCSPR, CSPLocalizer.getFormatStr("policyURIParseError", 1.471 + [dirvalue])); 1.472 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.473 + } 1.474 + 1.475 + // Verify that policy URI comes from the same origin 1.476 + if (selfUri) { 1.477 + if (selfUri.host !== uri.host) { 1.478 + cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingHost", 1.479 + [uri.host])); 1.480 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.481 + } 1.482 + if (selfUri.port !== uri.port) { 1.483 + cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingPort", 1.484 + [uri.port.toString()])); 1.485 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.486 + } 1.487 + if (selfUri.scheme !== uri.scheme) { 1.488 + cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingScheme", 1.489 + [uri.scheme])); 1.490 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.491 + } 1.492 + } 1.493 + 1.494 + // suspend the parent document request while we fetch the policy-uri 1.495 + try { 1.496 + docRequest.suspend(); 1.497 + var chan = gIoService.newChannel(uri.asciiSpec, null, null); 1.498 + // make request anonymous (no cookies, etc.) so the request for the 1.499 + // policy-uri can't be abused for CSRF 1.500 + chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS; 1.501 + chan.loadGroup = docRequest.loadGroup; 1.502 + chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp, reportOnly), null); 1.503 + } 1.504 + catch (e) { 1.505 + // resume the document request and apply most restrictive policy 1.506 + docRequest.resume(); 1.507 + cspError(aCSPR, CSPLocalizer.getFormatStr("errorFetchingPolicy", 1.508 + [e.toString()])); 1.509 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.510 + } 1.511 + 1.512 + // return a fully-open policy to be used until the contents of the 1.513 + // policy-uri come back. 1.514 + return CSPRep.fromString("default-src *", null, reportOnly); 1.515 + } 1.516 + 1.517 + // UNIDENTIFIED DIRECTIVE ///////////////////////////////////////////// 1.518 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective", 1.519 + [dirname])); 1.520 + 1.521 + } // end directive: loop 1.522 + 1.523 + // the X-Content-Security-Policy syntax requires an allow or default-src 1.524 + // directive to be present. 1.525 + if (!aCSPR._directives[SD.DEFAULT_SRC]) { 1.526 + cspWarn(aCSPR, CSPLocalizer.getStr("allowOrDefaultSrcRequired")); 1.527 + return CSPRep.fromString("default-src 'none'", null, reportOnly); 1.528 + } 1.529 + 1.530 + // If this is a Report-Only header and report-uri is not in the directive 1.531 + // list, tell developer either specify report-uri directive or use 1.532 + // a non-Report-Only CSP header. 1.533 + if (aCSPR._reportOnlyMode && !aCSPR._directives.hasOwnProperty(UD.REPORT_URI)) { 1.534 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotInReportOnlyHeader", 1.535 + [selfUri ? selfUri.prePath : "undefined"])) 1.536 + } 1.537 + 1.538 + return aCSPR; 1.539 +}; 1.540 + 1.541 +/** 1.542 + * Factory to create a new CSPRep, parsed from a string, compliant 1.543 + * with the CSP 1.0 spec. 1.544 + * 1.545 + * @param aStr 1.546 + * string rep of a CSP 1.547 + * @param self (optional) 1.548 + * URI representing the "self" source 1.549 + * @param reportOnly (optional) 1.550 + * whether or not this CSP is report-only (defaults to false) 1.551 + * @param docRequest (optional) 1.552 + * request for the parent document which may need to be suspended 1.553 + * while the policy-uri is asynchronously fetched 1.554 + * @param csp (optional) 1.555 + * the CSP object to update once the policy has been fetched 1.556 + * @param enforceSelfChecks (optional) 1.557 + * if present, and "true", will check to be sure "self" has the 1.558 + * appropriate values to inherit when they are omitted from the source. 1.559 + * @returns 1.560 + * an instance of CSPRep 1.561 + */ 1.562 +// When we deprecate our original CSP implementation, we rename this to 1.563 +// CSPRep.fromString and remove the existing CSPRep.fromString above. 1.564 +CSPRep.fromStringSpecCompliant = function(aStr, self, reportOnly, docRequest, csp, 1.565 + enforceSelfChecks) { 1.566 + var SD = CSPRep.SRC_DIRECTIVES_NEW; 1.567 + var UD = CSPRep.URI_DIRECTIVES; 1.568 + var aCSPR = new CSPRep(true); 1.569 + aCSPR._originalText = aStr; 1.570 + aCSPR._innerWindowID = innerWindowFromRequest(docRequest); 1.571 + if (typeof reportOnly === 'undefined') reportOnly = false; 1.572 + aCSPR._reportOnlyMode = reportOnly; 1.573 + 1.574 + var selfUri = null; 1.575 + if (self instanceof Ci.nsIURI) { 1.576 + selfUri = self.cloneIgnoringRef(); 1.577 + // clean userpass out of the URI (not used for CSP origin checking, but 1.578 + // shows up in prePath). 1.579 + try { 1.580 + // GetUserPass throws for some protocols without userPass 1.581 + selfUri.userPass = ''; 1.582 + } catch (ex) {} 1.583 + } 1.584 + 1.585 + var dirs_list = aStr.split(";"); 1.586 + var dirs = {}; 1.587 + for each(var dir in dirs_list) { 1.588 + dir = dir.trim(); 1.589 + if (dir.length < 1) continue; 1.590 + 1.591 + var dirname = dir.split(/\s+/)[0].toLowerCase(); 1.592 + var dirvalue = dir.substring(dirname.length).trim(); 1.593 + dirs[dirname] = dirvalue; 1.594 + } 1.595 + 1.596 + // Spec compliant policies have different default behavior for inline 1.597 + // scripts, styles, and eval. Bug 885433 1.598 + aCSPR._allowEval = true; 1.599 + aCSPR._allowInlineScripts = true; 1.600 + aCSPR._allowInlineStyles = true; 1.601 + 1.602 + // In CSP 1.0, you need to opt-in to blocking inline scripts and eval by 1.603 + // specifying either default-src or script-src, and to blocking inline 1.604 + // styles by specifying either default-src or style-src. 1.605 + if ("default-src" in dirs) { 1.606 + // Parse the source list (look ahead) so we can set the defaults properly, 1.607 + // honoring the 'unsafe-inline' and 'unsafe-eval' keywords 1.608 + var defaultSrcValue = CSPSourceList.fromString(dirs["default-src"], null, self); 1.609 + if (!defaultSrcValue._allowUnsafeInline) { 1.610 + aCSPR._allowInlineScripts = false; 1.611 + aCSPR._allowInlineStyles = false; 1.612 + } 1.613 + if (!defaultSrcValue._allowUnsafeEval) { 1.614 + aCSPR._allowEval = false; 1.615 + } 1.616 + } 1.617 + if ("script-src" in dirs) { 1.618 + aCSPR._allowInlineScripts = false; 1.619 + aCSPR._allowEval = false; 1.620 + } 1.621 + if ("style-src" in dirs) { 1.622 + aCSPR._allowInlineStyles = false; 1.623 + } 1.624 + 1.625 + directive: 1.626 + for (var dirname in dirs) { 1.627 + var dirvalue = dirs[dirname]; 1.628 + 1.629 + if (aCSPR._directives.hasOwnProperty(dirname)) { 1.630 + // Check for (most) duplicate directives 1.631 + cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective", 1.632 + [dirname])); 1.633 + CSPdebug("Skipping duplicate directive: \"" + dir + "\""); 1.634 + continue directive; 1.635 + } 1.636 + 1.637 + // SOURCE DIRECTIVES //////////////////////////////////////////////// 1.638 + for each(var sdi in SD) { 1.639 + if (dirname === sdi) { 1.640 + // process dirs, and enforce that 'self' is defined. 1.641 + var dv = CSPSourceList.fromString(dirvalue, aCSPR, self, 1.642 + enforceSelfChecks); 1.643 + if (dv) { 1.644 + // Check for unsafe-inline in style-src 1.645 + if (sdi === "style-src" && dv._allowUnsafeInline) { 1.646 + aCSPR._allowInlineStyles = true; 1.647 + } else if (sdi === "script-src") { 1.648 + // Check for unsafe-inline and unsafe-eval in script-src 1.649 + if (dv._allowUnsafeInline) { 1.650 + aCSPR._allowInlineScripts = true; 1.651 + } 1.652 + if (dv._allowUnsafeEval) { 1.653 + aCSPR._allowEval = true; 1.654 + } 1.655 + } 1.656 + 1.657 + aCSPR._directives[sdi] = dv; 1.658 + continue directive; 1.659 + } 1.660 + } 1.661 + } 1.662 + 1.663 + // REPORT URI /////////////////////////////////////////////////////// 1.664 + if (dirname === UD.REPORT_URI) { 1.665 + // might be space-separated list of URIs 1.666 + var uriStrings = dirvalue.split(/\s+/); 1.667 + var okUriStrings = []; 1.668 + 1.669 + for (let i in uriStrings) { 1.670 + var uri = null; 1.671 + try { 1.672 + // Relative URIs are okay, but to ensure we send the reports to the 1.673 + // right spot, the relative URIs are expanded here during parsing. 1.674 + // The resulting CSPRep instance will have only absolute URIs. 1.675 + uri = gIoService.newURI(uriStrings[i],null,selfUri); 1.676 + 1.677 + // if there's no host, this will throw NS_ERROR_FAILURE, causing a 1.678 + // parse failure. 1.679 + uri.host; 1.680 + 1.681 + // warn about, but do not prohibit non-http and non-https schemes for 1.682 + // reporting URIs. The spec allows unrestricted URIs resolved 1.683 + // relative to "self", but we should let devs know if the scheme is 1.684 + // abnormal and may fail a POST. 1.685 + if (!uri.schemeIs("http") && !uri.schemeIs("https")) { 1.686 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotHttpsOrHttp2", 1.687 + [uri.asciiSpec])); 1.688 + } 1.689 + } catch(e) { 1.690 + switch (e.result) { 1.691 + case Components.results.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS: 1.692 + case Components.results.NS_ERROR_HOST_IS_IP_ADDRESS: 1.693 + if (uri.host !== selfUri.host) { 1.694 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("pageCannotSendReportsTo", 1.695 + [selfUri.host, uri.host])); 1.696 + continue; 1.697 + } 1.698 + break; 1.699 + 1.700 + default: 1.701 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotParseReportURI", 1.702 + [uriStrings[i]])); 1.703 + continue; 1.704 + } 1.705 + } 1.706 + // all verification passed. 1.707 + okUriStrings.push(uri.asciiSpec); 1.708 + } 1.709 + aCSPR._directives[UD.REPORT_URI] = okUriStrings.join(' '); 1.710 + continue directive; 1.711 + } 1.712 + 1.713 + // POLICY URI ////////////////////////////////////////////////////////// 1.714 + if (dirname === UD.POLICY_URI) { 1.715 + // POLICY_URI can only be alone 1.716 + if (aCSPR._directives.length > 0 || dirs.length > 1) { 1.717 + cspError(aCSPR, CSPLocalizer.getStr("policyURINotAlone")); 1.718 + return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly); 1.719 + } 1.720 + // if we were called without a reference to the parent document request 1.721 + // we won't be able to suspend it while we fetch the policy -> fail closed 1.722 + if (!docRequest || !csp) { 1.723 + cspError(aCSPR, CSPLocalizer.getStr("noParentRequest")); 1.724 + return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly); 1.725 + } 1.726 + 1.727 + var uri = ''; 1.728 + try { 1.729 + uri = gIoService.newURI(dirvalue, null, selfUri); 1.730 + } catch(e) { 1.731 + cspError(aCSPR, CSPLocalizer.getFormatStr("policyURIParseError", [dirvalue])); 1.732 + return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly); 1.733 + } 1.734 + 1.735 + // Verify that policy URI comes from the same origin 1.736 + if (selfUri) { 1.737 + if (selfUri.host !== uri.host){ 1.738 + cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingHost", [uri.host])); 1.739 + return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly); 1.740 + } 1.741 + if (selfUri.port !== uri.port){ 1.742 + cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingPort", [uri.port.toString()])); 1.743 + return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly); 1.744 + } 1.745 + if (selfUri.scheme !== uri.scheme){ 1.746 + cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingScheme", [uri.scheme])); 1.747 + return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly); 1.748 + } 1.749 + } 1.750 + 1.751 + // suspend the parent document request while we fetch the policy-uri 1.752 + try { 1.753 + docRequest.suspend(); 1.754 + var chan = gIoService.newChannel(uri.asciiSpec, null, null); 1.755 + // make request anonymous (no cookies, etc.) so the request for the 1.756 + // policy-uri can't be abused for CSRF 1.757 + chan.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS; 1.758 + chan.loadGroup = docRequest.loadGroup; 1.759 + chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp, reportOnly), null); 1.760 + } 1.761 + catch (e) { 1.762 + // resume the document request and apply most restrictive policy 1.763 + docRequest.resume(); 1.764 + cspError(aCSPR, CSPLocalizer.getFormatStr("errorFetchingPolicy", [e.toString()])); 1.765 + return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly); 1.766 + } 1.767 + 1.768 + // return a fully-open policy to be used until the contents of the 1.769 + // policy-uri come back 1.770 + return CSPRep.fromStringSpecCompliant("default-src *", null, reportOnly); 1.771 + } 1.772 + 1.773 + // UNIDENTIFIED DIRECTIVE ///////////////////////////////////////////// 1.774 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective", [dirname])); 1.775 + 1.776 + } // end directive: loop 1.777 + 1.778 + // If this is a Report-Only header and report-uri is not in the directive 1.779 + // list, tell developer either specify report-uri directive or use 1.780 + // a non-Report-Only CSP header. 1.781 + if (aCSPR._reportOnlyMode && !aCSPR._directives.hasOwnProperty(UD.REPORT_URI)) { 1.782 + cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotInReportOnlyHeader", 1.783 + [selfUri ? selfUri.prePath : "undefined"])); 1.784 + } 1.785 + 1.786 + return aCSPR; 1.787 +}; 1.788 + 1.789 +CSPRep.prototype = { 1.790 + /** 1.791 + * Returns a space-separated list of all report uris defined, or 'none' if there are none. 1.792 + */ 1.793 + getReportURIs: 1.794 + function() { 1.795 + if (!this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI]) 1.796 + return ""; 1.797 + return this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI]; 1.798 + }, 1.799 + 1.800 + /** 1.801 + * Compares this CSPRep instance to another. 1.802 + */ 1.803 + equals: 1.804 + function(that) { 1.805 + if (this._directives.length != that._directives.length) { 1.806 + return false; 1.807 + } 1.808 + for (var i in this._directives) { 1.809 + if (!that._directives[i] || !this._directives[i].equals(that._directives[i])) { 1.810 + return false; 1.811 + } 1.812 + } 1.813 + return (this.allowsInlineScripts === that.allowsInlineScripts) 1.814 + && (this.allowsEvalInScripts === that.allowsEvalInScripts) 1.815 + && (this.allowsInlineStyles === that.allowsInlineStyles); 1.816 + }, 1.817 + 1.818 + /** 1.819 + * Generates canonical string representation of the policy. 1.820 + */ 1.821 + toString: 1.822 + function csp_toString() { 1.823 + var dirs = []; 1.824 + 1.825 + if (!this._specCompliant && (this._allowEval || this._allowInlineScripts)) { 1.826 + dirs.push("options" + (this._allowEval ? " eval-script" : "") 1.827 + + (this._allowInlineScripts ? " inline-script" : "")); 1.828 + } 1.829 + for (var i in this._directives) { 1.830 + if (this._directives[i]) { 1.831 + dirs.push(i + " " + this._directives[i].toString()); 1.832 + } 1.833 + } 1.834 + return dirs.join("; "); 1.835 + }, 1.836 + 1.837 + permitsNonce: 1.838 + function csp_permitsNonce(aNonce, aDirective) { 1.839 + if (!this._directives.hasOwnProperty(aDirective)) return false; 1.840 + return this._directives[aDirective]._sources.some(function (source) { 1.841 + return source instanceof CSPNonceSource && source.permits(aNonce); 1.842 + }); 1.843 + }, 1.844 + 1.845 + permitsHash: 1.846 + function csp_permitsHash(aContent, aDirective) { 1.847 + if (!this._directives.hasOwnProperty(aDirective)) return false; 1.848 + return this._directives[aDirective]._sources.some(function (source) { 1.849 + return source instanceof CSPHashSource && source.permits(aContent); 1.850 + }); 1.851 + }, 1.852 + 1.853 + /** 1.854 + * Determines if this policy accepts a URI. 1.855 + * @param aURI 1.856 + * URI of the requested resource 1.857 + * @param aDirective 1.858 + * one of the SRC_DIRECTIVES defined above 1.859 + * @returns 1.860 + * true if the policy permits the URI in given context. 1.861 + */ 1.862 + permits: 1.863 + function csp_permits(aURI, aDirective) { 1.864 + if (!aURI) return false; 1.865 + 1.866 + // GLOBALLY ALLOW "about:" SCHEME 1.867 + if (aURI instanceof String && aURI.substring(0,6) === "about:") 1.868 + return true; 1.869 + if (aURI instanceof Ci.nsIURI && aURI.scheme === "about") 1.870 + return true; 1.871 + 1.872 + // make sure the right directive set is used 1.873 + let DIRS = this._specCompliant ? CSPRep.SRC_DIRECTIVES_NEW : CSPRep.SRC_DIRECTIVES_OLD; 1.874 + 1.875 + let directiveInPolicy = false; 1.876 + for (var i in DIRS) { 1.877 + if (DIRS[i] === aDirective) { 1.878 + // for catching calls with invalid contexts (below) 1.879 + directiveInPolicy = true; 1.880 + if (this._directives.hasOwnProperty(aDirective)) { 1.881 + return this._directives[aDirective].permits(aURI); 1.882 + } 1.883 + //found matching dir, can stop looking 1.884 + break; 1.885 + } 1.886 + } 1.887 + 1.888 + // frame-ancestors is a special case; it doesn't fall back to default-src. 1.889 + if (aDirective === DIRS.FRAME_ANCESTORS) 1.890 + return true; 1.891 + 1.892 + // All directives that don't fall back to default-src should have an escape 1.893 + // hatch above (like frame-ancestors). 1.894 + if (!directiveInPolicy) { 1.895 + // if this code runs, there's probably something calling permits() that 1.896 + // shouldn't be calling permits(). 1.897 + CSPdebug("permits called with invalid load type: " + aDirective); 1.898 + return false; 1.899 + } 1.900 + 1.901 + // no directives specifically matched, fall back to default-src. 1.902 + // (default-src may not be present for CSP 1.0-compliant policies, and 1.903 + // indicates no relevant directives were present and the load should be 1.904 + // permitted). 1.905 + if (this._directives.hasOwnProperty(DIRS.DEFAULT_SRC)) { 1.906 + return this._directives[DIRS.DEFAULT_SRC].permits(aURI); 1.907 + } 1.908 + 1.909 + // no relevant directives present -- this means for CSP 1.0 that the load 1.910 + // should be permitted (and for the old CSP, to block it). 1.911 + return this._specCompliant; 1.912 + }, 1.913 + 1.914 + /** 1.915 + * Returns true if "eval" is enabled through the "eval" keyword. 1.916 + */ 1.917 + get allowsEvalInScripts () { 1.918 + return this._allowEval; 1.919 + }, 1.920 + 1.921 + /** 1.922 + * Returns true if inline scripts are enabled through the "inline" 1.923 + * keyword. 1.924 + */ 1.925 + get allowsInlineScripts () { 1.926 + return this._allowInlineScripts; 1.927 + }, 1.928 + 1.929 + /** 1.930 + * Returns true if inline styles are enabled through the "inline-style" 1.931 + * keyword. 1.932 + */ 1.933 + get allowsInlineStyles () { 1.934 + return this._allowInlineStyles; 1.935 + }, 1.936 + 1.937 + /** 1.938 + * Sends a message to the error console and web developer console. 1.939 + * @param aFlag 1.940 + * The nsIScriptError flag constant indicating severity 1.941 + * @param aMsg 1.942 + * The message to send 1.943 + * @param aSource (optional) 1.944 + * The URL of the file in which the error occurred 1.945 + * @param aScriptLine (optional) 1.946 + * The line in the source file which the error occurred 1.947 + * @param aLineNum (optional) 1.948 + * The number of the line where the error occurred 1.949 + */ 1.950 + log: 1.951 + function cspd_log(aFlag, aMsg, aSource, aScriptLine, aLineNum) { 1.952 + var textMessage = "Content Security Policy: " + aMsg; 1.953 + var consoleMsg = Components.classes["@mozilla.org/scripterror;1"] 1.954 + .createInstance(Ci.nsIScriptError); 1.955 + if (this._innerWindowID) { 1.956 + consoleMsg.initWithWindowID(textMessage, aSource, aScriptLine, aLineNum, 1.957 + 0, aFlag, 1.958 + "CSP", 1.959 + this._innerWindowID); 1.960 + } else { 1.961 + consoleMsg.init(textMessage, aSource, aScriptLine, aLineNum, 0, 1.962 + aFlag, 1.963 + "CSP"); 1.964 + } 1.965 + Components.classes["@mozilla.org/consoleservice;1"] 1.966 + .getService(Ci.nsIConsoleService).logMessage(consoleMsg); 1.967 + }, 1.968 + 1.969 +}; 1.970 + 1.971 +////////////////////////////////////////////////////////////////////// 1.972 +/** 1.973 + * Class to represent a list of sources 1.974 + */ 1.975 +this.CSPSourceList = function CSPSourceList() { 1.976 + this._sources = []; 1.977 + this._permitAllSources = false; 1.978 + 1.979 + // When this is true, the source list contains 'unsafe-inline'. 1.980 + this._allowUnsafeInline = false; 1.981 + 1.982 + // When this is true, the source list contains 'unsafe-eval'. 1.983 + this._allowUnsafeEval = false; 1.984 + 1.985 + // When this is true, the source list contains at least one nonce-source 1.986 + this._hasNonceSource = false; 1.987 + 1.988 + // When this is true, the source list contains at least one hash-source 1.989 + this._hasHashSource = false; 1.990 +} 1.991 + 1.992 +/** 1.993 + * Factory to create a new CSPSourceList, parsed from a string. 1.994 + * 1.995 + * @param aStr 1.996 + * string rep of a CSP Source List 1.997 + * @param aCSPRep 1.998 + * the CSPRep to which this souce list belongs. If null, CSP errors and 1.999 + * warnings will not be sent to the web console. 1.1000 + * @param self (optional) 1.1001 + * URI or CSPSource representing the "self" source 1.1002 + * @param enforceSelfChecks (optional) 1.1003 + * if present, and "true", will check to be sure "self" has the 1.1004 + * appropriate values to inherit when they are omitted from the source. 1.1005 + * @returns 1.1006 + * an instance of CSPSourceList 1.1007 + */ 1.1008 +CSPSourceList.fromString = function(aStr, aCSPRep, self, enforceSelfChecks) { 1.1009 + // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ] 1.1010 + // / *WSP "'none'" *WSP 1.1011 + 1.1012 + /* If self parameter is passed, convert to CSPSource, 1.1013 + unless it is already a CSPSource. */ 1.1014 + if (self && !(self instanceof CSPSource)) { 1.1015 + self = CSPSource.create(self, aCSPRep); 1.1016 + } 1.1017 + 1.1018 + var slObj = new CSPSourceList(); 1.1019 + slObj._CSPRep = aCSPRep; 1.1020 + aStr = aStr.trim(); 1.1021 + // w3 specifies case insensitive equality 1.1022 + if (aStr.toLowerCase() === "'none'") { 1.1023 + slObj._permitAllSources = false; 1.1024 + return slObj; 1.1025 + } 1.1026 + 1.1027 + var tokens = aStr.split(/\s+/); 1.1028 + for (var i in tokens) { 1.1029 + if (!R_SOURCEEXP.test(tokens[i])) { 1.1030 + cspWarn(aCSPRep, 1.1031 + CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource", 1.1032 + [tokens[i]])); 1.1033 + continue; 1.1034 + } 1.1035 + var src = CSPSource.create(tokens[i], aCSPRep, self, enforceSelfChecks); 1.1036 + if (!src) { 1.1037 + cspWarn(aCSPRep, 1.1038 + CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource", 1.1039 + [tokens[i]])); 1.1040 + continue; 1.1041 + } 1.1042 + 1.1043 + // if a source allows unsafe-inline, set our flag to indicate this. 1.1044 + if (src._allowUnsafeInline) 1.1045 + slObj._allowUnsafeInline = true; 1.1046 + 1.1047 + // if a source allows unsafe-eval, set our flag to indicate this. 1.1048 + if (src._allowUnsafeEval) 1.1049 + slObj._allowUnsafeEval = true; 1.1050 + 1.1051 + if (src instanceof CSPNonceSource) 1.1052 + slObj._hasNonceSource = true; 1.1053 + 1.1054 + if (src instanceof CSPHashSource) 1.1055 + slObj._hasHashSource = true; 1.1056 + 1.1057 + // if a source is a *, then we can permit all sources 1.1058 + if (src.permitAll) { 1.1059 + slObj._permitAllSources = true; 1.1060 + } else { 1.1061 + slObj._sources.push(src); 1.1062 + } 1.1063 + } 1.1064 + 1.1065 + return slObj; 1.1066 +}; 1.1067 + 1.1068 +CSPSourceList.prototype = { 1.1069 + /** 1.1070 + * Compares one CSPSourceList to another. 1.1071 + * 1.1072 + * @param that 1.1073 + * another CSPSourceList 1.1074 + * @returns 1.1075 + * true if they have the same data 1.1076 + */ 1.1077 + equals: 1.1078 + function(that) { 1.1079 + // special case to default-src * and 'none' to look different 1.1080 + // (both have a ._sources.length of 0). 1.1081 + if (that._permitAllSources != this._permitAllSources) { 1.1082 + return false; 1.1083 + } 1.1084 + if (that._sources.length != this._sources.length) { 1.1085 + return false; 1.1086 + } 1.1087 + // sort both arrays and compare like a zipper 1.1088 + // XXX (sid): I think we can make this more efficient 1.1089 + var sortfn = function(a,b) { 1.1090 + return a.toString.toLowerCase() > b.toString.toLowerCase(); 1.1091 + }; 1.1092 + var a_sorted = this._sources.sort(sortfn); 1.1093 + var b_sorted = that._sources.sort(sortfn); 1.1094 + for (var i in a_sorted) { 1.1095 + if (!a_sorted[i].equals(b_sorted[i])) { 1.1096 + return false; 1.1097 + } 1.1098 + } 1.1099 + return true; 1.1100 + }, 1.1101 + 1.1102 + /** 1.1103 + * Generates canonical string representation of the Source List. 1.1104 + */ 1.1105 + toString: 1.1106 + function() { 1.1107 + if (this.isNone()) { 1.1108 + return "'none'"; 1.1109 + } 1.1110 + if (this._permitAllSources) { 1.1111 + return "*"; 1.1112 + } 1.1113 + return this._sources.map(function(x) { return x.toString(); }).join(" "); 1.1114 + }, 1.1115 + 1.1116 + /** 1.1117 + * Returns whether or not this source list represents the "'none'" special 1.1118 + * case. 1.1119 + */ 1.1120 + isNone: 1.1121 + function() { 1.1122 + return (!this._permitAllSources) && (this._sources.length < 1); 1.1123 + }, 1.1124 + 1.1125 + /** 1.1126 + * Returns whether or not this source list permits all sources (*). 1.1127 + */ 1.1128 + isAll: 1.1129 + function() { 1.1130 + return this._permitAllSources; 1.1131 + }, 1.1132 + 1.1133 + /** 1.1134 + * Makes a new deep copy of this object. 1.1135 + * @returns 1.1136 + * a new CSPSourceList 1.1137 + */ 1.1138 + clone: 1.1139 + function() { 1.1140 + var aSL = new CSPSourceList(); 1.1141 + aSL._permitAllSources = this._permitAllSources; 1.1142 + aSL._CSPRep = this._CSPRep; 1.1143 + for (var i in this._sources) { 1.1144 + aSL._sources[i] = this._sources[i].clone(); 1.1145 + } 1.1146 + return aSL; 1.1147 + }, 1.1148 + 1.1149 + /** 1.1150 + * Determines if this directive accepts a URI. 1.1151 + * @param aURI 1.1152 + * the URI in question 1.1153 + * @returns 1.1154 + * true if the URI matches a source in this source list. 1.1155 + */ 1.1156 + permits: 1.1157 + function cspsd_permits(aURI) { 1.1158 + if (this.isNone()) return false; 1.1159 + if (this.isAll()) return true; 1.1160 + 1.1161 + for (var i in this._sources) { 1.1162 + if (this._sources[i].permits(aURI)) { 1.1163 + return true; 1.1164 + } 1.1165 + } 1.1166 + return false; 1.1167 + } 1.1168 +} 1.1169 + 1.1170 +////////////////////////////////////////////////////////////////////// 1.1171 +/** 1.1172 + * Class to model a source (scheme, host, port) 1.1173 + */ 1.1174 +this.CSPSource = function CSPSource() { 1.1175 + this._scheme = undefined; 1.1176 + this._port = undefined; 1.1177 + this._host = undefined; 1.1178 + 1.1179 + //when set to true, this allows all source 1.1180 + this._permitAll = false; 1.1181 + 1.1182 + // when set to true, this source represents 'self' 1.1183 + this._isSelf = false; 1.1184 + 1.1185 + // when set to true, this source allows inline scripts or styles 1.1186 + this._allowUnsafeInline = false; 1.1187 + 1.1188 + // when set to true, this source allows eval to be used 1.1189 + this._allowUnsafeEval = false; 1.1190 +} 1.1191 + 1.1192 +/** 1.1193 + * General factory method to create a new source from one of the following 1.1194 + * types: 1.1195 + * - nsURI 1.1196 + * - string 1.1197 + * - CSPSource (clone) 1.1198 + * @param aData 1.1199 + * string, nsURI, or CSPSource 1.1200 + * @param aCSPRep 1.1201 + * The CSPRep this source belongs to. If null, CSP errors and warnings 1.1202 + * will not be sent to the web console. 1.1203 + * @param self (optional) 1.1204 + * if present, string, URI, or CSPSource representing the "self" resource 1.1205 + * @param enforceSelfChecks (optional) 1.1206 + * if present, and "true", will check to be sure "self" has the 1.1207 + * appropriate values to inherit when they are omitted from the source. 1.1208 + * @returns 1.1209 + * an instance of CSPSource 1.1210 + */ 1.1211 +CSPSource.create = function(aData, aCSPRep, self, enforceSelfChecks) { 1.1212 + if (typeof aData === 'string') 1.1213 + return CSPSource.fromString(aData, aCSPRep, self, enforceSelfChecks); 1.1214 + 1.1215 + if (aData instanceof Ci.nsIURI) { 1.1216 + // clean userpass out of the URI (not used for CSP origin checking, but 1.1217 + // shows up in prePath). 1.1218 + let cleanedUri = aData.cloneIgnoringRef(); 1.1219 + try { 1.1220 + // GetUserPass throws for some protocols without userPass 1.1221 + cleanedUri.userPass = ''; 1.1222 + } catch (ex) {} 1.1223 + 1.1224 + return CSPSource.fromURI(cleanedUri, aCSPRep, self, enforceSelfChecks); 1.1225 + } 1.1226 + 1.1227 + if (aData instanceof CSPSource) { 1.1228 + var ns = aData.clone(); 1.1229 + ns._self = CSPSource.create(self); 1.1230 + return ns; 1.1231 + } 1.1232 + 1.1233 + return null; 1.1234 +} 1.1235 + 1.1236 +/** 1.1237 + * Factory to create a new CSPSource, from a nsIURI. 1.1238 + * 1.1239 + * Don't use this if you want to wildcard ports! 1.1240 + * 1.1241 + * @param aURI 1.1242 + * nsIURI rep of a URI 1.1243 + * @param aCSPRep 1.1244 + * The policy this source belongs to. If null, CSP errors and warnings 1.1245 + * will not be sent to the web console. 1.1246 + * @param self (optional) 1.1247 + * string or CSPSource representing the "self" source 1.1248 + * @param enforceSelfChecks (optional) 1.1249 + * if present, and "true", will check to be sure "self" has the 1.1250 + * appropriate values to inherit when they are omitted from aURI. 1.1251 + * @returns 1.1252 + * an instance of CSPSource 1.1253 + */ 1.1254 +CSPSource.fromURI = function(aURI, aCSPRep, self, enforceSelfChecks) { 1.1255 + if (!(aURI instanceof Ci.nsIURI)) { 1.1256 + cspError(aCSPRep, CSPLocalizer.getStr("cspSourceNotURI")); 1.1257 + return null; 1.1258 + } 1.1259 + 1.1260 + if (!self && enforceSelfChecks) { 1.1261 + cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided")); 1.1262 + return null; 1.1263 + } 1.1264 + 1.1265 + if (self && !(self instanceof CSPSource)) { 1.1266 + self = CSPSource.create(self, aCSPRep, undefined, false); 1.1267 + } 1.1268 + 1.1269 + var sObj = new CSPSource(); 1.1270 + sObj._self = self; 1.1271 + sObj._CSPRep = aCSPRep; 1.1272 + 1.1273 + // PARSE 1.1274 + // If 'self' is undefined, then use default port for scheme if there is one. 1.1275 + 1.1276 + // grab scheme (if there is one) 1.1277 + try { 1.1278 + sObj._scheme = aURI.scheme; 1.1279 + } catch(e) { 1.1280 + sObj._scheme = undefined; 1.1281 + cspError(aCSPRep, CSPLocalizer.getFormatStr("uriWithoutScheme", 1.1282 + [aURI.asciiSpec])); 1.1283 + return null; 1.1284 + } 1.1285 + 1.1286 + // grab host (if there is one) 1.1287 + try { 1.1288 + // if there's no host, an exception will get thrown 1.1289 + // (NS_ERROR_FAILURE) 1.1290 + sObj._host = CSPHost.fromString(aURI.host); 1.1291 + } catch(e) { 1.1292 + sObj._host = undefined; 1.1293 + } 1.1294 + 1.1295 + // grab port (if there is one) 1.1296 + // creating a source from an nsURI is limited in that one cannot specify "*" 1.1297 + // for port. In fact, there's no way to represent "*" differently than 1.1298 + // a blank port in an nsURI, since "*" turns into -1, and so does an 1.1299 + // absence of port declaration. 1.1300 + 1.1301 + // port is never inherited from self -- this gets too confusing. 1.1302 + // Instead, whatever scheme is used (an explicit one or the inherited 1.1303 + // one) dictates the port if no port is explicitly stated. 1.1304 + // Set it to undefined here and the default port will be resolved in the 1.1305 + // getter for .port. 1.1306 + sObj._port = undefined; 1.1307 + try { 1.1308 + // if there's no port, an exception will get thrown 1.1309 + // (NS_ERROR_FAILURE) 1.1310 + if (aURI.port > 0) { 1.1311 + sObj._port = aURI.port; 1.1312 + } 1.1313 + } catch(e) { 1.1314 + sObj._port = undefined; 1.1315 + } 1.1316 + 1.1317 + return sObj; 1.1318 +}; 1.1319 + 1.1320 +/** 1.1321 + * Factory to create a new CSPSource, parsed from a string. 1.1322 + * 1.1323 + * @param aStr 1.1324 + * string rep of a CSP Source 1.1325 + * @param aCSPRep 1.1326 + * the CSPRep this CSPSource belongs to 1.1327 + * @param self (optional) 1.1328 + * string, URI, or CSPSource representing the "self" source 1.1329 + * @param enforceSelfChecks (optional) 1.1330 + * if present, and "true", will check to be sure "self" has the 1.1331 + * appropriate values to inherit when they are omitted from aURI. 1.1332 + * @returns 1.1333 + * an instance of CSPSource 1.1334 + */ 1.1335 +CSPSource.fromString = function(aStr, aCSPRep, self, enforceSelfChecks) { 1.1336 + if (!aStr) 1.1337 + return null; 1.1338 + 1.1339 + if (!(typeof aStr === 'string')) { 1.1340 + cspError(aCSPRep, CSPLocalizer.getStr("argumentIsNotString")); 1.1341 + return null; 1.1342 + } 1.1343 + 1.1344 + var sObj = new CSPSource(); 1.1345 + sObj._self = self; 1.1346 + sObj._CSPRep = aCSPRep; 1.1347 + 1.1348 + 1.1349 + // if equal, return does match 1.1350 + if (aStr === "*") { 1.1351 + sObj._permitAll = true; 1.1352 + return sObj; 1.1353 + } 1.1354 + 1.1355 + if (!self && enforceSelfChecks) { 1.1356 + cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided")); 1.1357 + return null; 1.1358 + } 1.1359 + 1.1360 + if (self && !(self instanceof CSPSource)) { 1.1361 + self = CSPSource.create(self, aCSPRep, undefined, false); 1.1362 + } 1.1363 + 1.1364 + // check for 'unsafe-inline' (case insensitive) 1.1365 + if (aStr.toLowerCase() === "'unsafe-inline'"){ 1.1366 + sObj._allowUnsafeInline = true; 1.1367 + return sObj; 1.1368 + } 1.1369 + 1.1370 + // check for 'unsafe-eval' (case insensitive) 1.1371 + if (aStr.toLowerCase() === "'unsafe-eval'"){ 1.1372 + sObj._allowUnsafeEval = true; 1.1373 + return sObj; 1.1374 + } 1.1375 + 1.1376 + // Check for scheme-source match - this only matches if the source 1.1377 + // string is just a scheme with no host. 1.1378 + if (R_SCHEMESRC.test(aStr)) { 1.1379 + var schemeSrcMatch = R_GETSCHEME.exec(aStr); 1.1380 + sObj._scheme = schemeSrcMatch[0]; 1.1381 + if (!sObj._host) sObj._host = CSPHost.fromString("*"); 1.1382 + if (!sObj._port) sObj._port = "*"; 1.1383 + return sObj; 1.1384 + } 1.1385 + 1.1386 + // check for host-source or ext-host-source match 1.1387 + if (R_HOSTSRC.test(aStr) || R_EXTHOSTSRC.test(aStr)) { 1.1388 + var schemeMatch = R_GETSCHEME.exec(aStr); 1.1389 + // check that the scheme isn't accidentally matching the host. There should 1.1390 + // be '://' if there is a valid scheme in an (EXT)HOSTSRC 1.1391 + if (!schemeMatch || aStr.indexOf("://") == -1) { 1.1392 + sObj._scheme = self.scheme; 1.1393 + schemeMatch = null; 1.1394 + } else { 1.1395 + sObj._scheme = schemeMatch[0]; 1.1396 + } 1.1397 + 1.1398 + // Bug 916054: in CSP 1.0, source-expressions that are paths should have 1.1399 + // the path after the origin ignored and only the origin enforced. 1.1400 + if (R_EXTHOSTSRC.test(aStr)) { 1.1401 + var extHostMatch = R_EXTHOSTSRC.exec(aStr); 1.1402 + aStr = extHostMatch[1]; 1.1403 + } 1.1404 + 1.1405 + var hostMatch = R_HOSTSRC.exec(aStr); 1.1406 + if (!hostMatch) { 1.1407 + cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource", 1.1408 + [aStr])); 1.1409 + return null; 1.1410 + } 1.1411 + // Host regex gets scheme, so remove scheme from aStr. Add 3 for '://' 1.1412 + if (schemeMatch) { 1.1413 + hostMatch = R_HOSTSRC.exec(aStr.substring(schemeMatch[0].length + 3)); 1.1414 + } 1.1415 + 1.1416 + var portMatch = R_PORT.exec(hostMatch); 1.1417 + // Host regex also gets port, so remove the port here. 1.1418 + if (portMatch) { 1.1419 + hostMatch = R_HOSTSRC.exec(hostMatch[0].substring(0, hostMatch[0].length - portMatch[0].length)); 1.1420 + } 1.1421 + 1.1422 + sObj._host = CSPHost.fromString(hostMatch[0]); 1.1423 + if (!portMatch) { 1.1424 + // gets the default port for the given scheme 1.1425 + var defPort = Services.io.getProtocolHandler(sObj._scheme).defaultPort; 1.1426 + if (!defPort) { 1.1427 + cspError(aCSPRep, 1.1428 + CSPLocalizer.getFormatStr("couldntParseInvalidSource", 1.1429 + [aStr])); 1.1430 + return null; 1.1431 + } 1.1432 + sObj._port = defPort; 1.1433 + } 1.1434 + else { 1.1435 + // strip the ':' from the port 1.1436 + sObj._port = portMatch[0].substr(1); 1.1437 + } 1.1438 + // A CSP keyword without quotes is a valid hostname, but this can also be a mistake. 1.1439 + // Raise a CSP warning in the web console to developer to check his/her intent. 1.1440 + if (R_QUOTELESS_KEYWORDS.test(aStr)) { 1.1441 + cspWarn(aCSPRep, CSPLocalizer.getFormatStr("hostNameMightBeKeyword", 1.1442 + [aStr, aStr.toLowerCase()])); 1.1443 + } 1.1444 + return sObj; 1.1445 + } 1.1446 + 1.1447 + // check for a nonce-source match 1.1448 + if (R_NONCESRC.test(aStr)) { 1.1449 + return CSPNonceSource.fromString(aStr, aCSPRep); 1.1450 + } 1.1451 + 1.1452 + // check for a hash-source match 1.1453 + if (R_HASHSRC.test(aStr)) { 1.1454 + return CSPHashSource.fromString(aStr, aCSPRep); 1.1455 + } 1.1456 + 1.1457 + // check for 'self' (case insensitive) 1.1458 + if (aStr.toLowerCase() === "'self'") { 1.1459 + if (!self) { 1.1460 + cspError(aCSPRep, CSPLocalizer.getStr("selfKeywordNoSelfData")); 1.1461 + return null; 1.1462 + } 1.1463 + sObj._self = self.clone(); 1.1464 + sObj._isSelf = true; 1.1465 + return sObj; 1.1466 + } 1.1467 + 1.1468 + cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource", 1.1469 + [aStr])); 1.1470 + return null; 1.1471 +}; 1.1472 + 1.1473 +CSPSource.validSchemeName = function(aStr) { 1.1474 + // <scheme-name> ::= <alpha><scheme-suffix> 1.1475 + // <scheme-suffix> ::= <scheme-chr> 1.1476 + // | <scheme-suffix><scheme-chr> 1.1477 + // <scheme-chr> ::= <letter> | <digit> | "+" | "." | "-" 1.1478 + 1.1479 + return aStr.match(/^[a-zA-Z][a-zA-Z0-9+.-]*$/); 1.1480 +}; 1.1481 + 1.1482 +CSPSource.prototype = { 1.1483 + 1.1484 + get scheme () { 1.1485 + if (this._isSelf && this._self) 1.1486 + return this._self.scheme; 1.1487 + if (!this._scheme && this._self) 1.1488 + return this._self.scheme; 1.1489 + return this._scheme; 1.1490 + }, 1.1491 + 1.1492 + get host () { 1.1493 + if (this._isSelf && this._self) 1.1494 + return this._self.host; 1.1495 + if (!this._host && this._self) 1.1496 + return this._self.host; 1.1497 + return this._host; 1.1498 + }, 1.1499 + 1.1500 + get permitAll () { 1.1501 + if (this._isSelf && this._self) 1.1502 + return this._self.permitAll; 1.1503 + return this._permitAll; 1.1504 + }, 1.1505 + 1.1506 + /** 1.1507 + * If this doesn't have a nonstandard port (hard-defined), use the default 1.1508 + * port for this source's scheme. Should never inherit port from 'self'. 1.1509 + */ 1.1510 + get port () { 1.1511 + if (this._isSelf && this._self) 1.1512 + return this._self.port; 1.1513 + if (this._port) return this._port; 1.1514 + // if no port, get the default port for the scheme 1.1515 + // (which may be inherited from 'self') 1.1516 + if (this.scheme) { 1.1517 + try { 1.1518 + var port = gIoService.getProtocolHandler(this.scheme).defaultPort; 1.1519 + if (port > 0) return port; 1.1520 + } catch(e) { 1.1521 + // if any errors happen, fail gracefully. 1.1522 + } 1.1523 + } 1.1524 + 1.1525 + return undefined; 1.1526 + }, 1.1527 + 1.1528 + /** 1.1529 + * Generates canonical string representation of the Source. 1.1530 + */ 1.1531 + toString: 1.1532 + function() { 1.1533 + if (this._isSelf) 1.1534 + return this._self.toString(); 1.1535 + 1.1536 + if (this._allowUnsafeInline) 1.1537 + return "'unsafe-inline'"; 1.1538 + 1.1539 + if (this._allowUnsafeEval) 1.1540 + return "'unsafe-eval'"; 1.1541 + 1.1542 + var s = ""; 1.1543 + if (this.scheme) 1.1544 + s = s + this.scheme + "://"; 1.1545 + if (this._host) 1.1546 + s = s + this._host; 1.1547 + if (this.port) 1.1548 + s = s + ":" + this.port; 1.1549 + return s; 1.1550 + }, 1.1551 + 1.1552 + /** 1.1553 + * Makes a new deep copy of this object. 1.1554 + * @returns 1.1555 + * a new CSPSource 1.1556 + */ 1.1557 + clone: 1.1558 + function() { 1.1559 + var aClone = new CSPSource(); 1.1560 + aClone._self = this._self ? this._self.clone() : undefined; 1.1561 + aClone._scheme = this._scheme; 1.1562 + aClone._port = this._port; 1.1563 + aClone._host = this._host ? this._host.clone() : undefined; 1.1564 + aClone._isSelf = this._isSelf; 1.1565 + aClone._CSPRep = this._CSPRep; 1.1566 + return aClone; 1.1567 + }, 1.1568 + 1.1569 + /** 1.1570 + * Determines if this Source accepts a URI. 1.1571 + * @param aSource 1.1572 + * the URI, or CSPSource in question 1.1573 + * @returns 1.1574 + * true if the URI matches a source in this source list. 1.1575 + */ 1.1576 + permits: 1.1577 + function(aSource) { 1.1578 + if (!aSource) return false; 1.1579 + 1.1580 + if (!(aSource instanceof CSPSource)) 1.1581 + aSource = CSPSource.create(aSource, this._CSPRep); 1.1582 + 1.1583 + // verify scheme 1.1584 + if (this.scheme.toLowerCase() != aSource.scheme.toLowerCase()) 1.1585 + return false; 1.1586 + 1.1587 + // port is defined in 'this' (undefined means it may not be relevant 1.1588 + // to the scheme) AND this port (implicit or explicit) matches 1.1589 + // aSource's port 1.1590 + if (this.port && this.port !== "*" && this.port != aSource.port) 1.1591 + return false; 1.1592 + 1.1593 + // host is defined in 'this' (undefined means it may not be relevant 1.1594 + // to the scheme) AND this host (implicit or explicit) permits 1.1595 + // aSource's host. 1.1596 + if (this.host && !this.host.permits(aSource.host)) 1.1597 + return false; 1.1598 + 1.1599 + // all scheme, host and port matched! 1.1600 + return true; 1.1601 + }, 1.1602 + 1.1603 + /** 1.1604 + * Compares one CSPSource to another. 1.1605 + * 1.1606 + * @param that 1.1607 + * another CSPSource 1.1608 + * @param resolveSelf (optional) 1.1609 + * if present, and 'true', implied values are obtained from 'self' 1.1610 + * instead of assumed to be "anything" 1.1611 + * @returns 1.1612 + * true if they have the same data 1.1613 + */ 1.1614 + equals: 1.1615 + function(that, resolveSelf) { 1.1616 + // 1. schemes match 1.1617 + // 2. ports match 1.1618 + // 3. either both hosts are undefined, or one equals the other. 1.1619 + if (resolveSelf) 1.1620 + return this.scheme.toLowerCase() === that.scheme.toLowerCase() 1.1621 + && this.port === that.port 1.1622 + && (!(this.host || that.host) || 1.1623 + (this.host && this.host.equals(that.host))); 1.1624 + 1.1625 + // otherwise, compare raw (non-self-resolved values) 1.1626 + return this._scheme.toLowerCase() === that._scheme.toLowerCase() 1.1627 + && this._port === that._port 1.1628 + && (!(this._host || that._host) || 1.1629 + (this._host && this._host.equals(that._host))); 1.1630 + }, 1.1631 + 1.1632 +}; 1.1633 + 1.1634 +////////////////////////////////////////////////////////////////////// 1.1635 +/** 1.1636 + * Class to model a host *.x.y. 1.1637 + */ 1.1638 +this.CSPHost = function CSPHost() { 1.1639 + this._segments = []; 1.1640 +} 1.1641 + 1.1642 +/** 1.1643 + * Factory to create a new CSPHost, parsed from a string. 1.1644 + * 1.1645 + * @param aStr 1.1646 + * string rep of a CSP Host 1.1647 + * @returns 1.1648 + * an instance of CSPHost 1.1649 + */ 1.1650 +CSPHost.fromString = function(aStr) { 1.1651 + if (!aStr) return null; 1.1652 + 1.1653 + // host string must be LDH with dots and stars. 1.1654 + var invalidChar = aStr.match(R_INV_HCHAR); 1.1655 + if (invalidChar) { 1.1656 + CSPdebug("Invalid character '" + invalidChar + "' in host " + aStr); 1.1657 + return null; 1.1658 + } 1.1659 + 1.1660 + var hObj = new CSPHost(); 1.1661 + hObj._segments = aStr.split(/\./); 1.1662 + if (hObj._segments.length < 1) 1.1663 + return null; 1.1664 + 1.1665 + // validate data in segments 1.1666 + for (var i in hObj._segments) { 1.1667 + var seg = hObj._segments[i]; 1.1668 + if (seg == "*") { 1.1669 + if (i > 0) { 1.1670 + // Wildcard must be FIRST 1.1671 + CSPdebug("Wildcard char located at invalid position in '" + aStr + "'"); 1.1672 + return null; 1.1673 + } 1.1674 + } 1.1675 + else if (seg.match(R_COMP_HCHAR)) { 1.1676 + // Non-wildcard segment must be LDH string 1.1677 + CSPdebug("Invalid segment '" + seg + "' in host value"); 1.1678 + return null; 1.1679 + } 1.1680 + } 1.1681 + return hObj; 1.1682 +}; 1.1683 + 1.1684 +CSPHost.prototype = { 1.1685 + /** 1.1686 + * Generates canonical string representation of the Host. 1.1687 + */ 1.1688 + toString: 1.1689 + function() { 1.1690 + return this._segments.join("."); 1.1691 + }, 1.1692 + 1.1693 + /** 1.1694 + * Makes a new deep copy of this object. 1.1695 + * @returns 1.1696 + * a new CSPHost 1.1697 + */ 1.1698 + clone: 1.1699 + function() { 1.1700 + var aHost = new CSPHost(); 1.1701 + for (var i in this._segments) { 1.1702 + aHost._segments[i] = this._segments[i]; 1.1703 + } 1.1704 + return aHost; 1.1705 + }, 1.1706 + 1.1707 + /** 1.1708 + * Returns true if this host accepts the provided host (or the other way 1.1709 + * around). 1.1710 + * @param aHost 1.1711 + * the FQDN in question (CSPHost or String) 1.1712 + * @returns 1.1713 + */ 1.1714 + permits: 1.1715 + function(aHost) { 1.1716 + if (!aHost) { 1.1717 + aHost = CSPHost.fromString("*"); 1.1718 + } 1.1719 + 1.1720 + if (!(aHost instanceof CSPHost)) { 1.1721 + // -- compare CSPHost to String 1.1722 + aHost = CSPHost.fromString(aHost); 1.1723 + } 1.1724 + var thislen = this._segments.length; 1.1725 + var thatlen = aHost._segments.length; 1.1726 + 1.1727 + // don't accept a less specific host: 1.1728 + // \--> *.b.a doesn't accept b.a. 1.1729 + if (thatlen < thislen) { return false; } 1.1730 + 1.1731 + // check for more specific host (and wildcard): 1.1732 + // \--> *.b.a accepts d.c.b.a. 1.1733 + // \--> c.b.a doesn't accept d.c.b.a. 1.1734 + if ((thatlen > thislen) && this._segments[0] != "*") { 1.1735 + return false; 1.1736 + } 1.1737 + 1.1738 + // Given the wildcard condition (from above), 1.1739 + // only necessary to compare elements that are present 1.1740 + // in this host. Extra tokens in aHost are ok. 1.1741 + // * Compare from right to left. 1.1742 + for (var i=1; i <= thislen; i++) { 1.1743 + if (this._segments[thislen-i] != "*" && 1.1744 + (this._segments[thislen-i].toLowerCase() != 1.1745 + aHost._segments[thatlen-i].toLowerCase())) { 1.1746 + return false; 1.1747 + } 1.1748 + } 1.1749 + 1.1750 + // at this point, all conditions are met, so the host is allowed 1.1751 + return true; 1.1752 + }, 1.1753 + 1.1754 + /** 1.1755 + * Compares one CSPHost to another. 1.1756 + * 1.1757 + * @param that 1.1758 + * another CSPHost 1.1759 + * @returns 1.1760 + * true if they have the same data 1.1761 + */ 1.1762 + equals: 1.1763 + function(that) { 1.1764 + if (this._segments.length != that._segments.length) 1.1765 + return false; 1.1766 + 1.1767 + for (var i=0; i<this._segments.length; i++) { 1.1768 + if (this._segments[i].toLowerCase() != 1.1769 + that._segments[i].toLowerCase()) { 1.1770 + return false; 1.1771 + } 1.1772 + } 1.1773 + return true; 1.1774 + } 1.1775 +}; 1.1776 + 1.1777 +this.CSPNonceSource = function CSPNonceSource() { 1.1778 + this._nonce = undefined; 1.1779 +} 1.1780 + 1.1781 +CSPNonceSource.fromString = function(aStr, aCSPRep) { 1.1782 + let nonce = R_NONCESRC.exec(aStr)[1]; 1.1783 + if (!nonce) { 1.1784 + cspError(aCSPRep, "Error in parsing nonce-source from string: nonce was empty"); 1.1785 + return null; 1.1786 + } 1.1787 + 1.1788 + let nonceSourceObj = new CSPNonceSource(); 1.1789 + nonceSourceObj._nonce = nonce; 1.1790 + return nonceSourceObj; 1.1791 +}; 1.1792 + 1.1793 +CSPNonceSource.prototype = { 1.1794 + 1.1795 + permits: function(aContext) { 1.1796 + if (aContext instanceof Ci.nsIDOMHTMLElement) { 1.1797 + return this._nonce === aContext.getAttribute('nonce'); 1.1798 + } else if (typeof aContext === 'string') { 1.1799 + return this._nonce === aContext; 1.1800 + } 1.1801 + CSPdebug("permits called on nonce-source, but aContext was not nsIDOMHTMLElement or string (was " + typeof(aContext) + ")"); 1.1802 + return false; 1.1803 + }, 1.1804 + 1.1805 + toString: function() { 1.1806 + return "'nonce-" + this._nonce + "'"; 1.1807 + }, 1.1808 + 1.1809 + clone: function() { 1.1810 + let clone = new CSPNonceSource(); 1.1811 + clone._nonce = this._nonce; 1.1812 + return clone; 1.1813 + }, 1.1814 + 1.1815 + equals: function(that) { 1.1816 + return this._nonce === that._nonce; 1.1817 + } 1.1818 + 1.1819 +}; 1.1820 + 1.1821 +this.CSPHashSource = function CSPHashSource() { 1.1822 + this._algo = undefined; 1.1823 + this._hash = undefined; 1.1824 +} 1.1825 + 1.1826 +CSPHashSource.fromString = function(aStr, aCSPRep) { 1.1827 + let hashSrcMatch = R_HASHSRC.exec(aStr); 1.1828 + let algo = hashSrcMatch[1]; 1.1829 + let hash = hashSrcMatch[2]; 1.1830 + if (!algo) { 1.1831 + cspError(aCSPRep, "Error parsing hash-source from string: algo was empty"); 1.1832 + return null; 1.1833 + } 1.1834 + if (!hash) { 1.1835 + cspError(aCSPRep, "Error parsing hash-source from string: hash was empty"); 1.1836 + return null; 1.1837 + } 1.1838 + 1.1839 + let hashSourceObj = new CSPHashSource(); 1.1840 + hashSourceObj._algo = algo; 1.1841 + hashSourceObj._hash = hash; 1.1842 + return hashSourceObj; 1.1843 +}; 1.1844 + 1.1845 +CSPHashSource.prototype = { 1.1846 + 1.1847 + permits: function(aContext) { 1.1848 + let ScriptableUnicodeConverter = 1.1849 + Components.Constructor("@mozilla.org/intl/scriptableunicodeconverter", 1.1850 + "nsIScriptableUnicodeConverter"); 1.1851 + let converter = new ScriptableUnicodeConverter(); 1.1852 + converter.charset = 'utf8'; 1.1853 + let utf8InnerHTML = converter.convertToByteArray(aContext); 1.1854 + 1.1855 + let CryptoHash = 1.1856 + Components.Constructor("@mozilla.org/security/hash;1", 1.1857 + "nsICryptoHash", 1.1858 + "initWithString"); 1.1859 + let hash = new CryptoHash(this._algo); 1.1860 + hash.update(utf8InnerHTML, utf8InnerHTML.length); 1.1861 + // passing true causes a base64-encoded hash to be returned 1.1862 + let contentHash = hash.finish(true); 1.1863 + 1.1864 + // The NSS Base64 encoder automatically adds linebreaks "\r\n" every 64 1.1865 + // characters. We need to remove these so we can properly validate longer 1.1866 + // (SHA-512) base64-encoded hashes 1.1867 + contentHash = contentHash.replace('\r\n', ''); 1.1868 + 1.1869 + return contentHash === this._hash; 1.1870 + }, 1.1871 + 1.1872 + toString: function() { 1.1873 + return "'" + this._algo + '-' + this._hash + "'"; 1.1874 + }, 1.1875 + 1.1876 + clone: function() { 1.1877 + let clone = new CSPHashSource(); 1.1878 + clone._algo = this._algo; 1.1879 + clone._hash = this._hash; 1.1880 + return clone; 1.1881 + }, 1.1882 + 1.1883 + equals: function(that) { 1.1884 + return this._algo === that._algo && this._hash === that._hash; 1.1885 + } 1.1886 + 1.1887 +}; 1.1888 + 1.1889 +////////////////////////////////////////////////////////////////////// 1.1890 +/** 1.1891 + * Class that listens to violation report transmission and logs errors. 1.1892 + */ 1.1893 +this.CSPViolationReportListener = function CSPViolationReportListener(reportURI) { 1.1894 + this._reportURI = reportURI; 1.1895 +} 1.1896 + 1.1897 +CSPViolationReportListener.prototype = { 1.1898 + _reportURI: null, 1.1899 + 1.1900 + QueryInterface: function(iid) { 1.1901 + if (iid.equals(Ci.nsIStreamListener) || 1.1902 + iid.equals(Ci.nsIRequestObserver) || 1.1903 + iid.equals(Ci.nsISupports)) 1.1904 + return this; 1.1905 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.1906 + }, 1.1907 + 1.1908 + onStopRequest: 1.1909 + function(request, context, status) { 1.1910 + if (!Components.isSuccessCode(status)) { 1.1911 + CSPdebug("error " + status.toString(16) + 1.1912 + " while sending violation report to " + 1.1913 + this._reportURI); 1.1914 + } 1.1915 + }, 1.1916 + 1.1917 + onStartRequest: 1.1918 + function(request, context) { }, 1.1919 + 1.1920 + onDataAvailable: 1.1921 + function(request, context, inputStream, offset, count) { 1.1922 + // We MUST read equal to count from the inputStream to avoid an assertion. 1.1923 + var input = Components.classes['@mozilla.org/scriptableinputstream;1'] 1.1924 + .createInstance(Ci.nsIScriptableInputStream); 1.1925 + 1.1926 + input.init(inputStream); 1.1927 + input.read(count); 1.1928 + }, 1.1929 + 1.1930 +}; 1.1931 + 1.1932 +////////////////////////////////////////////////////////////////////// 1.1933 + 1.1934 +function innerWindowFromRequest(docRequest) { 1.1935 + let win = null; 1.1936 + let loadContext = null; 1.1937 + 1.1938 + try { 1.1939 + loadContext = docRequest.notificationCallbacks.getInterface(Ci.nsILoadContext); 1.1940 + } catch (ex) { 1.1941 + try { 1.1942 + loadContext = docRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); 1.1943 + } catch (ex) { 1.1944 + return null; 1.1945 + } 1.1946 + } 1.1947 + 1.1948 + if (loadContext) { 1.1949 + win = loadContext.associatedWindow; 1.1950 + } 1.1951 + if (win) { 1.1952 + try { 1.1953 + let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); 1.1954 + return winUtils.currentInnerWindowID; 1.1955 + } catch (ex) { 1.1956 + return null; 1.1957 + } 1.1958 + } 1.1959 + return null; 1.1960 +} 1.1961 + 1.1962 +function cspError(aCSPRep, aMessage) { 1.1963 + if (aCSPRep) { 1.1964 + aCSPRep.log(ERROR_FLAG, aMessage); 1.1965 + } else { 1.1966 + (new CSPRep()).log(ERROR_FLAG, aMessage); 1.1967 + } 1.1968 +} 1.1969 + 1.1970 +function cspWarn(aCSPRep, aMessage) { 1.1971 + if (aCSPRep) { 1.1972 + aCSPRep.log(WARN_FLAG, aMessage); 1.1973 + } else { 1.1974 + (new CSPRep()).log(WARN_FLAG, aMessage); 1.1975 + } 1.1976 +} 1.1977 + 1.1978 +////////////////////////////////////////////////////////////////////// 1.1979 + 1.1980 +this.CSPLocalizer = { 1.1981 + /** 1.1982 + * Retrieve a localized string. 1.1983 + * 1.1984 + * @param string aName 1.1985 + * The string name you want from the CSP string bundle. 1.1986 + * @return string 1.1987 + * The localized string. 1.1988 + */ 1.1989 + getStr: function CSPLoc_getStr(aName) 1.1990 + { 1.1991 + let result; 1.1992 + try { 1.1993 + result = this.stringBundle.GetStringFromName(aName); 1.1994 + } 1.1995 + catch (ex) { 1.1996 + Cu.reportError("Failed to get string: " + aName); 1.1997 + throw ex; 1.1998 + } 1.1999 + return result; 1.2000 + }, 1.2001 + 1.2002 + /** 1.2003 + * Retrieve a localized string formatted with values coming from the given 1.2004 + * array. 1.2005 + * 1.2006 + * @param string aName 1.2007 + * The string name you want from the CSP string bundle. 1.2008 + * @param array aArray 1.2009 + * The array of values you want in the formatted string. 1.2010 + * @return string 1.2011 + * The formatted local string. 1.2012 + */ 1.2013 + getFormatStr: function CSPLoc_getFormatStr(aName, aArray) 1.2014 + { 1.2015 + let result; 1.2016 + try { 1.2017 + result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length); 1.2018 + } 1.2019 + catch (ex) { 1.2020 + Cu.reportError("Failed to format string: " + aName); 1.2021 + throw ex; 1.2022 + } 1.2023 + return result; 1.2024 + }, 1.2025 +}; 1.2026 + 1.2027 +XPCOMUtils.defineLazyGetter(CSPLocalizer, "stringBundle", function() { 1.2028 + return Services.strings.createBundle(STRINGS_URI); 1.2029 +});