412 lines
13 KiB
JavaScript
412 lines
13 KiB
JavaScript
/* 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 */
|
|
|
|
// We don't normally allow localhost channels to be proxied, but this
|
|
// is easier than updating all the certs and/or domains.
|
|
Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
|
|
registerCleanupFunction(() => {
|
|
Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
|
|
});
|
|
|
|
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_setup(async function setup() {
|
|
// See Bug 1878505
|
|
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
|
|
registerCleanupFunction(async () => {
|
|
Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
|
|
});
|
|
});
|
|
|
|
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 = 0x4b0007;
|
|
// const NS_NET_STATUS_CONNECTED_TO = 0x4b0004;
|
|
// const NS_NET_STATUS_SENDING_TO = 0x4b0005;
|
|
const NS_NET_STATUS_WAITING_FOR = 0x4b000a; // 2152398858
|
|
const NS_NET_STATUS_RECEIVING_FROM = 0x4b0006;
|
|
// const NS_NET_STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c; // 2152398860
|
|
// const NS_NET_STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d; // 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() {},
|
|
onStatus(request, status) {
|
|
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) {
|
|
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();
|
|
}
|
|
}
|
|
);
|
|
});
|