tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 73e5637f996a553258ee097bad97a2cc3498cc4b
parent 13b332cedd5cb51ced0cf85c03681da7dc35ede3
Author: Kershaw Chang <kershaw@mozilla.com>
Date:   Fri, 17 Oct 2025 21:42:21 +0000

Bug 1994648 - Run current HTTP/2 tests with HTTP/3 proxy, r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D268838

Diffstat:
Mnetwerk/protocol/http/Http2Stream.cpp | 6++++++
Mnetwerk/protocol/http/Http3Session.cpp | 6++----
Mnetwerk/protocol/http/Http3Session.h | 6++++++
Mnetwerk/test/httpserver/NodeServer.sys.mjs | 12++++++++----
Mnetwerk/test/unit/http2_test_common.js | 144+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Anetwerk/test/unit/test_http2_with_http3_proxy.js | 344+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnetwerk/test/unit/test_servers.js | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnetwerk/test/unit/xpcshell.toml | 10++++++++++
Mtesting/xpcshell/moz-http2/moz-http2-child.js | 13+++++++------
Mtesting/xpcshell/moz-http2/moz-http2.js | 40+++++++++++++++++++++++++---------------
10 files changed, 555 insertions(+), 84 deletions(-)

diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp @@ -33,6 +33,12 @@ Http2Stream::Http2Stream(nsAHttpTransaction* httpTransaction, Http2Stream::~Http2Stream() {} void Http2Stream::CloseStream(nsresult reason) { + if (reason == NS_ERROR_NET_RESET) { + // If we got NS_ERROR_NET_RESET, the transaction will be retried. Keep the + // Alt-Svc in the connection info. Dropping it could trigger an + // unintended proxy connection fallback. + mTransaction->DoNotRemoveAltSvc(); + } mTransaction->Close(reason); mSession = nullptr; mClosed = true; diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp @@ -2163,9 +2163,7 @@ void Http3Session::CloseStreamInternal(Http3StreamBase* aStream, // Close(NS_OK) implies that the NeqoHttp3Conn will be closed, so we can only // do this when there is no Http3Steeam, WebTransportSession and // WebTransportStream. - if ((mShouldClose || mGoawayReceived) && - (!mStreamTransactionHash.Count() && mWebTransportSessions.IsEmpty() && - mWebTransportStreams.IsEmpty() && mTunnelStreams.IsEmpty())) { + if ((mShouldClose || mGoawayReceived) && HasNoActiveStreams()) { MOZ_ASSERT(!IsClosing()); Close(NS_OK); } @@ -2240,7 +2238,7 @@ void Http3Session::DontReuse() { } mShouldClose = true; - if (!mStreamTransactionHash.Count()) { + if (HasNoActiveStreams()) { // This is a temporary workaround and should be fixed properly in Happy // Eyeballs project. We should not exclude this domain if // Http3Session::DontReuse is called from diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h @@ -470,6 +470,12 @@ class Http3Session final : public Http3SessionBase, // are exchanged. void FinishNegotiation(ExtendedConnectKind aKind, bool aSuccess); + inline bool HasNoActiveStreams() const { + return mStreamTransactionHash.Count() == 0 && + mWebTransportSessions.IsEmpty() && mWebTransportStreams.IsEmpty() && + mTunnelStreams.IsEmpty(); + } + nsTArray<RefPtr<Http3StreamBase>> mWebTransportSessions; nsTArray<RefPtr<Http3StreamBase>> mWebTransportStreams; nsTArray<RefPtr<Http3StreamBase>> mTunnelStreams; diff --git a/netwerk/test/httpserver/NodeServer.sys.mjs b/netwerk/test/httpserver/NodeServer.sys.mjs @@ -1108,7 +1108,7 @@ export class HTTP3Server { /// Stops the server async stop() { if (this.processId) { - await NodeServer.kill(this.processId); + await NodeServer.kill(this.processId, true); this.processId = undefined; } } @@ -1171,13 +1171,13 @@ export class NodeServer { return this.sendCommand(command, `/execute/${id}`); } // Shuts down the server - static kill(id) { - return this.sendCommand("", `/kill/${id}`); + static kill(id, ignoreError = false) { + return this.sendCommand("", `/kill/${id}`, ignoreError); } // Issues a request to the node server (handler defined in moz-http2.js) // This method should not be called directly. - static sendCommand(command, path) { + static sendCommand(command, path, ignoreError = false) { let h2Port = Services.env.get("MOZNODE_EXEC_PORT"); if (!h2Port) { throw new Error("Could not find MOZNODE_EXEC_PORT"); @@ -1224,6 +1224,10 @@ export class NodeServer { if (x.error) { let e = new Error(x.error, "", 0); e.stack = x.errorStack; + if (ignoreError) { + resolve(undefined); + return; + } reject(e); return; } diff --git a/netwerk/test/unit/http2_test_common.js b/netwerk/test/unit/http2_test_common.js @@ -64,6 +64,14 @@ Http2CheckListener.prototype = { } Assert.ok(request instanceof Ci.nsIHttpChannel); + if (this.noResponseStatus) { + Assert.throws( + () => request.responseStatus, + /NS_ERROR_NOT_AVAILABLE/, + "getting response status should throw" + ); + return; + } Assert.equal(request.requestSucceeded, this.shouldSucceed); if (this.shouldSucceed) { Assert.equal(request.responseStatus, 200); @@ -314,8 +322,8 @@ async function test_http2_blocking_download(serverPort) { } // Make sure we make a HTTP2 connection and both us and the server mark it as such -async function test_http2_basic(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/`); +async function test_http2_basic(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`); var p = new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; @@ -324,9 +332,12 @@ async function test_http2_basic(serverPort) { return p; } -async function test_http2_basic_unblocked_dep(serverPort) { +async function test_http2_basic_unblocked_dep( + serverPort, + origin = "localhost" +) { var chan = makeHTTPChannel( - `https://localhost:${serverPort}/basic_unblocked_dep` + `https://${origin}:${serverPort}/basic_unblocked_dep` ); var cos = chan.QueryInterface(Ci.nsIClassOfService); cos.addClassFlags(Ci.nsIClassOfService.Unblocked); @@ -338,8 +349,8 @@ async function test_http2_basic_unblocked_dep(serverPort) { } // make sure we don't use h2 when disallowed -async function test_http2_nospdy(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/`); +async function test_http2_nospdy(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`); return new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; @@ -362,10 +373,10 @@ function checkXhr(xhr, finish) { } // Fires off an XHR request over h2 -async function test_http2_xhr(serverPort) { +async function test_http2_xhr(serverPort, origin = "localhost") { return new Promise(resolve => { var req = new XMLHttpRequest(); - req.open("GET", `https://localhost:${serverPort}/`, true); + req.open("GET", `https://${origin}:${serverPort}/`, true); req.addEventListener("readystatechange", function () { checkXhr(req, resolve); }); @@ -401,7 +412,11 @@ Http2ConcurrentListener.prototype.onStopRequest = function (request) { } }; -async function test_http2_concurrent(concurrent_channels, serverPort) { +async function test_http2_concurrent( + concurrent_channels, + serverPort, + origin = "localhost" +) { var p = new Promise(resolve => { var concurrent_listener = new Http2ConcurrentListener(); concurrent_listener.finish = resolve; @@ -413,7 +428,7 @@ async function test_http2_concurrent(concurrent_channels, serverPort) { for (var i = 0; i < concurrent_listener.target; i++) { concurrent_channels[i] = makeHTTPChannel( - `https://localhost:${serverPort}/750ms` + `https://${origin}:${serverPort}/750ms` ); concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE; concurrent_channels[i].asyncOpen(concurrent_listener); @@ -422,7 +437,11 @@ async function test_http2_concurrent(concurrent_channels, serverPort) { return p; } -async function test_http2_concurrent_post(concurrent_channels, serverPort) { +async function test_http2_concurrent_post( + concurrent_channels, + serverPort, + origin = "localhost" +) { return new Promise(resolve => { var concurrent_listener = new Http2ConcurrentListener(); concurrent_listener.finish = resolve; @@ -435,7 +454,7 @@ async function test_http2_concurrent_post(concurrent_channels, serverPort) { for (var i = 0; i < concurrent_listener.target; i++) { concurrent_channels[i] = makeHTTPChannel( - `https://localhost:${serverPort}/750msPost` + `https://${origin}:${serverPort}/750msPost` ); concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE; var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( @@ -473,8 +492,8 @@ async function test_http2_multiplex(serverPort) { } // Test to make sure we gateway non-standard headers properly -async function test_http2_header(serverPort) { - let chan = makeHTTPChannel(`https://localhost:${serverPort}/header`); +async function test_http2_header(serverPort, origin = "localhost") { + let chan = makeHTTPChannel(`https://${origin}:${serverPort}/header`); let hvalue = "Headers are fun"; chan.setRequestHeader("X-Test-Header", hvalue, false); return new Promise(resolve => { @@ -489,22 +508,26 @@ async function test_http2_header(serverPort) { } // Test to make sure headers with invalid characters in the name are rejected -async function test_http2_invalid_response_header(serverPort, invalid_kind) { +async function test_http2_invalid_response_header( + serverPort, + invalid_kind, + origin = "localhost" +) { return new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; listener.shouldSucceed = false; var chan = makeHTTPChannel( - `https://localhost:${serverPort}/invalid_response_header/${invalid_kind}` + `https://${origin}:${serverPort}/invalid_response_header/${invalid_kind}` ); chan.asyncOpen(listener); }); } // Test to make sure cookies are split into separate fields before compression -async function test_http2_cookie_crumbling(serverPort) { +async function test_http2_cookie_crumbling(serverPort, origin = "localhost") { var chan = makeHTTPChannel( - `https://localhost:${serverPort}/cookie_crumbling` + `https://${origin}:${serverPort}/cookie_crumbling` ); var cookiesSent = ["a=b", "c=d01234567890123456789", "e=f"].sort(); chan.setRequestHeader("Cookie", cookiesSent.join("; "), false); @@ -532,8 +555,8 @@ async function test_http2_cookie_crumbling(serverPort) { // this is a basic test where the server sends a simple document with 2 header // blocks. bug 1027364 -async function test_http2_doubleheader(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/doubleheader`); +async function test_http2_doubleheader(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/doubleheader`); return new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; @@ -542,8 +565,8 @@ async function test_http2_doubleheader(serverPort) { } // Make sure we handle GETs that cover more than 2 frames properly -async function test_http2_big(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/big`); +async function test_http2_big(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/big`); return new Promise(resolve => { var listener = new Http2BigListener(); listener.finish = resolve; @@ -551,8 +574,8 @@ async function test_http2_big(serverPort) { }); } -async function test_http2_huge_suspended(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/huge`); +async function test_http2_huge_suspended(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/huge`); return new Promise(resolve => { var listener = new Http2HugeSuspendedListener(); listener.finish = resolve; @@ -578,8 +601,8 @@ function do_post(content, chan, listener, method) { } // Make sure we can do a simple POST -async function test_http2_post(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`); +async function test_http2_post(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`); var p = new Promise(resolve => { var listener = new Http2PostListener(md5s[0]); listener.finish = resolve; @@ -588,8 +611,8 @@ async function test_http2_post(serverPort) { return p; } -async function test_http2_empty_post(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`); +async function test_http2_empty_post(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`); var p = new Promise(resolve => { var listener = new Http2PostListener("0"); listener.finish = resolve; @@ -599,8 +622,8 @@ async function test_http2_empty_post(serverPort) { } // Make sure we can do a simple PATCH -async function test_http2_patch(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/patch`); +async function test_http2_patch(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/patch`); return new Promise(resolve => { var listener = new Http2PostListener(md5s[0]); listener.finish = resolve; @@ -609,8 +632,8 @@ async function test_http2_patch(serverPort) { } // Make sure we can do a POST that covers more than 2 frames -async function test_http2_post_big(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/post`); +async function test_http2_post_big(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`); return new Promise(resolve => { var listener = new Http2PostListener(md5s[1]); listener.finish = resolve; @@ -764,13 +787,13 @@ WrongSuiteListener.prototype.onStopRequest = function (request, status) { // test that we use h1 without the mandatory cipher suite available when // offering at most tls1.2 -async function test_http2_wrongsuite_tls12(serverPort) { +async function test_http2_wrongsuite_tls12(serverPort, origin = "localhost") { Services.prefs.setBoolPref( "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", false ); Services.prefs.setIntPref("security.tls.version.max", 3); - var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`); + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`); chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION | Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; @@ -783,12 +806,12 @@ async function test_http2_wrongsuite_tls12(serverPort) { // test that we use h2 when offering tls1.3 or higher regardless of if the // mandatory cipher suite is available -async function test_http2_wrongsuite_tls13(serverPort) { +async function test_http2_wrongsuite_tls13(serverPort, origin = "localhost") { Services.prefs.setBoolPref( "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", false ); - var chan = makeHTTPChannel(`https://localhost:${serverPort}/wrongsuite`); + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`); chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION | Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; @@ -800,9 +823,9 @@ async function test_http2_wrongsuite_tls13(serverPort) { }); } -async function test_http2_h11required_stream(serverPort) { +async function test_http2_h11required_stream(serverPort, origin = "localhost") { var chan = makeHTTPChannel( - `https://localhost:${serverPort}/h11required_stream` + `https://${origin}:${serverPort}/h11required_stream` ); return new Promise(resolve => { var listener = new Http2CheckListener(); @@ -829,9 +852,12 @@ H11RequiredSessionListener.prototype.onStopRequest = function (request) { this.finish({ httpProxyConnectResponseCode }); }; -async function test_http2_h11required_session(serverPort) { +async function test_http2_h11required_session( + serverPort, + origin = "localhost" +) { var chan = makeHTTPChannel( - `https://localhost:${serverPort}/h11required_session` + `https://${origin}:${serverPort}/h11required_session` ); return new Promise(resolve => { var listener = new H11RequiredSessionListener(); @@ -841,8 +867,8 @@ async function test_http2_h11required_session(serverPort) { }); } -async function test_http2_retry_rst(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/rstonce`); +async function test_http2_retry_rst(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/rstonce`); return new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; @@ -852,16 +878,18 @@ async function test_http2_retry_rst(serverPort) { async function test_http2_continuations_over_max_response_limit( loadGroup, - serverPort + serverPort, + origin = "localhost" ) { var chan = makeHTTPChannel( - `https://localhost:${serverPort}/hugecontinuedheaders?size=385` + `https://${origin}:${serverPort}/hugecontinuedheaders?size=385` ); chan.loadGroup = loadGroup; return new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; listener.shouldSucceed = false; + listener.noResponseStatus = true; chan.asyncOpen(listener); }); } @@ -892,7 +920,7 @@ Http2IllegalHpackListener.prototype.shouldGoAway = false; Http2IllegalHpackListener.prototype.onStopRequest = function () { var chan = makeHTTPChannel( - `https://localhost:${this.serverPort}/illegalhpack_validate` + `https://${this.origin}:${this.serverPort}/illegalhpack_validate` ); var listener = new Http2IllegalHpackValidationListener(); listener.finish = this.finish; @@ -900,36 +928,42 @@ Http2IllegalHpackListener.prototype.onStopRequest = function () { chan.asyncOpen(listener); }; -async function test_http2_illegalhpacksoft(serverPort) { +async function test_http2_illegalhpacksoft(serverPort, origin = "localhost") { var chan = makeHTTPChannel( - `https://localhost:${serverPort}/illegalhpacksoft` + `https://${origin}:${serverPort}/illegalhpacksoft` ); return new Promise(resolve => { var listener = new Http2IllegalHpackListener(); listener.finish = resolve; listener.serverPort = serverPort; + listener.origin = origin; listener.shouldGoAway = false; listener.shouldSucceed = false; chan.asyncOpen(listener); }); } -async function test_http2_illegalhpackhard(serverPort) { +async function test_http2_illegalhpackhard(serverPort, origin = "localhost") { var chan = makeHTTPChannel( - `https://localhost:${serverPort}/illegalhpackhard` + `https://${origin}:${serverPort}/illegalhpackhard` ); return new Promise(resolve => { var listener = new Http2IllegalHpackListener(); listener.finish = resolve; listener.serverPort = serverPort; + listener.origin = origin; listener.shouldGoAway = true; listener.shouldSucceed = false; chan.asyncOpen(listener); }); } -async function test_http2_folded_header(loadGroup, serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/foldedheader`); +async function test_http2_folded_header( + loadGroup, + serverPort, + origin = "localhost" +) { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/foldedheader`); chan.loadGroup = loadGroup; return new Promise(resolve => { var listener = new Http2CheckListener(); @@ -939,8 +973,8 @@ async function test_http2_folded_header(loadGroup, serverPort) { }); } -async function test_http2_empty_data(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/emptydata`); +async function test_http2_empty_data(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/emptydata`); return new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; @@ -948,8 +982,8 @@ async function test_http2_empty_data(serverPort) { }); } -async function test_http2_status_phrase(serverPort) { - var chan = makeHTTPChannel(`https://localhost:${serverPort}/statusphrase`); +async function test_http2_status_phrase(serverPort, origin = "localhost") { + var chan = makeHTTPChannel(`https://${origin}:${serverPort}/statusphrase`); return new Promise(resolve => { var listener = new Http2CheckListener(); listener.finish = resolve; diff --git a/netwerk/test/unit/test_http2_with_http3_proxy.js b/netwerk/test/unit/test_http2_with_http3_proxy.js @@ -0,0 +1,344 @@ +// test HTTP/2 with a HTTP/3 prooxy + +"use strict"; + +/* import-globals-from http2_test_common.js */ + +const { Http3ProxyFilter, NodeHTTP2ProxyServer } = ChromeUtils.importESModule( + "resource://testing-common/NodeServer.sys.mjs" +); + +let concurrent_channels = []; +let loadGroup; +let serverPort; +let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(); +let proxyHost; +let proxyPort; +let proxyFilter; + +add_setup(async function setup() { + serverPort = Services.env.get("MOZHTTP2_PORT"); + Assert.notEqual(serverPort, null); + dump("using port " + serverPort + "\n"); + + // Set to allow the cert presented by our H2 server + do_get_profile(); + + 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"); + + Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); + Services.prefs.setBoolPref("network.http.http3.enable", true); + Services.prefs.setBoolPref("network.http.http2.enabled", true); + Services.prefs.setBoolPref("network.http.altsvc.enabled", true); + Services.prefs.setBoolPref("network.http.altsvc.oe", true); + Services.prefs.setCharPref( + "network.dns.localDomains", + "foo.example.com, alt1.example.com, bar.example.com" + ); + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + + loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance( + Ci.nsILoadGroup + ); + + Services.prefs.setStringPref( + "services.settings.server", + `data:,#remote-settings-dummy/v1` + ); + + proxyHost = "alt1.example.com"; + proxyPort = (await create_masque_proxy_server()).masqueProxyPort; + Assert.notEqual(proxyPort, null); + Assert.notEqual(proxyPort, ""); + proxyFilter = new Http3ProxyFilter( + proxyHost, + proxyPort, + 0, + "/.well-known/masque/udp/{target_host}/{target_port}/", + "" + ); + pps.registerFilter(proxyFilter, 10); + + let h2Proxy = new NodeHTTP2ProxyServer(); + await h2Proxy.startWithoutProxyFilter(proxyPort); + Assert.equal(proxyPort, h2Proxy.port()); + + registerCleanupFunction(async () => { + Services.prefs.clearUserPref("network.http.http3.enable"); + Services.prefs.clearUserPref("network.dns.localDomains"); + Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost"); + Services.prefs.clearUserPref("network.http.altsvc.oe"); + Services.prefs.clearUserPref( + "network.http.http3.alt-svc-mapping-for-testing" + ); + await h2Proxy.stop(); + pps.unregisterFilter(proxyFilter); + }); +}); + +// hack - the header test resets the multiplex object on the server, +// so make sure header is always run before the multiplex test. +// +// make sure post_big runs first to test race condition in restarting +// a stalled stream when a SETTINGS frame arrives +add_task(async function do_test_http2_post_big() { + const { httpProxyConnectResponseCode } = await test_http2_post_big( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_basic() { + const { httpProxyConnectResponseCode } = await test_http2_basic( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_concurrent() { + const { httpProxyConnectResponseCode } = await test_http2_concurrent( + concurrent_channels, + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_concurrent_post() { + const { httpProxyConnectResponseCode } = await test_http2_concurrent_post( + concurrent_channels, + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_basic_unblocked_dep() { + const { httpProxyConnectResponseCode } = await test_http2_basic_unblocked_dep( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_doubleheader() { + const { httpProxyConnectResponseCode } = await test_http2_doubleheader( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_xhr() { + await test_http2_xhr(serverPort, "foo.example.com"); +}); + +add_task(async function do_test_http2_header() { + const { httpProxyConnectResponseCode } = await test_http2_header( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_invalid_response_header_name_spaces() { + const { httpProxyConnectResponseCode } = + await test_http2_invalid_response_header( + serverPort, + "name_spaces", + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task( + async function do_test_http2_invalid_response_header_value_line_feed() { + const { httpProxyConnectResponseCode } = + await test_http2_invalid_response_header( + serverPort, + "value_line_feed", + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); + } +); + +add_task( + async function do_test_http2_invalid_response_header_value_carriage_return() { + const { httpProxyConnectResponseCode } = + await test_http2_invalid_response_header( + serverPort, + "value_carriage_return", + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); + } +); + +add_task(async function do_test_http2_invalid_response_header_value_null() { + const { httpProxyConnectResponseCode } = + await test_http2_invalid_response_header( + serverPort, + "value_null", + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_cookie_crumbling() { + const { httpProxyConnectResponseCode } = await test_http2_cookie_crumbling( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_multiplex() { + var values = await test_http2_multiplex(serverPort, "foo.example.com"); + Assert.equal(values[0].httpProxyConnectResponseCode, 200); + Assert.equal(values[1].httpProxyConnectResponseCode, 200); + Assert.notEqual(values[0].streamID, values[1].streamID); +}); + +add_task(async function do_test_http2_big() { + const { httpProxyConnectResponseCode } = await test_http2_big( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_huge_suspended() { + const { httpProxyConnectResponseCode } = await test_http2_huge_suspended( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_post() { + const { httpProxyConnectResponseCode } = await test_http2_post( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_empty_post() { + const { httpProxyConnectResponseCode } = await test_http2_empty_post( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_patch() { + const { httpProxyConnectResponseCode } = await test_http2_patch( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_blocking_download() { + const { httpProxyConnectResponseCode } = await test_http2_blocking_download( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_illegalhpacksoft() { + const { httpProxyConnectResponseCode } = await test_http2_illegalhpacksoft( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_illegalhpackhard() { + const { httpProxyConnectResponseCode } = await test_http2_illegalhpackhard( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_folded_header() { + const { httpProxyConnectResponseCode } = await test_http2_folded_header( + loadGroup, + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_empty_data() { + const { httpProxyConnectResponseCode } = await test_http2_empty_data( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_status_phrase() { + const { httpProxyConnectResponseCode } = await test_http2_status_phrase( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_h11required_stream() { + // Add new tests above here - best to add new tests before h1 + // streams get too involved + // These next two must always come in this order + const { httpProxyConnectResponseCode } = await test_http2_h11required_stream( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_h11required_session() { + const { httpProxyConnectResponseCode } = await test_http2_h11required_session( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_retry_rst() { + const { httpProxyConnectResponseCode } = await test_http2_retry_rst( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_wrongsuite_tls13() { + const { httpProxyConnectResponseCode } = await test_http2_wrongsuite_tls13( + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); + +add_task(async function do_test_http2_continuations_over_max_response_limit() { + const { httpProxyConnectResponseCode } = + await test_http2_continuations_over_max_response_limit( + loadGroup, + serverPort, + "foo.example.com" + ); + Assert.equal(httpProxyConnectResponseCode, 200); +}); diff --git a/netwerk/test/unit/test_servers.js b/netwerk/test/unit/test_servers.js @@ -305,3 +305,61 @@ add_task(async function test_proxy_with_redirects() { await proxy.stop(); } }); + +add_task(async function test_async_event() { + let server = new NodeHTTP2Server(); + await server.start(); + registerCleanupFunction(async () => { + await server.stop(); + }); + + await server.execute(`new Promise(r => setTimeout(r, 500))`); + + await server.stop(); +}); + +add_task(async function test_async_state_management() { + let server = new NodeHTTP2Server(); + await server.start(); + registerCleanupFunction(async () => { + await server.stop(); + }); + + await server.execute(`global.asyncResults = [];`); + + await server.execute(` + global.asyncCounter = 0; + global.performAsyncOperation = function(delay, value) { + return new Promise(resolve => { + setTimeout(() => { + global.asyncCounter++; + global.asyncResults.push({ counter: global.asyncCounter, value }); + resolve({ counter: global.asyncCounter, value }); + }, delay); + }); + }; + `); + + let op1 = server.execute(`performAsyncOperation(100, "first")`); + let op2 = server.execute(`performAsyncOperation(50, "second")`); + + let result1 = await op1; + let result2 = await op2; + // This ran after 100 ms, so it comes in second + equal(result1.counter, 2); + equal(result1.value, "first"); + + // this rand after 50 ms, so it comes in first. + equal(result2.counter, 1); + equal(result2.value, "second"); + + let results = await server.execute(`global.asyncResults`); + equal(results.length, 2); + equal(results[0].value, "second"); + equal(results[1].value, "first"); + + let counter = await server.execute(`global.asyncCounter`); + equal(counter, 2); + + await server.stop(); +}); diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml @@ -709,6 +709,16 @@ skip-if = ["os == 'android'"] run-sequentially = ["true"] # node server exceptions dont replay well head = "head_channels.js head_cache.js head_cookies.js head_trr.js head_http3.js http2_test_common.js" +["test_http2_with_http3_proxy.js"] +run-sequentially = ["true"] # node server exceptions dont replay well +head = "head_channels.js head_cache.js head_cookies.js head_trr.js head_http3.js http2_test_common.js" +skip-if = [ + "os == 'android' && os_version == '14' && processor == 'x86_64'", # Bug 1982955 + "os == 'win' && os_version == '10.2009' && processor == 'x86_64'", # Bug 1807931 + "os == 'win' && os_version == '11.26100' && processor == 'x86'", # Bug 1807931 + "os == 'win' && os_version == '11.26100' && processor == 'x86_64' && msix", # Bug 1807931 +] + ["test_http3.js"] head = "head_cookies.js head_channels.js head_cache.js head_http3.js http3_common.js" run-sequentially = ["true"] # http3server diff --git a/testing/xpcshell/moz-http2/moz-http2-child.js b/testing/xpcshell/moz-http2/moz-http2-child.js @@ -4,8 +4,8 @@ /* eslint-env node */ -function sendBackResponse(evalResult, e) { - const output = { result: evalResult, error: "", errorStack: "" }; +function sendBackResponse(messageId, evalResult, e) { + const output = { result: evalResult, error: "", errorStack: "", messageId }; if (e) { output.error = e.toString(); output.errorStack = e.stack; @@ -15,19 +15,20 @@ function sendBackResponse(evalResult, e) { process.on("message", msg => { const code = msg.code; + const messageId = msg.messageId; let evalResult = null; try { // eslint-disable-next-line no-eval evalResult = eval(code); if (evalResult instanceof Promise) { evalResult - .then(x => sendBackResponse(x)) - .catch(e => sendBackResponse(undefined, e)); + .then(x => sendBackResponse(messageId, x)) + .catch(e => sendBackResponse(messageId, undefined, e)); return; } } catch (e) { - sendBackResponse(undefined, e); + sendBackResponse(messageId, undefined, e); return; } - sendBackResponse(evalResult); + sendBackResponse(messageId, evalResult); }); diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js @@ -1486,10 +1486,10 @@ let httpServer = http.createServer((req, res) => { return; } + let messageId = makeid(6); new Promise((resolve, reject) => { - forked.resolve = resolve; - forked.reject = reject; - forked.send({ code }); + forked.messageHandlers[messageId] = { resolve, reject }; + forked.send({ code, messageId }); }) .then(x => sendBackResponse(x)) .catch(e => computeAndSendBackResponse(undefined, e)); @@ -1538,11 +1538,13 @@ function forkProcess() { function forkProcessInternal(forked) { let id = makeid(6); forked.errors = ""; + forked.messageHandlers = {}; globalObjects[id] = forked; forked.on("message", msg => { - if (forked.resolve) { - forked.resolve(msg); - forked.resolve = null; + if (msg.messageId && forked.messageHandlers[msg.messageId]) { + let handler = forked.messageHandlers[msg.messageId]; + delete forked.messageHandlers[msg.messageId]; + handler.resolve(msg); } else { console.log( `forked process without handler sent: ${JSON.stringify(msg)}` @@ -1561,22 +1563,30 @@ function forkProcessInternal(forked) { return; } - if (!forked.reject) { + let errorMsg = `child process exit closing code: ${code} signal: ${signal}`; + if (forked.errors != "") { + errorMsg = forked.errors; + forked.errors = ""; + } + + // Handle /kill/ case where forked.reject is set + if (forked.reject) { + forked.reject(new Error(errorMsg)); + forked.reject = null; + forked.resolve = null; + } + + if (Object.keys(forked.messageHandlers).length === 0) { console.log( `child process ${id} closing code: ${code} signal: ${signal}` ); return; } - if (forked.errors != "") { - forked.reject(forked.errors); - forked.errors = ""; - forked.reject = null; - return; + for (let messageId in forked.messageHandlers) { + forked.messageHandlers[messageId].reject(new Error(errorMsg)); } - - forked.reject(`child process exit closing code: ${code} signal: ${signal}`); - forked.reject = null; + forked.messageHandlers = {}; }; forked.on("error", exitFunction);