diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/test/unit/test_proxy_cancel.js | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_proxy_cancel.js b/netwerk/test/unit/test_proxy_cancel.js new file mode 100644 index 0000000000..2ad75898b0 --- /dev/null +++ b/netwerk/test/unit/test_proxy_cancel.js @@ -0,0 +1,397 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals setTimeout */ + +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +function makeChan(uri) { + let chan = NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + }).QueryInterface(Ci.nsIHttpChannel); + chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; + return chan; +} + +add_task(async function test_cancel_after_asyncOpen() { + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); + addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u"); + + let proxies = [ + NodeHTTPProxyServer, + NodeHTTPSProxyServer, + NodeHTTP2ProxyServer, + ]; + for (let p of proxies) { + let proxy = new p(); + await proxy.start(); + registerCleanupFunction(async () => { + await proxy.stop(); + }); + await with_node_servers( + [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server], + async server => { + info(`Testing ${p.name} with ${server.constructor.name}`); + await server.execute( + `global.server_name = "${server.constructor.name}";` + ); + + await server.registerPathHandler("/test", (req, resp) => { + resp.writeHead(200); + resp.end(global.server_name); + }); + + let chan = makeChan(`${server.origin()}/test`); + let openPromise = new Promise(resolve => { + chan.asyncOpen( + new ChannelListener( + (req, buff) => resolve({ req, buff }), + null, + CL_EXPECT_FAILURE + ) + ); + }); + chan.cancel(Cr.NS_ERROR_ABORT); + let { req } = await openPromise; + Assert.equal(req.status, Cr.NS_ERROR_ABORT); + } + ); + await proxy.stop(); + } +}); + +// const NS_NET_STATUS_CONNECTING_TO = 0x804b0007; +// const NS_NET_STATUS_CONNECTED_TO = 0x804b0004; +// const NS_NET_STATUS_SENDING_TO = 0x804b0005; +const NS_NET_STATUS_WAITING_FOR = 0x804b000a; // 2152398858 +const NS_NET_STATUS_RECEIVING_FROM = 0x804b0006; +// const NS_NET_STATUS_TLS_HANDSHAKE_STARTING = 0x804b000c; // 2152398860 +// const NS_NET_STATUS_TLS_HANDSHAKE_ENDED = 0x804b000d; // 2152398861 + +add_task(async function test_cancel_after_connect_http2proxy() { + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); + addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u"); + + await with_node_servers( + [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server], + async server => { + // Set up a proxy for each server to make sure proxy state is clean + // for each test. + let proxy = new NodeHTTP2ProxyServer(); + await proxy.start(); + registerCleanupFunction(async () => { + await proxy.stop(); + }); + await proxy.execute(` + global.session_counter = 0; + global.proxy.on("session", () => { + global.session_counter++; + }); + `); + + info(`Testing ${proxy.constructor.name} with ${server.constructor.name}`); + await server.execute( + `global.server_name = "${server.constructor.name}";` + ); + + await server.registerPathHandler("/test", (req, resp) => { + global.reqCount = (global.reqCount || 0) + 1; + resp.writeHead(200); + resp.end(global.server_name); + }); + + let chan = makeChan(`${server.origin()}/test`); + chan.notificationCallbacks = { + QueryInterface: ChromeUtils.generateQI([ + "nsIInterfaceRequestor", + "nsIProgressEventSink", + ]), + + getInterface(iid) { + return this.QueryInterface(iid); + }, + + onProgress(request, progress, progressMax) {}, + onStatus(request, status, statusArg) { + info(`status = ${status}`); + // XXX(valentin): Is this the best status to be cancelling? + if (status == NS_NET_STATUS_WAITING_FOR) { + info("cancelling connected channel"); + chan.cancel(Cr.NS_ERROR_ABORT); + } + }, + }; + let openPromise = new Promise(resolve => { + chan.asyncOpen( + new ChannelListener( + (req, buff) => resolve({ req, buff }), + null, + CL_EXPECT_FAILURE + ) + ); + }); + let { req } = await openPromise; + Assert.equal(req.status, Cr.NS_ERROR_ABORT); + + // Since we're cancelling just after connect, we'd expect that no + // requests are actually registered. But because we're cancelling on the + // main thread, and the request is being performed on the socket thread, + // it might actually reach the server, especially in chaos test mode. + // Assert.equal( + // await server.execute(`global.reqCount || 0`), + // 0, + // `No requests should have been made at this point` + // ); + Assert.equal(await proxy.execute(`global.session_counter`), 1); + + chan = makeChan(`${server.origin()}/test`); + await new Promise(resolve => { + chan.asyncOpen( + new ChannelListener( + // eslint-disable-next-line no-shadow + (req, buff) => resolve({ req, buff }), + null, + CL_ALLOW_UNKNOWN_CL + ) + ); + }); + + // Check that there's still only one session. + Assert.equal(await proxy.execute(`global.session_counter`), 1); + await proxy.stop(); + } + ); +}); + +add_task(async function test_cancel_after_sending_request() { + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); + addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u"); + + await with_node_servers( + [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server], + async server => { + let proxies = [ + NodeHTTPProxyServer, + NodeHTTPSProxyServer, + NodeHTTP2ProxyServer, + ]; + for (let p of proxies) { + let proxy = new p(); + await proxy.start(); + registerCleanupFunction(async () => { + await proxy.stop(); + }); + + await proxy.execute(` + global.session_counter = 0; + global.proxy.on("session", () => { + global.session_counter++; + }); + `); + info(`Testing ${p.name} with ${server.constructor.name}`); + await server.execute( + `global.server_name = "${server.constructor.name}";` + ); + + await server.registerPathHandler("/test", (req, resp) => { + // Here we simmulate a slow response to give the test time to + // cancel the channel before receiving the response. + global.request_count = (global.request_count || 0) + 1; + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => { + resp.writeHead(200); + resp.end(global.server_name); + }, 2000); + }); + await server.registerPathHandler("/instant", (req, resp) => { + resp.writeHead(200); + resp.end(global.server_name); + }); + + // It seems proxy.on("session") only gets emitted after a full request. + // So we first load a simple request, then the long lasting request + // that we then cancel before it has the chance to complete. + let chan = makeChan(`${server.origin()}/instant`); + await new Promise(resolve => { + chan.asyncOpen( + new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL) + ); + }); + + chan = makeChan(`${server.origin()}/test`); + let openPromise = new Promise(resolve => { + chan.asyncOpen( + new ChannelListener( + (req, buff) => resolve({ req, buff }), + null, + CL_EXPECT_FAILURE + ) + ); + }); + // XXX(valentin) This might be a little racy + await TestUtils.waitForCondition(async () => { + return (await server.execute("global.request_count")) > 0; + }); + + chan.cancel(Cr.NS_ERROR_ABORT); + + let { req } = await openPromise; + Assert.equal(req.status, Cr.NS_ERROR_ABORT); + + async function checkSessionCounter() { + if (p.name == "NodeHTTP2ProxyServer") { + Assert.equal(await proxy.execute(`global.session_counter`), 1); + } + } + + await checkSessionCounter(); + + chan = makeChan(`${server.origin()}/instant`); + await new Promise(resolve => { + chan.asyncOpen( + new ChannelListener( + // eslint-disable-next-line no-shadow + (req, buff) => resolve({ req, buff }), + null, + CL_ALLOW_UNKNOWN_CL + ) + ); + }); + await checkSessionCounter(); + + await proxy.stop(); + } + } + ); +}); + +add_task(async function test_cancel_during_response() { + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); + addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u"); + + await with_node_servers( + [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server], + async server => { + let proxies = [ + NodeHTTPProxyServer, + NodeHTTPSProxyServer, + NodeHTTP2ProxyServer, + ]; + for (let p of proxies) { + let proxy = new p(); + await proxy.start(); + registerCleanupFunction(async () => { + await proxy.stop(); + }); + + await proxy.execute(` + global.session_counter = 0; + global.proxy.on("session", () => { + global.session_counter++; + }); + `); + + info(`Testing ${p.name} with ${server.constructor.name}`); + await server.execute( + `global.server_name = "${server.constructor.name}";` + ); + + await server.registerPathHandler("/test", (req, resp) => { + resp.writeHead(200); + resp.write("a".repeat(1000)); + // Here we send the response back in two chunks. + // The channel should be cancelled after the first one. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => { + resp.write("a".repeat(1000)); + resp.end(global.server_name); + }, 2000); + }); + await server.registerPathHandler("/instant", (req, resp) => { + resp.writeHead(200); + resp.end(global.server_name); + }); + + let chan = makeChan(`${server.origin()}/test`); + + chan.notificationCallbacks = { + QueryInterface: ChromeUtils.generateQI([ + "nsIInterfaceRequestor", + "nsIProgressEventSink", + ]), + + getInterface(iid) { + return this.QueryInterface(iid); + }, + + onProgress(request, progress, progressMax) { + info(`progress: ${progress}/${progressMax}`); + // Check that we never get more than 1000 bytes. + Assert.equal(progress, 1000); + }, + onStatus(request, status, statusArg) { + if (status == NS_NET_STATUS_RECEIVING_FROM) { + info("cancelling when receiving request"); + chan.cancel(Cr.NS_ERROR_ABORT); + } + }, + }; + + let openPromise = new Promise(resolve => { + chan.asyncOpen( + new ChannelListener( + (req, buff) => resolve({ req, buff }), + null, + CL_EXPECT_FAILURE + ) + ); + }); + + let { req } = await openPromise; + Assert.equal(req.status, Cr.NS_ERROR_ABORT); + + async function checkSessionCounter() { + if (p.name == "NodeHTTP2ProxyServer") { + Assert.equal(await proxy.execute(`global.session_counter`), 1); + } + } + + await checkSessionCounter(); + + chan = makeChan(`${server.origin()}/instant`); + await new Promise(resolve => { + chan.asyncOpen( + new ChannelListener( + // eslint-disable-next-line no-shadow + (req, buff) => resolve({ req, buff }), + null, + CL_ALLOW_UNKNOWN_CL + ) + ); + }); + + await checkSessionCounter(); + + await proxy.stop(); + } + } + ); +}); |