|
1 <!DOCTYPE HTML> |
|
2 <html> |
|
3 <head> |
|
4 <title>Test for Content Security Policy multiple policy support (regular and Report-Only mode)</title> |
|
5 <script type="text/javascript" src="/MochiKit/packed.js"></script> |
|
6 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
|
7 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> |
|
8 </head> |
|
9 <body> |
|
10 <p id="display"></p> |
|
11 <div id="content" style="display: none"> |
|
12 </div> |
|
13 |
|
14 <iframe style="width:200px;height:200px;" id='cspframe'></iframe> |
|
15 <script class="testbody" type="text/javascript"> |
|
16 |
|
17 var path = "/tests/content/base/test/csp/"; |
|
18 |
|
19 // These are test results: verified indicates whether or not the test has run. |
|
20 // true/false is the pass/fail result. |
|
21 window.loads = { |
|
22 css_self: {expected: true, verified: false}, |
|
23 css_examplecom: {expected: false, verified: false}, |
|
24 img_self: {expected: false, verified: false}, |
|
25 img_examplecom: {expected: false, verified: false}, |
|
26 script_self: {expected: true, verified: false}, |
|
27 }; |
|
28 |
|
29 window.violation_reports = { |
|
30 css_self: |
|
31 {expected: 0, expected_ro: 0}, /* totally fine */ |
|
32 css_examplecom: |
|
33 {expected: 1, expected_ro: 0}, /* violates enforced CSP */ |
|
34 img_self: |
|
35 {expected: 1, expected_ro: 0}, /* violates enforced CSP */ |
|
36 img_examplecom: |
|
37 {expected: 1, expected_ro: 1}, /* violates both CSPs */ |
|
38 script_self: |
|
39 {expected: 0, expected_ro: 1}, /* violates report-only */ |
|
40 }; |
|
41 |
|
42 // This is used to watch the blocked data bounce off CSP and allowed data |
|
43 // get sent out to the wire. This also watches for violation reports to go out. |
|
44 function examiner() { |
|
45 SpecialPowers.addObserver(this, "csp-on-violate-policy", false); |
|
46 SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false); |
|
47 } |
|
48 examiner.prototype = { |
|
49 observe: function(subject, topic, data) { |
|
50 var testpat = new RegExp("testid=([a-z0-9_]+)"); |
|
51 |
|
52 if (topic === "specialpowers-http-notify-request") { |
|
53 var uri = data; |
|
54 if (!testpat.test(uri)) return; |
|
55 var testid = testpat.exec(uri)[1]; |
|
56 |
|
57 // violation reports don't come through here, but the requested resources do |
|
58 // if the test has already finished, move on. Some things throw multiple |
|
59 // requests (preloads and such) |
|
60 try { |
|
61 if (window.loads[testid].verified) return; |
|
62 } catch(e) { return; } |
|
63 |
|
64 // these are requests that were allowed by CSP |
|
65 var testid = testpat.exec(uri)[1]; |
|
66 window.testResult(testid, 'allowed', uri + " allowed by csp"); |
|
67 } |
|
68 |
|
69 if(topic === "csp-on-violate-policy") { |
|
70 // if the violated policy was report-only, the resource will still be |
|
71 // loaded even if this topic is notified. |
|
72 var asciiSpec = SpecialPowers.getPrivilegedProps( |
|
73 SpecialPowers.do_QueryInterface(subject, "nsIURI"), |
|
74 "asciiSpec"); |
|
75 if (!testpat.test(asciiSpec)) return; |
|
76 var testid = testpat.exec(asciiSpec)[1]; |
|
77 |
|
78 // if the test has already finished, move on. |
|
79 try { |
|
80 if (window.loads[testid].verified) return; |
|
81 } catch(e) { return; } |
|
82 |
|
83 // record the ones that were supposed to be blocked, but don't use this |
|
84 // as an indicator for tests that are not blocked but do generate reports. |
|
85 // We skip recording the result if the load is expected since a |
|
86 // report-only policy will generate a request *and* a violation note. |
|
87 if (!window.loads[testid].expected) { |
|
88 window.testResult(testid, |
|
89 'blocked', |
|
90 asciiSpec + " blocked by \"" + data + "\""); |
|
91 } |
|
92 } |
|
93 |
|
94 // if any test is unverified, keep waiting |
|
95 for (var v in window.loads) { |
|
96 if(!window.loads[v].verified) { |
|
97 return; |
|
98 } |
|
99 } |
|
100 |
|
101 window.bug836922examiner.remove(); |
|
102 window.resultPoller.pollForFinish(); |
|
103 }, |
|
104 |
|
105 // must eventually call this to remove the listener, |
|
106 // or mochitests might get borked. |
|
107 remove: function() { |
|
108 SpecialPowers.removeObserver(this, "csp-on-violate-policy"); |
|
109 SpecialPowers.removeObserver(this, "specialpowers-http-notify-request"); |
|
110 } |
|
111 } |
|
112 window.bug836922examiner = new examiner(); |
|
113 |
|
114 |
|
115 // Poll for results and see if enough reports came in. Keep trying |
|
116 // for a few seconds before failing with lack of reports. |
|
117 // Have to do this because there's a race between the async reporting |
|
118 // and this test finishing, and we don't want to win the race. |
|
119 window.resultPoller = { |
|
120 |
|
121 POLL_ATTEMPTS_LEFT: 14, |
|
122 |
|
123 pollForFinish: |
|
124 function() { |
|
125 var vr = resultPoller.tallyReceivedReports(); |
|
126 if (resultPoller.verifyReports(vr, resultPoller.POLL_ATTEMPTS_LEFT < 1)) { |
|
127 // report success condition. |
|
128 resultPoller.resetReportServer(); |
|
129 SimpleTest.finish(); |
|
130 } else { |
|
131 resultPoller.POLL_ATTEMPTS_LEFT--; |
|
132 // try again unless we reached the threshold. |
|
133 setTimeout(resultPoller.pollForFinish, 100); |
|
134 } |
|
135 }, |
|
136 |
|
137 resetReportServer: |
|
138 function() { |
|
139 var xhr = new XMLHttpRequest(); |
|
140 var xhr_ro = new XMLHttpRequest(); |
|
141 xhr.open("GET", "file_bug836922_npolicies_violation.sjs?reset", false); |
|
142 xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?reset", false); |
|
143 xhr.send(null); |
|
144 xhr_ro.send(null); |
|
145 }, |
|
146 |
|
147 tallyReceivedReports: |
|
148 function() { |
|
149 var xhr = new XMLHttpRequest(); |
|
150 var xhr_ro = new XMLHttpRequest(); |
|
151 xhr.open("GET", "file_bug836922_npolicies_violation.sjs?results", false); |
|
152 xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?results", false); |
|
153 xhr.send(null); |
|
154 xhr_ro.send(null); |
|
155 |
|
156 var received = JSON.parse(xhr.responseText); |
|
157 var received_ro = JSON.parse(xhr_ro.responseText); |
|
158 |
|
159 var results = {enforced: {}, reportonly: {}}; |
|
160 for (var r in window.violation_reports) { |
|
161 results.enforced[r] = 0; |
|
162 results.reportonly[r] = 0; |
|
163 } |
|
164 |
|
165 for (var r in received) { |
|
166 results.enforced[r] += received[r]; |
|
167 } |
|
168 for (var r in received_ro) { |
|
169 results.reportonly[r] += received_ro[r]; |
|
170 } |
|
171 |
|
172 return results; |
|
173 }, |
|
174 |
|
175 verifyReports: |
|
176 function(receivedCounts, lastAttempt) { |
|
177 for (var r in window.violation_reports) { |
|
178 var exp = window.violation_reports[r].expected; |
|
179 var exp_ro = window.violation_reports[r].expected_ro; |
|
180 var rec = receivedCounts.enforced[r]; |
|
181 var rec_ro = receivedCounts.reportonly[r]; |
|
182 |
|
183 // if this test breaks, these are helpful dumps: |
|
184 //dump(">>> Verifying " + r + "\n"); |
|
185 //dump(" > Expected: " + exp + " / " + exp_ro + " (ro)\n"); |
|
186 //dump(" > Received: " + rec + " / " + rec_ro + " (ro) \n"); |
|
187 |
|
188 // in all cases, we're looking for *at least* the expected number of |
|
189 // reports of each type (there could be more in some edge cases). |
|
190 // If there are not enough, we keep waiting and poll the server again |
|
191 // later. If there are enough, we can successfully finish. |
|
192 |
|
193 if (exp == 0) |
|
194 is(rec, 0, |
|
195 "Expected zero enforced-policy violation " + |
|
196 "reports for " + r + ", got " + rec); |
|
197 else if (lastAttempt) |
|
198 ok(rec >= exp, |
|
199 "Received (" + rec + "/" + exp + ") " + |
|
200 "enforced-policy reports for " + r); |
|
201 else if (rec < exp) |
|
202 return false; // continue waiting for more |
|
203 |
|
204 if(exp_ro == 0) |
|
205 is(rec_ro, 0, |
|
206 "Expected zero report-only-policy violation " + |
|
207 "reports for " + r + ", got " + rec_ro); |
|
208 else if (lastAttempt) |
|
209 ok(rec_ro >= exp_ro, |
|
210 "Received (" + rec_ro + "/" + exp_ro + ") " + |
|
211 "report-only-policy reports for " + r); |
|
212 else if (rec_ro < exp_ro) |
|
213 return false; // continue waiting for more |
|
214 } |
|
215 |
|
216 // if we complete the loop, we've found all of the violation |
|
217 // reports we expect. |
|
218 if (lastAttempt) return true; |
|
219 |
|
220 // Repeat successful tests once more to record successes via ok() |
|
221 return resultPoller.verifyReports(receivedCounts, true); |
|
222 } |
|
223 }; |
|
224 |
|
225 window.testResult = function(testname, result, msg) { |
|
226 // otherwise, make sure the allowed ones are expected and blocked ones are not. |
|
227 if (window.loads[testname].expected) { |
|
228 is(result, 'allowed', ">> " + msg); |
|
229 } else { |
|
230 is(result, 'blocked', ">> " + msg); |
|
231 } |
|
232 window.loads[testname].verified = true; |
|
233 } |
|
234 |
|
235 |
|
236 SimpleTest.waitForExplicitFinish(); |
|
237 |
|
238 SpecialPowers.pushPrefEnv( |
|
239 {'set':[["security.csp.speccompliant", true]]}, |
|
240 function() { |
|
241 // save this for last so that our listeners are registered. |
|
242 // ... this loads the testbed of good and bad requests. |
|
243 document.getElementById('cspframe').src = 'http://mochi.test:8888' + path + 'file_bug836922_npolicies.html'; |
|
244 }); |
|
245 |
|
246 </script> |
|
247 </pre> |
|
248 </body> |
|
249 </html> |