michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: /** michael@0: * Content Security Policy michael@0: * michael@0: * Overview michael@0: * This is a stub component that will be fleshed out to do all the fancy stuff michael@0: * that ContentSecurityPolicy has to do. michael@0: */ michael@0: michael@0: // these identifiers are also defined in contentSecurityPolicy.manifest. michael@0: const CSP_CLASS_ID = Components.ID("{d1680bb4-1ac0-4772-9437-1188375e44f2}"); michael@0: const CSP_CONTRACT_ID = "@mozilla.org/contentsecuritypolicy;1"; michael@0: michael@0: /* :::::::: Constants and Helpers ::::::::::::::: */ michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: const CSP_VIOLATION_TOPIC = "csp-on-violate-policy"; michael@0: michael@0: // Needed to support CSP 1.0 spec and our original CSP implementation - should michael@0: // be removed when our original implementation is deprecated. michael@0: const CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT = "csp_type_xmlhttprequest_spec_compliant"; michael@0: const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant"; michael@0: michael@0: const WARN_FLAG = Ci.nsIScriptError.warningFlag; michael@0: const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG; michael@0: michael@0: const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply'; michael@0: const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute'; michael@0: const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings'; michael@0: const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce' michael@0: const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce' michael@0: const SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid hash'; michael@0: const STYLE_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid hash'; michael@0: michael@0: // The cutoff length of content location in creating CSP cache key. michael@0: const CSP_CACHE_URI_CUTOFF_SIZE = 512; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/CSPUtils.jsm"); michael@0: michael@0: /* ::::: Policy Parsing & Data structures :::::: */ michael@0: michael@0: function ContentSecurityPolicy() { michael@0: CSPdebug("CSP CREATED"); michael@0: this._isInitialized = false; michael@0: michael@0: this._policies = []; michael@0: michael@0: this._request = ""; michael@0: this._requestOrigin = ""; michael@0: this._weakRequestPrincipal = { get : function() { return null; } }; michael@0: this._referrer = ""; michael@0: this._weakDocRequest = { get : function() { return null; } }; michael@0: CSPdebug("CSP object initialized, no policies to enforce yet"); michael@0: michael@0: this._cache = new Map(); michael@0: } michael@0: michael@0: /* michael@0: * Set up mappings from nsIContentPolicy content types to CSP directives. michael@0: */ michael@0: { michael@0: let cp = Ci.nsIContentPolicy; michael@0: let csp = ContentSecurityPolicy; michael@0: let cspr_sd_old = CSPRep.SRC_DIRECTIVES_OLD; michael@0: let cspr_sd_new = CSPRep.SRC_DIRECTIVES_NEW; michael@0: michael@0: csp._MAPPINGS=[]; michael@0: michael@0: /* default, catch-all case */ michael@0: // This is the same in old and new CSP so use the new mapping. michael@0: csp._MAPPINGS[cp.TYPE_OTHER] = cspr_sd_new.DEFAULT_SRC; michael@0: michael@0: /* self */ michael@0: csp._MAPPINGS[cp.TYPE_DOCUMENT] = null; michael@0: michael@0: /* shouldn't see this one */ michael@0: csp._MAPPINGS[cp.TYPE_REFRESH] = null; michael@0: michael@0: /* categorized content types */ michael@0: // These are the same in old and new CSP's so just use the new mappings. michael@0: csp._MAPPINGS[cp.TYPE_SCRIPT] = cspr_sd_new.SCRIPT_SRC; michael@0: csp._MAPPINGS[cp.TYPE_IMAGE] = cspr_sd_new.IMG_SRC; michael@0: csp._MAPPINGS[cp.TYPE_STYLESHEET] = cspr_sd_new.STYLE_SRC; michael@0: csp._MAPPINGS[cp.TYPE_OBJECT] = cspr_sd_new.OBJECT_SRC; michael@0: csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd_new.OBJECT_SRC; michael@0: csp._MAPPINGS[cp.TYPE_SUBDOCUMENT] = cspr_sd_new.FRAME_SRC; michael@0: csp._MAPPINGS[cp.TYPE_MEDIA] = cspr_sd_new.MEDIA_SRC; michael@0: csp._MAPPINGS[cp.TYPE_FONT] = cspr_sd_new.FONT_SRC; michael@0: csp._MAPPINGS[cp.TYPE_XSLT] = cspr_sd_new.SCRIPT_SRC; michael@0: csp._MAPPINGS[cp.TYPE_BEACON] = cspr_sd_new.CONNECT_SRC; michael@0: michael@0: /* Our original CSP implementation's mappings for XHR and websocket michael@0: * These should be changed to be = cspr_sd.CONNECT_SRC when we remove michael@0: * the original implementation - NOTE: order in this array is important !!! michael@0: */ michael@0: csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST] = cspr_sd_old.XHR_SRC; michael@0: csp._MAPPINGS[cp.TYPE_WEBSOCKET] = cspr_sd_old.XHR_SRC; michael@0: michael@0: /* CSP cannot block CSP reports */ michael@0: csp._MAPPINGS[cp.TYPE_CSP_REPORT] = null; michael@0: michael@0: /* These must go through the catch-all */ michael@0: csp._MAPPINGS[cp.TYPE_XBL] = cspr_sd_new.DEFAULT_SRC; michael@0: csp._MAPPINGS[cp.TYPE_PING] = cspr_sd_new.DEFAULT_SRC; michael@0: csp._MAPPINGS[cp.TYPE_DTD] = cspr_sd_new.DEFAULT_SRC; michael@0: michael@0: /* CSP 1.0 spec compliant mappings for XHR and websocket */ michael@0: // The directive name for XHR, websocket, and EventSource is different michael@0: // in the 1.0 spec than in our original implementation, these mappings michael@0: // address this. These won't be needed when we deprecate our original michael@0: // implementation. michael@0: csp._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC; michael@0: csp._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC; michael@0: // TODO : EventSource will be here and also will use connect-src michael@0: // after we fix Bug 802872 - CSP should restrict EventSource using the connect-src michael@0: // directive. For background see Bug 667490 - EventSource should use the same michael@0: // nsIContentPolicy type as XHR (which is fixed) michael@0: } michael@0: michael@0: ContentSecurityPolicy.prototype = { michael@0: classID: CSP_CLASS_ID, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy, michael@0: Ci.nsISerializable, michael@0: Ci.nsISupports]), michael@0: michael@0: // Class info is required to be able to serialize michael@0: classInfo: XPCOMUtils.generateCI({classID: CSP_CLASS_ID, michael@0: contractID: CSP_CONTRACT_ID, michael@0: interfaces: [Ci.nsIContentSecurityPolicy, michael@0: Ci.nsISerializable], michael@0: flags: Ci.nsIClassInfo.MAIN_THREAD_ONLY}), michael@0: michael@0: get isInitialized() { michael@0: return this._isInitialized; michael@0: }, michael@0: michael@0: set isInitialized (foo) { michael@0: this._isInitialized = foo; michael@0: }, michael@0: michael@0: _getPolicyInternal: function(index) { michael@0: if (index < 0 || index >= this._policies.length) { michael@0: throw Cr.NS_ERROR_FAILURE; michael@0: } michael@0: return this._policies[index]; michael@0: }, michael@0: michael@0: _buildViolatedDirectiveString: michael@0: function(aDirectiveName, aPolicy) { michael@0: var SD = CSPRep.SRC_DIRECTIVES_NEW; michael@0: var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC; michael@0: var directive = aPolicy._directives[cspContext]; michael@0: return cspContext + ' ' + directive.toString(); michael@0: }, michael@0: michael@0: /** michael@0: * Returns policy string representing the policy at "index". michael@0: */ michael@0: getPolicy: function(index) { michael@0: return this._getPolicyInternal(index).toString(); michael@0: }, michael@0: michael@0: /** michael@0: * Returns count of policies. michael@0: */ michael@0: get numPolicies() { michael@0: return this._policies.length; michael@0: }, michael@0: michael@0: getAllowsInlineScript: function(shouldReportViolations) { michael@0: // report it? (for each policy, is it violated?) michael@0: shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; }); michael@0: michael@0: // allow it to execute? (Do all the policies allow it to execute)? michael@0: return this._policies.every(function(a) { michael@0: return a._reportOnlyMode || a.allowsInlineScripts; michael@0: }); michael@0: }, michael@0: michael@0: getAllowsEval: function(shouldReportViolations) { michael@0: // report it? (for each policy, is it violated?) michael@0: shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; }); michael@0: michael@0: // allow it to execute? (Do all the policies allow it to execute)? michael@0: return this._policies.every(function(a) { michael@0: return a._reportOnlyMode || a.allowsEvalInScripts; michael@0: }); michael@0: }, michael@0: michael@0: getAllowsInlineStyle: function(shouldReportViolations) { michael@0: // report it? (for each policy, is it violated?) michael@0: shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; }); michael@0: michael@0: // allow it to execute? (Do all the policies allow it to execute)? michael@0: return this._policies.every(function(a) { michael@0: return a._reportOnlyMode || a.allowsInlineStyles; michael@0: }); michael@0: }, michael@0: michael@0: getAllowsNonce: function(aNonce, aContentType, shouldReportViolation) { michael@0: if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT || michael@0: aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) { michael@0: CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType); michael@0: return false; michael@0: } michael@0: michael@0: let directive = ContentSecurityPolicy._MAPPINGS[aContentType]; michael@0: let policyAllowsNonce = [ policy.permitsNonce(aNonce, directive) michael@0: for (policy of this._policies) ]; michael@0: michael@0: shouldReportViolation.value = this._policies.some(function(policy, i) { michael@0: // Don't report a violation if the policy didn't use nonce-source michael@0: return policy._directives.hasOwnProperty(directive) && michael@0: policy._directives[directive]._hasNonceSource && michael@0: !policyAllowsNonce[i]; michael@0: }); michael@0: michael@0: // allow it to execute? (Do all the policies allow it to execute)? michael@0: return this._policies.every(function(policy, i) { michael@0: return policy._reportOnlyMode || policyAllowsNonce[i]; michael@0: }); michael@0: }, michael@0: michael@0: getAllowsHash: function(aContent, aContentType, shouldReportViolation) { michael@0: if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT || michael@0: aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) { michael@0: CSPdebug("Hash check requested for an invalid content type (not script or style): " + aContentType); michael@0: return false; michael@0: } michael@0: michael@0: let directive = ContentSecurityPolicy._MAPPINGS[aContentType]; michael@0: let policyAllowsHash = [ policy.permitsHash(aContent, directive) michael@0: for (policy of this._policies) ]; michael@0: michael@0: shouldReportViolation.value = this._policies.some(function(policy, i) { michael@0: // Don't report a violation if the policy didn't use hash-source michael@0: return policy._directives.hasOwnProperty(directive) && michael@0: policy._directives[directive]._hasHashSource && michael@0: !policyAllowsHash[i]; michael@0: }); michael@0: michael@0: // allow it to execute? (Do all the policies allow it to execute)? michael@0: return this._policies.every(function(policy, i) { michael@0: return policy._reportOnlyMode || policyAllowsHash[i]; michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * For each policy, log any violation on the Error Console and send a report michael@0: * if a report-uri is present in the policy michael@0: * michael@0: * @param aViolationType michael@0: * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval michael@0: * @param aSourceFile michael@0: * name of the source file containing the violation (if available) michael@0: * @param aContentSample michael@0: * sample of the violating content (to aid debugging) michael@0: * @param aLineNum michael@0: * source line number of the violation (if available) michael@0: * @param aNonce michael@0: * (optional) If this is a nonce violation, include the nonce so we can michael@0: * recheck to determine which policies were violated and send the michael@0: * appropriate reports. michael@0: * @param aContent michael@0: * (optional) If this is a hash violation, include contents of the inline michael@0: * resource in the question so we can recheck the hash in order to michael@0: * determine which policies were violated and send the appropriate michael@0: * reports. michael@0: */ michael@0: logViolationDetails: michael@0: function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce, aContent) { michael@0: for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) { michael@0: let policy = this._policies[policyIndex]; michael@0: michael@0: // call-sites to the eval/inline checks recieve two return values: allows michael@0: // and violates. Policies that are report-only allow the michael@0: // loads/compilations but violations should still be reported. Not all michael@0: // policies in this nsIContentSecurityPolicy instance will be violated, michael@0: // which is why we must check again here. michael@0: switch (aViolationType) { michael@0: case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE: michael@0: if (!policy.allowsInlineStyles) { michael@0: var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy); michael@0: this._asyncReportViolation('self', null, violatedDirective, policyIndex, michael@0: INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: } michael@0: break; michael@0: case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT: michael@0: if (!policy.allowsInlineScripts) { michael@0: var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); michael@0: this._asyncReportViolation('self', null, violatedDirective, policyIndex, michael@0: INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: } michael@0: break; michael@0: case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL: michael@0: if (!policy.allowsEvalInScripts) { michael@0: var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); michael@0: this._asyncReportViolation('self', null, violatedDirective, policyIndex, michael@0: EVAL_VIOLATION_OBSERVER_SUBJECT, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: } michael@0: break; michael@0: case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT: michael@0: var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT]; michael@0: if (!policy.permitsNonce(aNonce, scriptType)) { michael@0: var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); michael@0: this._asyncReportViolation('self', null, violatedDirective, policyIndex, michael@0: SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: } michael@0: break; michael@0: case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE: michael@0: var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE]; michael@0: if (!policy.permitsNonce(aNonce, styleType)) { michael@0: var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy); michael@0: this._asyncReportViolation('self', null, violatedDirective, policyIndex, michael@0: STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: } michael@0: break; michael@0: case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_SCRIPT: michael@0: var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT]; michael@0: if (!policy.permitsHash(aContent, scriptType)) { michael@0: var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy); michael@0: this._asyncReportViolation('self', null, violatedDirective, policyIndex, michael@0: SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: } michael@0: break; michael@0: case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_STYLE: michael@0: var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE]; michael@0: if (!policy.permitsHash(aContent, styleType)) { michael@0: var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy); michael@0: this._asyncReportViolation('self', null, violatedDirective, policyIndex, michael@0: STYLE_HASH_VIOLATION_OBSERVER_SUBJECT, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Given an nsIHttpChannel, fill out the appropriate data. michael@0: */ michael@0: setRequestContext: michael@0: function(aSelfURI, aReferrerURI, aPrincipal, aChannel) { michael@0: michael@0: // this requires either a self URI or a http channel. michael@0: if (!aSelfURI && !aChannel) michael@0: return; michael@0: michael@0: if (aChannel) { michael@0: // Save the docRequest for fetching a policy-uri michael@0: this._weakDocRequest = Cu.getWeakReference(aChannel); michael@0: } michael@0: michael@0: // save the document URI (minus ) and referrer for reporting michael@0: let uri = aSelfURI ? aSelfURI.cloneIgnoringRef() : aChannel.URI.cloneIgnoringRef(); michael@0: try { // GetUserPass throws for some protocols without userPass michael@0: uri.userPass = ''; michael@0: } catch (ex) {} michael@0: this._request = uri.asciiSpec; michael@0: this._requestOrigin = uri; michael@0: michael@0: // store a reference to the principal, that can later be used in shouldLoad michael@0: if (aPrincipal) { michael@0: this._weakRequestPrincipal = Cu.getWeakReference(aPrincipal); michael@0: } else if (aChannel) { michael@0: this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"] michael@0: .getService(Ci.nsIScriptSecurityManager) michael@0: .getChannelPrincipal(aChannel)); michael@0: } else { michael@0: CSPdebug("No principal or channel for document context; violation reports may not work."); michael@0: } michael@0: michael@0: // pick one: referrerURI, channel's referrer, or null (first available) michael@0: let ref = null; michael@0: if (aReferrerURI) michael@0: ref = aReferrerURI; michael@0: else if (aChannel instanceof Ci.nsIHttpChannel) michael@0: ref = aChannel.referrer; michael@0: michael@0: if (ref) { michael@0: let referrer = aChannel.referrer.cloneIgnoringRef(); michael@0: try { // GetUserPass throws for some protocols without userPass michael@0: referrer.userPass = ''; michael@0: } catch (ex) {} michael@0: this._referrer = referrer.asciiSpec; michael@0: } michael@0: }, michael@0: michael@0: /* ........ Methods .............. */ michael@0: michael@0: /** michael@0: * Adds a new policy to our list of policies for this CSP context. michael@0: * @returns the count of policies. michael@0: */ michael@0: appendPolicy: michael@0: function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) { michael@0: return this._appendPolicyInternal(aPolicy, selfURI, aReportOnly, michael@0: aSpecCompliant, true); michael@0: }, michael@0: michael@0: /** michael@0: * Adds a new policy to our list of policies for this CSP context. michael@0: * Only to be called from this module (not exported) michael@0: * @returns the count of policies. michael@0: */ michael@0: _appendPolicyInternal: michael@0: function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant, michael@0: aEnforceSelfChecks) { michael@0: #ifndef MOZ_B2G michael@0: CSPdebug("APPENDING POLICY: " + aPolicy); michael@0: CSPdebug(" SELF: " + (selfURI ? selfURI.asciiSpec : " null")); michael@0: CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant); michael@0: #endif michael@0: michael@0: // For nested schemes such as view-source: make sure we are taking the michael@0: // innermost URI to use as 'self' since that's where we will extract the michael@0: // scheme, host and port from michael@0: if (selfURI instanceof Ci.nsINestedURI) { michael@0: #ifndef MOZ_B2G michael@0: CSPdebug(" INNER: " + selfURI.innermostURI.asciiSpec); michael@0: #endif michael@0: selfURI = selfURI.innermostURI; michael@0: } michael@0: michael@0: // stay uninitialized until policy setup is done michael@0: var newpolicy; michael@0: michael@0: // If there is a policy-uri, fetch the policy, then re-call this function. michael@0: // (1) parse and create a CSPRep object michael@0: // Note that we pass the full URI since when it's parsed as 'self' to construct a michael@0: // CSPSource only the scheme, host, and port are kept. michael@0: michael@0: // If we want to be CSP 1.0 spec compliant, use the new parser. michael@0: // The old one will be deprecated in the future and will be michael@0: // removed at that time. michael@0: if (aSpecCompliant) { michael@0: newpolicy = CSPRep.fromStringSpecCompliant(aPolicy, michael@0: selfURI, michael@0: aReportOnly, michael@0: this._weakDocRequest.get(), michael@0: this, michael@0: aEnforceSelfChecks); michael@0: } else { michael@0: newpolicy = CSPRep.fromString(aPolicy, michael@0: selfURI, michael@0: aReportOnly, michael@0: this._weakDocRequest.get(), michael@0: this, michael@0: aEnforceSelfChecks); michael@0: } michael@0: michael@0: newpolicy._specCompliant = !!aSpecCompliant; michael@0: newpolicy._isInitialized = true; michael@0: this._policies.push(newpolicy); michael@0: this._cache.clear(); // reset cache since effective policy changes michael@0: }, michael@0: michael@0: /** michael@0: * Removes a policy from the array of policies. michael@0: */ michael@0: removePolicy: michael@0: function csp_removePolicy(index) { michael@0: if (index < 0 || index >= this._policies.length) { michael@0: CSPdebug("Cannot remove policy " + index + "; not enough policies."); michael@0: return; michael@0: } michael@0: this._policies.splice(index, 1); michael@0: this._cache.clear(); // reset cache since effective policy changes michael@0: }, michael@0: michael@0: /** michael@0: * Generates and sends a violation report to the specified report URIs. michael@0: */ michael@0: sendReports: michael@0: function(blockedUri, originalUri, violatedDirective, michael@0: violatedPolicyIndex, aSourceFile, michael@0: aScriptSample, aLineNum) { michael@0: michael@0: let policy = this._getPolicyInternal(violatedPolicyIndex); michael@0: if (!policy) { michael@0: CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined."); michael@0: return; michael@0: } michael@0: michael@0: var uriString = policy.getReportURIs(); michael@0: var uris = uriString.split(/\s+/); michael@0: if (uris.length > 0) { michael@0: // see if we need to sanitize the blocked-uri michael@0: let blocked = ''; michael@0: if (originalUri) { michael@0: // We've redirected, only report the blocked origin michael@0: try { michael@0: let clone = blockedUri.clone(); michael@0: clone.path = ''; michael@0: blocked = clone.asciiSpec; michael@0: } catch(e) { michael@0: CSPdebug(".... blockedUri can't be cloned: " + blockedUri); michael@0: } michael@0: } michael@0: else if (blockedUri instanceof Ci.nsIURI) { michael@0: blocked = blockedUri.cloneIgnoringRef().asciiSpec; michael@0: } michael@0: else { michael@0: // blockedUri is a string for eval/inline-script violations michael@0: blocked = blockedUri; michael@0: } michael@0: michael@0: // Generate report to send composed of michael@0: // { michael@0: // csp-report: { michael@0: // document-uri: "http://example.com/file.html?params", michael@0: // referrer: "...", michael@0: // blocked-uri: "...", michael@0: // violated-directive: "..." michael@0: // } michael@0: // } michael@0: var report = { michael@0: 'csp-report': { michael@0: 'document-uri': this._request, michael@0: 'referrer': this._referrer, michael@0: 'blocked-uri': blocked, michael@0: 'violated-directive': violatedDirective michael@0: } michael@0: } michael@0: michael@0: // extra report fields for script errors (if available) michael@0: if (originalUri) michael@0: report["csp-report"]["original-uri"] = originalUri.cloneIgnoringRef().asciiSpec; michael@0: if (aSourceFile) michael@0: report["csp-report"]["source-file"] = aSourceFile; michael@0: if (aScriptSample) michael@0: report["csp-report"]["script-sample"] = aScriptSample; michael@0: if (aLineNum) michael@0: report["csp-report"]["line-number"] = aLineNum; michael@0: michael@0: var reportString = JSON.stringify(report); michael@0: CSPdebug("Constructed violation report:\n" + reportString); michael@0: michael@0: // For each URI in the report list, send out a report. michael@0: // We make the assumption that all of the URIs are absolute URIs; this michael@0: // should be taken care of in CSPRep.fromString (where it converts any michael@0: // relative URIs into absolute ones based on "self"). michael@0: for (let i in uris) { michael@0: if (uris[i] === "") michael@0: continue; michael@0: michael@0: try { michael@0: var chan = Services.io.newChannel(uris[i], null, null); michael@0: if (!chan) { michael@0: CSPdebug("Error creating channel for " + uris[i]); michael@0: continue; michael@0: } michael@0: michael@0: var content = Cc["@mozilla.org/io/string-input-stream;1"] michael@0: .createInstance(Ci.nsIStringInputStream); michael@0: content.data = reportString + "\n\n"; michael@0: michael@0: // make sure this is an anonymous request (no cookies) so in case the michael@0: // policy URI is injected, it can't be abused for CSRF. michael@0: chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS; michael@0: michael@0: // we need to set an nsIChannelEventSink on the channel object michael@0: // so we can tell it to not follow redirects when posting the reports michael@0: chan.notificationCallbacks = new CSPReportRedirectSink(policy); michael@0: if (this._weakDocRequest.get()) { michael@0: chan.loadGroup = this._weakDocRequest.get().loadGroup; michael@0: } michael@0: michael@0: chan.QueryInterface(Ci.nsIUploadChannel) michael@0: .setUploadStream(content, "application/json", content.available()); michael@0: michael@0: try { michael@0: // if this is an HTTP channel, set the request method to post michael@0: chan.QueryInterface(Ci.nsIHttpChannel); michael@0: chan.requestMethod = "POST"; michael@0: } catch(e) {} // throws only if chan is not an nsIHttpChannel. michael@0: michael@0: // check with the content policy service to see if we're allowed to michael@0: // send this request. michael@0: try { michael@0: var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"] michael@0: .getService(Ci.nsIContentPolicy); michael@0: if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT, michael@0: chan.URI, this._requestOrigin, michael@0: null, null, null, this._weakRequestPrincipal.get()) michael@0: != Ci.nsIContentPolicy.ACCEPT) { michael@0: continue; // skip unauthorized URIs michael@0: } michael@0: } catch(e) { michael@0: continue; // refuse to load if we can't do a security check. michael@0: } michael@0: michael@0: //send data (and set up error notifications) michael@0: chan.asyncOpen(new CSPViolationReportListener(uris[i]), null); michael@0: CSPdebug("Sent violation report to " + uris[i]); michael@0: } catch(e) { michael@0: // it's possible that the URI was invalid, just log a michael@0: // warning and skip over that. michael@0: policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]])); michael@0: policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()])); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Logs a meaningful CSP warning to the developer console. michael@0: */ michael@0: logToConsole: michael@0: function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex, michael@0: aSourceFile, aScriptSample, aLineNum, aObserverSubject) { michael@0: let policy = this._policies[aViolatedPolicyIndex]; michael@0: switch(aObserverSubject.data) { michael@0: case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT: michael@0: violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked"); michael@0: break; michael@0: case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT: michael@0: violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked"); michael@0: break; michael@0: case EVAL_VIOLATION_OBSERVER_SUBJECT: michael@0: violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked"); michael@0: break; michael@0: } michael@0: var violationMessage = null; michael@0: if (blockedUri["asciiSpec"]) { michael@0: violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]); michael@0: } else { michael@0: violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]); michael@0: } michael@0: policy.log(WARN_FLAG, violationMessage, michael@0: (aSourceFile) ? aSourceFile : null, michael@0: (aScriptSample) ? decodeURIComponent(aScriptSample) : null, michael@0: (aLineNum) ? aLineNum : null); michael@0: }, michael@0: michael@0: /** michael@0: * Exposed Method to analyze docShell for approved frame ancestry. michael@0: * NOTE: Also sends violation reports if necessary. michael@0: * @param docShell michael@0: * the docShell for this policy's resource. michael@0: * @return michael@0: * true if the frame ancestry is allowed by this policy and the load michael@0: * should progress. michael@0: */ michael@0: permitsAncestry: michael@0: function(docShell) { michael@0: // Cannot shortcut checking all the policies since violation reports have michael@0: // to be triggered if any policy wants it. michael@0: var permitted = true; michael@0: for (let i = 0; i < this._policies.length; i++) { michael@0: if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) { michael@0: permitted = false; michael@0: } michael@0: } michael@0: return permitted; michael@0: }, michael@0: michael@0: _permitsAncestryInternal: michael@0: function(docShell, policy, policyIndex) { michael@0: if (!docShell) { return false; } michael@0: michael@0: // walk up this docShell tree until we hit chrome michael@0: var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDocShellTreeItem); michael@0: michael@0: // collect ancestors and make sure they're allowed. michael@0: var ancestors = []; michael@0: while (dst.parent) { michael@0: dst = dst.parent; michael@0: let it = dst.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation); michael@0: if (it.currentURI) { michael@0: if (it.currentURI.scheme === "chrome") { michael@0: break; michael@0: } michael@0: // delete any userpass michael@0: let ancestor = it.currentURI.cloneIgnoringRef(); michael@0: try { // GetUserPass throws for some protocols without userPass michael@0: ancestor.userPass = ''; michael@0: } catch (ex) {} michael@0: michael@0: #ifndef MOZ_B2G michael@0: CSPdebug(" found frame ancestor " + ancestor.asciiSpec); michael@0: #endif michael@0: ancestors.push(ancestor); michael@0: } michael@0: } michael@0: michael@0: // scan the discovered ancestors michael@0: // frame-ancestors is the same in both old and new source directives, michael@0: // so don't need to differentiate here. michael@0: let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS; michael@0: for (let i in ancestors) { michael@0: let ancestor = ancestors[i]; michael@0: if (!policy.permits(ancestor, cspContext)) { michael@0: // report the frame-ancestor violation michael@0: let directive = policy._directives[cspContext]; michael@0: let violatedPolicy = 'frame-ancestors ' + directive.toString(); michael@0: michael@0: this._asyncReportViolation(ancestors[i], null, violatedPolicy, michael@0: policyIndex); michael@0: michael@0: // need to lie if we are testing in report-only mode michael@0: return policy._reportOnlyMode; michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Creates a cache key from content location and content type. michael@0: */ michael@0: _createCacheKey: michael@0: function (aContentLocation, aContentType) { michael@0: if (aContentType != Ci.nsIContentPolicy.TYPE_SCRIPT && michael@0: aContentLocation.scheme == "data") { michael@0: // For non-script data: URI, use ("data:", aContentType) as the cache key. michael@0: return aContentLocation.scheme + ":" + aContentType; michael@0: } michael@0: michael@0: let uri = aContentLocation.spec; michael@0: if (uri.length > CSP_CACHE_URI_CUTOFF_SIZE) { michael@0: // Don't cache for a URI longer than the cutoff size. michael@0: return null; michael@0: } michael@0: return uri + "!" + aContentType; michael@0: }, michael@0: michael@0: /** michael@0: * Delegate method called by the service when sub-elements of the protected michael@0: * document are being loaded. Given a bit of information about the request, michael@0: * decides whether or not the policy is satisfied. michael@0: */ michael@0: shouldLoad: michael@0: function csp_shouldLoad(aContentType, michael@0: aContentLocation, michael@0: aRequestOrigin, michael@0: aContext, michael@0: aMimeTypeGuess, michael@0: aOriginalUri) { michael@0: let key = this._createCacheKey(aContentLocation, aContentType); michael@0: if (key && this._cache.has(key)) { michael@0: return this._cache.get(key); michael@0: } michael@0: michael@0: #ifndef MOZ_B2G michael@0: // Try to remove as much as possible from the hot path on b2g. michael@0: CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec); michael@0: CSPdebug("shouldLoad content type = " + aContentType); michael@0: #endif michael@0: michael@0: // The mapping for XHR and websockets is different between our original michael@0: // implementation and the 1.0 spec, we handle this here. michael@0: var cspContext; michael@0: michael@0: let cp = Ci.nsIContentPolicy; michael@0: michael@0: // Infer if this is a preload for elements that use nonce-source. Since, michael@0: // for preloads, aContext is the document and not the element associated michael@0: // with the resource, we cannot determine the nonce. See Bug 612921 and michael@0: // Bug 855326. michael@0: let nonceSourceValid = aContentType == cp.TYPE_SCRIPT || michael@0: aContentType == cp.TYPE_STYLESHEET; michael@0: var possiblePreloadNonceConflict = nonceSourceValid && michael@0: aContext instanceof Ci.nsIDOMHTMLDocument; michael@0: michael@0: // iterate through all the _policies and send reports where a policy is michael@0: // violated. After the check, determine the overall effect (blocked or michael@0: // loaded?) and cache it. michael@0: let policyAllowsLoadArray = []; michael@0: for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) { michael@0: let policy = this._policies[policyIndex]; michael@0: michael@0: #ifndef MOZ_B2G michael@0: CSPdebug("policy is " + (policy._specCompliant ? michael@0: "1.0 compliant" : "pre-1.0")); michael@0: CSPdebug("policy is " + (policy._reportOnlyMode ? michael@0: "report-only" : "blocking")); michael@0: #endif michael@0: michael@0: if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) { michael@0: cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT]; michael@0: } else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) { michael@0: cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT]; michael@0: } else { michael@0: cspContext = ContentSecurityPolicy._MAPPINGS[aContentType]; michael@0: } michael@0: michael@0: #ifndef MOZ_B2G michael@0: CSPdebug("shouldLoad cspContext = " + cspContext); michael@0: #endif michael@0: michael@0: // if the mapping is null, there's no policy, let it through. michael@0: if (!cspContext) { michael@0: return Ci.nsIContentPolicy.ACCEPT; michael@0: } michael@0: michael@0: // check if location is permitted michael@0: let permitted = policy.permits(aContentLocation, cspContext); michael@0: michael@0: // check any valid content type for nonce if location is not permitted michael@0: if (!permitted && nonceSourceValid && michael@0: aContext instanceof Ci.nsIDOMHTMLElement && michael@0: aContext.hasAttribute('nonce')) { michael@0: permitted = policy.permitsNonce(aContext.getAttribute('nonce'), michael@0: cspContext); michael@0: } michael@0: michael@0: // record whether the thing should be blocked or just reported. michael@0: policyAllowsLoadArray.push(permitted || policy._reportOnlyMode); michael@0: let res = permitted ? cp.ACCEPT : cp.REJECT_SERVER; michael@0: michael@0: // frame-ancestors is taken care of early on (as this document is loaded) michael@0: michael@0: // If the result is *NOT* ACCEPT, then send report michael@0: // Do not send report if this is a nonce-source preload - the decision may michael@0: // be wrong and will incorrectly fail the unit tests. michael@0: if (res != cp.ACCEPT && !possiblePreloadNonceConflict) { michael@0: CSPdebug("blocking request for " + aContentLocation.asciiSpec); michael@0: try { michael@0: let directive = "unknown directive", michael@0: violatedPolicy = "unknown policy"; michael@0: michael@0: // The policy might not explicitly declare each source directive (so michael@0: // the cspContext may be implicit). If so, we have to report michael@0: // violations as appropriate: specific or the default-src directive. michael@0: if (policy._directives.hasOwnProperty(cspContext)) { michael@0: directive = policy._directives[cspContext]; michael@0: violatedPolicy = cspContext + ' ' + directive.toString(); michael@0: } else if (policy._directives.hasOwnProperty("default-src")) { michael@0: directive = policy._directives["default-src"]; michael@0: violatedPolicy = "default-src " + directive.toString(); michael@0: } else { michael@0: violatedPolicy = "unknown directive"; michael@0: CSPdebug('ERROR in blocking content: ' + michael@0: 'CSP is not sure which part of the policy caused this block'); michael@0: } michael@0: michael@0: this._asyncReportViolation(aContentLocation, michael@0: aOriginalUri, michael@0: violatedPolicy, michael@0: policyIndex); michael@0: } catch(e) { michael@0: CSPdebug('---------------- ERROR: ' + e); michael@0: } michael@0: } michael@0: } // end for-each loop over policies michael@0: michael@0: // the ultimate decision is based on whether any policies want to reject michael@0: // the load. The array keeps track of whether the policies allowed the michael@0: // loads. If any doesn't, we'll reject the load (and cache the result). michael@0: let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ? michael@0: cp.REJECT_SERVER : cp.ACCEPT); michael@0: michael@0: // Do not cache the result if this is a nonce-source preload michael@0: if (key && !possiblePreloadNonceConflict) { michael@0: this._cache.set(key, ret); michael@0: } michael@0: return ret; michael@0: }, michael@0: michael@0: shouldProcess: michael@0: function csp_shouldProcess(aContentType, michael@0: aContentLocation, michael@0: aRequestOrigin, michael@0: aContext, michael@0: aMimeType, michael@0: aExtra) { michael@0: // frame-ancestors check is done outside the ContentPolicy michael@0: var res = Ci.nsIContentPolicy.ACCEPT; michael@0: CSPdebug("shouldProcess aContext=" + aContext); michael@0: return res; michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously notifies any nsIObservers listening to the CSP violation michael@0: * topic that a violation occurred. Also triggers report sending. All michael@0: * asynchronous on the main thread. michael@0: * michael@0: * @param aBlockedContentSource michael@0: * Either a CSP Source (like 'self', as string) or nsIURI: the source michael@0: * of the violation. michael@0: * @param aOriginalUri michael@0: * The original URI if the blocked content is a redirect, else null michael@0: * @param aViolatedDirective michael@0: * the directive that was violated (string). michael@0: * @param aViolatedPolicyIndex michael@0: * the index of the policy that was violated (so we know where to send michael@0: * the reports). michael@0: * @param aObserverSubject michael@0: * optional, subject sent to the nsIObservers listening to the CSP michael@0: * violation topic. michael@0: * @param aSourceFile michael@0: * name of the file containing the inline script violation michael@0: * @param aScriptSample michael@0: * a sample of the violating inline script michael@0: * @param aLineNum michael@0: * source line number of the violation (if available) michael@0: */ michael@0: _asyncReportViolation: michael@0: function(aBlockedContentSource, aOriginalUri, aViolatedDirective, michael@0: aViolatedPolicyIndex, aObserverSubject, michael@0: aSourceFile, aScriptSample, aLineNum) { michael@0: // if optional observerSubject isn't specified, default to the source of michael@0: // the violation. michael@0: if (!aObserverSubject) michael@0: aObserverSubject = aBlockedContentSource; michael@0: michael@0: // gotta wrap things that aren't nsISupports, since it's sent out to michael@0: // observers as such. Objects that are not nsISupports are converted to michael@0: // strings and then wrapped into a nsISupportsCString. michael@0: if (!(aObserverSubject instanceof Ci.nsISupports)) { michael@0: let d = aObserverSubject; michael@0: aObserverSubject = Cc["@mozilla.org/supports-cstring;1"] michael@0: .createInstance(Ci.nsISupportsCString); michael@0: aObserverSubject.data = d; michael@0: } michael@0: michael@0: var reportSender = this; michael@0: Services.tm.mainThread.dispatch( michael@0: function() { michael@0: Services.obs.notifyObservers(aObserverSubject, michael@0: CSP_VIOLATION_TOPIC, michael@0: aViolatedDirective); michael@0: reportSender.sendReports(aBlockedContentSource, aOriginalUri, michael@0: aViolatedDirective, aViolatedPolicyIndex, michael@0: aSourceFile, aScriptSample, aLineNum); michael@0: reportSender.logToConsole(aBlockedContentSource, aOriginalUri, michael@0: aViolatedDirective, aViolatedPolicyIndex, michael@0: aSourceFile, aScriptSample, michael@0: aLineNum, aObserverSubject); michael@0: michael@0: }, Ci.nsIThread.DISPATCH_NORMAL); michael@0: }, michael@0: michael@0: /* ........ nsISerializable Methods: .............. */ michael@0: michael@0: read: michael@0: function(aStream) { michael@0: michael@0: this._requestOrigin = aStream.readObject(true); michael@0: this._requestOrigin.QueryInterface(Ci.nsIURI); michael@0: michael@0: for (let pCount = aStream.read32(); pCount > 0; pCount--) { michael@0: let polStr = aStream.readString(); michael@0: let reportOnly = aStream.readBoolean(); michael@0: let specCompliant = aStream.readBoolean(); michael@0: // don't need self info because when the policy is turned back into a michael@0: // string, 'self' is replaced with the explicit source expression. michael@0: this._appendPolicyInternal(polStr, null, reportOnly, specCompliant, michael@0: false); michael@0: } michael@0: michael@0: // NOTE: the document instance that's deserializing this object (via its michael@0: // principal) should hook itself into this._principal manually. If they michael@0: // don't, the CSP reports will likely be blocked by nsMixedContentBlocker. michael@0: }, michael@0: michael@0: write: michael@0: function(aStream) { michael@0: // need to serialize the context: request origin and such. They are michael@0: // used when sending reports. Since _request and _requestOrigin are just michael@0: // different representations of the same thing, only save _requestOrigin michael@0: // (an nsIURI). michael@0: aStream.writeCompoundObject(this._requestOrigin, Ci.nsIURI, true); michael@0: michael@0: // we can't serialize a reference to the principal that triggered this michael@0: // instance to serialize, so when this is deserialized by the principal the michael@0: // caller must hook it up manually by calling setRequestContext on this michael@0: // instance with the appropriate nsIChannel. michael@0: michael@0: // Finally, serialize all the policies. michael@0: aStream.write32(this._policies.length); michael@0: michael@0: for each (var policy in this._policies) { michael@0: aStream.writeWStringZ(policy.toString()); michael@0: aStream.writeBoolean(policy._reportOnlyMode); michael@0: aStream.writeBoolean(policy._specCompliant); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: // The POST of the violation report (if it happens) should not follow michael@0: // redirects, per the spec. hence, we implement an nsIChannelEventSink michael@0: // with an object so we can tell XHR to abort if a redirect happens. michael@0: function CSPReportRedirectSink(policy) { michael@0: this._policy = policy; michael@0: } michael@0: michael@0: CSPReportRedirectSink.prototype = { michael@0: QueryInterface: function requestor_qi(iid) { michael@0: if (iid.equals(Ci.nsISupports) || michael@0: iid.equals(Ci.nsIInterfaceRequestor) || michael@0: iid.equals(Ci.nsIChannelEventSink)) michael@0: return this; michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: michael@0: // nsIInterfaceRequestor michael@0: getInterface: function requestor_gi(iid) { michael@0: if (iid.equals(Ci.nsIChannelEventSink)) michael@0: return this; michael@0: michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: michael@0: // nsIChannelEventSink michael@0: asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel, michael@0: flags, callback) { michael@0: this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec])); michael@0: michael@0: // cancel the old channel so XHR failure callback happens michael@0: oldChannel.cancel(Cr.NS_ERROR_ABORT); michael@0: michael@0: // notify an observer that we have blocked the report POST due to a redirect, michael@0: // used in testing, do this async since we're in an async call now to begin with michael@0: Services.tm.mainThread.dispatch( michael@0: function() { michael@0: observerSubject = Cc["@mozilla.org/supports-cstring;1"] michael@0: .createInstance(Ci.nsISupportsCString); michael@0: observerSubject.data = oldChannel.URI.asciiSpec; michael@0: michael@0: Services.obs.notifyObservers(observerSubject, michael@0: CSP_VIOLATION_TOPIC, michael@0: "denied redirect while sending violation report"); michael@0: }, Ci.nsIThread.DISPATCH_NORMAL); michael@0: michael@0: // throw to stop the redirect happening michael@0: throw Cr.NS_BINDING_REDIRECTED; michael@0: } michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentSecurityPolicy]);