test_anchor_ping.html (10294B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=786347 5 --> 6 <head> 7 <meta charset="utf-8"> 8 <title>Test for Bug 786347</title> 9 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 10 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> 11 <script type="application/javascript"> 12 13 /** Test for Bug 786347 */ 14 15 SimpleTest.waitForExplicitFinish(); 16 17 const {NetUtil} = ChromeUtils.importESModule( 18 "resource://gre/modules/NetUtil.sys.mjs" 19 ); 20 const {HttpServer} = ChromeUtils.importESModule( 21 "resource://testing-common/httpd.sys.mjs" 22 ); 23 24 addLoadEvent(function () { 25 (async function run_tests() { 26 while (tests.length) { 27 let test = tests.shift(); 28 info("-- running " + test.name); 29 await test(); 30 } 31 32 SimpleTest.finish(); 33 })(); 34 }); 35 36 let tests = [ 37 38 // Ensure that sending pings is enabled. 39 function setup() { 40 Services.prefs.setBoolPref("browser.send_pings", true); 41 Services.prefs.setIntPref("browser.send_pings.max_per_link", -1); 42 43 SimpleTest.registerCleanupFunction(() => { 44 Services.prefs.clearUserPref("browser.send_pings"); 45 Services.prefs.clearUserPref("browser.send_pings.max_per_link"); 46 }); 47 }, 48 49 // If both the address of the document containing the hyperlink being audited 50 // and ping URL have the same origin then the request must include a Ping-From 51 // HTTP header with, as its value, the address of the document containing the 52 // hyperlink, and a Ping-To HTTP header with, as its value, the target URL. 53 // The request must not include a Referer (sic) HTTP header. 54 async function same_origin() { 55 let from = "/ping-from/" + Math.random(); 56 let to = "/ping-to/" + Math.random(); 57 let ping = "/ping/" + Math.random(); 58 59 let base; 60 let server = new HttpServer(); 61 62 // The page that contains the link. 63 createFromPathHandler(server, from, to, () => ping); 64 65 // The page that the link's href points to. 66 let promiseHref = createToPathHandler(server, to); 67 68 // The ping we want to receive. 69 let promisePing = createPingPathHandler(server, ping, () => { 70 return {from: base + from, to: base + to}; 71 }); 72 73 // Start the server, get its base URL and run the test. 74 server.start(-1); 75 base = "http://localhost:" + server.identity.primaryPort; 76 navigate(base + from); 77 78 // Wait until the target and ping url have loaded. 79 await Promise.all([promiseHref, promisePing]); 80 81 // Cleanup. 82 await stopServer(server); 83 }, 84 85 // If the origins are different, but the document containing the hyperlink 86 // being audited was not retrieved over an encrypted connection then the 87 // request must include a Referer (sic) HTTP header with, as its value, the 88 // address of the document containing the hyperlink, a Ping-From HTTP header 89 // with the same value, and a Ping-To HTTP header with, as its value, target 90 // URL. 91 async function diff_origin() { 92 let from = "/ping-from/" + Math.random(); 93 let to = "/ping-to/" + Math.random(); 94 let ping = "/ping/" + Math.random(); 95 96 // We will use two servers to simulate two different origins. 97 let base, base2; 98 let server = new HttpServer(); 99 let server2 = new HttpServer(); 100 101 // The page that contains the link. 102 createFromPathHandler(server, from, to, () => base2 + ping); 103 104 // The page that the link's href points to. 105 let promiseHref = createToPathHandler(server, to); 106 107 // Start the first server and get its base URL. 108 server.start(-1); 109 base = "http://localhost:" + server.identity.primaryPort; 110 111 // The ping we want to receive. 112 let promisePing = createPingPathHandler(server2, ping, () => { 113 return {referrer: base + from, from: base + from, to: base + to}; 114 }); 115 116 // Start the second server, get its base URL and run the test. 117 server2.start(-1); 118 base2 = "http://localhost:" + server2.identity.primaryPort; 119 navigate(base + from); 120 121 // Wait until the target and ping url have loaded. 122 await Promise.all([promiseHref, promisePing]); 123 124 // Cleanup. 125 await stopServer(server); 126 await stopServer(server2); 127 }, 128 129 // If the origins are different and the document containing the hyperlink 130 // being audited was retrieved over an encrypted connection then the request 131 // must include a Ping-To HTTP header with, as its value, target URL. The 132 // request must neither include a Referer (sic) HTTP header nor include a 133 // Ping-From HTTP header. 134 async function diff_origin_secure_referrer() { 135 let ping = "/ping/" + Math.random(); 136 let server = new HttpServer(); 137 138 // The ping we want to receive. 139 let promisePing = createPingPathHandler(server, ping, () => { 140 return {to: "https://example.com/"}; 141 }); 142 143 // Start the server and run the test. 144 server.start(-1); 145 146 // The referrer will be loaded using a secure channel. 147 navigate("https://example.com/chrome/dom/html/test/" + 148 "file_anchor_ping.html?" + "http://127.0.0.1:" + 149 server.identity.primaryPort + ping); 150 151 // Wait until the ping has been sent. 152 await promisePing; 153 154 // Cleanup. 155 await stopServer(server); 156 }, 157 158 // Test that the <a ping> attribute is properly tokenized using ASCII white 159 // space characters as separators. 160 async function tokenize_white_space() { 161 let from = "/ping-from/" + Math.random(); 162 let to = "/ping-to/" + Math.random(); 163 164 let base; 165 let server = new HttpServer(); 166 167 let pings = [ 168 "/ping1/" + Math.random(), 169 "/ping2/" + Math.random(), 170 "/ping3/" + Math.random(), 171 "/ping4/" + Math.random() 172 ]; 173 174 // The page that contains the link. 175 createFromPathHandler(server, from, to, () => { 176 return " " + pings[0] + " \r " + pings[1] + " \t " + 177 pings[2] + " \n " + pings[3] + " "; 178 }); 179 180 // The page that the link's href points to. 181 let promiseHref = createToPathHandler(server, to); 182 183 // The pings we want to receive. 184 let pingPathHandlers = createPingPathHandlers(server, pings, () => { 185 return {from: base + from, to: base + to}; 186 }); 187 188 // Start the server, get its base URL and run the test. 189 server.start(-1); 190 base = "http://localhost:" + server.identity.primaryPort; 191 navigate(base + from); 192 193 // Wait until the target and ping url have loaded. 194 await Promise.all([promiseHref, ...pingPathHandlers]); 195 196 // Cleanup. 197 await stopServer(server); 198 } 199 ]; 200 201 // Navigate the iframe used for testing to a new URL. 202 function navigate(uri) { 203 document.getElementById("frame").src = uri; 204 } 205 206 // Registers a path handler for the given server that will serve a page 207 // containing an <a ping> element. The page will automatically simulate 208 // clicking the link after it has loaded. 209 function createFromPathHandler(server, path, href, lazyPing) { 210 server.registerPathHandler(path, function (request, response) { 211 response.setStatusLine(request.httpVersion, 200, "OK"); 212 response.setHeader("Content-Type", "text/html;charset=utf-8", false); 213 response.setHeader("Cache-Control", "no-cache", false); 214 215 let body = '<body onload="document.body.firstChild.click()">' + 216 '<a href="' + href + '" ping="' + lazyPing() + '"></a></body>'; 217 response.write(body); 218 }); 219 } 220 221 // Registers a path handler for the given server that will serve a simple empty 222 // page we can use as the href attribute for links. It returns a promise that 223 // will be resolved once the page has been requested. 224 function createToPathHandler(server, path) { 225 return new Promise(resolve => { 226 227 server.registerPathHandler(path, function (request, response) { 228 response.setStatusLine(request.httpVersion, 200, "OK"); 229 response.setHeader("Content-Type", "text/html;charset=utf-8", false); 230 response.setHeader("Cache-Control", "no-cache", false); 231 response.write("OK"); 232 233 resolve(); 234 }); 235 236 }); 237 } 238 239 // Register multiple path handlers for the given server that will receive 240 // pings as sent when an <a ping> element is clicked. This method uses 241 // createPingPathHandler() defined below to ensure all headers are sent 242 // and received as expected. 243 function createPingPathHandlers(server, paths, lazyHeaders) { 244 return Array.from(paths, (path) => createPingPathHandler(server, path, lazyHeaders)); 245 } 246 247 // Registers a path handler for the given server that will receive pings as 248 // sent when an <a ping> element has been clicked. It will check that the 249 // correct http method has been used, the post data is correct and all headers 250 // are given as expected. It returns a promise that will be resolved once the 251 // ping has been received. 252 function createPingPathHandler(server, path, lazyHeaders) { 253 return new Promise(resolve => { 254 255 server.registerPathHandler(path, function (request, response) { 256 let headers = lazyHeaders(); 257 258 is(request.method, "POST", "correct http method used"); 259 is(request.getHeader("Ping-To"), headers.to, "valid ping-to header"); 260 261 if ("from" in headers) { 262 is(request.getHeader("Ping-From"), headers.from, "valid ping-from header"); 263 } else { 264 ok(!request.hasHeader("Ping-From"), "no ping-from header"); 265 } 266 267 if ("referrer" in headers) { 268 let expectedReferrer = headers.referrer.match(/https?:\/\/[^\/]+\/?/i)[0]; 269 is(request.getHeader("Referer"), expectedReferrer, "valid referer header"); 270 } else { 271 ok(!request.hasHeader("Referer"), "no referer header"); 272 } 273 274 let bs = request.bodyInputStream; 275 let body = NetUtil.readInputStreamToString(bs, bs.available()); 276 is(body, "PING", "correct body sent"); 277 278 response.setStatusLine(request.httpVersion, 200, "OK"); 279 response.setHeader("Content-Type", "text/html;charset=utf-8", false); 280 response.setHeader("Cache-Control", "no-cache", false); 281 response.write("OK"); 282 283 resolve(); 284 }); 285 286 }); 287 } 288 289 // Returns a promise that is resolved when the given http server instance has 290 // been stopped. 291 function stopServer(server) { 292 return new Promise(resolve => { 293 server.stop(resolve); 294 }); 295 } 296 297 </script> 298 </head> 299 <body> 300 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=786347">Mozilla Bug 786347</a> 301 <p id="display"></p> 302 <iframe id="frame" /> 303 </body> 304 </html>