diff --git a/changes-entries/pr65881.txt b/changes-entries/pr65881.txt new file mode 100644 index 0000000000..816158a2f3 --- /dev/null +++ b/changes-entries/pr65881.txt @@ -0,0 +1,3 @@ + *) mod_http2: preserve the port number given in a HTTP/1.1 + request that was Upgraded to HTTP/2. Fixes PR65881. + [Stefan Eissing] \ No newline at end of file diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index e759c2e455..7899f282d4 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -90,12 +90,25 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, return APR_EINVAL; } - if (!ap_strchr_c(authority, ':') && r->server && r->server->port) { - apr_port_t defport = apr_uri_port_of_scheme(scheme); - if (defport != r->server->port) { - /* port info missing and port is not default for scheme: append */ - authority = apr_psprintf(pool, "%s:%d", authority, - (int)r->server->port); + /* The authority we carry in h2_request is the 'authority' part of + * the URL for the request. r->hostname has stripped any port info that + * might have been present. Do we need to add it? + */ + if (!ap_strchr_c(authority, ':')) { + if (r->parsed_uri.port_str) { + /* Yes, it was there, add it again. */ + authority = apr_psprintf(pool, "%s:%s", authority, r->parsed_uri.port_str); + } + else if (!r->parsed_uri.hostname && r->server && r->server->port) { + /* If there was no hostname in the parsed URL, the URL was relative. + * In that case, we restore port from our server->port, if it + * is known and not the default port for the scheme. */ + apr_port_t defport = apr_uri_port_of_scheme(scheme); + if (defport != r->server->port) { + /* port info missing and port is not default for scheme: append */ + authority = apr_psprintf(pool, "%s:%d", authority, + (int)r->server->port); + } } } diff --git a/test/modules/http2/htdocs/cgi/hello.py b/test/modules/http2/htdocs/cgi/hello.py index 191acb2fed..9fb2eb689d 100644 --- a/test/modules/http2/htdocs/cgi/hello.py +++ b/test/modules/http2/htdocs/cgi/hello.py @@ -7,6 +7,7 @@ print() print("{") print(" \"https\" : \"%s\"," % (os.getenv('HTTPS', ''))) print(" \"host\" : \"%s\"," % (os.getenv('SERVER_NAME', ''))) +print(" \"port\" : \"%s\"," % (os.getenv('SERVER_PORT', ''))) print(" \"protocol\" : \"%s\"," % (os.getenv('SERVER_PROTOCOL', ''))) print(" \"ssl_protocol\" : \"%s\"," % (os.getenv('SSL_PROTOCOL', ''))) print(" \"h2\" : \"%s\"," % (os.getenv('HTTP2', ''))) diff --git a/test/modules/http2/test_502_proxy_port.py b/test/modules/http2/test_502_proxy_port.py new file mode 100644 index 0000000000..472f625ad5 --- /dev/null +++ b/test/modules/http2/test_502_proxy_port.py @@ -0,0 +1,40 @@ +import pytest + +from .env import H2Conf + + +class TestProxyPort: + + @pytest.fixture(autouse=True, scope='class') + def _class_scope(self, env): + conf = H2Conf(env, extras={ + 'base': [ + f'Listen {env.proxy_port}', + 'Protocols h2c http/1.1', + 'LogLevel proxy_http2:trace2 proxy:trace2', + ], + f'cgi.{env.http_tld}': [ + "Header unset Server", + "Header always set Server cgi", + ] + }) + conf.add_vhost_cgi(proxy_self=False, h2proxy_self=False) + conf.start_vhost(domains=[f"test1.{env.http_tld}"], port=env.proxy_port) + conf.add([ + 'Protocols h2c', + 'RewriteEngine On', + 'RewriteRule "^/(.*)$" "h2c://%{HTTP_HOST}/$1"[NC,P]', + 'ProxyPassMatch / "h2c://$1/"', + ]) + conf.end_vhost() + conf.install() + assert env.apache_restart() == 0 + + # Test PR 65881 + # h2c upgraded request via a dynamic proxy onto another port + def test_h2_502_01(self, env): + url = f'http://localhost:{env.http_port}/hello.py' + r = env.curl_get(url, 5, options=['--http2', + '--proxy', f'localhost:{env.proxy_port}']) + assert r.response['status'] == 200 + assert r.json['port'] == f'{env.http_port}'