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