summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_http2-proxy.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/test/unit/test_http2-proxy.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/test/unit/test_http2-proxy.js')
-rw-r--r--netwerk/test/unit/test_http2-proxy.js862
1 files changed, 862 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_http2-proxy.js b/netwerk/test/unit/test_http2-proxy.js
new file mode 100644
index 0000000000..d8aa0019fe
--- /dev/null
+++ b/netwerk/test/unit/test_http2-proxy.js
@@ -0,0 +1,862 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/**
+ * This test checks following expectations when using HTTP/2 proxy:
+ *
+ * - when we request https access, we don't create different sessions for
+ * different origins, only new tunnels inside a single session
+ * - when the isolation key (`proxy_isolation`) is changed, new single session
+ * is created for new requests to same origins as before
+ * - error code returned from the tunnel (a proxy error - not end-server
+ * error!) doesn't kill the existing session
+ * - check we are seeing expected nsresult error codes on channels
+ * (nsIChannel.status) corresponding to different proxy status code
+ * responses (502, 504, 407, ...)
+ * - check we don't try to ask for credentials or otherwise authenticate to
+ * the proxy when 407 is returned and there is no Proxy-Authenticate
+ * response header sent
+ * - a stream reset for a connect stream to the proxy does not cause session to
+ * be closed and the request through the proxy will failed.
+ * - a "soft" stream error on a connection to the origin server will close the
+ * stream, but it will not close niether the HTTP/2 session to the proxy nor
+ * to the origin server.
+ * - a "hard" stream error on a connection to the origin server will close the
+ * HTTP/2 session to the origin server, but it will not close the HTTP/2
+ * session to the proxy.
+ */
+
+/* eslint-env node */
+/* global serverPort */
+
+"use strict";
+
+const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+let proxy_port;
+let filter;
+let proxy;
+
+// See moz-http2
+const proxy_auth = "authorization-token";
+let proxy_isolation;
+
+class ProxyFilter {
+ constructor(type, host, port, flags) {
+ this._type = type;
+ this._host = host;
+ this._port = port;
+ this._flags = flags;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
+ }
+ applyFilter(uri, pi, cb) {
+ cb.onProxyFilterResult(
+ pps.newProxyInfo(
+ this._type,
+ this._host,
+ this._port,
+ proxy_auth,
+ proxy_isolation,
+ this._flags,
+ 1000,
+ null
+ )
+ );
+ }
+}
+
+class UnxpectedAuthPrompt2 {
+ constructor(signal) {
+ this.signal = signal;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
+ }
+ asyncPromptAuth() {
+ this.signal.triggered = true;
+ throw Components.Exception("", Cr.ERROR_UNEXPECTED);
+ }
+}
+
+class SimpleAuthPrompt2 {
+ constructor(signal) {
+ this.signal = signal;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
+ }
+ asyncPromptAuth(channel, callback, context, encryptionLevel, authInfo) {
+ this.signal.triggered = true;
+ executeSoon(function () {
+ authInfo.username = "user";
+ authInfo.password = "pass";
+ callback.onAuthAvailable(context, authInfo);
+ });
+ }
+}
+
+class AuthRequestor {
+ constructor(prompt) {
+ this.prompt = prompt;
+ this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]);
+ }
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ return this.prompt();
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ }
+}
+
+function createPrincipal(url) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ return ssm.createContentPrincipal(Services.io.newURI(url), {});
+ } catch (e) {
+ return null;
+ }
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: createPrincipal(url),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ // Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+}
+
+function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL, delay = 0) {
+ return new Promise(resolve => {
+ var listener = new ChannelListener(
+ (request, data) => {
+ request.QueryInterface(Ci.nsIHttpChannel);
+ const status = request.status;
+ const http_code = status ? undefined : request.responseStatus;
+ request.QueryInterface(Ci.nsIProxiedChannel);
+ const proxy_connect_response_code =
+ request.httpProxyConnectResponseCode;
+ resolve({ status, http_code, data, proxy_connect_response_code });
+ },
+ null,
+ flags
+ );
+ if (delay > 0) {
+ do_timeout(delay, function () {
+ channel.asyncOpen(listener);
+ });
+ } else {
+ channel.asyncOpen(listener);
+ }
+ });
+}
+
+let initial_session_count = 0;
+
+class http2ProxyCode {
+ static listen(server, envport) {
+ if (!server) {
+ return Promise.resolve(0);
+ }
+
+ let portSelection = 0;
+ if (envport !== undefined) {
+ try {
+ portSelection = parseInt(envport, 10);
+ } catch (e) {
+ portSelection = -1;
+ }
+ }
+ return new Promise(resolve => {
+ server.listen(portSelection, "0.0.0.0", 2000, () => {
+ resolve(server.address().port);
+ });
+ });
+ }
+
+ static startNewProxy() {
+ const fs = require("fs");
+ const options = {
+ key: fs.readFileSync(__dirname + "/http2-cert.key"),
+ cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
+ };
+ const http2 = require("http2");
+ global.proxy = http2.createSecureServer(options);
+ this.setupProxy();
+ return http2ProxyCode.listen(proxy).then(port => {
+ return { port, success: true };
+ });
+ }
+
+ static closeProxy() {
+ proxy.closeSockets();
+ return new Promise(resolve => {
+ proxy.close(resolve);
+ });
+ }
+
+ static proxySessionCount() {
+ if (!proxy) {
+ return 0;
+ }
+ return proxy.proxy_session_count;
+ }
+
+ static proxySessionToOriginServersCount() {
+ if (!proxy) {
+ return 0;
+ }
+ return proxy.sessionToOriginServersCount;
+ }
+
+ static setupProxy() {
+ if (!proxy) {
+ throw new Error("proxy is null");
+ }
+ proxy.proxy_session_count = 0;
+ proxy.sessionToOriginServersCount = 0;
+ proxy.on("session", () => {
+ ++proxy.proxy_session_count;
+ });
+
+ // We need to track active connections so we can forcefully close keep-alive
+ // connections when shutting down the proxy.
+ proxy.socketIndex = 0;
+ proxy.socketMap = {};
+ proxy.on("connection", function (socket) {
+ let index = proxy.socketIndex++;
+ proxy.socketMap[index] = socket;
+ socket.on("close", function () {
+ delete proxy.socketMap[index];
+ });
+ });
+ proxy.closeSockets = function () {
+ for (let i in proxy.socketMap) {
+ proxy.socketMap[i].destroy();
+ }
+ };
+
+ proxy.on("stream", (stream, headers) => {
+ if (headers[":method"] !== "CONNECT") {
+ // Only accept CONNECT requests
+ stream.respond({ ":status": 405 });
+ stream.end();
+ return;
+ }
+
+ const target = headers[":authority"];
+
+ const authorization_token = headers["proxy-authorization"];
+ if (target == "407.example.com:443") {
+ stream.respond({ ":status": 407 });
+ // Deliberately send no Proxy-Authenticate header
+ stream.end();
+ return;
+ }
+ if (target == "407.basic.example.com:443") {
+ // we want to return a different response than 407 to not re-request
+ // credentials (and thus loop) but also not 200 to not let the channel
+ // attempt to waste time connecting a non-existing https server - hence
+ // 418 I'm a teapot :)
+ if ("Basic dXNlcjpwYXNz" == authorization_token) {
+ stream.respond({ ":status": 418 });
+ stream.end();
+ return;
+ }
+ stream.respond({
+ ":status": 407,
+ "proxy-authenticate": "Basic realm='foo'",
+ });
+ stream.end();
+ return;
+ }
+ if (target == "404.example.com:443") {
+ // 404 Not Found, a response code that a proxy should return when the host can't be found
+ stream.respond({ ":status": 404 });
+ stream.end();
+ return;
+ }
+ if (target == "429.example.com:443") {
+ // 429 Too Many Requests, a response code that a proxy should return when receiving too many requests
+ stream.respond({ ":status": 429 });
+ stream.end();
+ return;
+ }
+ if (target == "502.example.com:443") {
+ // 502 Bad Gateway, a response code mostly resembling immediate connection error
+ stream.respond({ ":status": 502 });
+ stream.end();
+ return;
+ }
+ if (target == "504.example.com:443") {
+ // 504 Gateway Timeout, did not receive a timely response from an upstream server
+ stream.respond({ ":status": 504 });
+ stream.end();
+ return;
+ }
+ if (target == "reset.example.com:443") {
+ // always reset the stream.
+ stream.close(0x0);
+ return;
+ }
+
+ ++proxy.sessionToOriginServersCount;
+ const net = require("net");
+ const socket = net.connect(serverPort, "127.0.0.1", () => {
+ try {
+ stream.respond({ ":status": 200 });
+ socket.pipe(stream);
+ stream.pipe(socket);
+ } catch (exception) {
+ console.log(exception);
+ stream.close();
+ }
+ });
+ socket.on("error", error => {
+ throw new Error(
+ `Unexpected error when conneting the HTTP/2 server from the HTTP/2 proxy during CONNECT handling: '${error}'`
+ );
+ });
+ });
+ }
+}
+
+async function proxy_session_counter() {
+ let data = await NodeServer.execute(
+ processId,
+ `http2ProxyCode.proxySessionCount()`
+ );
+ return parseInt(data) - initial_session_count;
+}
+async function proxy_session_to_origin_server_counter() {
+ let data = await NodeServer.execute(
+ processId,
+ `http2ProxyCode.proxySessionToOriginServersCount()`
+ );
+ return parseInt(data) - initial_session_count;
+}
+let processId;
+add_task(async function setup() {
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
+ // so add that cert to the trust list as a signing cert.
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ let server_port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(server_port, null);
+ processId = await NodeServer.fork();
+ await NodeServer.execute(processId, `serverPort = ${server_port}`);
+ await NodeServer.execute(processId, http2ProxyCode);
+ let newProxy = await NodeServer.execute(
+ processId,
+ `http2ProxyCode.startNewProxy()`
+ );
+ proxy_port = newProxy.port;
+ Assert.notEqual(proxy_port, null);
+
+ Services.prefs.setStringPref(
+ "services.settings.server",
+ `data:,#remote-settings-dummy/v1`
+ );
+
+ Services.prefs.setBoolPref("network.http.http2.enabled", true);
+
+ // Even with network state isolation active, we don't end up using the
+ // partitioned principal.
+ Services.prefs.setBoolPref("privacy.partition.network_state", true);
+
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ filter = new ProxyFilter("https", "localhost", proxy_port, 0);
+ pps.registerFilter(filter, 10);
+
+ initial_session_count = await proxy_session_counter();
+ info(`Initial proxy session count = ${initial_session_count}`);
+});
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("services.settings.server");
+ Services.prefs.clearUserPref("network.http.http2.enabled");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+
+ pps.unregisterFilter(filter);
+
+ await NodeServer.execute(processId, `http2ProxyCode.closeProxy()`);
+ await NodeServer.kill(processId);
+});
+
+/**
+ * Test series beginning.
+ */
+
+// Check we reach the h2 end server and keep only one session with the proxy for two different origin.
+// Here we use the first isolation token.
+add_task(async function proxy_success_one_session() {
+ proxy_isolation = "TOKEN1";
+
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.ok(alt1.data.match("You Win!"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "Created just one session with the proxy"
+ );
+});
+
+// The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
+// code from the channel and not try to ask for any credentials.
+add_task(async function proxy_auth_failure() {
+ const chan = make_channel(`https://407.example.com/`);
+ const auth_prompt = { triggered: false };
+ chan.notificationCallbacks = new AuthRequestor(
+ () => new UnxpectedAuthPrompt2(auth_prompt)
+ );
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ chan,
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
+ Assert.equal(proxy_connect_response_code, 407);
+ Assert.equal(http_code, undefined);
+ Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 407"
+ );
+});
+
+// The proxy responses with 407 with Proxy-Authenticate header presence. Make
+// sure that we prompt the auth prompt to ask for credentials.
+add_task(async function proxy_auth_basic() {
+ const chan = make_channel(`https://407.basic.example.com/`);
+ const auth_prompt = { triggered: false };
+ chan.notificationCallbacks = new AuthRequestor(
+ () => new SimpleAuthPrompt2(auth_prompt)
+ );
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ chan,
+ CL_EXPECT_FAILURE
+ );
+
+ // 418 indicates we pass the basic authentication.
+ Assert.equal(status, Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);
+ Assert.equal(proxy_connect_response_code, 418);
+ Assert.equal(http_code, undefined);
+ Assert.equal(auth_prompt.triggered, true, "Auth prompt should trigger");
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 407"
+ );
+});
+
+// 502 Bad gateway code returned by the proxy, still one session only, proper different code
+// from the channel.
+add_task(async function proxy_bad_gateway_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(proxy_connect_response_code, 502);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 502 after 407"
+ );
+});
+
+// Second 502 Bad gateway code returned by the proxy, still one session only with the proxy.
+add_task(async function proxy_bad_gateway_failure_two() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(proxy_connect_response_code, 502);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by second 502"
+ );
+});
+
+// 504 Gateway timeout code returned by the proxy, still one session only, proper different code
+// from the channel.
+add_task(async function proxy_gateway_timeout_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://504.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
+ Assert.equal(proxy_connect_response_code, 504);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 504 after 502"
+ );
+});
+
+// 404 Not Found means the proxy could not resolve the host. As for other error responses
+// we still expect this not to close the existing session.
+add_task(async function proxy_host_not_found_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://404.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
+ Assert.equal(proxy_connect_response_code, 404);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 404 after 504"
+ );
+});
+
+add_task(async function proxy_too_many_requests_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://429.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
+ Assert.equal(proxy_connect_response_code, 429);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 429 after 504"
+ );
+});
+
+add_task(async function proxy_stream_reset_failure() {
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://reset.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_NET_INTERRUPT);
+ Assert.equal(proxy_connect_response_code, 0);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session created by 429 after 504"
+ );
+});
+
+// The soft errors are not closing the session.
+add_task(async function origin_server_stream_soft_failure() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://foo.example.com/illegalhpacksoft`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, Cr.NS_ERROR_ILLEGAL_VALUE);
+ Assert.equal(proxy_connect_response_code, 200);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No session to the proxy closed by soft stream errors"
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No session to the origin server closed by soft stream errors"
+ );
+});
+
+// The soft errors are not closing the session.
+add_task(
+ async function origin_server_stream_soft_failure_multiple_streams_not_affected() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+
+ let should_succeed = get_response(
+ make_channel(`https://foo.example.com/750ms`)
+ );
+
+ const failed = await get_response(
+ make_channel(`https://foo.example.com/illegalhpacksoft`),
+ CL_EXPECT_FAILURE,
+ 20
+ );
+
+ const succeeded = await should_succeed;
+
+ Assert.equal(failed.status, Cr.NS_ERROR_ILLEGAL_VALUE);
+ Assert.equal(failed.proxy_connect_response_code, 200);
+ Assert.equal(failed.http_code, undefined);
+ Assert.equal(succeeded.status, Cr.NS_OK);
+ Assert.equal(succeeded.proxy_connect_response_code, 200);
+ Assert.equal(succeeded.http_code, 200);
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No session to the proxy closed by soft stream errors"
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No session to the origin server closed by soft stream errors"
+ );
+ }
+);
+
+// Make sure that the above error codes don't kill the session to the proxy.
+add_task(async function proxy_success_still_one_session() {
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.http_code, 200);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 1,
+ "No new session to the proxy created after stream error codes"
+ );
+});
+
+// Have a new isolation key, this means we are expected to create a new, and again one only,
+// session with the proxy to reach the end server.
+add_task(async function proxy_success_isolated_session() {
+ Assert.notEqual(proxy_isolation, "TOKEN2");
+ proxy_isolation = "TOKEN2";
+
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+ const lh = await get_response(
+ make_channel(`https://localhost/random-request-3`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.ok(alt1.data.match("You Win!"));
+ Assert.equal(lh.status, Cr.NS_OK);
+ Assert.equal(lh.proxy_connect_response_code, 200);
+ Assert.equal(lh.http_code, 200);
+ Assert.ok(lh.data.match("random-request-3"));
+ Assert.ok(lh.data.match("You Win!"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "Just one new session seen after changing the isolation key"
+ );
+});
+
+// Check that error codes are still handled the same way with new isolation, just in case.
+add_task(async function proxy_bad_gateway_failure_isolated() {
+ const failure1 = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+ const failure2 = await get_response(
+ make_channel(`https://502.example.com/`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(failure1.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(failure1.proxy_connect_response_code, 502);
+ Assert.equal(failure1.http_code, undefined);
+ Assert.equal(failure2.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
+ Assert.equal(failure2.proxy_connect_response_code, 502);
+ Assert.equal(failure2.http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session created by 502"
+ );
+});
+
+add_task(async function proxy_success_check_number_of_session() {
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+ const alt1 = await get_response(
+ make_channel(`https://alt1.example.com/random-request-2`)
+ );
+ const lh = await get_response(
+ make_channel(`https://localhost/random-request-3`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+ Assert.equal(alt1.status, Cr.NS_OK);
+ Assert.equal(alt1.proxy_connect_response_code, 200);
+ Assert.equal(alt1.http_code, 200);
+ Assert.ok(alt1.data.match("random-request-2"));
+ Assert.ok(alt1.data.match("You Win!"));
+ Assert.equal(lh.status, Cr.NS_OK);
+ Assert.equal(lh.proxy_connect_response_code, 200);
+ Assert.equal(lh.http_code, 200);
+ Assert.ok(lh.data.match("random-request-3"));
+ Assert.ok(lh.data.match("You Win!"));
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "The number of sessions has not changed"
+ );
+});
+
+// The hard errors are closing the session.
+add_task(async function origin_server_stream_hard_failure() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+ const { status, http_code, proxy_connect_response_code } = await get_response(
+ make_channel(`https://foo.example.com/illegalhpackhard`),
+ CL_EXPECT_FAILURE
+ );
+
+ Assert.equal(status, 0x804b0053);
+ Assert.equal(proxy_connect_response_code, 200);
+ Assert.equal(http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy."
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No new session to the origin server yet."
+ );
+
+ // Check the a new session ill be opened.
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy is created after a hard stream failure on the session to the origin server."
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server + 1,
+ "A new session to the origin server after a hard stream error"
+ );
+});
+
+// The hard errors are closing the session.
+add_task(
+ async function origin_server_stream_hard_failure_multiple_streams_affected() {
+ var current_num_sessions_to_origin_server =
+ await proxy_session_to_origin_server_counter();
+ let should_fail = get_response(
+ make_channel(`https://foo.example.com/750msNoData`),
+ CL_EXPECT_FAILURE
+ );
+ const failed1 = await get_response(
+ make_channel(`https://foo.example.com/illegalhpackhard`),
+ CL_EXPECT_FAILURE,
+ 10
+ );
+
+ const failed2 = await should_fail;
+
+ Assert.equal(failed1.status, 0x804b0053);
+ Assert.equal(failed1.proxy_connect_response_code, 200);
+ Assert.equal(failed1.http_code, undefined);
+ Assert.equal(failed2.status, 0x804b0053);
+ Assert.equal(failed2.proxy_connect_response_code, 200);
+ Assert.equal(failed2.http_code, undefined);
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy"
+ );
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server,
+ "No session to the origin server yet."
+ );
+ // Check the a new session ill be opened.
+ const foo = await get_response(
+ make_channel(`https://foo.example.com/random-request-1`)
+ );
+
+ Assert.equal(foo.status, Cr.NS_OK);
+ Assert.equal(foo.proxy_connect_response_code, 200);
+ Assert.equal(foo.http_code, 200);
+ Assert.ok(foo.data.match("random-request-1"));
+ Assert.ok(foo.data.match("You Win!"));
+
+ Assert.equal(
+ await proxy_session_counter(),
+ 2,
+ "No new session to the proxy is created after a hard stream failure on the session to the origin server."
+ );
+
+ Assert.equal(
+ await proxy_session_to_origin_server_counter(),
+ current_num_sessions_to_origin_server + 1,
+ "A new session to the origin server after a hard stream error"
+ );
+ }
+);