diff options
Diffstat (limited to 'test/modules')
-rw-r--r-- | test/modules/http2/mod_h2test/mod_h2test.c | 69 | ||||
-rw-r--r-- | test/modules/http2/test_004_post.py | 2 | ||||
-rw-r--r-- | test/modules/http2/test_105_timeout.py | 41 | ||||
-rw-r--r-- | test/modules/http2/test_200_header_invalid.py | 28 | ||||
-rw-r--r-- | test/modules/http2/test_500_proxy.py | 6 | ||||
-rw-r--r-- | test/modules/http2/test_601_h2proxy_twisted.py | 89 |
6 files changed, 201 insertions, 34 deletions
diff --git a/test/modules/http2/mod_h2test/mod_h2test.c b/test/modules/http2/mod_h2test/mod_h2test.c index b5ee8ad6e4..f20b9547e7 100644 --- a/test/modules/http2/mod_h2test/mod_h2test.c +++ b/test/modules/http2/mod_h2test/mod_h2test.c @@ -138,7 +138,12 @@ static int h2test_echo_handler(request_rec *r) char buffer[8192]; const char *ct; long l; - + int i; + apr_time_t chunk_delay = 0; + apr_array_header_t *args = NULL; + apr_size_t blen, fail_after = 0; + int fail_requested = 0, error_bucket = 1; + if (strcmp(r->handler, "h2test-echo")) { return DECLINED; } @@ -146,6 +151,40 @@ static int h2test_echo_handler(request_rec *r) return DECLINED; } + if(r->args) { + args = apr_cstr_split(r->args, "&", 1, r->pool); + for(i = 0; i < args->nelts; ++i) { + char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*); + s = strchr(arg, '='); + if(s) { + *s = '\0'; + val = s + 1; + if(!strcmp("id", arg)) { + /* accepted, but not processed */ + continue; + } + else if(!strcmp("chunk_delay", arg)) { + rv = duration_parse(&chunk_delay, val, "s"); + if(APR_SUCCESS == rv) { + continue; + } + } + else if(!strcmp("fail_after", arg)) { + fail_after = (int)apr_atoi64(val); + if(fail_after >= 0) { + fail_requested = 1; + continue; + } + } + } + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not " + "understood: '%s' in %s", + arg, r->args); + ap_die(HTTP_BAD_REQUEST, r); + return OK; + } + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: processing request"); r->status = 200; r->clength = -1; @@ -166,12 +205,26 @@ static int h2test_echo_handler(request_rec *r) while (0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) { ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: copying %ld bytes from request body", l); - rv = apr_brigade_write(bb, NULL, NULL, buffer, l); + blen = (apr_size_t)l; + if (fail_requested) { + if (blen > fail_after) { + blen = fail_after; + } + fail_after -= blen; + } + rv = apr_brigade_write(bb, NULL, NULL, buffer, blen); if (APR_SUCCESS != rv) goto cleanup; + if (chunk_delay) { + apr_sleep(chunk_delay); + } rv = ap_pass_brigade(r->output_filters, bb); if (APR_SUCCESS != rv) goto cleanup; ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: passed %ld bytes from request body", l); + if (fail_requested && fail_after == 0) { + rv = APR_EINVAL; + goto cleanup; + } } } /* we are done */ @@ -195,6 +248,12 @@ cleanup: ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler: request handled"); return OK; } + else if (error_bucket) { + int status = ap_map_http_request_error(rv, HTTP_BAD_REQUEST); + b = ap_bucket_error_create(status, NULL, r->pool, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + ap_pass_brigade(r->output_filters, bb); + } else { /* no way to know what type of error occurred */ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "h2test_echo_handler failed"); @@ -419,18 +478,20 @@ static int h2test_error_handler(request_rec *r) } } else if (!strcmp("delay", arg)) { - rv = duration_parse(&delay, r->args, "s"); + rv = duration_parse(&delay, val, "s"); if (APR_SUCCESS == rv) { continue; } } else if (!strcmp("body_delay", arg)) { - rv = duration_parse(&body_delay, r->args, "s"); + rv = duration_parse(&body_delay, val, "s"); if (APR_SUCCESS == rv) { continue; } } } + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "error_handler: " + "did not understand '%s'", arg); ap_die(HTTP_BAD_REQUEST, r); return OK; } diff --git a/test/modules/http2/test_004_post.py b/test/modules/http2/test_004_post.py index e7938f007b..0095e69990 100644 --- a/test/modules/http2/test_004_post.py +++ b/test/modules/http2/test_004_post.py @@ -19,7 +19,7 @@ class TestPost: def _class_scope(self, env): TestPost._local_dir = os.path.dirname(inspect.getfile(TestPost)) conf = H2Conf(env) - conf.add_vhost_cgi(proxy_self=True).install() + conf.add_vhost_cgi(proxy_self=True, h2proxy_self=True).install() assert env.apache_restart() == 0 def local_src(self, fname): diff --git a/test/modules/http2/test_105_timeout.py b/test/modules/http2/test_105_timeout.py index 13aa8ed07a..f7d3859caf 100644 --- a/test/modules/http2/test_105_timeout.py +++ b/test/modules/http2/test_105_timeout.py @@ -128,22 +128,25 @@ class TestTimeout: def test_h2_105_12(self, env): # long connection timeout, short stream timeout # sending a slow POST - if env.httpd_is_at_least("2.5.0"): - conf = H2Conf(env) - conf.add_vhost_cgi() - conf.add("Timeout 10") - conf.add("H2StreamTimeout 1") - conf.install() - assert env.apache_restart() == 0 - url = env.mkurl("https", "cgi", "/h2test/delay?5") - piper = CurlPiper(env=env, url=url) - piper.start() - for _ in range(3): - time.sleep(2) - try: - piper.send("0123456789\n") - except BrokenPipeError: - break - piper.close() - assert piper.response - assert piper.response['status'] == 408, f"{piper.response}" + if not env.curl_is_at_least('8.0.0'): + pytest.skip(f'need at least curl v8.0.0 for this') + if not env.httpd_is_at_least("2.5.0"): + pytest.skip(f'need at least httpd 2.5.0 for this') + conf = H2Conf(env) + conf.add_vhost_cgi() + conf.add("Timeout 10") + conf.add("H2StreamTimeout 1") + conf.install() + assert env.apache_restart() == 0 + url = env.mkurl("https", "cgi", "/h2test/delay?5") + piper = CurlPiper(env=env, url=url) + piper.start() + for _ in range(3): + time.sleep(2) + try: + piper.send("0123456789\n") + except BrokenPipeError: + break + piper.close() + assert piper.response, f'{piper}' + assert piper.response['status'] == 408, f"{piper.response}" diff --git a/test/modules/http2/test_200_header_invalid.py b/test/modules/http2/test_200_header_invalid.py index 80ad5a1650..fe94487840 100644 --- a/test/modules/http2/test_200_header_invalid.py +++ b/test/modules/http2/test_200_header_invalid.py @@ -13,6 +13,7 @@ class TestInvalidHeaders: # let the hecho.py CGI echo chars < 0x20 in field name # for almost all such characters, the stream returns a 500 + # or in httpd >= 2.5.0 gets aborted with a h2 error # cr is handled special def test_h2_200_01(self, env): url = env.mkurl("https", "cgi", "/hecho.py") @@ -22,12 +23,15 @@ class TestInvalidHeaders: if x in [13]: assert 0 == r.exit_code, f'unexpected exit code for char 0x{x:02}' assert 200 == r.response["status"], f'unexpected status for char 0x{x:02}' + elif x in [10] or env.httpd_is_at_least('2.5.0'): + assert 0 == r.exit_code, f'unexpected exit code for char 0x{x:02}' + assert 500 == r.response["status"], f'unexpected status for char 0x{x:02}' else: - assert 0 == r.exit_code, f'"unexpected exit code for char 0x{x:02}' - assert 500 == r.response["status"], f'posting "{data}" unexpected status, {r}' + assert 0 != r.exit_code, f'unexpected exit code for char 0x{x:02}' # let the hecho.py CGI echo chars < 0x20 in field value # for almost all such characters, the stream returns a 500 + # or in httpd >= 2.5.0 gets aborted with a h2 error # cr and lf are handled special def test_h2_200_02(self, env): url = env.mkurl("https", "cgi", "/hecho.py") @@ -37,20 +41,28 @@ class TestInvalidHeaders: if x in [10, 13]: assert 0 == r.exit_code, "unexpected exit code for char 0x%02x" % x assert 200 == r.response["status"], "unexpected status for char 0x%02x" % x + elif env.httpd_is_at_least('2.5.0'): + assert 0 == r.exit_code, f'unexpected exit code for char 0x{x:02}' + assert 500 == r.response["status"], f'unexpected status for char 0x{x:02}' else: - assert 0 == r.exit_code, "unexpected exit code for char 0x%02x" % x - assert 500 == r.response["status"], "unexpected status for char 0x%02x" % x + assert 0 != r.exit_code, "unexpected exit code for char 0x%02x" % x # let the hecho.py CGI echo 0x10 and 0x7f in field name and value def test_h2_200_03(self, env): url = env.mkurl("https", "cgi", "/hecho.py") for h in ["10", "7f"]: r = env.curl_post_data(url, "name=x%%%s&value=yz" % h) - assert 0 == r.exit_code, "unexpected exit code for char 0x%02x" % h - assert 500 == r.response["status"], "unexpected status for char 0x%02x" % h + if env.httpd_is_at_least('2.5.0'): + assert 0 == r.exit_code, f"unexpected exit code for char 0x{h:02}" + assert 500 == r.response["status"], f"unexpected exit code for char 0x{h:02}" + else: + assert 0 != r.exit_code r = env.curl_post_data(url, "name=x&value=y%%%sz" % h) - assert 0 == r.exit_code, "unexpected exit code for char 0x%02x" % h - assert 500 == r.response["status"], "unexpected status for char 0x%02x" % h + if env.httpd_is_at_least('2.5.0'): + assert 0 == r.exit_code, f"unexpected exit code for char 0x{h:02}" + assert 500 == r.response["status"], f"unexpected exit code for char 0x{h:02}" + else: + assert 0 != r.exit_code # test header field lengths check, LimitRequestLine (default 8190) def test_h2_200_10(self, env): diff --git a/test/modules/http2/test_500_proxy.py b/test/modules/http2/test_500_proxy.py index 6ab8275b11..306568e2d5 100644 --- a/test/modules/http2/test_500_proxy.py +++ b/test/modules/http2/test_500_proxy.py @@ -146,14 +146,16 @@ class TestProxy: # produce an error during response body def test_h2_500_31(self, env, repeat): - pytest.skip("needs fix in core protocol handling") + if env.httpd_is_at_least("2.5.0"): + pytest.skip("needs fix in core protocol handling") url = env.mkurl("https", "cgi", "/proxy/h2test/error?body_error=timeout") r = env.curl_get(url) assert r.exit_code != 0, r # produce an error, fail to generate an error bucket def test_h2_500_32(self, env, repeat): - pytest.skip("needs fix in core protocol handling") + if env.httpd_is_at_least("2.5.0"): + pytest.skip("needs fix in core protocol handling") url = env.mkurl("https", "cgi", "/proxy/h2test/error?body_error=timeout&error_bucket=0") r = env.curl_get(url) assert r.exit_code != 0, r diff --git a/test/modules/http2/test_601_h2proxy_twisted.py b/test/modules/http2/test_601_h2proxy_twisted.py new file mode 100644 index 0000000000..748a494086 --- /dev/null +++ b/test/modules/http2/test_601_h2proxy_twisted.py @@ -0,0 +1,89 @@ +import json +import logging +import os +import pytest + +from .env import H2Conf, H2TestEnv + + +log = logging.getLogger(__name__) + + +@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here") +class TestH2ProxyTwisted: + + @pytest.fixture(autouse=True, scope='class') + def _class_scope(self, env): + H2Conf(env).add_vhost_cgi(proxy_self=True, h2proxy_self=True).install() + assert env.apache_restart() == 0 + + @pytest.mark.parametrize("name", [ + "data-1k", "data-10k", "data-100k", "data-1m", + ]) + def test_h2_601_01_echo_uploads(self, env, name): + fpath = os.path.join(env.gen_dir, name) + url = env.mkurl("https", "cgi", "/h2proxy/h2test/echo") + r = env.curl_upload(url, fpath, options=[]) + assert r.exit_code == 0 + assert 200 <= r.response["status"] < 300 + # we POST a form, so echoed input is larger than the file itself + assert len(r.response["body"]) > os.path.getsize(fpath) + + @pytest.mark.parametrize("name", [ + "data-1k", "data-10k", "data-100k", "data-1m", + ]) + def test_h2_601_02_echo_delayed(self, env, name): + fpath = os.path.join(env.gen_dir, name) + url = env.mkurl("https", "cgi", "/h2proxy/h2test/echo?chunk_delay=10ms") + r = env.curl_upload(url, fpath, options=[]) + assert r.exit_code == 0 + assert 200 <= r.response["status"] < 300 + # we POST a form, so echoed input is larger than the file itself + assert len(r.response["body"]) > os.path.getsize(fpath) + + @pytest.mark.parametrize("name", [ + "data-1k", "data-10k", "data-100k", "data-1m", + ]) + def test_h2_601_03_echo_fail_early(self, env, name): + if env.httpd_is_at_least("2.5.0"): + pytest.skip("needs mod_proxy_http2 fix") + fpath = os.path.join(env.gen_dir, name) + url = env.mkurl("https", "cgi", "/h2proxy/h2test/echo?fail_after=512") + r = env.curl_upload(url, fpath, options=[]) + # 92 is curl's CURLE_HTTP2_STREAM + assert r.exit_code == 92 or r.response["status"] == 502 + + @pytest.mark.parametrize("name", [ + "data-1k", "data-10k", "data-100k", "data-1m", + ]) + def test_h2_601_04_echo_fail_late(self, env, name): + if env.httpd_is_at_least("2.5.0"): + pytest.skip("needs mod_proxy_http2 fix") + fpath = os.path.join(env.gen_dir, name) + url = env.mkurl("https", "cgi", f"/h2proxy/h2test/echo?fail_after={os.path.getsize(fpath)}") + r = env.curl_upload(url, fpath, options=[]) + # 92 is curl's CURLE_HTTP2_STREAM + assert r.exit_code == 92 or r.response["status"] == 502 + + def test_h2_601_05_echo_fail_many(self, env): + if env.httpd_is_at_least("2.5.0"): + pytest.skip("needs mod_proxy_http2 fix") + count = 200 + fpath = os.path.join(env.gen_dir, "data-100k") + args = [env.curl, '--parallel', '--parallel-max', '20'] + for i in range(count): + if i > 0: + args.append('--next') + url = env.mkurl("https", "cgi", f"/h2proxy/h2test/echo?id={i}&fail_after={os.path.getsize(fpath)}") + args.extend(env.curl_resolve_args(url=url)) + args.extend([ + '-o', '/dev/null', '-w', '%{json}\\n', '--form', f'file=@{fpath}', url + ]) + log.error(f'run: {args}') + r = env.run(args) + stats = [] + for line in r.stdout.splitlines(): + stats.append(json.loads(line)) + assert len(stats) == count + for st in stats: + assert st['exitcode'] == 92 or st['http_code'] == 502, f'unexpected: {st}' |