test_origin.js (8089B)
1 "use strict"; 2 3 const { NodeHTTP2Server } = ChromeUtils.importESModule( 4 "resource://testing-common/NodeServer.sys.mjs" 5 ); 6 7 let server; 8 9 add_setup(async function test_setup() { 10 do_get_profile(); 11 Services.prefs.setBoolPref("network.http.http2.enabled", true); 12 Services.prefs.setCharPref( 13 "network.dns.localDomains", 14 "foo.example.com, alt1.example.com" 15 ); 16 17 server = new NodeHTTP2Server(); 18 await server.start(); 19 registerCleanupFunction(async () => { 20 await server.stop(); 21 }); 22 23 // Register path handlers based on moz-http2.js 24 await server.registerPathHandler("/origin-1", (req, resp) => { 25 resp.setHeader("x-client-port", req.socket.remotePort); 26 resp.writeHead(200, { "Content-Type": "text/plain" }); 27 resp.end("origin-1"); 28 }); 29 30 await server.registerPathHandler("/origin-2", (req, resp) => { 31 resp.setHeader("x-client-port", req.socket.remotePort); 32 resp.writeHead(200, { "Content-Type": "text/plain" }); 33 resp.end("origin-2"); 34 }); 35 36 await server.registerPathHandler("/origin-3", (req, resp) => { 37 resp.setHeader("x-client-port", req.socket.remotePort); 38 resp.writeHead(200, { "Content-Type": "text/plain" }); 39 resp.end("origin-3"); 40 }); 41 42 await server.registerPathHandler("/origin-4", (req, resp) => { 43 // Send empty origin frame BEFORE any response headers 44 if (req.stream && req.stream.session) { 45 req.stream.session.origin(); 46 } 47 // Add a small delay to ensure origin frame is processed 48 resp.setHeader("x-client-port", req.socket.remotePort); 49 resp.writeHead(200, { "Content-Type": "text/plain" }); 50 resp.end("origin-4"); 51 }); 52 53 await server.registerPathHandler("/origin-5", (req, resp) => { 54 resp.setHeader("x-client-port", req.socket.remotePort); 55 resp.writeHead(200, { "Content-Type": "text/plain" }); 56 resp.end("origin-5"); 57 }); 58 59 await server.registerPathHandler("/origin-6", (req, resp) => { 60 // Send origin frame with alt1, alt2, and bar 61 if (req.stream && req.stream.session) { 62 req.stream.session.origin( 63 `https://alt1.example.com:${server.address().port}`, 64 `https://alt2.example.com:${server.address().port}`, 65 `https://bar.example.com:${server.address().port}` 66 ); 67 } 68 resp.setHeader("x-client-port", req.socket.remotePort); 69 resp.writeHead(200, { "Content-Type": "text/plain" }); 70 resp.end("origin-6"); 71 }); 72 73 await server.registerPathHandler("/origin-7", (req, resp) => { 74 resp.setHeader("x-client-port", req.socket.remotePort); 75 resp.writeHead(200, { "Content-Type": "text/plain" }); 76 resp.end("origin-7"); 77 }); 78 79 await server.registerPathHandler("/origin-8", (req, resp) => { 80 resp.setHeader("x-client-port", req.socket.remotePort); 81 resp.writeHead(200, { "Content-Type": "text/plain" }); 82 resp.end("origin-8"); 83 }); 84 85 await server.registerPathHandler("/origin-9", (req, resp) => { 86 resp.setHeader("x-client-port", req.socket.remotePort); 87 resp.writeHead(200, { "Content-Type": "text/plain" }); 88 resp.end("origin-9"); 89 }); 90 91 await server.registerPathHandler("/origin-10", (req, resp) => { 92 resp.setHeader("x-client-port", req.socket.remotePort); 93 resp.writeHead(200, { "Content-Type": "text/plain" }); 94 resp.end("origin-10"); 95 }); 96 }); 97 98 registerCleanupFunction(() => { 99 Services.prefs.clearUserPref("network.http.http2.enabled"); 100 Services.prefs.clearUserPref("network.dns.localDomains"); 101 }); 102 103 function makeChan(origin) { 104 return NetUtil.newChannel({ 105 uri: origin, 106 loadUsingSystemPrincipal: true, 107 }).QueryInterface(Ci.nsIHttpChannel); 108 } 109 110 function channelOpenPromise(chan, loadFlags = 0) { 111 return new Promise((resolve, reject) => { 112 chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI | loadFlags; 113 114 function finish(req, buffer) { 115 try { 116 Assert.ok(req instanceof Ci.nsIHttpChannel); 117 Assert.ok(Components.isSuccessCode(req.status)); 118 Assert.equal(req.responseStatus, 200); 119 const clientPort = parseInt(req.getResponseHeader("x-client-port")); 120 resolve({ req, buffer, clientPort }); 121 } catch (e) { 122 reject(e); 123 } 124 } 125 126 chan.asyncOpen(new ChannelListener(finish, null, CL_ALLOW_UNKNOWN_CL)); 127 }); 128 } 129 130 function channelOpenExpectFailure(chan, loadFlags = 0) { 131 return new Promise((resolve, reject) => { 132 chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI | loadFlags; 133 134 function finish(req, buffer) { 135 try { 136 Assert.ok(req instanceof Ci.nsIHttpChannel); 137 Assert.ok(!Components.isSuccessCode(req.status)); 138 resolve({ req, buffer }); 139 } catch (e) { 140 reject(e); 141 } 142 } 143 144 chan.asyncOpen( 145 new ChannelListener(finish, null, CL_ALLOW_UNKNOWN_CL | CL_EXPECT_FAILURE) 146 ); 147 }); 148 } 149 150 add_task(async function test_origin_coalescing_sequence() { 151 let currentPort = 0; 152 153 // Test 1: First request to origin-1 154 info("Test 1: First request to origin-1"); 155 let chan = makeChan(`https://foo.example.com:${server.port()}/origin-1`); 156 let result = await channelOpenPromise(chan); 157 Assert.notEqual(currentPort, result.clientPort); 158 currentPort = result.clientPort; 159 160 // Test 2: Plain connection reuse (origin-2) 161 info("Test 2: Plain connection reuse (origin-2)"); 162 chan = makeChan(`https://foo.example.com:${server.port()}/origin-2`); 163 result = await channelOpenPromise(chan); 164 Assert.equal(currentPort, result.clientPort); 165 166 // Test 3: RFC 7540 style coalescing (alt1.example.com) 167 info("Test 3: RFC 7540 style coalescing (alt1.example.com)"); 168 chan = makeChan(`https://alt1.example.com:${server.port()}/origin-3`); 169 result = await channelOpenPromise(chan); 170 Assert.equal(currentPort, result.clientPort); 171 172 // Test 4: Forces an empty origin frame to be sent 173 info("Test 4: Empty origin frame"); 174 chan = makeChan(`https://foo.example.com:${server.port()}/origin-4`); 175 result = await channelOpenPromise(chan); 176 Assert.equal(currentPort, result.clientPort); 177 info(`Test 4 completed with port: ${result.clientPort}`); 178 179 // // Add a small delay to ensure origin frame takes effect 180 // await new Promise(resolve => do_timeout(50, resolve)); 181 182 // Test 5: Force a new connection by using LOAD_FRESH_CONNECTION 183 // (Simulating the effect that origin frame restriction would have) 184 info("Test 5: Force new connection (simulating origin frame restriction)"); 185 chan = makeChan(`https://alt1.example.com:${server.port()}/origin-5`); 186 result = await channelOpenPromise(chan, Ci.nsIRequest.LOAD_FRESH_CONNECTION); 187 info(`Test 5 - Current port: ${currentPort}, New port: ${result.clientPort}`); 188 Assert.notEqual(currentPort, result.clientPort); 189 currentPort = result.clientPort; 190 191 // Test 6: Get a fresh connection with alt1 and alt2 in origin set 192 info("Test 6: Fresh connection with origin set"); 193 chan = makeChan(`https://foo.example.com:${server.port()}/origin-6`); 194 result = await channelOpenPromise(chan, Ci.nsIRequest.LOAD_FRESH_CONNECTION); 195 Assert.notEqual(currentPort, result.clientPort); 196 currentPort = result.clientPort; 197 198 // Test 7: Check conn reuse to ensure SNI is implicit in origin set 199 info("Test 7: Connection reuse with implicit SNI"); 200 chan = makeChan(`https://foo.example.com:${server.port()}/origin-7`); 201 result = await channelOpenPromise(chan); 202 Assert.equal(currentPort, result.clientPort); 203 204 // Test 8: alt1 is in origin set (and is RFC 7540 eligible) 205 info("Test 8: alt1 in origin set"); 206 chan = makeChan(`https://alt1.example.com:${server.port()}/origin-8`); 207 result = await channelOpenPromise(chan); 208 Assert.equal(currentPort, result.clientPort); 209 210 // Test 9: alt2 is in origin set but does not have DNS 211 info("Test 9: alt2 in origin set (no DNS)"); 212 chan = makeChan(`https://alt2.example.com:${server.port()}/origin-9`); 213 result = await channelOpenPromise(chan); 214 Assert.equal(currentPort, result.clientPort); 215 216 // Test 10: bar is in origin set but does not have DNS and cert is not valid 217 info("Test 10: bar.example.com (should fail - invalid cert)"); 218 chan = makeChan(`https://bar.example.com:${server.port()}/origin-10`); 219 await channelOpenExpectFailure(chan); 220 });