1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/base/src/contentSecurityPolicy.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1049 @@ 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 +/** 1.10 + * Content Security Policy 1.11 + * 1.12 + * Overview 1.13 + * This is a stub component that will be fleshed out to do all the fancy stuff 1.14 + * that ContentSecurityPolicy has to do. 1.15 + */ 1.16 + 1.17 +// these identifiers are also defined in contentSecurityPolicy.manifest. 1.18 +const CSP_CLASS_ID = Components.ID("{d1680bb4-1ac0-4772-9437-1188375e44f2}"); 1.19 +const CSP_CONTRACT_ID = "@mozilla.org/contentsecuritypolicy;1"; 1.20 + 1.21 +/* :::::::: Constants and Helpers ::::::::::::::: */ 1.22 + 1.23 +const Cc = Components.classes; 1.24 +const Ci = Components.interfaces; 1.25 +const Cr = Components.results; 1.26 +const Cu = Components.utils; 1.27 + 1.28 +const CSP_VIOLATION_TOPIC = "csp-on-violate-policy"; 1.29 + 1.30 +// Needed to support CSP 1.0 spec and our original CSP implementation - should 1.31 +// be removed when our original implementation is deprecated. 1.32 +const CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT = "csp_type_xmlhttprequest_spec_compliant"; 1.33 +const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant"; 1.34 + 1.35 +const WARN_FLAG = Ci.nsIScriptError.warningFlag; 1.36 +const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG; 1.37 + 1.38 +const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply'; 1.39 +const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute'; 1.40 +const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings'; 1.41 +const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce' 1.42 +const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce' 1.43 +const SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid hash'; 1.44 +const STYLE_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid hash'; 1.45 + 1.46 +// The cutoff length of content location in creating CSP cache key. 1.47 +const CSP_CACHE_URI_CUTOFF_SIZE = 512; 1.48 + 1.49 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.50 +Cu.import("resource://gre/modules/Services.jsm"); 1.51 +Cu.import("resource://gre/modules/CSPUtils.jsm"); 1.52 + 1.53 +/* ::::: Policy Parsing & Data structures :::::: */ 1.54 + 1.55 +function ContentSecurityPolicy() { 1.56 + CSPdebug("CSP CREATED"); 1.57 + this._isInitialized = false; 1.58 + 1.59 + this._policies = []; 1.60 + 1.61 + this._request = ""; 1.62 + this._requestOrigin = ""; 1.63 + this._weakRequestPrincipal = { get : function() { return null; } }; 1.64 + this._referrer = ""; 1.65 + this._weakDocRequest = { get : function() { return null; } }; 1.66 + CSPdebug("CSP object initialized, no policies to enforce yet"); 1.67 + 1.68 + this._cache = new Map(); 1.69 +} 1.70 + 1.71 +/* 1.72 + * Set up mappings from nsIContentPolicy content types to CSP directives. 1.73 + */ 1.74 +{ 1.75 + let cp = Ci.nsIContentPolicy; 1.76 + let csp = ContentSecurityPolicy; 1.77 + let cspr_sd_old = CSPRep.SRC_DIRECTIVES_OLD; 1.78 + let cspr_sd_new = CSPRep.SRC_DIRECTIVES_NEW; 1.79 + 1.80 + csp._MAPPINGS=[]; 1.81 + 1.82 + /* default, catch-all case */ 1.83 + // This is the same in old and new CSP so use the new mapping. 1.84 + csp._MAPPINGS[cp.TYPE_OTHER] = cspr_sd_new.DEFAULT_SRC; 1.85 + 1.86 + /* self */ 1.87 + csp._MAPPINGS[cp.TYPE_DOCUMENT] = null; 1.88 + 1.89 + /* shouldn't see this one */ 1.90 + csp._MAPPINGS[cp.TYPE_REFRESH] = null; 1.91 + 1.92 + /* categorized content types */ 1.93 + // These are the same in old and new CSP's so just use the new mappings. 1.94 + csp._MAPPINGS[cp.TYPE_SCRIPT] = cspr_sd_new.SCRIPT_SRC; 1.95 + csp._MAPPINGS[cp.TYPE_IMAGE] = cspr_sd_new.IMG_SRC; 1.96 + csp._MAPPINGS[cp.TYPE_STYLESHEET] = cspr_sd_new.STYLE_SRC; 1.97 + csp._MAPPINGS[cp.TYPE_OBJECT] = cspr_sd_new.OBJECT_SRC; 1.98 + csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd_new.OBJECT_SRC; 1.99 + csp._MAPPINGS[cp.TYPE_SUBDOCUMENT] = cspr_sd_new.FRAME_SRC; 1.100 + csp._MAPPINGS[cp.TYPE_MEDIA] = cspr_sd_new.MEDIA_SRC; 1.101 + csp._MAPPINGS[cp.TYPE_FONT] = cspr_sd_new.FONT_SRC; 1.102 + csp._MAPPINGS[cp.TYPE_XSLT] = cspr_sd_new.SCRIPT_SRC; 1.103 + csp._MAPPINGS[cp.TYPE_BEACON] = cspr_sd_new.CONNECT_SRC; 1.104 + 1.105 + /* Our original CSP implementation's mappings for XHR and websocket 1.106 + * These should be changed to be = cspr_sd.CONNECT_SRC when we remove 1.107 + * the original implementation - NOTE: order in this array is important !!! 1.108 + */ 1.109 + csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST] = cspr_sd_old.XHR_SRC; 1.110 + csp._MAPPINGS[cp.TYPE_WEBSOCKET] = cspr_sd_old.XHR_SRC; 1.111 + 1.112 + /* CSP cannot block CSP reports */ 1.113 + csp._MAPPINGS[cp.TYPE_CSP_REPORT] = null; 1.114 + 1.115 + /* These must go through the catch-all */ 1.116 + csp._MAPPINGS[cp.TYPE_XBL] = cspr_sd_new.DEFAULT_SRC; 1.117 + csp._MAPPINGS[cp.TYPE_PING] = cspr_sd_new.DEFAULT_SRC; 1.118 + csp._MAPPINGS[cp.TYPE_DTD] = cspr_sd_new.DEFAULT_SRC; 1.119 + 1.120 + /* CSP 1.0 spec compliant mappings for XHR and websocket */ 1.121 + // The directive name for XHR, websocket, and EventSource is different 1.122 + // in the 1.0 spec than in our original implementation, these mappings 1.123 + // address this. These won't be needed when we deprecate our original 1.124 + // implementation. 1.125 + csp._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC; 1.126 + csp._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC; 1.127 + // TODO : EventSource will be here and also will use connect-src 1.128 + // after we fix Bug 802872 - CSP should restrict EventSource using the connect-src 1.129 + // directive. For background see Bug 667490 - EventSource should use the same 1.130 + // nsIContentPolicy type as XHR (which is fixed) 1.131 +} 1.132 + 1.133 +ContentSecurityPolicy.prototype = { 1.134 + classID: CSP_CLASS_ID, 1.135 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy, 1.136 + Ci.nsISerializable, 1.137 + Ci.nsISupports]), 1.138 + 1.139 + // Class info is required to be able to serialize 1.140 + classInfo: XPCOMUtils.generateCI({classID: CSP_CLASS_ID, 1.141 + contractID: CSP_CONTRACT_ID, 1.142 + interfaces: [Ci.nsIContentSecurityPolicy, 1.143 + Ci.nsISerializable], 1.144 + flags: Ci.nsIClassInfo.MAIN_THREAD_ONLY}), 1.145 + 1.146 + get isInitialized() { 1.147 + return this._isInitialized; 1.148 + }, 1.149 + 1.150 + set isInitialized (foo) { 1.151 + this._isInitialized = foo; 1.152 + }, 1.153 + 1.154 + _getPolicyInternal: function(index) { 1.155 + if (index < 0 || index >= this._policies.length) { 1.156 + throw Cr.NS_ERROR_FAILURE; 1.157 + } 1.158 + return this._policies[index]; 1.159 + }, 1.160 + 1.161 + _buildViolatedDirectiveString: 1.162 + function(aDirectiveName, aPolicy) { 1.163 + var SD = CSPRep.SRC_DIRECTIVES_NEW; 1.164 + var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC; 1.165 + var directive = aPolicy._directives[cspContext]; 1.166 + return cspContext + ' ' + directive.toString(); 1.167 + }, 1.168 + 1.169 + /** 1.170 + * Returns policy string representing the policy at "index". 1.171 + */ 1.172 + getPolicy: function(index) { 1.173 + return this._getPolicyInternal(index).toString(); 1.174 + }, 1.175 + 1.176 + /** 1.177 + * Returns count of policies. 1.178 + */ 1.179 + get numPolicies() { 1.180 + return this._policies.length; 1.181 + }, 1.182 + 1.183 + getAllowsInlineScript: function(shouldReportViolations) { 1.184 + // report it? (for each policy, is it violated?) 1.185 + shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; }); 1.186 + 1.187 + // allow it to execute? (Do all the policies allow it to execute)? 1.188 + return this._policies.every(function(a) { 1.189 + return a._reportOnlyMode || a.allowsInlineScripts; 1.190 + }); 1.191 + }, 1.192 + 1.193 + getAllowsEval: function(shouldReportViolations) { 1.194 + // report it? (for each policy, is it violated?) 1.195 + shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; }); 1.196 + 1.197 + // allow it to execute? (Do all the policies allow it to execute)? 1.198 + return this._policies.every(function(a) { 1.199 + return a._reportOnlyMode || a.allowsEvalInScripts; 1.200 + }); 1.201 + }, 1.202 + 1.203 + getAllowsInlineStyle: function(shouldReportViolations) { 1.204 + // report it? (for each policy, is it violated?) 1.205 + shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; }); 1.206 + 1.207 + // allow it to execute? (Do all the policies allow it to execute)? 1.208 + return this._policies.every(function(a) { 1.209 + return a._reportOnlyMode || a.allowsInlineStyles; 1.210 + }); 1.211 + }, 1.212 + 1.213 + getAllowsNonce: function(aNonce, aContentType, shouldReportViolation) { 1.214 + if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT || 1.215 + aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) { 1.216 + CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType); 1.217 + return false; 1.218 + } 1.219 + 1.220 + let directive = ContentSecurityPolicy._MAPPINGS[aContentType]; 1.221 + let policyAllowsNonce = [ policy.permitsNonce(aNonce, directive) 1.222 + for (policy of this._policies) ]; 1.223 + 1.224 + shouldReportViolation.value = this._policies.some(function(policy, i) { 1.225 + // Don't report a violation if the policy didn't use nonce-source 1.226 + return policy._directives.hasOwnProperty(directive) && 1.227 + policy._directives[directive]._hasNonceSource && 1.228 + !policyAllowsNonce[i]; 1.229 + }); 1.230 + 1.231 + // allow it to execute? (Do all the policies allow it to execute)? 1.232 + return this._policies.every(function(policy, i) { 1.233 + return policy._reportOnlyMode || policyAllowsNonce[i]; 1.234 + }); 1.235 + }, 1.236 + 1.237 + getAllowsHash: function(aContent, aContentType, shouldReportViolation) { 1.238 + if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT || 1.239 + aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) { 1.240 + CSPdebug("Hash check requested for an invalid content type (not script or style): " + aContentType); 1.241 + return false; 1.242 + } 1.243 + 1.244 + let directive = ContentSecurityPolicy._MAPPINGS[aContentType]; 1.245 + let policyAllowsHash = [ policy.permitsHash(aContent, directive) 1.246 + for (policy of this._policies) ]; 1.247 + 1.248 + shouldReportViolation.value = this._policies.some(function(policy, i) { 1.249 + // Don't report a violation if the policy didn't use hash-source 1.250 + return policy._directives.hasOwnProperty(directive) && 1.251 + policy._directives[directive]._hasHashSource && 1.252 + !policyAllowsHash[i]; 1.253 + }); 1.254 + 1.255 + // allow it to execute? (Do all the policies allow it to execute)? 1.256 + return this._policies.every(function(policy, i) { 1.257 + return policy._reportOnlyMode || policyAllowsHash[i]; 1.258 + }); 1.259 + }, 1.260 + 1.261 + /** 1.262 + * For each policy, log any violation on the Error Console and send a report 1.263 + * if a report-uri is present in the policy 1.264 + * 1.265 + * @param aViolationType 1.266 + * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval 1.267 + * @param aSourceFile 1.268 + * name of the source file containing the violation (if available) 1.269 + * @param aContentSample 1.270 + * sample of the violating content (to aid debugging) 1.271 + * @param aLineNum 1.272 + * source line number of the violation (if available) 1.273 + * @param aNonce 1.274 + * (optional) If this is a nonce violation, include the nonce so we can 1.275 + * recheck to determine which policies were violated and send the 1.276 + * appropriate reports. 1.277 + * @param aContent 1.278 + * (optional) If this is a hash violation, include contents of the inline 1.279 + * resource in the question so we can recheck the hash in order to 1.280 + * determine which policies were violated and send the appropriate 1.281 + * reports. 1.282 + */ 1.283 + logViolationDetails: 1.284 + function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce, aContent) { 1.285 + for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) { 1.286 + let policy = this._policies[policyIndex]; 1.287 + 1.288 + // call-sites to the eval/inline checks recieve two return values: allows 1.289 + // and violates. Policies that are report-only allow the 1.290 + // loads/compilations but violations should still be reported. Not all 1.291 + // policies in this nsIContentSecurityPolicy instance will be violated, 1.292 + // which is why we must check again here. 1.293 + switch (aViolationType) { 1.294 + case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE: 1.295 + if (!policy.allowsInlineStyles) { 1.296 + var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy); 1.297 + this._asyncReportViolation('self', null, violatedDirective, policyIndex, 1.298 + INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT, 1.299 + aSourceFile, aScriptSample, aLineNum); 1.300 + } 1.301 + break; 1.302 + case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT: 1.303 + if (!policy.allowsInlineScripts) { 1.304 + var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); 1.305 + this._asyncReportViolation('self', null, violatedDirective, policyIndex, 1.306 + INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT, 1.307 + aSourceFile, aScriptSample, aLineNum); 1.308 + } 1.309 + break; 1.310 + case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL: 1.311 + if (!policy.allowsEvalInScripts) { 1.312 + var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); 1.313 + this._asyncReportViolation('self', null, violatedDirective, policyIndex, 1.314 + EVAL_VIOLATION_OBSERVER_SUBJECT, 1.315 + aSourceFile, aScriptSample, aLineNum); 1.316 + } 1.317 + break; 1.318 + case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT: 1.319 + var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT]; 1.320 + if (!policy.permitsNonce(aNonce, scriptType)) { 1.321 + var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); 1.322 + this._asyncReportViolation('self', null, violatedDirective, policyIndex, 1.323 + SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT, 1.324 + aSourceFile, aScriptSample, aLineNum); 1.325 + } 1.326 + break; 1.327 + case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE: 1.328 + var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE]; 1.329 + if (!policy.permitsNonce(aNonce, styleType)) { 1.330 + var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy); 1.331 + this._asyncReportViolation('self', null, violatedDirective, policyIndex, 1.332 + STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT, 1.333 + aSourceFile, aScriptSample, aLineNum); 1.334 + } 1.335 + break; 1.336 + case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_SCRIPT: 1.337 + var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT]; 1.338 + if (!policy.permitsHash(aContent, scriptType)) { 1.339 + var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); 1.340 + this._asyncReportViolation('self', null, violatedDirective, policyIndex, 1.341 + SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT, 1.342 + aSourceFile, aScriptSample, aLineNum); 1.343 + } 1.344 + break; 1.345 + case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_STYLE: 1.346 + var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE]; 1.347 + if (!policy.permitsHash(aContent, styleType)) { 1.348 + var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy); 1.349 + this._asyncReportViolation('self', null, violatedDirective, policyIndex, 1.350 + STYLE_HASH_VIOLATION_OBSERVER_SUBJECT, 1.351 + aSourceFile, aScriptSample, aLineNum); 1.352 + } 1.353 + break; 1.354 + } 1.355 + } 1.356 + }, 1.357 + 1.358 + /** 1.359 + * Given an nsIHttpChannel, fill out the appropriate data. 1.360 + */ 1.361 + setRequestContext: 1.362 + function(aSelfURI, aReferrerURI, aPrincipal, aChannel) { 1.363 + 1.364 + // this requires either a self URI or a http channel. 1.365 + if (!aSelfURI && !aChannel) 1.366 + return; 1.367 + 1.368 + if (aChannel) { 1.369 + // Save the docRequest for fetching a policy-uri 1.370 + this._weakDocRequest = Cu.getWeakReference(aChannel); 1.371 + } 1.372 + 1.373 + // save the document URI (minus <fragment>) and referrer for reporting 1.374 + let uri = aSelfURI ? aSelfURI.cloneIgnoringRef() : aChannel.URI.cloneIgnoringRef(); 1.375 + try { // GetUserPass throws for some protocols without userPass 1.376 + uri.userPass = ''; 1.377 + } catch (ex) {} 1.378 + this._request = uri.asciiSpec; 1.379 + this._requestOrigin = uri; 1.380 + 1.381 + // store a reference to the principal, that can later be used in shouldLoad 1.382 + if (aPrincipal) { 1.383 + this._weakRequestPrincipal = Cu.getWeakReference(aPrincipal); 1.384 + } else if (aChannel) { 1.385 + this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"] 1.386 + .getService(Ci.nsIScriptSecurityManager) 1.387 + .getChannelPrincipal(aChannel)); 1.388 + } else { 1.389 + CSPdebug("No principal or channel for document context; violation reports may not work."); 1.390 + } 1.391 + 1.392 + // pick one: referrerURI, channel's referrer, or null (first available) 1.393 + let ref = null; 1.394 + if (aReferrerURI) 1.395 + ref = aReferrerURI; 1.396 + else if (aChannel instanceof Ci.nsIHttpChannel) 1.397 + ref = aChannel.referrer; 1.398 + 1.399 + if (ref) { 1.400 + let referrer = aChannel.referrer.cloneIgnoringRef(); 1.401 + try { // GetUserPass throws for some protocols without userPass 1.402 + referrer.userPass = ''; 1.403 + } catch (ex) {} 1.404 + this._referrer = referrer.asciiSpec; 1.405 + } 1.406 + }, 1.407 + 1.408 +/* ........ Methods .............. */ 1.409 + 1.410 + /** 1.411 + * Adds a new policy to our list of policies for this CSP context. 1.412 + * @returns the count of policies. 1.413 + */ 1.414 + appendPolicy: 1.415 + function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) { 1.416 + return this._appendPolicyInternal(aPolicy, selfURI, aReportOnly, 1.417 + aSpecCompliant, true); 1.418 + }, 1.419 + 1.420 + /** 1.421 + * Adds a new policy to our list of policies for this CSP context. 1.422 + * Only to be called from this module (not exported) 1.423 + * @returns the count of policies. 1.424 + */ 1.425 + _appendPolicyInternal: 1.426 + function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant, 1.427 + aEnforceSelfChecks) { 1.428 +#ifndef MOZ_B2G 1.429 + CSPdebug("APPENDING POLICY: " + aPolicy); 1.430 + CSPdebug(" SELF: " + (selfURI ? selfURI.asciiSpec : " null")); 1.431 + CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant); 1.432 +#endif 1.433 + 1.434 + // For nested schemes such as view-source: make sure we are taking the 1.435 + // innermost URI to use as 'self' since that's where we will extract the 1.436 + // scheme, host and port from 1.437 + if (selfURI instanceof Ci.nsINestedURI) { 1.438 +#ifndef MOZ_B2G 1.439 + CSPdebug(" INNER: " + selfURI.innermostURI.asciiSpec); 1.440 +#endif 1.441 + selfURI = selfURI.innermostURI; 1.442 + } 1.443 + 1.444 + // stay uninitialized until policy setup is done 1.445 + var newpolicy; 1.446 + 1.447 + // If there is a policy-uri, fetch the policy, then re-call this function. 1.448 + // (1) parse and create a CSPRep object 1.449 + // Note that we pass the full URI since when it's parsed as 'self' to construct a 1.450 + // CSPSource only the scheme, host, and port are kept. 1.451 + 1.452 + // If we want to be CSP 1.0 spec compliant, use the new parser. 1.453 + // The old one will be deprecated in the future and will be 1.454 + // removed at that time. 1.455 + if (aSpecCompliant) { 1.456 + newpolicy = CSPRep.fromStringSpecCompliant(aPolicy, 1.457 + selfURI, 1.458 + aReportOnly, 1.459 + this._weakDocRequest.get(), 1.460 + this, 1.461 + aEnforceSelfChecks); 1.462 + } else { 1.463 + newpolicy = CSPRep.fromString(aPolicy, 1.464 + selfURI, 1.465 + aReportOnly, 1.466 + this._weakDocRequest.get(), 1.467 + this, 1.468 + aEnforceSelfChecks); 1.469 + } 1.470 + 1.471 + newpolicy._specCompliant = !!aSpecCompliant; 1.472 + newpolicy._isInitialized = true; 1.473 + this._policies.push(newpolicy); 1.474 + this._cache.clear(); // reset cache since effective policy changes 1.475 + }, 1.476 + 1.477 + /** 1.478 + * Removes a policy from the array of policies. 1.479 + */ 1.480 + removePolicy: 1.481 + function csp_removePolicy(index) { 1.482 + if (index < 0 || index >= this._policies.length) { 1.483 + CSPdebug("Cannot remove policy " + index + "; not enough policies."); 1.484 + return; 1.485 + } 1.486 + this._policies.splice(index, 1); 1.487 + this._cache.clear(); // reset cache since effective policy changes 1.488 + }, 1.489 + 1.490 + /** 1.491 + * Generates and sends a violation report to the specified report URIs. 1.492 + */ 1.493 + sendReports: 1.494 + function(blockedUri, originalUri, violatedDirective, 1.495 + violatedPolicyIndex, aSourceFile, 1.496 + aScriptSample, aLineNum) { 1.497 + 1.498 + let policy = this._getPolicyInternal(violatedPolicyIndex); 1.499 + if (!policy) { 1.500 + CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined."); 1.501 + return; 1.502 + } 1.503 + 1.504 + var uriString = policy.getReportURIs(); 1.505 + var uris = uriString.split(/\s+/); 1.506 + if (uris.length > 0) { 1.507 + // see if we need to sanitize the blocked-uri 1.508 + let blocked = ''; 1.509 + if (originalUri) { 1.510 + // We've redirected, only report the blocked origin 1.511 + try { 1.512 + let clone = blockedUri.clone(); 1.513 + clone.path = ''; 1.514 + blocked = clone.asciiSpec; 1.515 + } catch(e) { 1.516 + CSPdebug(".... blockedUri can't be cloned: " + blockedUri); 1.517 + } 1.518 + } 1.519 + else if (blockedUri instanceof Ci.nsIURI) { 1.520 + blocked = blockedUri.cloneIgnoringRef().asciiSpec; 1.521 + } 1.522 + else { 1.523 + // blockedUri is a string for eval/inline-script violations 1.524 + blocked = blockedUri; 1.525 + } 1.526 + 1.527 + // Generate report to send composed of 1.528 + // { 1.529 + // csp-report: { 1.530 + // document-uri: "http://example.com/file.html?params", 1.531 + // referrer: "...", 1.532 + // blocked-uri: "...", 1.533 + // violated-directive: "..." 1.534 + // } 1.535 + // } 1.536 + var report = { 1.537 + 'csp-report': { 1.538 + 'document-uri': this._request, 1.539 + 'referrer': this._referrer, 1.540 + 'blocked-uri': blocked, 1.541 + 'violated-directive': violatedDirective 1.542 + } 1.543 + } 1.544 + 1.545 + // extra report fields for script errors (if available) 1.546 + if (originalUri) 1.547 + report["csp-report"]["original-uri"] = originalUri.cloneIgnoringRef().asciiSpec; 1.548 + if (aSourceFile) 1.549 + report["csp-report"]["source-file"] = aSourceFile; 1.550 + if (aScriptSample) 1.551 + report["csp-report"]["script-sample"] = aScriptSample; 1.552 + if (aLineNum) 1.553 + report["csp-report"]["line-number"] = aLineNum; 1.554 + 1.555 + var reportString = JSON.stringify(report); 1.556 + CSPdebug("Constructed violation report:\n" + reportString); 1.557 + 1.558 + // For each URI in the report list, send out a report. 1.559 + // We make the assumption that all of the URIs are absolute URIs; this 1.560 + // should be taken care of in CSPRep.fromString (where it converts any 1.561 + // relative URIs into absolute ones based on "self"). 1.562 + for (let i in uris) { 1.563 + if (uris[i] === "") 1.564 + continue; 1.565 + 1.566 + try { 1.567 + var chan = Services.io.newChannel(uris[i], null, null); 1.568 + if (!chan) { 1.569 + CSPdebug("Error creating channel for " + uris[i]); 1.570 + continue; 1.571 + } 1.572 + 1.573 + var content = Cc["@mozilla.org/io/string-input-stream;1"] 1.574 + .createInstance(Ci.nsIStringInputStream); 1.575 + content.data = reportString + "\n\n"; 1.576 + 1.577 + // make sure this is an anonymous request (no cookies) so in case the 1.578 + // policy URI is injected, it can't be abused for CSRF. 1.579 + chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS; 1.580 + 1.581 + // we need to set an nsIChannelEventSink on the channel object 1.582 + // so we can tell it to not follow redirects when posting the reports 1.583 + chan.notificationCallbacks = new CSPReportRedirectSink(policy); 1.584 + if (this._weakDocRequest.get()) { 1.585 + chan.loadGroup = this._weakDocRequest.get().loadGroup; 1.586 + } 1.587 + 1.588 + chan.QueryInterface(Ci.nsIUploadChannel) 1.589 + .setUploadStream(content, "application/json", content.available()); 1.590 + 1.591 + try { 1.592 + // if this is an HTTP channel, set the request method to post 1.593 + chan.QueryInterface(Ci.nsIHttpChannel); 1.594 + chan.requestMethod = "POST"; 1.595 + } catch(e) {} // throws only if chan is not an nsIHttpChannel. 1.596 + 1.597 + // check with the content policy service to see if we're allowed to 1.598 + // send this request. 1.599 + try { 1.600 + var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"] 1.601 + .getService(Ci.nsIContentPolicy); 1.602 + if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT, 1.603 + chan.URI, this._requestOrigin, 1.604 + null, null, null, this._weakRequestPrincipal.get()) 1.605 + != Ci.nsIContentPolicy.ACCEPT) { 1.606 + continue; // skip unauthorized URIs 1.607 + } 1.608 + } catch(e) { 1.609 + continue; // refuse to load if we can't do a security check. 1.610 + } 1.611 + 1.612 + //send data (and set up error notifications) 1.613 + chan.asyncOpen(new CSPViolationReportListener(uris[i]), null); 1.614 + CSPdebug("Sent violation report to " + uris[i]); 1.615 + } catch(e) { 1.616 + // it's possible that the URI was invalid, just log a 1.617 + // warning and skip over that. 1.618 + policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]])); 1.619 + policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()])); 1.620 + } 1.621 + } 1.622 + } 1.623 + }, 1.624 + 1.625 + /** 1.626 + * Logs a meaningful CSP warning to the developer console. 1.627 + */ 1.628 + logToConsole: 1.629 + function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex, 1.630 + aSourceFile, aScriptSample, aLineNum, aObserverSubject) { 1.631 + let policy = this._policies[aViolatedPolicyIndex]; 1.632 + switch(aObserverSubject.data) { 1.633 + case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT: 1.634 + violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked"); 1.635 + break; 1.636 + case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT: 1.637 + violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked"); 1.638 + break; 1.639 + case EVAL_VIOLATION_OBSERVER_SUBJECT: 1.640 + violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked"); 1.641 + break; 1.642 + } 1.643 + var violationMessage = null; 1.644 + if (blockedUri["asciiSpec"]) { 1.645 + violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]); 1.646 + } else { 1.647 + violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]); 1.648 + } 1.649 + policy.log(WARN_FLAG, violationMessage, 1.650 + (aSourceFile) ? aSourceFile : null, 1.651 + (aScriptSample) ? decodeURIComponent(aScriptSample) : null, 1.652 + (aLineNum) ? aLineNum : null); 1.653 + }, 1.654 + 1.655 +/** 1.656 + * Exposed Method to analyze docShell for approved frame ancestry. 1.657 + * NOTE: Also sends violation reports if necessary. 1.658 + * @param docShell 1.659 + * the docShell for this policy's resource. 1.660 + * @return 1.661 + * true if the frame ancestry is allowed by this policy and the load 1.662 + * should progress. 1.663 + */ 1.664 + permitsAncestry: 1.665 + function(docShell) { 1.666 + // Cannot shortcut checking all the policies since violation reports have 1.667 + // to be triggered if any policy wants it. 1.668 + var permitted = true; 1.669 + for (let i = 0; i < this._policies.length; i++) { 1.670 + if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) { 1.671 + permitted = false; 1.672 + } 1.673 + } 1.674 + return permitted; 1.675 + }, 1.676 + 1.677 + _permitsAncestryInternal: 1.678 + function(docShell, policy, policyIndex) { 1.679 + if (!docShell) { return false; } 1.680 + 1.681 + // walk up this docShell tree until we hit chrome 1.682 + var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.683 + .getInterface(Ci.nsIDocShellTreeItem); 1.684 + 1.685 + // collect ancestors and make sure they're allowed. 1.686 + var ancestors = []; 1.687 + while (dst.parent) { 1.688 + dst = dst.parent; 1.689 + let it = dst.QueryInterface(Ci.nsIInterfaceRequestor) 1.690 + .getInterface(Ci.nsIWebNavigation); 1.691 + if (it.currentURI) { 1.692 + if (it.currentURI.scheme === "chrome") { 1.693 + break; 1.694 + } 1.695 + // delete any userpass 1.696 + let ancestor = it.currentURI.cloneIgnoringRef(); 1.697 + try { // GetUserPass throws for some protocols without userPass 1.698 + ancestor.userPass = ''; 1.699 + } catch (ex) {} 1.700 + 1.701 +#ifndef MOZ_B2G 1.702 + CSPdebug(" found frame ancestor " + ancestor.asciiSpec); 1.703 +#endif 1.704 + ancestors.push(ancestor); 1.705 + } 1.706 + } 1.707 + 1.708 + // scan the discovered ancestors 1.709 + // frame-ancestors is the same in both old and new source directives, 1.710 + // so don't need to differentiate here. 1.711 + let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS; 1.712 + for (let i in ancestors) { 1.713 + let ancestor = ancestors[i]; 1.714 + if (!policy.permits(ancestor, cspContext)) { 1.715 + // report the frame-ancestor violation 1.716 + let directive = policy._directives[cspContext]; 1.717 + let violatedPolicy = 'frame-ancestors ' + directive.toString(); 1.718 + 1.719 + this._asyncReportViolation(ancestors[i], null, violatedPolicy, 1.720 + policyIndex); 1.721 + 1.722 + // need to lie if we are testing in report-only mode 1.723 + return policy._reportOnlyMode; 1.724 + } 1.725 + } 1.726 + return true; 1.727 + }, 1.728 + 1.729 + /** 1.730 + * Creates a cache key from content location and content type. 1.731 + */ 1.732 + _createCacheKey: 1.733 + function (aContentLocation, aContentType) { 1.734 + if (aContentType != Ci.nsIContentPolicy.TYPE_SCRIPT && 1.735 + aContentLocation.scheme == "data") { 1.736 + // For non-script data: URI, use ("data:", aContentType) as the cache key. 1.737 + return aContentLocation.scheme + ":" + aContentType; 1.738 + } 1.739 + 1.740 + let uri = aContentLocation.spec; 1.741 + if (uri.length > CSP_CACHE_URI_CUTOFF_SIZE) { 1.742 + // Don't cache for a URI longer than the cutoff size. 1.743 + return null; 1.744 + } 1.745 + return uri + "!" + aContentType; 1.746 + }, 1.747 + 1.748 + /** 1.749 + * Delegate method called by the service when sub-elements of the protected 1.750 + * document are being loaded. Given a bit of information about the request, 1.751 + * decides whether or not the policy is satisfied. 1.752 + */ 1.753 + shouldLoad: 1.754 + function csp_shouldLoad(aContentType, 1.755 + aContentLocation, 1.756 + aRequestOrigin, 1.757 + aContext, 1.758 + aMimeTypeGuess, 1.759 + aOriginalUri) { 1.760 + let key = this._createCacheKey(aContentLocation, aContentType); 1.761 + if (key && this._cache.has(key)) { 1.762 + return this._cache.get(key); 1.763 + } 1.764 + 1.765 +#ifndef MOZ_B2G 1.766 + // Try to remove as much as possible from the hot path on b2g. 1.767 + CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec); 1.768 + CSPdebug("shouldLoad content type = " + aContentType); 1.769 +#endif 1.770 + 1.771 + // The mapping for XHR and websockets is different between our original 1.772 + // implementation and the 1.0 spec, we handle this here. 1.773 + var cspContext; 1.774 + 1.775 + let cp = Ci.nsIContentPolicy; 1.776 + 1.777 + // Infer if this is a preload for elements that use nonce-source. Since, 1.778 + // for preloads, aContext is the document and not the element associated 1.779 + // with the resource, we cannot determine the nonce. See Bug 612921 and 1.780 + // Bug 855326. 1.781 + let nonceSourceValid = aContentType == cp.TYPE_SCRIPT || 1.782 + aContentType == cp.TYPE_STYLESHEET; 1.783 + var possiblePreloadNonceConflict = nonceSourceValid && 1.784 + aContext instanceof Ci.nsIDOMHTMLDocument; 1.785 + 1.786 + // iterate through all the _policies and send reports where a policy is 1.787 + // violated. After the check, determine the overall effect (blocked or 1.788 + // loaded?) and cache it. 1.789 + let policyAllowsLoadArray = []; 1.790 + for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) { 1.791 + let policy = this._policies[policyIndex]; 1.792 + 1.793 +#ifndef MOZ_B2G 1.794 + CSPdebug("policy is " + (policy._specCompliant ? 1.795 + "1.0 compliant" : "pre-1.0")); 1.796 + CSPdebug("policy is " + (policy._reportOnlyMode ? 1.797 + "report-only" : "blocking")); 1.798 +#endif 1.799 + 1.800 + if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) { 1.801 + cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT]; 1.802 + } else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) { 1.803 + cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT]; 1.804 + } else { 1.805 + cspContext = ContentSecurityPolicy._MAPPINGS[aContentType]; 1.806 + } 1.807 + 1.808 +#ifndef MOZ_B2G 1.809 + CSPdebug("shouldLoad cspContext = " + cspContext); 1.810 +#endif 1.811 + 1.812 + // if the mapping is null, there's no policy, let it through. 1.813 + if (!cspContext) { 1.814 + return Ci.nsIContentPolicy.ACCEPT; 1.815 + } 1.816 + 1.817 + // check if location is permitted 1.818 + let permitted = policy.permits(aContentLocation, cspContext); 1.819 + 1.820 + // check any valid content type for nonce if location is not permitted 1.821 + if (!permitted && nonceSourceValid && 1.822 + aContext instanceof Ci.nsIDOMHTMLElement && 1.823 + aContext.hasAttribute('nonce')) { 1.824 + permitted = policy.permitsNonce(aContext.getAttribute('nonce'), 1.825 + cspContext); 1.826 + } 1.827 + 1.828 + // record whether the thing should be blocked or just reported. 1.829 + policyAllowsLoadArray.push(permitted || policy._reportOnlyMode); 1.830 + let res = permitted ? cp.ACCEPT : cp.REJECT_SERVER; 1.831 + 1.832 + // frame-ancestors is taken care of early on (as this document is loaded) 1.833 + 1.834 + // If the result is *NOT* ACCEPT, then send report 1.835 + // Do not send report if this is a nonce-source preload - the decision may 1.836 + // be wrong and will incorrectly fail the unit tests. 1.837 + if (res != cp.ACCEPT && !possiblePreloadNonceConflict) { 1.838 + CSPdebug("blocking request for " + aContentLocation.asciiSpec); 1.839 + try { 1.840 + let directive = "unknown directive", 1.841 + violatedPolicy = "unknown policy"; 1.842 + 1.843 + // The policy might not explicitly declare each source directive (so 1.844 + // the cspContext may be implicit). If so, we have to report 1.845 + // violations as appropriate: specific or the default-src directive. 1.846 + if (policy._directives.hasOwnProperty(cspContext)) { 1.847 + directive = policy._directives[cspContext]; 1.848 + violatedPolicy = cspContext + ' ' + directive.toString(); 1.849 + } else if (policy._directives.hasOwnProperty("default-src")) { 1.850 + directive = policy._directives["default-src"]; 1.851 + violatedPolicy = "default-src " + directive.toString(); 1.852 + } else { 1.853 + violatedPolicy = "unknown directive"; 1.854 + CSPdebug('ERROR in blocking content: ' + 1.855 + 'CSP is not sure which part of the policy caused this block'); 1.856 + } 1.857 + 1.858 + this._asyncReportViolation(aContentLocation, 1.859 + aOriginalUri, 1.860 + violatedPolicy, 1.861 + policyIndex); 1.862 + } catch(e) { 1.863 + CSPdebug('---------------- ERROR: ' + e); 1.864 + } 1.865 + } 1.866 + } // end for-each loop over policies 1.867 + 1.868 + // the ultimate decision is based on whether any policies want to reject 1.869 + // the load. The array keeps track of whether the policies allowed the 1.870 + // loads. If any doesn't, we'll reject the load (and cache the result). 1.871 + let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ? 1.872 + cp.REJECT_SERVER : cp.ACCEPT); 1.873 + 1.874 + // Do not cache the result if this is a nonce-source preload 1.875 + if (key && !possiblePreloadNonceConflict) { 1.876 + this._cache.set(key, ret); 1.877 + } 1.878 + return ret; 1.879 + }, 1.880 + 1.881 + shouldProcess: 1.882 + function csp_shouldProcess(aContentType, 1.883 + aContentLocation, 1.884 + aRequestOrigin, 1.885 + aContext, 1.886 + aMimeType, 1.887 + aExtra) { 1.888 + // frame-ancestors check is done outside the ContentPolicy 1.889 + var res = Ci.nsIContentPolicy.ACCEPT; 1.890 + CSPdebug("shouldProcess aContext=" + aContext); 1.891 + return res; 1.892 + }, 1.893 + 1.894 + /** 1.895 + * Asynchronously notifies any nsIObservers listening to the CSP violation 1.896 + * topic that a violation occurred. Also triggers report sending. All 1.897 + * asynchronous on the main thread. 1.898 + * 1.899 + * @param aBlockedContentSource 1.900 + * Either a CSP Source (like 'self', as string) or nsIURI: the source 1.901 + * of the violation. 1.902 + * @param aOriginalUri 1.903 + * The original URI if the blocked content is a redirect, else null 1.904 + * @param aViolatedDirective 1.905 + * the directive that was violated (string). 1.906 + * @param aViolatedPolicyIndex 1.907 + * the index of the policy that was violated (so we know where to send 1.908 + * the reports). 1.909 + * @param aObserverSubject 1.910 + * optional, subject sent to the nsIObservers listening to the CSP 1.911 + * violation topic. 1.912 + * @param aSourceFile 1.913 + * name of the file containing the inline script violation 1.914 + * @param aScriptSample 1.915 + * a sample of the violating inline script 1.916 + * @param aLineNum 1.917 + * source line number of the violation (if available) 1.918 + */ 1.919 + _asyncReportViolation: 1.920 + function(aBlockedContentSource, aOriginalUri, aViolatedDirective, 1.921 + aViolatedPolicyIndex, aObserverSubject, 1.922 + aSourceFile, aScriptSample, aLineNum) { 1.923 + // if optional observerSubject isn't specified, default to the source of 1.924 + // the violation. 1.925 + if (!aObserverSubject) 1.926 + aObserverSubject = aBlockedContentSource; 1.927 + 1.928 + // gotta wrap things that aren't nsISupports, since it's sent out to 1.929 + // observers as such. Objects that are not nsISupports are converted to 1.930 + // strings and then wrapped into a nsISupportsCString. 1.931 + if (!(aObserverSubject instanceof Ci.nsISupports)) { 1.932 + let d = aObserverSubject; 1.933 + aObserverSubject = Cc["@mozilla.org/supports-cstring;1"] 1.934 + .createInstance(Ci.nsISupportsCString); 1.935 + aObserverSubject.data = d; 1.936 + } 1.937 + 1.938 + var reportSender = this; 1.939 + Services.tm.mainThread.dispatch( 1.940 + function() { 1.941 + Services.obs.notifyObservers(aObserverSubject, 1.942 + CSP_VIOLATION_TOPIC, 1.943 + aViolatedDirective); 1.944 + reportSender.sendReports(aBlockedContentSource, aOriginalUri, 1.945 + aViolatedDirective, aViolatedPolicyIndex, 1.946 + aSourceFile, aScriptSample, aLineNum); 1.947 + reportSender.logToConsole(aBlockedContentSource, aOriginalUri, 1.948 + aViolatedDirective, aViolatedPolicyIndex, 1.949 + aSourceFile, aScriptSample, 1.950 + aLineNum, aObserverSubject); 1.951 + 1.952 + }, Ci.nsIThread.DISPATCH_NORMAL); 1.953 + }, 1.954 + 1.955 +/* ........ nsISerializable Methods: .............. */ 1.956 + 1.957 + read: 1.958 + function(aStream) { 1.959 + 1.960 + this._requestOrigin = aStream.readObject(true); 1.961 + this._requestOrigin.QueryInterface(Ci.nsIURI); 1.962 + 1.963 + for (let pCount = aStream.read32(); pCount > 0; pCount--) { 1.964 + let polStr = aStream.readString(); 1.965 + let reportOnly = aStream.readBoolean(); 1.966 + let specCompliant = aStream.readBoolean(); 1.967 + // don't need self info because when the policy is turned back into a 1.968 + // string, 'self' is replaced with the explicit source expression. 1.969 + this._appendPolicyInternal(polStr, null, reportOnly, specCompliant, 1.970 + false); 1.971 + } 1.972 + 1.973 + // NOTE: the document instance that's deserializing this object (via its 1.974 + // principal) should hook itself into this._principal manually. If they 1.975 + // don't, the CSP reports will likely be blocked by nsMixedContentBlocker. 1.976 + }, 1.977 + 1.978 + write: 1.979 + function(aStream) { 1.980 + // need to serialize the context: request origin and such. They are 1.981 + // used when sending reports. Since _request and _requestOrigin are just 1.982 + // different representations of the same thing, only save _requestOrigin 1.983 + // (an nsIURI). 1.984 + aStream.writeCompoundObject(this._requestOrigin, Ci.nsIURI, true); 1.985 + 1.986 + // we can't serialize a reference to the principal that triggered this 1.987 + // instance to serialize, so when this is deserialized by the principal the 1.988 + // caller must hook it up manually by calling setRequestContext on this 1.989 + // instance with the appropriate nsIChannel. 1.990 + 1.991 + // Finally, serialize all the policies. 1.992 + aStream.write32(this._policies.length); 1.993 + 1.994 + for each (var policy in this._policies) { 1.995 + aStream.writeWStringZ(policy.toString()); 1.996 + aStream.writeBoolean(policy._reportOnlyMode); 1.997 + aStream.writeBoolean(policy._specCompliant); 1.998 + } 1.999 + }, 1.1000 +}; 1.1001 + 1.1002 +// The POST of the violation report (if it happens) should not follow 1.1003 +// redirects, per the spec. hence, we implement an nsIChannelEventSink 1.1004 +// with an object so we can tell XHR to abort if a redirect happens. 1.1005 +function CSPReportRedirectSink(policy) { 1.1006 + this._policy = policy; 1.1007 +} 1.1008 + 1.1009 +CSPReportRedirectSink.prototype = { 1.1010 + QueryInterface: function requestor_qi(iid) { 1.1011 + if (iid.equals(Ci.nsISupports) || 1.1012 + iid.equals(Ci.nsIInterfaceRequestor) || 1.1013 + iid.equals(Ci.nsIChannelEventSink)) 1.1014 + return this; 1.1015 + throw Cr.NS_ERROR_NO_INTERFACE; 1.1016 + }, 1.1017 + 1.1018 + // nsIInterfaceRequestor 1.1019 + getInterface: function requestor_gi(iid) { 1.1020 + if (iid.equals(Ci.nsIChannelEventSink)) 1.1021 + return this; 1.1022 + 1.1023 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.1024 + }, 1.1025 + 1.1026 + // nsIChannelEventSink 1.1027 + asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel, 1.1028 + flags, callback) { 1.1029 + this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec])); 1.1030 + 1.1031 + // cancel the old channel so XHR failure callback happens 1.1032 + oldChannel.cancel(Cr.NS_ERROR_ABORT); 1.1033 + 1.1034 + // notify an observer that we have blocked the report POST due to a redirect, 1.1035 + // used in testing, do this async since we're in an async call now to begin with 1.1036 + Services.tm.mainThread.dispatch( 1.1037 + function() { 1.1038 + observerSubject = Cc["@mozilla.org/supports-cstring;1"] 1.1039 + .createInstance(Ci.nsISupportsCString); 1.1040 + observerSubject.data = oldChannel.URI.asciiSpec; 1.1041 + 1.1042 + Services.obs.notifyObservers(observerSubject, 1.1043 + CSP_VIOLATION_TOPIC, 1.1044 + "denied redirect while sending violation report"); 1.1045 + }, Ci.nsIThread.DISPATCH_NORMAL); 1.1046 + 1.1047 + // throw to stop the redirect happening 1.1048 + throw Cr.NS_BINDING_REDIRECTED; 1.1049 + } 1.1050 +}; 1.1051 + 1.1052 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentSecurityPolicy]);