#define H2_USE_PIPES (APR_FILES_AS_SOCKETS && APR_VERSION_AT_LEAST(1,6,0))
#endif
+#if AP_MODULE_MAGIC_AT_LEAST(20211221, 15)
+#define H2_USE_POLLFD_FROM_CONN 1
+#else
+#define H2_USE_POLLFD_FROM_CONN 0
+#endif
+
+#if H2_USE_POLLFD_FROM_CONN && H2_USE_PIPES
+#define H2_USE_WEBSOCKETS 1
+#else
+#define H2_USE_WEBSOCKETS 0
+#endif
+
/**
* The magic PRIamble of RFC 7540 that is always sent when starting
* a h2 communication.
return OK;
}
+#if H2_USE_POLLFD_FROM_CONN
static apr_status_t c2_get_pollfd_from_conn(conn_rec *c,
struct apr_pollfd_t *pfd,
apr_interval_time_t *ptimeout)
}
return APR_ENOTIMPL;
}
+#endif
void h2_c2_register_hooks(void)
{
ap_hook_post_read_request(c2_post_read_request, NULL, NULL,
APR_HOOK_REALLY_FIRST);
ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
-#if AP_MODULE_MAGIC_AT_LEAST(20211221, 15)
+#if H2_USE_POLLFD_FROM_CONN
ap_hook_get_pollfd_from_conn(c2_get_pollfd_from_conn, NULL, NULL,
APR_HOOK_MIDDLE);
#endif
-
c2_net_in_filter_handle =
ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
NULL, AP_FTYPE_NETWORK);
cs->state = CONN_STATE_WRITE_COMPLETION;
cleanup:
- return APR_SUCCESS;
+ return rv;
}
conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
return APR_EGENERAL;
}
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_request_in(%s): adding request bucket",
+ conn_ctx->id);
+ b = h2_request_create_bucket(req, f->r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
if (req->http_status != H2_HTTP_STATUS_UNSET) {
/* error was encountered preparing this request */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_request_in(%s): adding error bucket %d",
+ conn_ctx->id, req->http_status);
b = ap_bucket_error_create(req->http_status, NULL, f->r->pool,
f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
return APR_SUCCESS;
}
- b = h2_request_create_bucket(req, f->r);
- APR_BRIGADE_INSERT_TAIL(bb, b);
if (!conn_ctx->beam_in) {
b = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
}
+
return APR_SUCCESS;
}
void *dirconf, const char *value)
{
if (!strcasecmp(value, "On")) {
-#if H2_USE_PIPES
+#if H2_USE_WEBSOCKETS
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 1);
return NULL;
-#else
+#elif !H2_USE_PIPES
return "HTTP/2 WebSockets are not supported on this platform";
+#else
+ return "HTTP/2 WebSockets are not supported in this server version";
#endif
}
else if (!strcasecmp(value, "Off")) {
apr_table_t *headers = apr_table_clone(r->pool, req->headers);
const char *uri = req->path;
+ AP_DEBUG_ASSERT(req->method);
AP_DEBUG_ASSERT(req->authority);
- if (req->scheme && (ap_cstr_casecmp(req->scheme,
- ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
- || !ap_cstr_casecmp("CONNECT", req->method))) {
- /* Client sent a non-matching ':scheme' pseudo header or CONNECT.
- * In this case, we use an absolute URI.
- */
+ if (!ap_cstr_casecmp("CONNECT", req->method)) {
+ uri = req->authority;
+ }
+ else if (req->scheme && (ap_cstr_casecmp(req->scheme, "http") &&
+ ap_cstr_casecmp(req->scheme, "https"))) {
+ /* Client sent a non-http ':scheme', use an absolute URI */
uri = apr_psprintf(r->pool, "%s://%s%s",
req->scheme, req->authority, req->path ? req->path : "");
}
AP_DEBUG_ASSERT(req->authority);
if (is_connect) {
/* CONNECT MUST NOT have scheme or path */
- if (req->scheme) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
- "':scheme: %s' header present in CONNECT request",
- req->scheme);
- access_status = HTTP_BAD_REQUEST;
- goto die;
- }
- if (req->path) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
- "':path: %s' header present in CONNECT request",
- req->path);
- access_status = HTTP_BAD_REQUEST;
- goto die;
- }
- r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
- req->method, req->authority);
- }
- else if (req->protocol) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10460)
- "':protocol: %s' header present in %s request",
- req->protocol, req->method);
- access_status = HTTP_BAD_REQUEST;
- goto die;
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ req->method, req->authority);
+ if (req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
+ "':scheme: %s' header present in CONNECT request",
+ req->scheme);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ else if (req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
+ "':path: %s' header present in CONNECT request",
+ req->path);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
}
- else if (req->scheme &&
- ap_cstr_casecmp(req->scheme, ap_ssl_conn_is_ssl(c->master? c->master : c)?
- "https" : "http")) {
+ else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+ && ap_cstr_casecmp(req->scheme, "https")) {
/* Client sent a ':scheme' pseudo header for something else
* than what we have on this connection. Make an absolute URI. */
r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
* of CONNECT requests (see [RFC7230], Section 5.3)).
*/
if (!ap_cstr_casecmp(req->method, "CONNECT")) {
- if (req->protocol && !strcmp("websocket", req->protocol)) {
- if (!req->scheme || !req->path) {
- ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
- H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
- "without :scheme or :path, sending 400 answer"));
+ if (req->protocol) {
+ if (!strcmp("websocket", req->protocol)) {
+ if (!req->scheme || !req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
+ "without :scheme or :path, sending 400 answer"));
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+ }
+ else {
+ /* do not know that protocol */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1, APLOGNO(10460)
+ "':protocol: %s' header present in %s request",
+ req->protocol, req->method);
+ set_error_response(stream, HTTP_NOT_IMPLEMENTED);
+ goto cleanup;
}
}
else if (req->scheme || req->path) {
#include "h2_request.h"
#include "h2_ws.h"
+#if H2_USE_WEBSOCKETS
+
static ap_filter_rec_t *c2_ws_out_filter_handle;
struct ws_filter_ctx {
return ap_pass_brigade(f->next, bb);
}
+static int ws_post_read(request_rec *r)
+{
+
+ if (r->connection->master) {
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
+ if (conn_ctx && conn_ctx->is_upgrade &&
+ !h2_config_sgeti(r->server, H2_CONF_WEBSOCKETS)) {
+ return HTTP_NOT_IMPLEMENTED;
+ }
+ }
+ return DECLINED;
+}
+
void h2_ws_register_hooks(void)
{
+ ap_hook_post_read_request(ws_post_read, NULL, NULL, APR_HOOK_MIDDLE);
c2_ws_out_filter_handle =
ap_register_output_filter("H2_C2_WS_OUT", h2_c2_ws_filter_out,
NULL, AP_FTYPE_NETWORK);
}
+
+#else /* H2_USE_WEBSOCKETS */
+
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+ conn_rec *c2, int no_body)
+{
+ (void)c2;
+ (void)no_body;
+ /* no rewriting */
+ return req;
+}
+
+void h2_ws_register_hooks(void)
+{
+ /* NOP */
+}
+
+#endif /* H2_USE_WEBSOCKETS (else part) */
import subprocess
import time
from datetime import timedelta, datetime
-from typing import Tuple, Union, List
-import packaging.version
import pytest
-import websockets
from pyhttpd.result import ExecResult
from pyhttpd.ws_util import WsFrameReader, WsFrame
log = logging.getLogger(__name__)
-ws_version = packaging.version.parse(websockets.version.version)
-ws_version_min = packaging.version.Version('10.4')
-
-def ws_run(env: H2TestEnv, path, do_input=None,
- inbytes=None, send_close=True,
- timeout=5, scenario='ws-stdin',
- wait_close: float = 0.0) -> Tuple[ExecResult, List[str], Union[List[WsFrame], bytes]]:
+def ws_run(env: H2TestEnv, path, authority=None, do_input=None, inbytes=None,
+ send_close=True, timeout=5, scenario='ws-stdin',
+ wait_close: float = 0.0):
""" Run the h2ws test client in various scenarios with given input and
timings.
:param env: the test environment
:param path: the path on the Apache server to CONNECt to
+ :param authority: the host:port to use as
:param do_input: a Callable for sending input to h2ws
:param inbytes: fixed bytes to send to h2ws, unless do_input is given
:param send_close: send a CLOSE WebSockets frame at the end
h2ws = os.path.join(env.clients_dir, 'h2ws')
if not os.path.exists(h2ws):
pytest.fail(f'test client not build: {h2ws}')
+ if authority is None:
+ authority = f'cgi.{env.http_tld}:{env.http_port}'
args = [
h2ws, '-vv', '-c', f'localhost:{env.http_port}',
- f'ws://cgi.{env.http_tld}:{env.http_port}{path}',
+ f'ws://{authority}{path}',
scenario
]
# we write all output to files, because we manipulate input timings
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
-@pytest.mark.skipif(condition=ws_version < ws_version_min,
- reason=f'websockets is {ws_version}, need at least {ws_version_min}')
+@pytest.mark.skipif(condition=not H2TestEnv().httpd_is_at_least("2.5.0"),
+ reason=f'need at least httpd 2.5.0 for this')
class TestWebSockets:
@pytest.fixture(autouse=True, scope='class')
]
})
conf.add_vhost_cgi(proxy_self=True, h2proxy_self=True).install()
+ conf.add_vhost_test1(proxy_self=True, h2proxy_self=True).install()
assert env.apache_restart() == 0
def ws_check_alive(self, env, timeout=5):
def test_h2_800_02_fail_proto(self, env: H2TestEnv, ws_server):
r, infos, frames = ws_run(env, path='/ws/echo/', scenario='fail-proto')
assert r.exit_code == 0, f'{r}'
- assert infos == ['[1] :status: 400', '[1] EOF'], f'{r}'
+ assert infos == ['[1] :status: 501', '[1] EOF'], f'{r}'
# CONNECT to a URL path that does not exist on the server
def test_h2_800_03_not_found(self, env: H2TestEnv, ws_server):
assert infos == ['[1] RST'], f'{r}'
# CONNECT missing the :authority header
- def test_h2_800_09_miss_authority(self, env: H2TestEnv, ws_server):
+ def test_h2_800_09a_miss_authority(self, env: H2TestEnv, ws_server):
r, infos, frames = ws_run(env, path='/ws/echo/', scenario='miss-authority')
assert r.exit_code == 0, f'{r}'
assert infos == ['[1] RST'], f'{r}'
+ # CONNECT to authority with disabled websockets
+ def test_h2_800_09b_unsupported(self, env: H2TestEnv, ws_server):
+ r, infos, frames = ws_run(env, path='/ws/echo/',
+ authority=f'test1.{env.http_tld}:{env.http_port}')
+ assert r.exit_code == 0, f'{r}'
+ assert infos == ['[1] :status: 501', '[1] EOF'], f'{r}'
+
# CONNECT and exchange a PING
def test_h2_800_10_ws_ping(self, env: H2TestEnv, ws_server):
ping = WsFrame.client_ping(b'12345')