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