tor-browser

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

commit 49a4fde0b0645eed550c6ccc417143f1fa774237
parent a2e94e7190047c2adbec6c12dd72f41274ad82aa
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date:   Mon, 17 Nov 2025 08:35:45 +0000

Bug 1999128 - Make HTTP3 transactions that fail to activate fallback to HTTP2 right away r=necko-reviewers,kershaw

In this case the transactions fail to be dispatched because the
headers contain a non-ascii character and parse_headers in neqo_glue
uses str::from_utf8 to parse the header buffer.
When this happens, instead of returning NS_OK and waiting for the
H3 connection to potentially time out, we return NS_ERROR_HTTP2_FALLBACK_TO_HTTP1
to force the transaction to be dispatched to a TCP connection.

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

Diffstat:
Mnetwerk/protocol/http/Http3Session.cpp | 8+++++++-
Mnetwerk/socket/neqo_glue/src/lib.rs | 6++++--
Anetwerk/test/unit/test_http3_non_ascii_header.js | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnetwerk/test/unit/xpcshell.toml | 8++++++++
4 files changed, 106 insertions(+), 3 deletions(-)

diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp @@ -1503,8 +1503,14 @@ nsresult Http3Session::TryActivating( QueueStream(aStream); return rv; } + if (rv == NS_ERROR_DOM_INVALID_HEADER_VALUE) { + // neqo_http3conn_fetch may fail if the headers contain non-ascii + // values. In that case we want to fallback to HTTP/2 right away. + // HACK: This should be removed when we fix it in bug 1999659 + return NS_ERROR_HTTP2_FALLBACK_TO_HTTP1; + } + // Ignore this error. This may happen if some events are not handled yet. - // TODO we may try to add an assertion here. return NS_OK; } diff --git a/netwerk/socket/neqo_glue/src/lib.rs b/netwerk/socket/neqo_glue/src/lib.rs @@ -45,7 +45,7 @@ use nserror::{ NS_ERROR_FILE_ALREADY_EXISTS, NS_ERROR_ILLEGAL_VALUE, NS_ERROR_INVALID_ARG, NS_ERROR_NET_HTTP3_PROTOCOL_ERROR, NS_ERROR_NET_INTERRUPT, NS_ERROR_NET_RESET, NS_ERROR_NET_TIMEOUT, NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_CONNECTED, NS_ERROR_OUT_OF_MEMORY, - NS_ERROR_SOCKET_ADDRESS_IN_USE, NS_ERROR_UNEXPECTED, NS_OK, + NS_ERROR_SOCKET_ADDRESS_IN_USE, NS_ERROR_UNEXPECTED, NS_OK, NS_ERROR_DOM_INVALID_HEADER_VALUE, }; use nsstring::{nsACString, nsCString}; use thin_vec::ThinVec; @@ -1146,7 +1146,9 @@ fn parse_headers(headers: &nsACString) -> Result<Vec<Header>, nsresult> { // They need to be split into (String, String) pairs. match str::from_utf8(headers) { Err(_) => { - return Err(NS_ERROR_INVALID_ARG); + // Header names are checked for UTF8 in Necko + // Values aren't necessaryly UTF-8 - Should be fixed in bug 1999659 + return Err(NS_ERROR_DOM_INVALID_HEADER_VALUE); } Ok(h) => { for elem in h.split("\r\n").skip(1) { diff --git a/netwerk/test/unit/test_http3_non_ascii_header.js b/netwerk/test/unit/test_http3_non_ascii_header.js @@ -0,0 +1,87 @@ +/* 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"; + +/* import-globals-from head_http3.js */ + +function makeChan(uri) { + let chan = NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + }).QueryInterface(Ci.nsIHttpChannel); + chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; + return chan; +} + +function channelOpenPromise(chan, flags) { + return new Promise(resolve => { + function finish(req, buffer) { + resolve([req, buffer]); + } + chan.asyncOpen(new ChannelListener(finish, null, flags)); + }); +} + +let h2Port; +let h3Port; + +add_setup(async function setup() { + h2Port = Services.env.get("MOZHTTP2_PORT"); + Assert.notEqual(h2Port, null); + Assert.notEqual(h2Port, ""); + + h3Port = Services.env.get("MOZHTTP3_PORT"); + Assert.notEqual(h3Port, null); + Assert.notEqual(h3Port, ""); + + Services.prefs.setBoolPref("network.http.http3.enable", true); + Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com"); + Services.prefs.setBoolPref("network.dns.disableIPv6", true); + Services.prefs.setCharPref( + "network.http.http3.alt-svc-mapping-for-testing", + `foo.example.com;h3=:${h3Port}` + ); + + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); + + let chan = makeChan(`https://localhost`); + await channelOpenPromise(chan, CL_EXPECT_FAILURE); +}); + +// Test non-ASCII header with HTTP/3 and HTTP/2 fallback +// Http3Session::TryActivating fails to parse the non-utf8 headers +// and returns NS_ERROR_HTTP2_FALLBACK_TO_HTTP1 so we fallback to h2. +// When bug 1999659 is fixed we should instead succeed. +add_task(async function test_non_ascii_header() { + // First request with non-ASCII header + let chan1 = makeChan(`https://foo.example.com:${h2Port}/100`); + chan1.setRequestHeader("x-panel-title", "ä", false); + + let [req1, buf1] = await channelOpenPromise( + chan1, + CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL + ); + + let protocol1 = req1.protocolVersion; + Assert.ok(protocol1 === "h3" || protocol1 === "h2", `Using ${protocol1}`); + Assert.equal(req1.responseStatus, 200); + info(buf1); + + // Second request with different non-ASCII header + let chan2 = makeChan(`https://foo.example.com:${h2Port}/100`); + chan2.setRequestHeader("x-panel-title", "ö", false); + + let [req2] = await channelOpenPromise( + chan2, + CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL + ); + + let protocol2 = req2.protocolVersion; + Assert.ok(protocol2 === "h3" || protocol2 === "h2", `Using ${protocol2}`); + Assert.equal(req2.responseStatus, 200); +}); diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml @@ -850,6 +850,14 @@ skip-if = [ "os == 'win' && os_version == '11.26100' && processor == 'x86_64'", ] +["test_http3_non_ascii_header.js"] +head = "head_cookies.js head_channels.js head_cache.js head_http3.js" +run-sequentially = ["true"] # http3server +skip-if = [ + "os == 'win' && msix", + "os == 'android'", # Bug 1982955 +] + ["test_http3_perf.js"] run-sequentially = ["true"] # http3server skip-if = [