content/base/src/contentSecurityPolicy.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5
michael@0 6 /**
michael@0 7 * Content Security Policy
michael@0 8 *
michael@0 9 * Overview
michael@0 10 * This is a stub component that will be fleshed out to do all the fancy stuff
michael@0 11 * that ContentSecurityPolicy has to do.
michael@0 12 */
michael@0 13
michael@0 14 // these identifiers are also defined in contentSecurityPolicy.manifest.
michael@0 15 const CSP_CLASS_ID = Components.ID("{d1680bb4-1ac0-4772-9437-1188375e44f2}");
michael@0 16 const CSP_CONTRACT_ID = "@mozilla.org/contentsecuritypolicy;1";
michael@0 17
michael@0 18 /* :::::::: Constants and Helpers ::::::::::::::: */
michael@0 19
michael@0 20 const Cc = Components.classes;
michael@0 21 const Ci = Components.interfaces;
michael@0 22 const Cr = Components.results;
michael@0 23 const Cu = Components.utils;
michael@0 24
michael@0 25 const CSP_VIOLATION_TOPIC = "csp-on-violate-policy";
michael@0 26
michael@0 27 // Needed to support CSP 1.0 spec and our original CSP implementation - should
michael@0 28 // be removed when our original implementation is deprecated.
michael@0 29 const CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT = "csp_type_xmlhttprequest_spec_compliant";
michael@0 30 const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant";
michael@0 31
michael@0 32 const WARN_FLAG = Ci.nsIScriptError.warningFlag;
michael@0 33 const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
michael@0 34
michael@0 35 const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
michael@0 36 const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
michael@0 37 const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
michael@0 38 const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce'
michael@0 39 const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce'
michael@0 40 const SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid hash';
michael@0 41 const STYLE_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid hash';
michael@0 42
michael@0 43 // The cutoff length of content location in creating CSP cache key.
michael@0 44 const CSP_CACHE_URI_CUTOFF_SIZE = 512;
michael@0 45
michael@0 46 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 47 Cu.import("resource://gre/modules/Services.jsm");
michael@0 48 Cu.import("resource://gre/modules/CSPUtils.jsm");
michael@0 49
michael@0 50 /* ::::: Policy Parsing & Data structures :::::: */
michael@0 51
michael@0 52 function ContentSecurityPolicy() {
michael@0 53 CSPdebug("CSP CREATED");
michael@0 54 this._isInitialized = false;
michael@0 55
michael@0 56 this._policies = [];
michael@0 57
michael@0 58 this._request = "";
michael@0 59 this._requestOrigin = "";
michael@0 60 this._weakRequestPrincipal = { get : function() { return null; } };
michael@0 61 this._referrer = "";
michael@0 62 this._weakDocRequest = { get : function() { return null; } };
michael@0 63 CSPdebug("CSP object initialized, no policies to enforce yet");
michael@0 64
michael@0 65 this._cache = new Map();
michael@0 66 }
michael@0 67
michael@0 68 /*
michael@0 69 * Set up mappings from nsIContentPolicy content types to CSP directives.
michael@0 70 */
michael@0 71 {
michael@0 72 let cp = Ci.nsIContentPolicy;
michael@0 73 let csp = ContentSecurityPolicy;
michael@0 74 let cspr_sd_old = CSPRep.SRC_DIRECTIVES_OLD;
michael@0 75 let cspr_sd_new = CSPRep.SRC_DIRECTIVES_NEW;
michael@0 76
michael@0 77 csp._MAPPINGS=[];
michael@0 78
michael@0 79 /* default, catch-all case */
michael@0 80 // This is the same in old and new CSP so use the new mapping.
michael@0 81 csp._MAPPINGS[cp.TYPE_OTHER] = cspr_sd_new.DEFAULT_SRC;
michael@0 82
michael@0 83 /* self */
michael@0 84 csp._MAPPINGS[cp.TYPE_DOCUMENT] = null;
michael@0 85
michael@0 86 /* shouldn't see this one */
michael@0 87 csp._MAPPINGS[cp.TYPE_REFRESH] = null;
michael@0 88
michael@0 89 /* categorized content types */
michael@0 90 // These are the same in old and new CSP's so just use the new mappings.
michael@0 91 csp._MAPPINGS[cp.TYPE_SCRIPT] = cspr_sd_new.SCRIPT_SRC;
michael@0 92 csp._MAPPINGS[cp.TYPE_IMAGE] = cspr_sd_new.IMG_SRC;
michael@0 93 csp._MAPPINGS[cp.TYPE_STYLESHEET] = cspr_sd_new.STYLE_SRC;
michael@0 94 csp._MAPPINGS[cp.TYPE_OBJECT] = cspr_sd_new.OBJECT_SRC;
michael@0 95 csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd_new.OBJECT_SRC;
michael@0 96 csp._MAPPINGS[cp.TYPE_SUBDOCUMENT] = cspr_sd_new.FRAME_SRC;
michael@0 97 csp._MAPPINGS[cp.TYPE_MEDIA] = cspr_sd_new.MEDIA_SRC;
michael@0 98 csp._MAPPINGS[cp.TYPE_FONT] = cspr_sd_new.FONT_SRC;
michael@0 99 csp._MAPPINGS[cp.TYPE_XSLT] = cspr_sd_new.SCRIPT_SRC;
michael@0 100 csp._MAPPINGS[cp.TYPE_BEACON] = cspr_sd_new.CONNECT_SRC;
michael@0 101
michael@0 102 /* Our original CSP implementation's mappings for XHR and websocket
michael@0 103 * These should be changed to be = cspr_sd.CONNECT_SRC when we remove
michael@0 104 * the original implementation - NOTE: order in this array is important !!!
michael@0 105 */
michael@0 106 csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST] = cspr_sd_old.XHR_SRC;
michael@0 107 csp._MAPPINGS[cp.TYPE_WEBSOCKET] = cspr_sd_old.XHR_SRC;
michael@0 108
michael@0 109 /* CSP cannot block CSP reports */
michael@0 110 csp._MAPPINGS[cp.TYPE_CSP_REPORT] = null;
michael@0 111
michael@0 112 /* These must go through the catch-all */
michael@0 113 csp._MAPPINGS[cp.TYPE_XBL] = cspr_sd_new.DEFAULT_SRC;
michael@0 114 csp._MAPPINGS[cp.TYPE_PING] = cspr_sd_new.DEFAULT_SRC;
michael@0 115 csp._MAPPINGS[cp.TYPE_DTD] = cspr_sd_new.DEFAULT_SRC;
michael@0 116
michael@0 117 /* CSP 1.0 spec compliant mappings for XHR and websocket */
michael@0 118 // The directive name for XHR, websocket, and EventSource is different
michael@0 119 // in the 1.0 spec than in our original implementation, these mappings
michael@0 120 // address this. These won't be needed when we deprecate our original
michael@0 121 // implementation.
michael@0 122 csp._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC;
michael@0 123 csp._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT] = cspr_sd_new.CONNECT_SRC;
michael@0 124 // TODO : EventSource will be here and also will use connect-src
michael@0 125 // after we fix Bug 802872 - CSP should restrict EventSource using the connect-src
michael@0 126 // directive. For background see Bug 667490 - EventSource should use the same
michael@0 127 // nsIContentPolicy type as XHR (which is fixed)
michael@0 128 }
michael@0 129
michael@0 130 ContentSecurityPolicy.prototype = {
michael@0 131 classID: CSP_CLASS_ID,
michael@0 132 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy,
michael@0 133 Ci.nsISerializable,
michael@0 134 Ci.nsISupports]),
michael@0 135
michael@0 136 // Class info is required to be able to serialize
michael@0 137 classInfo: XPCOMUtils.generateCI({classID: CSP_CLASS_ID,
michael@0 138 contractID: CSP_CONTRACT_ID,
michael@0 139 interfaces: [Ci.nsIContentSecurityPolicy,
michael@0 140 Ci.nsISerializable],
michael@0 141 flags: Ci.nsIClassInfo.MAIN_THREAD_ONLY}),
michael@0 142
michael@0 143 get isInitialized() {
michael@0 144 return this._isInitialized;
michael@0 145 },
michael@0 146
michael@0 147 set isInitialized (foo) {
michael@0 148 this._isInitialized = foo;
michael@0 149 },
michael@0 150
michael@0 151 _getPolicyInternal: function(index) {
michael@0 152 if (index < 0 || index >= this._policies.length) {
michael@0 153 throw Cr.NS_ERROR_FAILURE;
michael@0 154 }
michael@0 155 return this._policies[index];
michael@0 156 },
michael@0 157
michael@0 158 _buildViolatedDirectiveString:
michael@0 159 function(aDirectiveName, aPolicy) {
michael@0 160 var SD = CSPRep.SRC_DIRECTIVES_NEW;
michael@0 161 var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
michael@0 162 var directive = aPolicy._directives[cspContext];
michael@0 163 return cspContext + ' ' + directive.toString();
michael@0 164 },
michael@0 165
michael@0 166 /**
michael@0 167 * Returns policy string representing the policy at "index".
michael@0 168 */
michael@0 169 getPolicy: function(index) {
michael@0 170 return this._getPolicyInternal(index).toString();
michael@0 171 },
michael@0 172
michael@0 173 /**
michael@0 174 * Returns count of policies.
michael@0 175 */
michael@0 176 get numPolicies() {
michael@0 177 return this._policies.length;
michael@0 178 },
michael@0 179
michael@0 180 getAllowsInlineScript: function(shouldReportViolations) {
michael@0 181 // report it? (for each policy, is it violated?)
michael@0 182 shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; });
michael@0 183
michael@0 184 // allow it to execute? (Do all the policies allow it to execute)?
michael@0 185 return this._policies.every(function(a) {
michael@0 186 return a._reportOnlyMode || a.allowsInlineScripts;
michael@0 187 });
michael@0 188 },
michael@0 189
michael@0 190 getAllowsEval: function(shouldReportViolations) {
michael@0 191 // report it? (for each policy, is it violated?)
michael@0 192 shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; });
michael@0 193
michael@0 194 // allow it to execute? (Do all the policies allow it to execute)?
michael@0 195 return this._policies.every(function(a) {
michael@0 196 return a._reportOnlyMode || a.allowsEvalInScripts;
michael@0 197 });
michael@0 198 },
michael@0 199
michael@0 200 getAllowsInlineStyle: function(shouldReportViolations) {
michael@0 201 // report it? (for each policy, is it violated?)
michael@0 202 shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; });
michael@0 203
michael@0 204 // allow it to execute? (Do all the policies allow it to execute)?
michael@0 205 return this._policies.every(function(a) {
michael@0 206 return a._reportOnlyMode || a.allowsInlineStyles;
michael@0 207 });
michael@0 208 },
michael@0 209
michael@0 210 getAllowsNonce: function(aNonce, aContentType, shouldReportViolation) {
michael@0 211 if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
michael@0 212 aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
michael@0 213 CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType);
michael@0 214 return false;
michael@0 215 }
michael@0 216
michael@0 217 let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
michael@0 218 let policyAllowsNonce = [ policy.permitsNonce(aNonce, directive)
michael@0 219 for (policy of this._policies) ];
michael@0 220
michael@0 221 shouldReportViolation.value = this._policies.some(function(policy, i) {
michael@0 222 // Don't report a violation if the policy didn't use nonce-source
michael@0 223 return policy._directives.hasOwnProperty(directive) &&
michael@0 224 policy._directives[directive]._hasNonceSource &&
michael@0 225 !policyAllowsNonce[i];
michael@0 226 });
michael@0 227
michael@0 228 // allow it to execute? (Do all the policies allow it to execute)?
michael@0 229 return this._policies.every(function(policy, i) {
michael@0 230 return policy._reportOnlyMode || policyAllowsNonce[i];
michael@0 231 });
michael@0 232 },
michael@0 233
michael@0 234 getAllowsHash: function(aContent, aContentType, shouldReportViolation) {
michael@0 235 if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
michael@0 236 aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
michael@0 237 CSPdebug("Hash check requested for an invalid content type (not script or style): " + aContentType);
michael@0 238 return false;
michael@0 239 }
michael@0 240
michael@0 241 let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
michael@0 242 let policyAllowsHash = [ policy.permitsHash(aContent, directive)
michael@0 243 for (policy of this._policies) ];
michael@0 244
michael@0 245 shouldReportViolation.value = this._policies.some(function(policy, i) {
michael@0 246 // Don't report a violation if the policy didn't use hash-source
michael@0 247 return policy._directives.hasOwnProperty(directive) &&
michael@0 248 policy._directives[directive]._hasHashSource &&
michael@0 249 !policyAllowsHash[i];
michael@0 250 });
michael@0 251
michael@0 252 // allow it to execute? (Do all the policies allow it to execute)?
michael@0 253 return this._policies.every(function(policy, i) {
michael@0 254 return policy._reportOnlyMode || policyAllowsHash[i];
michael@0 255 });
michael@0 256 },
michael@0 257
michael@0 258 /**
michael@0 259 * For each policy, log any violation on the Error Console and send a report
michael@0 260 * if a report-uri is present in the policy
michael@0 261 *
michael@0 262 * @param aViolationType
michael@0 263 * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
michael@0 264 * @param aSourceFile
michael@0 265 * name of the source file containing the violation (if available)
michael@0 266 * @param aContentSample
michael@0 267 * sample of the violating content (to aid debugging)
michael@0 268 * @param aLineNum
michael@0 269 * source line number of the violation (if available)
michael@0 270 * @param aNonce
michael@0 271 * (optional) If this is a nonce violation, include the nonce so we can
michael@0 272 * recheck to determine which policies were violated and send the
michael@0 273 * appropriate reports.
michael@0 274 * @param aContent
michael@0 275 * (optional) If this is a hash violation, include contents of the inline
michael@0 276 * resource in the question so we can recheck the hash in order to
michael@0 277 * determine which policies were violated and send the appropriate
michael@0 278 * reports.
michael@0 279 */
michael@0 280 logViolationDetails:
michael@0 281 function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce, aContent) {
michael@0 282 for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
michael@0 283 let policy = this._policies[policyIndex];
michael@0 284
michael@0 285 // call-sites to the eval/inline checks recieve two return values: allows
michael@0 286 // and violates. Policies that are report-only allow the
michael@0 287 // loads/compilations but violations should still be reported. Not all
michael@0 288 // policies in this nsIContentSecurityPolicy instance will be violated,
michael@0 289 // which is why we must check again here.
michael@0 290 switch (aViolationType) {
michael@0 291 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
michael@0 292 if (!policy.allowsInlineStyles) {
michael@0 293 var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
michael@0 294 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
michael@0 295 INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
michael@0 296 aSourceFile, aScriptSample, aLineNum);
michael@0 297 }
michael@0 298 break;
michael@0 299 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
michael@0 300 if (!policy.allowsInlineScripts) {
michael@0 301 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
michael@0 302 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
michael@0 303 INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
michael@0 304 aSourceFile, aScriptSample, aLineNum);
michael@0 305 }
michael@0 306 break;
michael@0 307 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
michael@0 308 if (!policy.allowsEvalInScripts) {
michael@0 309 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
michael@0 310 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
michael@0 311 EVAL_VIOLATION_OBSERVER_SUBJECT,
michael@0 312 aSourceFile, aScriptSample, aLineNum);
michael@0 313 }
michael@0 314 break;
michael@0 315 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT:
michael@0 316 var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
michael@0 317 if (!policy.permitsNonce(aNonce, scriptType)) {
michael@0 318 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
michael@0 319 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
michael@0 320 SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT,
michael@0 321 aSourceFile, aScriptSample, aLineNum);
michael@0 322 }
michael@0 323 break;
michael@0 324 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE:
michael@0 325 var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
michael@0 326 if (!policy.permitsNonce(aNonce, styleType)) {
michael@0 327 var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
michael@0 328 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
michael@0 329 STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT,
michael@0 330 aSourceFile, aScriptSample, aLineNum);
michael@0 331 }
michael@0 332 break;
michael@0 333 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_SCRIPT:
michael@0 334 var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
michael@0 335 if (!policy.permitsHash(aContent, scriptType)) {
michael@0 336 var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
michael@0 337 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
michael@0 338 SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT,
michael@0 339 aSourceFile, aScriptSample, aLineNum);
michael@0 340 }
michael@0 341 break;
michael@0 342 case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_STYLE:
michael@0 343 var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
michael@0 344 if (!policy.permitsHash(aContent, styleType)) {
michael@0 345 var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
michael@0 346 this._asyncReportViolation('self', null, violatedDirective, policyIndex,
michael@0 347 STYLE_HASH_VIOLATION_OBSERVER_SUBJECT,
michael@0 348 aSourceFile, aScriptSample, aLineNum);
michael@0 349 }
michael@0 350 break;
michael@0 351 }
michael@0 352 }
michael@0 353 },
michael@0 354
michael@0 355 /**
michael@0 356 * Given an nsIHttpChannel, fill out the appropriate data.
michael@0 357 */
michael@0 358 setRequestContext:
michael@0 359 function(aSelfURI, aReferrerURI, aPrincipal, aChannel) {
michael@0 360
michael@0 361 // this requires either a self URI or a http channel.
michael@0 362 if (!aSelfURI && !aChannel)
michael@0 363 return;
michael@0 364
michael@0 365 if (aChannel) {
michael@0 366 // Save the docRequest for fetching a policy-uri
michael@0 367 this._weakDocRequest = Cu.getWeakReference(aChannel);
michael@0 368 }
michael@0 369
michael@0 370 // save the document URI (minus <fragment>) and referrer for reporting
michael@0 371 let uri = aSelfURI ? aSelfURI.cloneIgnoringRef() : aChannel.URI.cloneIgnoringRef();
michael@0 372 try { // GetUserPass throws for some protocols without userPass
michael@0 373 uri.userPass = '';
michael@0 374 } catch (ex) {}
michael@0 375 this._request = uri.asciiSpec;
michael@0 376 this._requestOrigin = uri;
michael@0 377
michael@0 378 // store a reference to the principal, that can later be used in shouldLoad
michael@0 379 if (aPrincipal) {
michael@0 380 this._weakRequestPrincipal = Cu.getWeakReference(aPrincipal);
michael@0 381 } else if (aChannel) {
michael@0 382 this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"]
michael@0 383 .getService(Ci.nsIScriptSecurityManager)
michael@0 384 .getChannelPrincipal(aChannel));
michael@0 385 } else {
michael@0 386 CSPdebug("No principal or channel for document context; violation reports may not work.");
michael@0 387 }
michael@0 388
michael@0 389 // pick one: referrerURI, channel's referrer, or null (first available)
michael@0 390 let ref = null;
michael@0 391 if (aReferrerURI)
michael@0 392 ref = aReferrerURI;
michael@0 393 else if (aChannel instanceof Ci.nsIHttpChannel)
michael@0 394 ref = aChannel.referrer;
michael@0 395
michael@0 396 if (ref) {
michael@0 397 let referrer = aChannel.referrer.cloneIgnoringRef();
michael@0 398 try { // GetUserPass throws for some protocols without userPass
michael@0 399 referrer.userPass = '';
michael@0 400 } catch (ex) {}
michael@0 401 this._referrer = referrer.asciiSpec;
michael@0 402 }
michael@0 403 },
michael@0 404
michael@0 405 /* ........ Methods .............. */
michael@0 406
michael@0 407 /**
michael@0 408 * Adds a new policy to our list of policies for this CSP context.
michael@0 409 * @returns the count of policies.
michael@0 410 */
michael@0 411 appendPolicy:
michael@0 412 function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) {
michael@0 413 return this._appendPolicyInternal(aPolicy, selfURI, aReportOnly,
michael@0 414 aSpecCompliant, true);
michael@0 415 },
michael@0 416
michael@0 417 /**
michael@0 418 * Adds a new policy to our list of policies for this CSP context.
michael@0 419 * Only to be called from this module (not exported)
michael@0 420 * @returns the count of policies.
michael@0 421 */
michael@0 422 _appendPolicyInternal:
michael@0 423 function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant,
michael@0 424 aEnforceSelfChecks) {
michael@0 425 #ifndef MOZ_B2G
michael@0 426 CSPdebug("APPENDING POLICY: " + aPolicy);
michael@0 427 CSPdebug(" SELF: " + (selfURI ? selfURI.asciiSpec : " null"));
michael@0 428 CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant);
michael@0 429 #endif
michael@0 430
michael@0 431 // For nested schemes such as view-source: make sure we are taking the
michael@0 432 // innermost URI to use as 'self' since that's where we will extract the
michael@0 433 // scheme, host and port from
michael@0 434 if (selfURI instanceof Ci.nsINestedURI) {
michael@0 435 #ifndef MOZ_B2G
michael@0 436 CSPdebug(" INNER: " + selfURI.innermostURI.asciiSpec);
michael@0 437 #endif
michael@0 438 selfURI = selfURI.innermostURI;
michael@0 439 }
michael@0 440
michael@0 441 // stay uninitialized until policy setup is done
michael@0 442 var newpolicy;
michael@0 443
michael@0 444 // If there is a policy-uri, fetch the policy, then re-call this function.
michael@0 445 // (1) parse and create a CSPRep object
michael@0 446 // Note that we pass the full URI since when it's parsed as 'self' to construct a
michael@0 447 // CSPSource only the scheme, host, and port are kept.
michael@0 448
michael@0 449 // If we want to be CSP 1.0 spec compliant, use the new parser.
michael@0 450 // The old one will be deprecated in the future and will be
michael@0 451 // removed at that time.
michael@0 452 if (aSpecCompliant) {
michael@0 453 newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
michael@0 454 selfURI,
michael@0 455 aReportOnly,
michael@0 456 this._weakDocRequest.get(),
michael@0 457 this,
michael@0 458 aEnforceSelfChecks);
michael@0 459 } else {
michael@0 460 newpolicy = CSPRep.fromString(aPolicy,
michael@0 461 selfURI,
michael@0 462 aReportOnly,
michael@0 463 this._weakDocRequest.get(),
michael@0 464 this,
michael@0 465 aEnforceSelfChecks);
michael@0 466 }
michael@0 467
michael@0 468 newpolicy._specCompliant = !!aSpecCompliant;
michael@0 469 newpolicy._isInitialized = true;
michael@0 470 this._policies.push(newpolicy);
michael@0 471 this._cache.clear(); // reset cache since effective policy changes
michael@0 472 },
michael@0 473
michael@0 474 /**
michael@0 475 * Removes a policy from the array of policies.
michael@0 476 */
michael@0 477 removePolicy:
michael@0 478 function csp_removePolicy(index) {
michael@0 479 if (index < 0 || index >= this._policies.length) {
michael@0 480 CSPdebug("Cannot remove policy " + index + "; not enough policies.");
michael@0 481 return;
michael@0 482 }
michael@0 483 this._policies.splice(index, 1);
michael@0 484 this._cache.clear(); // reset cache since effective policy changes
michael@0 485 },
michael@0 486
michael@0 487 /**
michael@0 488 * Generates and sends a violation report to the specified report URIs.
michael@0 489 */
michael@0 490 sendReports:
michael@0 491 function(blockedUri, originalUri, violatedDirective,
michael@0 492 violatedPolicyIndex, aSourceFile,
michael@0 493 aScriptSample, aLineNum) {
michael@0 494
michael@0 495 let policy = this._getPolicyInternal(violatedPolicyIndex);
michael@0 496 if (!policy) {
michael@0 497 CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined.");
michael@0 498 return;
michael@0 499 }
michael@0 500
michael@0 501 var uriString = policy.getReportURIs();
michael@0 502 var uris = uriString.split(/\s+/);
michael@0 503 if (uris.length > 0) {
michael@0 504 // see if we need to sanitize the blocked-uri
michael@0 505 let blocked = '';
michael@0 506 if (originalUri) {
michael@0 507 // We've redirected, only report the blocked origin
michael@0 508 try {
michael@0 509 let clone = blockedUri.clone();
michael@0 510 clone.path = '';
michael@0 511 blocked = clone.asciiSpec;
michael@0 512 } catch(e) {
michael@0 513 CSPdebug(".... blockedUri can't be cloned: " + blockedUri);
michael@0 514 }
michael@0 515 }
michael@0 516 else if (blockedUri instanceof Ci.nsIURI) {
michael@0 517 blocked = blockedUri.cloneIgnoringRef().asciiSpec;
michael@0 518 }
michael@0 519 else {
michael@0 520 // blockedUri is a string for eval/inline-script violations
michael@0 521 blocked = blockedUri;
michael@0 522 }
michael@0 523
michael@0 524 // Generate report to send composed of
michael@0 525 // {
michael@0 526 // csp-report: {
michael@0 527 // document-uri: "http://example.com/file.html?params",
michael@0 528 // referrer: "...",
michael@0 529 // blocked-uri: "...",
michael@0 530 // violated-directive: "..."
michael@0 531 // }
michael@0 532 // }
michael@0 533 var report = {
michael@0 534 'csp-report': {
michael@0 535 'document-uri': this._request,
michael@0 536 'referrer': this._referrer,
michael@0 537 'blocked-uri': blocked,
michael@0 538 'violated-directive': violatedDirective
michael@0 539 }
michael@0 540 }
michael@0 541
michael@0 542 // extra report fields for script errors (if available)
michael@0 543 if (originalUri)
michael@0 544 report["csp-report"]["original-uri"] = originalUri.cloneIgnoringRef().asciiSpec;
michael@0 545 if (aSourceFile)
michael@0 546 report["csp-report"]["source-file"] = aSourceFile;
michael@0 547 if (aScriptSample)
michael@0 548 report["csp-report"]["script-sample"] = aScriptSample;
michael@0 549 if (aLineNum)
michael@0 550 report["csp-report"]["line-number"] = aLineNum;
michael@0 551
michael@0 552 var reportString = JSON.stringify(report);
michael@0 553 CSPdebug("Constructed violation report:\n" + reportString);
michael@0 554
michael@0 555 // For each URI in the report list, send out a report.
michael@0 556 // We make the assumption that all of the URIs are absolute URIs; this
michael@0 557 // should be taken care of in CSPRep.fromString (where it converts any
michael@0 558 // relative URIs into absolute ones based on "self").
michael@0 559 for (let i in uris) {
michael@0 560 if (uris[i] === "")
michael@0 561 continue;
michael@0 562
michael@0 563 try {
michael@0 564 var chan = Services.io.newChannel(uris[i], null, null);
michael@0 565 if (!chan) {
michael@0 566 CSPdebug("Error creating channel for " + uris[i]);
michael@0 567 continue;
michael@0 568 }
michael@0 569
michael@0 570 var content = Cc["@mozilla.org/io/string-input-stream;1"]
michael@0 571 .createInstance(Ci.nsIStringInputStream);
michael@0 572 content.data = reportString + "\n\n";
michael@0 573
michael@0 574 // make sure this is an anonymous request (no cookies) so in case the
michael@0 575 // policy URI is injected, it can't be abused for CSRF.
michael@0 576 chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
michael@0 577
michael@0 578 // we need to set an nsIChannelEventSink on the channel object
michael@0 579 // so we can tell it to not follow redirects when posting the reports
michael@0 580 chan.notificationCallbacks = new CSPReportRedirectSink(policy);
michael@0 581 if (this._weakDocRequest.get()) {
michael@0 582 chan.loadGroup = this._weakDocRequest.get().loadGroup;
michael@0 583 }
michael@0 584
michael@0 585 chan.QueryInterface(Ci.nsIUploadChannel)
michael@0 586 .setUploadStream(content, "application/json", content.available());
michael@0 587
michael@0 588 try {
michael@0 589 // if this is an HTTP channel, set the request method to post
michael@0 590 chan.QueryInterface(Ci.nsIHttpChannel);
michael@0 591 chan.requestMethod = "POST";
michael@0 592 } catch(e) {} // throws only if chan is not an nsIHttpChannel.
michael@0 593
michael@0 594 // check with the content policy service to see if we're allowed to
michael@0 595 // send this request.
michael@0 596 try {
michael@0 597 var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
michael@0 598 .getService(Ci.nsIContentPolicy);
michael@0 599 if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT,
michael@0 600 chan.URI, this._requestOrigin,
michael@0 601 null, null, null, this._weakRequestPrincipal.get())
michael@0 602 != Ci.nsIContentPolicy.ACCEPT) {
michael@0 603 continue; // skip unauthorized URIs
michael@0 604 }
michael@0 605 } catch(e) {
michael@0 606 continue; // refuse to load if we can't do a security check.
michael@0 607 }
michael@0 608
michael@0 609 //send data (and set up error notifications)
michael@0 610 chan.asyncOpen(new CSPViolationReportListener(uris[i]), null);
michael@0 611 CSPdebug("Sent violation report to " + uris[i]);
michael@0 612 } catch(e) {
michael@0 613 // it's possible that the URI was invalid, just log a
michael@0 614 // warning and skip over that.
michael@0 615 policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
michael@0 616 policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
michael@0 617 }
michael@0 618 }
michael@0 619 }
michael@0 620 },
michael@0 621
michael@0 622 /**
michael@0 623 * Logs a meaningful CSP warning to the developer console.
michael@0 624 */
michael@0 625 logToConsole:
michael@0 626 function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex,
michael@0 627 aSourceFile, aScriptSample, aLineNum, aObserverSubject) {
michael@0 628 let policy = this._policies[aViolatedPolicyIndex];
michael@0 629 switch(aObserverSubject.data) {
michael@0 630 case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT:
michael@0 631 violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked");
michael@0 632 break;
michael@0 633 case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT:
michael@0 634 violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked");
michael@0 635 break;
michael@0 636 case EVAL_VIOLATION_OBSERVER_SUBJECT:
michael@0 637 violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked");
michael@0 638 break;
michael@0 639 }
michael@0 640 var violationMessage = null;
michael@0 641 if (blockedUri["asciiSpec"]) {
michael@0 642 violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
michael@0 643 } else {
michael@0 644 violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
michael@0 645 }
michael@0 646 policy.log(WARN_FLAG, violationMessage,
michael@0 647 (aSourceFile) ? aSourceFile : null,
michael@0 648 (aScriptSample) ? decodeURIComponent(aScriptSample) : null,
michael@0 649 (aLineNum) ? aLineNum : null);
michael@0 650 },
michael@0 651
michael@0 652 /**
michael@0 653 * Exposed Method to analyze docShell for approved frame ancestry.
michael@0 654 * NOTE: Also sends violation reports if necessary.
michael@0 655 * @param docShell
michael@0 656 * the docShell for this policy's resource.
michael@0 657 * @return
michael@0 658 * true if the frame ancestry is allowed by this policy and the load
michael@0 659 * should progress.
michael@0 660 */
michael@0 661 permitsAncestry:
michael@0 662 function(docShell) {
michael@0 663 // Cannot shortcut checking all the policies since violation reports have
michael@0 664 // to be triggered if any policy wants it.
michael@0 665 var permitted = true;
michael@0 666 for (let i = 0; i < this._policies.length; i++) {
michael@0 667 if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) {
michael@0 668 permitted = false;
michael@0 669 }
michael@0 670 }
michael@0 671 return permitted;
michael@0 672 },
michael@0 673
michael@0 674 _permitsAncestryInternal:
michael@0 675 function(docShell, policy, policyIndex) {
michael@0 676 if (!docShell) { return false; }
michael@0 677
michael@0 678 // walk up this docShell tree until we hit chrome
michael@0 679 var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 680 .getInterface(Ci.nsIDocShellTreeItem);
michael@0 681
michael@0 682 // collect ancestors and make sure they're allowed.
michael@0 683 var ancestors = [];
michael@0 684 while (dst.parent) {
michael@0 685 dst = dst.parent;
michael@0 686 let it = dst.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 687 .getInterface(Ci.nsIWebNavigation);
michael@0 688 if (it.currentURI) {
michael@0 689 if (it.currentURI.scheme === "chrome") {
michael@0 690 break;
michael@0 691 }
michael@0 692 // delete any userpass
michael@0 693 let ancestor = it.currentURI.cloneIgnoringRef();
michael@0 694 try { // GetUserPass throws for some protocols without userPass
michael@0 695 ancestor.userPass = '';
michael@0 696 } catch (ex) {}
michael@0 697
michael@0 698 #ifndef MOZ_B2G
michael@0 699 CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
michael@0 700 #endif
michael@0 701 ancestors.push(ancestor);
michael@0 702 }
michael@0 703 }
michael@0 704
michael@0 705 // scan the discovered ancestors
michael@0 706 // frame-ancestors is the same in both old and new source directives,
michael@0 707 // so don't need to differentiate here.
michael@0 708 let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS;
michael@0 709 for (let i in ancestors) {
michael@0 710 let ancestor = ancestors[i];
michael@0 711 if (!policy.permits(ancestor, cspContext)) {
michael@0 712 // report the frame-ancestor violation
michael@0 713 let directive = policy._directives[cspContext];
michael@0 714 let violatedPolicy = 'frame-ancestors ' + directive.toString();
michael@0 715
michael@0 716 this._asyncReportViolation(ancestors[i], null, violatedPolicy,
michael@0 717 policyIndex);
michael@0 718
michael@0 719 // need to lie if we are testing in report-only mode
michael@0 720 return policy._reportOnlyMode;
michael@0 721 }
michael@0 722 }
michael@0 723 return true;
michael@0 724 },
michael@0 725
michael@0 726 /**
michael@0 727 * Creates a cache key from content location and content type.
michael@0 728 */
michael@0 729 _createCacheKey:
michael@0 730 function (aContentLocation, aContentType) {
michael@0 731 if (aContentType != Ci.nsIContentPolicy.TYPE_SCRIPT &&
michael@0 732 aContentLocation.scheme == "data") {
michael@0 733 // For non-script data: URI, use ("data:", aContentType) as the cache key.
michael@0 734 return aContentLocation.scheme + ":" + aContentType;
michael@0 735 }
michael@0 736
michael@0 737 let uri = aContentLocation.spec;
michael@0 738 if (uri.length > CSP_CACHE_URI_CUTOFF_SIZE) {
michael@0 739 // Don't cache for a URI longer than the cutoff size.
michael@0 740 return null;
michael@0 741 }
michael@0 742 return uri + "!" + aContentType;
michael@0 743 },
michael@0 744
michael@0 745 /**
michael@0 746 * Delegate method called by the service when sub-elements of the protected
michael@0 747 * document are being loaded. Given a bit of information about the request,
michael@0 748 * decides whether or not the policy is satisfied.
michael@0 749 */
michael@0 750 shouldLoad:
michael@0 751 function csp_shouldLoad(aContentType,
michael@0 752 aContentLocation,
michael@0 753 aRequestOrigin,
michael@0 754 aContext,
michael@0 755 aMimeTypeGuess,
michael@0 756 aOriginalUri) {
michael@0 757 let key = this._createCacheKey(aContentLocation, aContentType);
michael@0 758 if (key && this._cache.has(key)) {
michael@0 759 return this._cache.get(key);
michael@0 760 }
michael@0 761
michael@0 762 #ifndef MOZ_B2G
michael@0 763 // Try to remove as much as possible from the hot path on b2g.
michael@0 764 CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec);
michael@0 765 CSPdebug("shouldLoad content type = " + aContentType);
michael@0 766 #endif
michael@0 767
michael@0 768 // The mapping for XHR and websockets is different between our original
michael@0 769 // implementation and the 1.0 spec, we handle this here.
michael@0 770 var cspContext;
michael@0 771
michael@0 772 let cp = Ci.nsIContentPolicy;
michael@0 773
michael@0 774 // Infer if this is a preload for elements that use nonce-source. Since,
michael@0 775 // for preloads, aContext is the document and not the element associated
michael@0 776 // with the resource, we cannot determine the nonce. See Bug 612921 and
michael@0 777 // Bug 855326.
michael@0 778 let nonceSourceValid = aContentType == cp.TYPE_SCRIPT ||
michael@0 779 aContentType == cp.TYPE_STYLESHEET;
michael@0 780 var possiblePreloadNonceConflict = nonceSourceValid &&
michael@0 781 aContext instanceof Ci.nsIDOMHTMLDocument;
michael@0 782
michael@0 783 // iterate through all the _policies and send reports where a policy is
michael@0 784 // violated. After the check, determine the overall effect (blocked or
michael@0 785 // loaded?) and cache it.
michael@0 786 let policyAllowsLoadArray = [];
michael@0 787 for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
michael@0 788 let policy = this._policies[policyIndex];
michael@0 789
michael@0 790 #ifndef MOZ_B2G
michael@0 791 CSPdebug("policy is " + (policy._specCompliant ?
michael@0 792 "1.0 compliant" : "pre-1.0"));
michael@0 793 CSPdebug("policy is " + (policy._reportOnlyMode ?
michael@0 794 "report-only" : "blocking"));
michael@0 795 #endif
michael@0 796
michael@0 797 if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) {
michael@0 798 cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
michael@0 799 } else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) {
michael@0 800 cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
michael@0 801 } else {
michael@0 802 cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
michael@0 803 }
michael@0 804
michael@0 805 #ifndef MOZ_B2G
michael@0 806 CSPdebug("shouldLoad cspContext = " + cspContext);
michael@0 807 #endif
michael@0 808
michael@0 809 // if the mapping is null, there's no policy, let it through.
michael@0 810 if (!cspContext) {
michael@0 811 return Ci.nsIContentPolicy.ACCEPT;
michael@0 812 }
michael@0 813
michael@0 814 // check if location is permitted
michael@0 815 let permitted = policy.permits(aContentLocation, cspContext);
michael@0 816
michael@0 817 // check any valid content type for nonce if location is not permitted
michael@0 818 if (!permitted && nonceSourceValid &&
michael@0 819 aContext instanceof Ci.nsIDOMHTMLElement &&
michael@0 820 aContext.hasAttribute('nonce')) {
michael@0 821 permitted = policy.permitsNonce(aContext.getAttribute('nonce'),
michael@0 822 cspContext);
michael@0 823 }
michael@0 824
michael@0 825 // record whether the thing should be blocked or just reported.
michael@0 826 policyAllowsLoadArray.push(permitted || policy._reportOnlyMode);
michael@0 827 let res = permitted ? cp.ACCEPT : cp.REJECT_SERVER;
michael@0 828
michael@0 829 // frame-ancestors is taken care of early on (as this document is loaded)
michael@0 830
michael@0 831 // If the result is *NOT* ACCEPT, then send report
michael@0 832 // Do not send report if this is a nonce-source preload - the decision may
michael@0 833 // be wrong and will incorrectly fail the unit tests.
michael@0 834 if (res != cp.ACCEPT && !possiblePreloadNonceConflict) {
michael@0 835 CSPdebug("blocking request for " + aContentLocation.asciiSpec);
michael@0 836 try {
michael@0 837 let directive = "unknown directive",
michael@0 838 violatedPolicy = "unknown policy";
michael@0 839
michael@0 840 // The policy might not explicitly declare each source directive (so
michael@0 841 // the cspContext may be implicit). If so, we have to report
michael@0 842 // violations as appropriate: specific or the default-src directive.
michael@0 843 if (policy._directives.hasOwnProperty(cspContext)) {
michael@0 844 directive = policy._directives[cspContext];
michael@0 845 violatedPolicy = cspContext + ' ' + directive.toString();
michael@0 846 } else if (policy._directives.hasOwnProperty("default-src")) {
michael@0 847 directive = policy._directives["default-src"];
michael@0 848 violatedPolicy = "default-src " + directive.toString();
michael@0 849 } else {
michael@0 850 violatedPolicy = "unknown directive";
michael@0 851 CSPdebug('ERROR in blocking content: ' +
michael@0 852 'CSP is not sure which part of the policy caused this block');
michael@0 853 }
michael@0 854
michael@0 855 this._asyncReportViolation(aContentLocation,
michael@0 856 aOriginalUri,
michael@0 857 violatedPolicy,
michael@0 858 policyIndex);
michael@0 859 } catch(e) {
michael@0 860 CSPdebug('---------------- ERROR: ' + e);
michael@0 861 }
michael@0 862 }
michael@0 863 } // end for-each loop over policies
michael@0 864
michael@0 865 // the ultimate decision is based on whether any policies want to reject
michael@0 866 // the load. The array keeps track of whether the policies allowed the
michael@0 867 // loads. If any doesn't, we'll reject the load (and cache the result).
michael@0 868 let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
michael@0 869 cp.REJECT_SERVER : cp.ACCEPT);
michael@0 870
michael@0 871 // Do not cache the result if this is a nonce-source preload
michael@0 872 if (key && !possiblePreloadNonceConflict) {
michael@0 873 this._cache.set(key, ret);
michael@0 874 }
michael@0 875 return ret;
michael@0 876 },
michael@0 877
michael@0 878 shouldProcess:
michael@0 879 function csp_shouldProcess(aContentType,
michael@0 880 aContentLocation,
michael@0 881 aRequestOrigin,
michael@0 882 aContext,
michael@0 883 aMimeType,
michael@0 884 aExtra) {
michael@0 885 // frame-ancestors check is done outside the ContentPolicy
michael@0 886 var res = Ci.nsIContentPolicy.ACCEPT;
michael@0 887 CSPdebug("shouldProcess aContext=" + aContext);
michael@0 888 return res;
michael@0 889 },
michael@0 890
michael@0 891 /**
michael@0 892 * Asynchronously notifies any nsIObservers listening to the CSP violation
michael@0 893 * topic that a violation occurred. Also triggers report sending. All
michael@0 894 * asynchronous on the main thread.
michael@0 895 *
michael@0 896 * @param aBlockedContentSource
michael@0 897 * Either a CSP Source (like 'self', as string) or nsIURI: the source
michael@0 898 * of the violation.
michael@0 899 * @param aOriginalUri
michael@0 900 * The original URI if the blocked content is a redirect, else null
michael@0 901 * @param aViolatedDirective
michael@0 902 * the directive that was violated (string).
michael@0 903 * @param aViolatedPolicyIndex
michael@0 904 * the index of the policy that was violated (so we know where to send
michael@0 905 * the reports).
michael@0 906 * @param aObserverSubject
michael@0 907 * optional, subject sent to the nsIObservers listening to the CSP
michael@0 908 * violation topic.
michael@0 909 * @param aSourceFile
michael@0 910 * name of the file containing the inline script violation
michael@0 911 * @param aScriptSample
michael@0 912 * a sample of the violating inline script
michael@0 913 * @param aLineNum
michael@0 914 * source line number of the violation (if available)
michael@0 915 */
michael@0 916 _asyncReportViolation:
michael@0 917 function(aBlockedContentSource, aOriginalUri, aViolatedDirective,
michael@0 918 aViolatedPolicyIndex, aObserverSubject,
michael@0 919 aSourceFile, aScriptSample, aLineNum) {
michael@0 920 // if optional observerSubject isn't specified, default to the source of
michael@0 921 // the violation.
michael@0 922 if (!aObserverSubject)
michael@0 923 aObserverSubject = aBlockedContentSource;
michael@0 924
michael@0 925 // gotta wrap things that aren't nsISupports, since it's sent out to
michael@0 926 // observers as such. Objects that are not nsISupports are converted to
michael@0 927 // strings and then wrapped into a nsISupportsCString.
michael@0 928 if (!(aObserverSubject instanceof Ci.nsISupports)) {
michael@0 929 let d = aObserverSubject;
michael@0 930 aObserverSubject = Cc["@mozilla.org/supports-cstring;1"]
michael@0 931 .createInstance(Ci.nsISupportsCString);
michael@0 932 aObserverSubject.data = d;
michael@0 933 }
michael@0 934
michael@0 935 var reportSender = this;
michael@0 936 Services.tm.mainThread.dispatch(
michael@0 937 function() {
michael@0 938 Services.obs.notifyObservers(aObserverSubject,
michael@0 939 CSP_VIOLATION_TOPIC,
michael@0 940 aViolatedDirective);
michael@0 941 reportSender.sendReports(aBlockedContentSource, aOriginalUri,
michael@0 942 aViolatedDirective, aViolatedPolicyIndex,
michael@0 943 aSourceFile, aScriptSample, aLineNum);
michael@0 944 reportSender.logToConsole(aBlockedContentSource, aOriginalUri,
michael@0 945 aViolatedDirective, aViolatedPolicyIndex,
michael@0 946 aSourceFile, aScriptSample,
michael@0 947 aLineNum, aObserverSubject);
michael@0 948
michael@0 949 }, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 950 },
michael@0 951
michael@0 952 /* ........ nsISerializable Methods: .............. */
michael@0 953
michael@0 954 read:
michael@0 955 function(aStream) {
michael@0 956
michael@0 957 this._requestOrigin = aStream.readObject(true);
michael@0 958 this._requestOrigin.QueryInterface(Ci.nsIURI);
michael@0 959
michael@0 960 for (let pCount = aStream.read32(); pCount > 0; pCount--) {
michael@0 961 let polStr = aStream.readString();
michael@0 962 let reportOnly = aStream.readBoolean();
michael@0 963 let specCompliant = aStream.readBoolean();
michael@0 964 // don't need self info because when the policy is turned back into a
michael@0 965 // string, 'self' is replaced with the explicit source expression.
michael@0 966 this._appendPolicyInternal(polStr, null, reportOnly, specCompliant,
michael@0 967 false);
michael@0 968 }
michael@0 969
michael@0 970 // NOTE: the document instance that's deserializing this object (via its
michael@0 971 // principal) should hook itself into this._principal manually. If they
michael@0 972 // don't, the CSP reports will likely be blocked by nsMixedContentBlocker.
michael@0 973 },
michael@0 974
michael@0 975 write:
michael@0 976 function(aStream) {
michael@0 977 // need to serialize the context: request origin and such. They are
michael@0 978 // used when sending reports. Since _request and _requestOrigin are just
michael@0 979 // different representations of the same thing, only save _requestOrigin
michael@0 980 // (an nsIURI).
michael@0 981 aStream.writeCompoundObject(this._requestOrigin, Ci.nsIURI, true);
michael@0 982
michael@0 983 // we can't serialize a reference to the principal that triggered this
michael@0 984 // instance to serialize, so when this is deserialized by the principal the
michael@0 985 // caller must hook it up manually by calling setRequestContext on this
michael@0 986 // instance with the appropriate nsIChannel.
michael@0 987
michael@0 988 // Finally, serialize all the policies.
michael@0 989 aStream.write32(this._policies.length);
michael@0 990
michael@0 991 for each (var policy in this._policies) {
michael@0 992 aStream.writeWStringZ(policy.toString());
michael@0 993 aStream.writeBoolean(policy._reportOnlyMode);
michael@0 994 aStream.writeBoolean(policy._specCompliant);
michael@0 995 }
michael@0 996 },
michael@0 997 };
michael@0 998
michael@0 999 // The POST of the violation report (if it happens) should not follow
michael@0 1000 // redirects, per the spec. hence, we implement an nsIChannelEventSink
michael@0 1001 // with an object so we can tell XHR to abort if a redirect happens.
michael@0 1002 function CSPReportRedirectSink(policy) {
michael@0 1003 this._policy = policy;
michael@0 1004 }
michael@0 1005
michael@0 1006 CSPReportRedirectSink.prototype = {
michael@0 1007 QueryInterface: function requestor_qi(iid) {
michael@0 1008 if (iid.equals(Ci.nsISupports) ||
michael@0 1009 iid.equals(Ci.nsIInterfaceRequestor) ||
michael@0 1010 iid.equals(Ci.nsIChannelEventSink))
michael@0 1011 return this;
michael@0 1012 throw Cr.NS_ERROR_NO_INTERFACE;
michael@0 1013 },
michael@0 1014
michael@0 1015 // nsIInterfaceRequestor
michael@0 1016 getInterface: function requestor_gi(iid) {
michael@0 1017 if (iid.equals(Ci.nsIChannelEventSink))
michael@0 1018 return this;
michael@0 1019
michael@0 1020 throw Components.results.NS_ERROR_NO_INTERFACE;
michael@0 1021 },
michael@0 1022
michael@0 1023 // nsIChannelEventSink
michael@0 1024 asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel,
michael@0 1025 flags, callback) {
michael@0 1026 this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec]));
michael@0 1027
michael@0 1028 // cancel the old channel so XHR failure callback happens
michael@0 1029 oldChannel.cancel(Cr.NS_ERROR_ABORT);
michael@0 1030
michael@0 1031 // notify an observer that we have blocked the report POST due to a redirect,
michael@0 1032 // used in testing, do this async since we're in an async call now to begin with
michael@0 1033 Services.tm.mainThread.dispatch(
michael@0 1034 function() {
michael@0 1035 observerSubject = Cc["@mozilla.org/supports-cstring;1"]
michael@0 1036 .createInstance(Ci.nsISupportsCString);
michael@0 1037 observerSubject.data = oldChannel.URI.asciiSpec;
michael@0 1038
michael@0 1039 Services.obs.notifyObservers(observerSubject,
michael@0 1040 CSP_VIOLATION_TOPIC,
michael@0 1041 "denied redirect while sending violation report");
michael@0 1042 }, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 1043
michael@0 1044 // throw to stop the redirect happening
michael@0 1045 throw Cr.NS_BINDING_REDIRECTED;
michael@0 1046 }
michael@0 1047 };
michael@0 1048
michael@0 1049 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentSecurityPolicy]);

mercurial