summaryrefslogtreecommitdiffstats
path: root/test/modules
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--test/modules/core/conftest.py22
-rw-r--r--test/modules/core/env.py25
-rw-r--r--test/modules/core/test_001_encoding.py41
-rw-r--r--test/modules/core/test_002_restarts.py150
-rw-r--r--test/modules/http1/__init__.py0
-rw-r--r--test/modules/http1/conftest.py36
-rw-r--r--test/modules/http1/env.py81
-rw-r--r--test/modules/http1/htdocs/cgi/files/empty.txt0
-rwxr-xr-xtest/modules/http1/htdocs/cgi/hello.py15
-rw-r--r--test/modules/http1/htdocs/cgi/requestparser.py57
-rwxr-xr-xtest/modules/http1/htdocs/cgi/upload.py55
-rw-r--r--test/modules/http1/mod_h1test/mod_h1test.c129
-rw-r--r--test/modules/http1/mod_h1test/mod_h1test.slo0
-rw-r--r--test/modules/http1/test_001_alive.py20
-rw-r--r--test/modules/http1/test_003_get.py27
-rw-r--r--test/modules/http1/test_004_post.py53
-rw-r--r--test/modules/http1/test_005_trailers.py42
-rw-r--r--test/modules/http1/test_006_unsafe.py134
-rw-r--r--test/modules/http1/test_007_strict.py126
-rw-r--r--test/modules/http2/conftest.py11
-rw-r--r--test/modules/http2/env.py36
-rw-r--r--test/modules/http2/test_007_ssi.py1
-rw-r--r--test/modules/http2/test_008_ranges.py20
-rw-r--r--test/modules/http2/test_100_conn_reuse.py12
-rw-r--r--test/modules/http2/test_101_ssl_reneg.py46
-rw-r--r--test/modules/http2/test_102_require.py6
-rw-r--r--test/modules/http2/test_103_upgrade.py3
-rw-r--r--test/modules/http2/test_105_timeout.py14
-rw-r--r--test/modules/http2/test_106_shutdown.py8
-rw-r--r--test/modules/http2/test_200_header_invalid.py22
-rw-r--r--test/modules/http2/test_203_rfc9113.py24
-rw-r--r--test/modules/http2/test_500_proxy.py12
-rw-r--r--test/modules/http2/test_600_h2proxy.py7
-rw-r--r--test/modules/http2/test_700_load_get.py34
-rw-r--r--test/modules/http2/test_712_buffering.py14
-rw-r--r--test/modules/http2/test_800_websockets.py5
-rwxr-xr-xtest/modules/md/conftest.py45
-rw-r--r--test/modules/md/test_300_conf_validate.py165
-rw-r--r--test/modules/md/test_702_auto.py54
-rw-r--r--test/modules/md/test_720_wildcard.py28
-rw-r--r--test/modules/md/test_730_static.py7
-rw-r--r--test/modules/md/test_740_acme_errors.py18
-rw-r--r--test/modules/md/test_741_setup_errors.py10
-rw-r--r--test/modules/md/test_750_eab.py105
-rw-r--r--test/modules/md/test_780_tailscale.py12
-rw-r--r--test/modules/md/test_790_failover.py18
-rw-r--r--test/modules/md/test_900_notify.py14
-rw-r--r--test/modules/md/test_901_message.py32
-rw-r--r--test/modules/md/test_920_status.py6
-rw-r--r--test/modules/proxy/conftest.py20
-rw-r--r--test/modules/proxy/env.py1
-rw-r--r--test/modules/proxy/test_02_unix.py6
-rw-r--r--test/modules/tls/conf.py11
-rw-r--r--test/modules/tls/conftest.py6
-rw-r--r--test/modules/tls/env.py5
-rw-r--r--test/modules/tls/test_02_conf.py12
-rw-r--r--test/modules/tls/test_03_sni.py18
-rw-r--r--test/modules/tls/test_06_ciphers.py21
-rw-r--r--test/modules/tls/test_08_vars.py21
-rw-r--r--test/modules/tls/test_14_proxy_ssl.py49
-rw-r--r--test/modules/tls/test_15_proxy_tls.py10
-rw-r--r--test/modules/tls/test_16_proxy_mixed.py3
-rw-r--r--test/modules/tls/test_17_proxy_machine_cert.py3
63 files changed, 1788 insertions, 200 deletions
diff --git a/test/modules/core/conftest.py b/test/modules/core/conftest.py
index 439cd22..22906ef 100644
--- a/test/modules/core/conftest.py
+++ b/test/modules/core/conftest.py
@@ -4,41 +4,27 @@ import os
import pytest
import sys
+from .env import CoreTestEnv
from pyhttpd.env import HttpdTestEnv
sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
def pytest_report_header(config, startdir):
- env = HttpdTestEnv()
+ env = CoreTestEnv()
return f"core [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
@pytest.fixture(scope="package")
-def env(pytestconfig) -> HttpdTestEnv:
+def env(pytestconfig) -> CoreTestEnv:
level = logging.INFO
console = logging.StreamHandler()
console.setLevel(level)
console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
logging.getLogger('').addHandler(console)
logging.getLogger('').setLevel(level=level)
- env = HttpdTestEnv(pytestconfig=pytestconfig)
+ env = CoreTestEnv(pytestconfig=pytestconfig)
env.setup_httpd()
env.apache_access_log_clear()
env.httpd_error_log.clear_log()
return env
-
-
-@pytest.fixture(autouse=True, scope="package")
-def _session_scope(env):
- env.httpd_error_log.set_ignored_lognos([
- 'AH10244', # core: invalid URI path
- 'AH01264', # mod_cgid script not found
- ])
- yield
- assert env.apache_stop() == 0
- errors, warnings = env.httpd_error_log.get_missed()
- assert (len(errors), len(warnings)) == (0, 0),\
- f"apache logged {len(errors)} errors and {len(warnings)} warnings: \n"\
- "{0}\n{1}\n".format("\n".join(errors), "\n".join(warnings))
-
diff --git a/test/modules/core/env.py b/test/modules/core/env.py
new file mode 100644
index 0000000..9c63380
--- /dev/null
+++ b/test/modules/core/env.py
@@ -0,0 +1,25 @@
+import inspect
+import logging
+import os
+
+from pyhttpd.env import HttpdTestEnv, HttpdTestSetup
+
+log = logging.getLogger(__name__)
+
+
+class CoreTestSetup(HttpdTestSetup):
+
+ def __init__(self, env: 'HttpdTestEnv'):
+ super().__init__(env=env)
+ self.add_source_dir(os.path.dirname(inspect.getfile(CoreTestSetup)))
+ self.add_modules(["cgid"])
+
+
+class CoreTestEnv(HttpdTestEnv):
+
+ def __init__(self, pytestconfig=None):
+ super().__init__(pytestconfig=pytestconfig)
+ self.add_httpd_log_modules(["http", "core"])
+
+ def setup_httpd(self, setup: HttpdTestSetup = None):
+ super().setup_httpd(setup=CoreTestSetup(env=self))
diff --git a/test/modules/core/test_001_encoding.py b/test/modules/core/test_001_encoding.py
index b7ffbaa..a3b24d0 100644
--- a/test/modules/core/test_001_encoding.py
+++ b/test/modules/core/test_001_encoding.py
@@ -1,12 +1,11 @@
import pytest
+from typing import List, Optional
from pyhttpd.conf import HttpdConf
class TestEncoding:
- EXP_AH10244_ERRS = 0
-
@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
conf = HttpdConf(env, extras={
@@ -57,29 +56,29 @@ class TestEncoding:
assert r.response["status"] == 200
# check path traversals
- @pytest.mark.parametrize(["path", "status"], [
- ["/../echo.py", 400],
- ["/nothing/../../echo.py", 400],
- ["/cgi-bin/../../echo.py", 400],
- ["/nothing/%2e%2e/%2e%2e/echo.py", 400],
- ["/cgi-bin/%2e%2e/%2e%2e/echo.py", 400],
- ["/nothing/%%32%65%%32%65/echo.py", 400],
- ["/cgi-bin/%%32%65%%32%65/echo.py", 400],
- ["/nothing/%%32%65%%32%65/%%32%65%%32%65/h2_env.py", 400],
- ["/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/h2_env.py", 400],
- ["/nothing/%25%32%65%25%32%65/echo.py", 404],
- ["/cgi-bin/%25%32%65%25%32%65/echo.py", 404],
- ["/nothing/%25%32%65%25%32%65/%25%32%65%25%32%65/h2_env.py", 404],
- ["/cgi-bin/%25%32%65%25%32%65/%25%32%65%25%32%65/h2_env.py", 404],
+ @pytest.mark.parametrize(["path", "status", "lognos"], [
+ ["/../echo.py", 400, ["AH10244"]],
+ ["/nothing/../../echo.py", 400, ["AH10244"]],
+ ["/cgi-bin/../../echo.py", 400, ["AH10244"]],
+ ["/nothing/%2e%2e/%2e%2e/echo.py", 400, ["AH10244"]],
+ ["/cgi-bin/%2e%2e/%2e%2e/echo.py", 400, ["AH10244"]],
+ ["/nothing/%%32%65%%32%65/echo.py", 400, ["AH10244"]],
+ ["/cgi-bin/%%32%65%%32%65/echo.py", 400, ["AH10244"]],
+ ["/nothing/%%32%65%%32%65/%%32%65%%32%65/h2_env.py", 400, ["AH10244"]],
+ ["/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/h2_env.py", 400, ["AH10244"]],
+ ["/nothing/%25%32%65%25%32%65/echo.py", 404, ["AH01264"]],
+ ["/cgi-bin/%25%32%65%25%32%65/echo.py", 404, ["AH01264"]],
+ ["/nothing/%25%32%65%25%32%65/%25%32%65%25%32%65/h2_env.py", 404, ["AH01264"]],
+ ["/cgi-bin/%25%32%65%25%32%65/%25%32%65%25%32%65/h2_env.py", 404, ["AH01264"]],
])
- def test_core_001_04(self, env, path, status):
+ def test_core_001_04(self, env, path, status, lognos: Optional[List[str]]):
url = env.mkurl("https", "test1", path)
r = env.curl_get(url)
assert r.response["status"] == status
- if status == 400:
- TestEncoding.EXP_AH10244_ERRS += 1
- # the log will have a core:err about invalid URI path
-
+ #
+ if lognos is not None:
+ env.httpd_error_log.ignore_recent(lognos = lognos)
+
# check handling of %2f url encodings that are not decoded by default
@pytest.mark.parametrize(["host", "path", "status"], [
["test1", "/006%2f006.css", 404],
diff --git a/test/modules/core/test_002_restarts.py b/test/modules/core/test_002_restarts.py
new file mode 100644
index 0000000..cf203bc
--- /dev/null
+++ b/test/modules/core/test_002_restarts.py
@@ -0,0 +1,150 @@
+import os
+import re
+import time
+from datetime import datetime, timedelta
+from threading import Thread
+
+import pytest
+
+from .env import CoreTestEnv
+from pyhttpd.conf import HttpdConf
+
+
+class Loader:
+
+ def __init__(self, env, url: str, clients: int, req_per_client: int = 10):
+ self.env = env
+ self.url = url
+ self.clients = clients
+ self.req_per_client = req_per_client
+ self.result = None
+ self.total_request = 0
+ self._thread = None
+
+ def run(self):
+ self.total_requests = self.clients * self.req_per_client
+ conn_per_client = 5
+ args = [self.env.h2load, f"--connect-to=localhost:{self.env.https_port}",
+ "--h1", # use only http/1.1
+ "-n", str(self.total_requests), # total # of requests to make
+ "-c", str(conn_per_client * self.clients), # total # of connections to make
+ "-r", str(self.clients), # connections at a time
+ "--rate-period", "2", # create conns every 2 sec
+ self.url,
+ ]
+ self.result = self.env.run(args)
+
+ def start(self):
+ self._thread = Thread(target=self.run)
+ self._thread.start()
+
+ def join(self):
+ self._thread.join()
+
+
+class ChildDynamics:
+
+ RE_DATE_TIME = re.compile(r'\[(?P<date_time>[^\]]+)\] .*')
+ RE_TIME_FRAC = re.compile(r'(?P<dt>.* \d\d:\d\d:\d\d)(?P<frac>.(?P<micros>.\d+)) (?P<year>\d+)')
+ RE_CHILD_CHANGE = re.compile(r'\[(?P<date_time>[^\]]+)\] '
+ r'\[mpm_event:\w+\]'
+ r' \[pid (?P<main_pid>\d+):tid \w+\] '
+ r'.* Child (?P<child_no>\d+) (?P<action>\w+): '
+ r'pid (?P<pid>\d+), gen (?P<generation>\d+), .*')
+
+ def __init__(self, env: CoreTestEnv):
+ self.env = env
+ self.changes = list()
+ self._start = None
+ for l in open(env.httpd_error_log.path):
+ m = self.RE_CHILD_CHANGE.match(l)
+ if m:
+ self.changes.append({
+ 'pid': int(m.group('pid')),
+ 'child_no': int(m.group('child_no')),
+ 'gen': int(m.group('generation')),
+ 'action': m.group('action'),
+ 'rtime' : self._rtime(m.group('date_time'))
+ })
+ continue
+ if self._start is None:
+ m = self.RE_DATE_TIME.match(l)
+ if m:
+ self._rtime(m.group('date_time'))
+
+ def _rtime(self, s: str) -> timedelta:
+ micros = 0
+ m = self.RE_TIME_FRAC.match(s)
+ if m:
+ micros = int(m.group('micros'))
+ s = f"{m.group('dt')} {m.group('year')}"
+ d = datetime.strptime(s, '%a %b %d %H:%M:%S %Y') + timedelta(microseconds=micros)
+ if self._start is None:
+ self._start = d
+ delta = d - self._start
+ return f"{delta.seconds:+02d}.{delta.microseconds:06d}"
+
+
+
+@pytest.mark.skipif(condition='STRESS_TEST' not in os.environ,
+ reason="STRESS_TEST not set in env")
+@pytest.mark.skipif(condition=not CoreTestEnv().h2load_is_at_least('1.41.0'),
+ reason="h2load unavailable or misses --connect-to option")
+class TestRestarts:
+
+ def test_core_002_01(self, env):
+ # Lets make a tight config that triggers dynamic child behaviour
+ conf = HttpdConf(env, extras={
+ 'base': f"""
+ StartServers 1
+ ServerLimit 3
+ ThreadLimit 4
+ ThreadsPerChild 4
+ MinSpareThreads 4
+ MaxSpareThreads 6
+ MaxRequestWorkers 12
+ MaxConnectionsPerChild 0
+
+ LogLevel mpm_event:trace6
+ """,
+ })
+ conf.add_vhost_cgi()
+ conf.install()
+
+ # clear logs and start server, start load
+ env.httpd_error_log.clear_log()
+ assert env.apache_restart() == 0
+ # we should see a single child started
+ cd = ChildDynamics(env)
+ assert len(cd.changes) == 1, f"{cd.changes}"
+ assert cd.changes[0]['action'] == 'started'
+ # This loader simulates 6 clients, each making 10 requests.
+ # delay.py sleeps for 1sec, so this should run for about 10 seconds
+ loader = Loader(env=env, url=env.mkurl("https", "cgi", "/delay.py"),
+ clients=6, req_per_client=10)
+ loader.start()
+ # Expect 2 more children to have been started after half time
+ time.sleep(5)
+ cd = ChildDynamics(env)
+ assert len(cd.changes) == 3, f"{cd.changes}"
+ assert len([x for x in cd.changes if x['action'] == 'started']) == 3, f"{cd.changes}"
+
+ # Trigger a server reload
+ assert env.apache_reload() == 0
+ # a graceful reload lets ongoing requests continue, but
+ # after a while all gen 0 children should have stopped
+ time.sleep(3) # FIXME: this pbly depends on the runtime a lot, do we have expectations?
+ cd = ChildDynamics(env)
+ gen0 = [x for x in cd.changes if x['gen'] == 0]
+ assert len([x for x in gen0 if x['action'] == 'stopped']) == 3
+
+ # wait for the loader to finish and stop the server
+ loader.join()
+ env.apache_stop()
+
+ # Similar to before the reload, we expect 3 children to have
+ # been started and stopped again on server stop
+ cd = ChildDynamics(env)
+ gen1 = [x for x in cd.changes if x['gen'] == 1]
+ assert len([x for x in gen1 if x['action'] == 'started']) == 3
+ assert len([x for x in gen1 if x['action'] == 'stopped']) == 3
diff --git a/test/modules/http1/__init__.py b/test/modules/http1/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/modules/http1/__init__.py
diff --git a/test/modules/http1/conftest.py b/test/modules/http1/conftest.py
new file mode 100644
index 0000000..33a16a1
--- /dev/null
+++ b/test/modules/http1/conftest.py
@@ -0,0 +1,36 @@
+import logging
+import os
+
+import pytest
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
+
+from .env import H1TestEnv
+
+
+def pytest_report_header(config, startdir):
+ env = H1TestEnv()
+ return f"mod_http [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
+
+
+def pytest_generate_tests(metafunc):
+ if "repeat" in metafunc.fixturenames:
+ count = int(metafunc.config.getoption("repeat"))
+ metafunc.fixturenames.append('tmp_ct')
+ metafunc.parametrize('repeat', range(count))
+
+
+@pytest.fixture(scope="package")
+def env(pytestconfig) -> H1TestEnv:
+ level = logging.INFO
+ console = logging.StreamHandler()
+ console.setLevel(level)
+ console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
+ logging.getLogger('').addHandler(console)
+ logging.getLogger('').setLevel(level=level)
+ env = H1TestEnv(pytestconfig=pytestconfig)
+ env.setup_httpd()
+ env.apache_access_log_clear()
+ env.httpd_error_log.clear_log()
+ return env
diff --git a/test/modules/http1/env.py b/test/modules/http1/env.py
new file mode 100644
index 0000000..e2df1a5
--- /dev/null
+++ b/test/modules/http1/env.py
@@ -0,0 +1,81 @@
+import inspect
+import logging
+import os
+import subprocess
+from typing import Dict, Any
+
+from pyhttpd.certs import CertificateSpec
+from pyhttpd.conf import HttpdConf
+from pyhttpd.env import HttpdTestEnv, HttpdTestSetup
+
+log = logging.getLogger(__name__)
+
+
+class H1TestSetup(HttpdTestSetup):
+
+ def __init__(self, env: 'HttpdTestEnv'):
+ super().__init__(env=env)
+ self.add_source_dir(os.path.dirname(inspect.getfile(H1TestSetup)))
+ self.add_modules(["cgid", "autoindex", "ssl"])
+
+ def make(self):
+ super().make()
+ self._add_h1test()
+ self._setup_data_1k_1m()
+
+ def _add_h1test(self):
+ local_dir = os.path.dirname(inspect.getfile(H1TestSetup))
+ p = subprocess.run([self.env.apxs, '-c', 'mod_h1test.c'],
+ capture_output=True,
+ cwd=os.path.join(local_dir, 'mod_h1test'))
+ rv = p.returncode
+ if rv != 0:
+ log.error(f"compiling md_h1test failed: {p.stderr}")
+ raise Exception(f"compiling md_h1test failed: {p.stderr}")
+
+ modules_conf = os.path.join(self.env.server_dir, 'conf/modules.conf')
+ with open(modules_conf, 'a') as fd:
+ # load our test module which is not installed
+ fd.write(f"LoadModule h1test_module \"{local_dir}/mod_h1test/.libs/mod_h1test.so\"\n")
+
+ def _setup_data_1k_1m(self):
+ s90 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"
+ with open(os.path.join(self.env.gen_dir, "data-1k"), 'w') as f:
+ for i in range(10):
+ f.write(f"{i:09d}-{s90}")
+ with open(os.path.join(self.env.gen_dir, "data-10k"), 'w') as f:
+ for i in range(100):
+ f.write(f"{i:09d}-{s90}")
+ with open(os.path.join(self.env.gen_dir, "data-100k"), 'w') as f:
+ for i in range(1000):
+ f.write(f"{i:09d}-{s90}")
+ with open(os.path.join(self.env.gen_dir, "data-1m"), 'w') as f:
+ for i in range(10000):
+ f.write(f"{i:09d}-{s90}")
+
+
+class H1TestEnv(HttpdTestEnv):
+
+ def __init__(self, pytestconfig=None):
+ super().__init__(pytestconfig=pytestconfig)
+ self.add_httpd_log_modules(["http", "core"])
+
+ def setup_httpd(self, setup: HttpdTestSetup = None):
+ super().setup_httpd(setup=H1TestSetup(env=self))
+
+
+class H1Conf(HttpdConf):
+
+ def __init__(self, env: HttpdTestEnv, extras: Dict[str, Any] = None):
+ super().__init__(env=env, extras=HttpdConf.merge_extras(extras, {
+ "base": [
+ "LogLevel http:trace4",
+ ],
+ f"cgi.{env.http_tld}": [
+ "SSLOptions +StdEnvVars",
+ "AddHandler cgi-script .py",
+ "<Location \"/h1test/echo\">",
+ " SetHandler h1test-echo",
+ "</Location>",
+ ]
+ }))
diff --git a/test/modules/http1/htdocs/cgi/files/empty.txt b/test/modules/http1/htdocs/cgi/files/empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/modules/http1/htdocs/cgi/files/empty.txt
diff --git a/test/modules/http1/htdocs/cgi/hello.py b/test/modules/http1/htdocs/cgi/hello.py
new file mode 100755
index 0000000..191acb2
--- /dev/null
+++ b/test/modules/http1/htdocs/cgi/hello.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python3
+
+import os
+
+print("Content-Type: application/json")
+print()
+print("{")
+print(" \"https\" : \"%s\"," % (os.getenv('HTTPS', '')))
+print(" \"host\" : \"%s\"," % (os.getenv('SERVER_NAME', '')))
+print(" \"protocol\" : \"%s\"," % (os.getenv('SERVER_PROTOCOL', '')))
+print(" \"ssl_protocol\" : \"%s\"," % (os.getenv('SSL_PROTOCOL', '')))
+print(" \"h2\" : \"%s\"," % (os.getenv('HTTP2', '')))
+print(" \"h2push\" : \"%s\"" % (os.getenv('H2PUSH', '')))
+print("}")
+
diff --git a/test/modules/http1/htdocs/cgi/requestparser.py b/test/modules/http1/htdocs/cgi/requestparser.py
new file mode 100644
index 0000000..c7e0648
--- /dev/null
+++ b/test/modules/http1/htdocs/cgi/requestparser.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+import os
+import sys
+from urllib import parse
+import multipart # https://github.com/andrew-d/python-multipart (`apt install python3-multipart`)
+import shutil
+
+
+try: # Windows needs stdio set for binary mode.
+ import msvcrt
+
+ msvcrt.setmode(0, os.O_BINARY) # stdin = 0
+ msvcrt.setmode(1, os.O_BINARY) # stdout = 1
+except ImportError:
+ pass
+
+
+class FileItem:
+
+ def __init__(self, mparse_item):
+ self.item = mparse_item
+
+ @property
+ def file_name(self):
+ return os.path.basename(self.item.file_name.decode())
+
+ def save_to(self, destpath: str):
+ fsrc = self.item.file_object
+ fsrc.seek(0)
+ with open(destpath, 'wb') as fd:
+ shutil.copyfileobj(fsrc, fd)
+
+
+def get_request_params():
+ oforms = {}
+ ofiles = {}
+ if "REQUEST_URI" in os.environ:
+ qforms = parse.parse_qs(parse.urlsplit(os.environ["REQUEST_URI"]).query)
+ for name, values in qforms.items():
+ oforms[name] = values[0]
+ if "CONTENT_TYPE" in os.environ:
+ ctype = os.environ["CONTENT_TYPE"]
+ if ctype == "application/x-www-form-urlencoded":
+ s = sys.stdin.read()
+ qforms = parse.parse_qs(s)
+ for name, values in qforms.items():
+ oforms[name] = values[0]
+ elif ctype.startswith("multipart/"):
+ def on_field(field):
+ oforms[field.field_name.decode()] = field.value.decode()
+ def on_file(file):
+ ofiles[file.field_name.decode()] = FileItem(file)
+ multipart.parse_form(headers={"Content-Type": ctype},
+ input_stream=sys.stdin.buffer,
+ on_field=on_field, on_file=on_file)
+ return oforms, ofiles
+
diff --git a/test/modules/http1/htdocs/cgi/upload.py b/test/modules/http1/htdocs/cgi/upload.py
new file mode 100755
index 0000000..632b7e9
--- /dev/null
+++ b/test/modules/http1/htdocs/cgi/upload.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+import os
+import sys
+from requestparser import get_request_params
+
+
+forms, files = get_request_params()
+
+status = '200 Ok'
+
+# Test if the file was uploaded
+if 'file' in files:
+ fitem = files['file']
+ # strip leading path from file name to avoid directory traversal attacks
+ fname = fitem.file_name
+ fpath = f'{os.environ["DOCUMENT_ROOT"]}/files/{fname}'
+ fitem.save_to(fpath)
+ message = "The file %s was uploaded successfully" % (fname)
+ print("Status: 201 Created")
+ print("Content-Type: text/html")
+ print("Location: %s://%s/files/%s" % (os.environ["REQUEST_SCHEME"], os.environ["HTTP_HOST"], fname))
+ print("")
+ print("<html><body><p>%s</p></body></html>" % (message))
+
+elif 'remove' in forms:
+ remove = forms['remove']
+ try:
+ fname = os.path.basename(remove)
+ os.remove('./files/' + fname)
+ message = 'The file "' + fname + '" was removed successfully'
+ except OSError as e:
+ message = 'Error removing ' + fname + ': ' + e.strerror
+ status = '404 File Not Found'
+ print("Status: %s" % (status))
+ print("""
+Content-Type: text/html
+
+<html><body>
+<p>%s</p>
+</body></html>""" % (message))
+
+else:
+ message = '''\
+ Upload File<form method="POST" enctype="multipart/form-data">
+ <input type="file" name="file">
+ <button type="submit">Upload</button></form>
+ '''
+ print("Status: %s" % (status))
+ print("""\
+Content-Type: text/html
+
+<html><body>
+<p>%s</p>
+</body></html>""" % (message))
+
diff --git a/test/modules/http1/mod_h1test/mod_h1test.c b/test/modules/http1/mod_h1test/mod_h1test.c
new file mode 100644
index 0000000..cbd87b5
--- /dev/null
+++ b/test/modules/http1/mod_h1test/mod_h1test.c
@@ -0,0 +1,129 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+#include <apr_strings.h>
+#include <apr_cstr.h>
+#include <apr_time.h>
+#include <apr_want.h>
+
+#include <httpd.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+
+static void h1test_hooks(apr_pool_t *pool);
+
+AP_DECLARE_MODULE(h1test) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* func to create per dir config */
+ NULL, /* func to merge per dir config */
+ NULL, /* func to create per server config */
+ NULL, /* func to merge per server config */
+ NULL, /* command handlers */
+ h1test_hooks,
+#if defined(AP_MODULE_FLAG_NONE)
+ AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
+};
+
+
+static int h1test_echo_handler(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ apr_status_t rv;
+ char buffer[8192];
+ const char *ct;
+ long l;
+
+ if (strcmp(r->handler, "h1test-echo")) {
+ return DECLINED;
+ }
+ if (r->method_number != M_GET && r->method_number != M_POST) {
+ return DECLINED;
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: processing request");
+ r->status = 200;
+ r->clength = -1;
+ r->chunked = 1;
+ ct = apr_table_get(r->headers_in, "content-type");
+ ap_set_content_type(r, ct? ct : "application/octet-stream");
+
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+ /* copy any request body into the response */
+ if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
+ if (ap_should_client_block(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);
+ if (APR_SUCCESS != rv) goto cleanup;
+ 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);
+ }
+ }
+ /* we are done */
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: request read");
+
+ if (r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "echo_handler: seeing incoming trailers");
+ apr_table_setn(r->trailers_out, "h1test-trailers-in",
+ apr_itoa(r->pool, 1));
+ }
+ if (apr_table_get(r->headers_in, "Add-Trailer")) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+ "echo_handler: seeing incoming Add-Trailer header");
+ apr_table_setn(r->trailers_out, "h1test-add-trailer",
+ apr_table_get(r->headers_in, "Add-Trailer"));
+ }
+
+ rv = ap_pass_brigade(r->output_filters, bb);
+
+cleanup:
+ if (rv == APR_SUCCESS
+ || r->status != HTTP_OK
+ || c->aborted) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler: request handled");
+ return OK;
+ }
+ else {
+ /* no way to know what type of error occurred */
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "h1test_echo_handler failed");
+ return AP_FILTER_ERROR;
+ }
+ return DECLINED;
+}
+
+
+/* Install this module into the apache2 infrastructure.
+ */
+static void h1test_hooks(apr_pool_t *pool)
+{
+ ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks and handlers");
+
+ /* test h1 handlers */
+ ap_hook_handler(h1test_echo_handler, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
diff --git a/test/modules/http1/mod_h1test/mod_h1test.slo b/test/modules/http1/mod_h1test/mod_h1test.slo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/modules/http1/mod_h1test/mod_h1test.slo
diff --git a/test/modules/http1/test_001_alive.py b/test/modules/http1/test_001_alive.py
new file mode 100644
index 0000000..0a1de1d
--- /dev/null
+++ b/test/modules/http1/test_001_alive.py
@@ -0,0 +1,20 @@
+import pytest
+
+from .env import H1Conf
+
+
+class TestBasicAlive:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env):
+ H1Conf(env).add_vhost_test1().install()
+ assert env.apache_restart() == 0
+
+ # we expect to see the document from the generic server
+ def test_h1_001_01(self, env):
+ url = env.mkurl("https", "test1", "/alive.json")
+ r = env.curl_get(url, 5)
+ assert r.exit_code == 0, r.stderr + r.stdout
+ assert r.response["json"]
+ assert r.response["json"]["alive"] is True
+ assert r.response["json"]["host"] == "test1"
diff --git a/test/modules/http1/test_003_get.py b/test/modules/http1/test_003_get.py
new file mode 100644
index 0000000..1cd5917
--- /dev/null
+++ b/test/modules/http1/test_003_get.py
@@ -0,0 +1,27 @@
+import socket
+
+import pytest
+
+from .env import H1Conf
+
+
+class TestGet:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env):
+ H1Conf(env).add_vhost_cgi(
+ proxy_self=True
+ ).add_vhost_test1(
+ proxy_self=True
+ ).install()
+ assert env.apache_restart() == 0
+
+ # check SSL environment variables from CGI script
+ def test_h1_003_01(self, env):
+ url = env.mkurl("https", "cgi", "/hello.py")
+ r = env.curl_get(url)
+ assert r.response["status"] == 200
+ assert r.response["json"]["protocol"] == "HTTP/1.1"
+ assert r.response["json"]["https"] == "on"
+ tls_version = r.response["json"]["ssl_protocol"]
+ assert tls_version in ["TLSv1.2", "TLSv1.3"]
diff --git a/test/modules/http1/test_004_post.py b/test/modules/http1/test_004_post.py
new file mode 100644
index 0000000..005a8c2
--- /dev/null
+++ b/test/modules/http1/test_004_post.py
@@ -0,0 +1,53 @@
+import difflib
+import email.parser
+import inspect
+import json
+import os
+import sys
+
+import pytest
+
+from .env import H1Conf
+
+
+class TestPost:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env):
+ TestPost._local_dir = os.path.dirname(inspect.getfile(TestPost))
+ H1Conf(env).add_vhost_cgi().install()
+ assert env.apache_restart() == 0
+
+ def local_src(self, fname):
+ return os.path.join(TestPost._local_dir, fname)
+
+ # upload and GET again using curl, compare to original content
+ def curl_upload_and_verify(self, env, fname, options=None):
+ url = env.mkurl("https", "cgi", "/upload.py")
+ fpath = os.path.join(env.gen_dir, fname)
+ r = env.curl_upload(url, fpath, options=options)
+ assert r.exit_code == 0, f"{r}"
+ assert 200 <= r.response["status"] < 300
+
+ r2 = env.curl_get(r.response["header"]["location"])
+ assert r2.exit_code == 0
+ assert r2.response["status"] == 200
+ with open(self.local_src(fpath), mode='rb') as file:
+ src = file.read()
+ assert src == r2.response["body"]
+ return r
+
+ def test_h1_004_01(self, env):
+ self.curl_upload_and_verify(env, "data-1k", ["-vvv"])
+
+ def test_h1_004_02(self, env):
+ self.curl_upload_and_verify(env, "data-10k", [])
+
+ def test_h1_004_03(self, env):
+ self.curl_upload_and_verify(env, "data-100k", [])
+
+ def test_h1_004_04(self, env):
+ self.curl_upload_and_verify(env, "data-1m", [])
+
+ def test_h1_004_05(self, env):
+ r = self.curl_upload_and_verify(env, "data-1k", ["-vvv", "-H", "Expect: 100-continue"])
diff --git a/test/modules/http1/test_005_trailers.py b/test/modules/http1/test_005_trailers.py
new file mode 100644
index 0000000..ca717a0
--- /dev/null
+++ b/test/modules/http1/test_005_trailers.py
@@ -0,0 +1,42 @@
+import os
+import pytest
+
+from .env import H1Conf
+
+
+# The trailer tests depend on "nghttp" as no other client seems to be able to send those
+# rare things.
+class TestTrailers:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env):
+ H1Conf(env).add_vhost_cgi(proxy_self=True).install()
+ assert env.apache_restart() == 0
+
+ # check that we get a trailer out when telling the handler to add one
+ def test_h1_005_01(self, env):
+ if not env.httpd_is_at_least("2.5.0"):
+ pytest.skip(f'need at least httpd 2.5.0 for this')
+ url = env.mkurl("https", "cgi", "/h1test/echo")
+ host = f"cgi.{env.http_tld}"
+ fpath = os.path.join(env.gen_dir, "data-1k")
+ r = env.curl_upload(url, fpath, options=["--header", "Add-Trailer: 005_01"])
+ assert r.exit_code == 0, f"{r}"
+ assert 200 <= r.response["status"] < 300
+ assert r.response["trailer"], f"no trailers received: {r}"
+ assert "h1test-add-trailer" in r.response["trailer"]
+ assert r.response["trailer"]["h1test-add-trailer"] == "005_01"
+
+ # check that we get out trailers through the proxy
+ def test_h1_005_02(self, env):
+ if not env.httpd_is_at_least("2.5.0"):
+ pytest.skip(f'need at least httpd 2.5.0 for this')
+ url = env.mkurl("https", "cgi", "/proxy/h1test/echo")
+ host = f"cgi.{env.http_tld}"
+ fpath = os.path.join(env.gen_dir, "data-1k")
+ r = env.curl_upload(url, fpath, options=["--header", "Add-Trailer: 005_01"])
+ assert r.exit_code == 0, f"{r}"
+ assert 200 <= r.response["status"] < 300
+ assert r.response["trailer"], f"no trailers received: {r}"
+ assert "h1test-add-trailer" in r.response["trailer"]
+ assert r.response["trailer"]["h1test-add-trailer"] == "005_01"
diff --git a/test/modules/http1/test_006_unsafe.py b/test/modules/http1/test_006_unsafe.py
new file mode 100644
index 0000000..eb83217
--- /dev/null
+++ b/test/modules/http1/test_006_unsafe.py
@@ -0,0 +1,134 @@
+import re
+import socket
+from typing import List, Optional
+
+import pytest
+
+from .env import H1Conf
+
+class TestRequestUnsafe:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env):
+ conf = H1Conf(env)
+ conf.add([
+ "HttpProtocolOptions Unsafe",
+ ])
+ conf.install()
+ assert env.apache_restart() == 0
+
+ # unsafe tests from t/apache/http_strict.t
+ # possible expected results:
+ # 0: any HTTP error
+ # 1: any HTTP success
+ # 200-500: specific HTTP status code
+ # None: HTTPD should drop connection without error message
+ @pytest.mark.parametrize(["intext", "status", "lognos"], [
+ ["GET / HTTP/1.0\r\n\r\n", 1, None],
+ ["GET / HTTP/1.0\n\n", 1, None],
+ ["get / HTTP/1.0\r\n\r\n", 501, ["AH00135"]],
+ ["G ET / HTTP/1.0\r\n\r\n", 400, None],
+ ["G\0ET / HTTP/1.0\r\n\r\n", 400, None],
+ ["G/T / HTTP/1.0\r\n\r\n", 501, ["AH00135"]],
+ ["GET /\0 HTTP/1.0\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\0\r\n\r\n", 400, None],
+ ["GET\f/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET\r/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET\t/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET / HTT/1.0\r\n\r\n", 0, None],
+ ["GET / HTTP/1.0\r\nHost: localhost\r\n\r\n", 1, None],
+ ["GET / HTTP/2.0\r\nHost: localhost\r\n\r\n", 1, None],
+ ["GET / HTTP/1.2\r\nHost: localhost\r\n\r\n", 1, None],
+ ["GET / HTTP/1.11\r\nHost: localhost\r\n\r\n", 400, None],
+ ["GET / HTTP/10.0\r\nHost: localhost\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0 \r\nHost: localhost\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0 x\r\nHost: localhost\r\n\r\n", 400, None],
+ ["GET / HTTP/\r\nHost: localhost\r\n\r\n", 0, None],
+ ["GET / HTTP/0.9\r\n\r\n", 0, None],
+ ["GET / HTTP/0.8\r\n\r\n", 0, None],
+ ["GET /\x01 HTTP/1.0\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nFoo: bar\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nFoo:bar\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nFoo: b\0ar\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nFoo: b\x01ar\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nFoo\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nFoo bar\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\n: bar\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nX: bar\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nFoo bar:bash\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nFoo :bar\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\n Foo:bar\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nF\x01o: bar\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nF\ro: bar\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nF\to: bar\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nFo: b\tar\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nFo: bar\r\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\r", None, None],
+ ["GET /\r\n", 0, None],
+ ["GET /#frag HTTP/1.0\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nHost: localhost\r\nHost: localhost\r\n\r\n", 200, None],
+ ["GET http://017700000001/ HTTP/1.0\r\n\r\n", 200, None],
+ ["GET http://0x7f.1/ HTTP/1.0\r\n\r\n", 200, None],
+ ["GET http://127.0.0.1/ HTTP/1.0\r\n\r\n", 200, None],
+ ["GET http://127.01.0.1/ HTTP/1.0\r\n\r\n", 200, None],
+ ["GET http://%3127.0.0.1/ HTTP/1.0\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nHost: localhost:80\r\nHost: localhost:80\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nHost: localhost:80 x\r\n\r", 400, None],
+ ["GET http://localhost:80/ HTTP/1.0\r\n\r\n", 200, None],
+ ["GET http://localhost:80x/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET http://localhost:80:80/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET http://localhost::80/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET http://foo@localhost:80/ HTTP/1.0\r\n\r\n", 200, None],
+ ["GET http://[::1]/ HTTP/1.0\r\n\r\n", 1, None],
+ ["GET http://[::1:2]/ HTTP/1.0\r\n\r\n", 1, None],
+ ["GET http://[4712::abcd]/ HTTP/1.0\r\n\r\n", 1, None],
+ ["GET http://[4712::abcd:1]/ HTTP/1.0\r\n\r\n", 1, None],
+ ["GET http://[4712::abcd::]/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET http://[4712:abcd::,]/ HTTP/1.0\r\n\r\n", 1, None],
+ ["GET http://[4712::abcd]:8000/ HTTP/1.0\r\n\r\n", 1, None],
+ ["GET http://4713::abcd:8001/ HTTP/1.0\r\n\r\n", 400, None],
+ ["GET / HTTP/1.0\r\nHost: [::1]\r\n\r\n", 1, None],
+ ["GET / HTTP/1.0\r\nHost: [::1:2]\r\n\r\n", 1, None],
+ ["GET / HTTP/1.0\r\nHost: [4711::abcd]\r\n\r\n", 1, None],
+ ["GET / HTTP/1.0\r\nHost: [4711::abcd:1]\r\n\r\n", 1, None],
+ ["GET / HTTP/1.0\r\nHost: [4711:abcd::]\r\n\r\n", 1, None],
+ ["GET / HTTP/1.0\r\nHost: [4711::abcd]:8000\r\n\r\n", 1, None],
+ ["GET / HTTP/1.0\r\nHost: 4714::abcd:8001\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nHost: abc\xa0\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nHost: abc\\foo\r\n\r\n", 400, None],
+ ["GET http://foo/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200, None],
+ ["GET http://foo:81/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200, None],
+ ["GET http://[::1]:81/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200, None],
+ ["GET http://10.0.0.1:81/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nHost: foo-bar.example.com\r\n\r\n", 200, None],
+ ["GET / HTTP/1.0\r\nHost: foo_bar.example.com\r\n\r\n", 200, None],
+ ["GET http://foo_bar/ HTTP/1.0\r\n\r\n", 200, None],
+ ])
+ def test_h1_006_01(self, env, intext, status: Optional[int], lognos: Optional[List[str]]):
+ with socket.create_connection(('localhost', int(env.http_port))) as sock:
+ # on some OS, the server does not see our connection until there is
+ # something incoming
+ sock.sendall(intext.encode())
+ sock.shutdown(socket.SHUT_WR)
+ buff = sock.recv(1024)
+ msg = buff.decode()
+ if status is None:
+ assert len(msg) == 0, f"unexpected answer: {msg}"
+ else:
+ assert len(msg) > 0, "no answer from server"
+ rlines = msg.splitlines()
+ response = rlines[0]
+ m = re.match(r'^HTTP/1.1 (\d+)\s+(\S+)', response)
+ assert m or status == 0, f"unrecognized response: {rlines}"
+ if status == 1:
+ assert int(m.group(1)) >= 200
+ elif status == 0:
+ # headerless 0.9 response, yuk
+ assert len(rlines) >= 1, f"{rlines}"
+ elif status > 0:
+ assert int(m.group(1)) == status, f"{rlines}"
+ else:
+ assert int(m.group(1)) >= 400, f"{rlines}"
+ #
+ if lognos is not None:
+ env.httpd_error_log.ignore_recent(lognos = lognos)
diff --git a/test/modules/http1/test_007_strict.py b/test/modules/http1/test_007_strict.py
new file mode 100644
index 0000000..7c52f68
--- /dev/null
+++ b/test/modules/http1/test_007_strict.py
@@ -0,0 +1,126 @@
+import re
+import socket
+from typing import List, Optional
+
+import pytest
+
+from .env import H1Conf
+
+
+class TestRequestStrict:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env):
+ conf = H1Conf(env)
+ conf.add([
+ "HttpProtocolOptions Strict",
+ ])
+ conf.install()
+ assert env.apache_restart() == 0
+
+ # strict tests from t/apache/http_strict.t
+ # possible expected results:
+ # 0: any HTTP error
+ # 1: any HTTP success
+ # 200-500: specific HTTP status code
+ # undef: HTTPD should drop connection without error message
+ @pytest.mark.parametrize(["intext", "status"], [
+ ["GET / HTTP/1.0\n\n", 400],
+ ["G/T / HTTP/1.0\r\n\r\n", 400],
+ ["GET / HTTP/1.0 \r\nHost: localhost\r\n\r\n", 400],
+ ["GET / HTTP/1.0\r\nFoo: b\x01ar\r\n\r\n", 400],
+ ["GET / HTTP/1.0\r\nF\x01o: bar\r\n\r\n", 400],
+ ["GET / HTTP/1.0\r\r", None],
+ ["GET / HTTP/1.0\r\nHost: localhost\r\nHost: localhost\r\n\r\n", 400],
+ ["GET http://017700000001/ HTTP/1.0\r\n\r\n", 400],
+ ["GET http://0x7f.1/ HTTP/1.0\r\n\r\n", 400],
+ ["GET http://127.01.0.1/ HTTP/1.0\r\n\r\n", 400],
+ ["GET http://%3127.0.0.1/ HTTP/1.0\r\n\r\n", 400],
+ ["GET / HTTP/1.0\r\nHost: localhost:80\r\nHost: localhost:80\r\n\r\n", 400],
+ ["GET http://foo@localhost:80/ HTTP/1.0\r\n\r\n", 400],
+ ["GET / HTTP/1.0\r\nHost: 4714::abcd:8001\r\n\r\n", 400],
+ ["GET / HTTP/1.0\r\nHost: abc\xa0\r\n\r\n", 400],
+ ["GET / HTTP/1.0\r\nHost: foo_bar.example.com\r\n\r\n", 200],
+ ["GET http://foo_bar/ HTTP/1.0\r\n\r\n", 200],
+ ])
+ def test_h1_007_01(self, env, intext, status: Optional[int]):
+ with socket.create_connection(('localhost', int(env.http_port))) as sock:
+ # on some OS, the server does not see our connection until there is
+ # something incoming
+ sock.sendall(intext.encode())
+ sock.shutdown(socket.SHUT_WR)
+ buff = sock.recv(1024)
+ msg = buff.decode()
+ if status is None:
+ assert len(msg) == 0, f"unexpected answer: {msg}"
+ else:
+ assert len(msg) > 0, "no answer from server"
+ rlines = msg.splitlines()
+ response = rlines[0]
+ m = re.match(r'^HTTP/1.1 (\d+)\s+(\S+)', response)
+ assert m, f"unrecognized response: {rlines}"
+ if status == 1:
+ assert int(m.group(1)) >= 200
+ elif status == 90:
+ assert len(rlines) >= 1, f"{rlines}"
+ elif status > 0:
+ assert int(m.group(1)) == status, f"{rlines}"
+ else:
+ assert int(m.group(1)) >= 400, f"{rlines}"
+
+ @pytest.mark.parametrize(["hvalue", "expvalue", "status", "lognos"], [
+ ['"123"', '123', 200, None],
+ ['"123 "', '123 ', 200, None], # trailing space stays
+ ['"123\t"', '123\t', 200, None], # trailing tab stays
+ ['" 123"', '123', 200, None], # leading space is stripped
+ ['" 123"', '123', 200, None], # leading spaces are stripped
+ ['"\t123"', '123', 200, None], # leading tab is stripped
+ ['"expr=%{unescape:123%0A 123}"', '', 500, ["AH02430"]], # illegal char
+ ['" \t "', '', 200, None], # just ws
+ ])
+ def test_h1_007_02(self, env, hvalue, expvalue, status, lognos: Optional[List[str]]):
+ hname = 'ap-test-007'
+ conf = H1Conf(env, extras={
+ f'test1.{env.http_tld}': [
+ '<Location /index.html>',
+ f'Header add {hname} {hvalue}',
+ '</Location>',
+ ]
+ })
+ conf.add_vhost_test1(proxy_self=True)
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "test1", "/index.html")
+ r = env.curl_get(url, options=['--http1.1'])
+ assert r.response["status"] == status
+ if int(status) < 400:
+ assert r.response["header"][hname] == expvalue
+ #
+ if lognos is not None:
+ env.httpd_error_log.ignore_recent(lognos = lognos)
+
+ @pytest.mark.parametrize(["hvalue", "expvalue"], [
+ ['123', '123'],
+ ['123 ', '123'], # trailing space is stripped
+ ['123\t', '123'], # trailing tab is stripped
+ [' 123', '123'], # leading space is stripped
+ [' 123', '123'], # leading spaces are stripped
+ ['\t123', '123'], # leading tab is stripped
+ ])
+ def test_h1_007_03(self, env, hvalue, expvalue):
+ # same as 007_02, but http1 proxied
+ hname = 'ap-test-007'
+ conf = H1Conf(env, extras={
+ f'test1.{env.http_tld}': [
+ '<Location /index.html>',
+ f'Header add {hname} "{hvalue}"',
+ '</Location>',
+ ]
+ })
+ conf.add_vhost_test1(proxy_self=True)
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "test1", "/proxy/index.html")
+ r = env.curl_get(url, options=['--http1.1'])
+ assert r.response["status"] == 200
+ assert r.response["header"][hname] == expvalue
diff --git a/test/modules/http2/conftest.py b/test/modules/http2/conftest.py
index 55d0c3a..118cef1 100644
--- a/test/modules/http2/conftest.py
+++ b/test/modules/http2/conftest.py
@@ -30,11 +30,10 @@ def env(pytestconfig) -> H2TestEnv:
@pytest.fixture(autouse=True, scope="package")
-def _session_scope(env):
+def _h2_package_scope(env):
+ env.httpd_error_log.add_ignored_lognos([
+ 'AH10400', # warning that 'enablereuse' has not effect in certain configs
+ 'AH00045', # child did not exit in time, SIGTERM was sent
+ ])
yield
assert env.apache_stop() == 0
- errors, warnings = env.httpd_error_log.get_missed()
- assert (len(errors), len(warnings)) == (0, 0),\
- f"apache logged {len(errors)} errors and {len(warnings)} warnings: \n"\
- "{0}\n{1}\n".format("\n".join(errors), "\n".join(warnings))
-
diff --git a/test/modules/http2/env.py b/test/modules/http2/env.py
index 34d196d..b2443e0 100644
--- a/test/modules/http2/env.py
+++ b/test/modules/http2/env.py
@@ -1,8 +1,8 @@
import inspect
import logging
import os
-import re
import subprocess
+from shutil import copyfile
from typing import Dict, Any
from pyhttpd.certs import CertificateSpec
@@ -53,6 +53,12 @@ class H2TestSetup(HttpdTestSetup):
with open(os.path.join(self.env.gen_dir, "data-1m"), 'w') as f:
for i in range(10000):
f.write(f"{i:09d}-{s90}")
+ test1_docs = os.path.join(self.env.server_docs_dir, 'test1')
+ self.env.mkpath(test1_docs)
+ for fname in ["data-1k", "data-10k", "data-100k", "data-1m"]:
+ src = os.path.join(self.env.gen_dir, fname)
+ dest = os.path.join(test1_docs, fname)
+ copyfile(src, dest)
class H2TestEnv(HttpdTestEnv):
@@ -85,34 +91,6 @@ class H2TestEnv(HttpdTestEnv):
CertificateSpec(domains=[f"noh2.{self.http_tld}"], key_type='rsa2048'),
])
- self.httpd_error_log.set_ignored_lognos([
- 'AH02032',
- 'AH01276',
- 'AH01630',
- 'AH00135',
- 'AH02261', # Re-negotiation handshake failed (our test_101)
- 'AH03490', # scoreboard full, happens on limit tests
- 'AH02429', # invalid chars in response header names, see test_h2_200
- 'AH02430', # invalid chars in response header values, see test_h2_200
- 'AH10373', # SSL errors on uncompleted handshakes, see test_h2_105
- 'AH01247', # mod_cgid sometimes freaks out on load tests
- 'AH01110', # error by proxy reading response
- 'AH10400', # warning that 'enablereuse' has not effect in certain configs test_h2_600
- 'AH00045', # child did not exit in time, SIGTERM was sent
- ])
- self.httpd_error_log.add_ignored_patterns([
- re.compile(r'.*malformed header from script \'hecho.py\': Bad header: x.*'),
- re.compile(r'.*:tls_post_process_client_hello:.*'),
- # OSSL 3 dropped the function name from the error description. Use the code instead:
- # 0A0000C1 = no shared cipher -- Too restrictive SSLCipherSuite or using DSA server certificate?
- re.compile(r'.*SSL Library Error: error:0A0000C1:.*'),
- re.compile(r'.*:tls_process_client_certificate:.*'),
- # OSSL 3 dropped the function name from the error description. Use the code instead:
- # 0A0000C7 = peer did not return a certificate -- No CAs known to server for verification?
- re.compile(r'.*SSL Library Error: error:0A0000C7:.*'),
- re.compile(r'.*have incompatible TLS configurations.'),
- ])
-
def setup_httpd(self, setup: HttpdTestSetup = None):
super().setup_httpd(setup=H2TestSetup(env=self))
diff --git a/test/modules/http2/test_007_ssi.py b/test/modules/http2/test_007_ssi.py
index 97e38df..f5411bc 100644
--- a/test/modules/http2/test_007_ssi.py
+++ b/test/modules/http2/test_007_ssi.py
@@ -1,4 +1,3 @@
-import re
import pytest
from .env import H2Conf, H2TestEnv
diff --git a/test/modules/http2/test_008_ranges.py b/test/modules/http2/test_008_ranges.py
index 4dcdcc8..dd695bb 100644
--- a/test/modules/http2/test_008_ranges.py
+++ b/test/modules/http2/test_008_ranges.py
@@ -1,13 +1,16 @@
import inspect
import json
+import logging
import os
import re
-import time
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 TestRanges:
@@ -123,13 +126,17 @@ class TestRanges:
'--limit-rate', '2k', '-m', '2'
])
assert r.exit_code != 0, f'{r}'
+ # Restart for logs to be flushed out
+ assert env.apache_restart() == 0
found = False
for line in open(TestRanges.LOGFILE).readlines():
e = json.loads(line)
+ log.info(f'inspecting logged request: {e["request"]}')
if e['request'] == f'GET {path}?03broken HTTP/2.0':
assert e['bytes_rx_I'] > 0
assert e['bytes_resp_B'] == 100*1024*1024
assert e['bytes_tx_O'] > 1024
+ assert e['bytes_tx_O'] < 100*1024*1024 # curl buffers, but not that much
found = True
break
assert found, f'request not found in {self.LOGFILE}'
@@ -141,18 +148,13 @@ class TestRanges:
assert env.apache_restart() == 0
stats = self.get_server_status(env)
# we see the server uptime check request here
- assert 1 == int(stats['Total Accesses']), f'{stats}'
- assert 1 == int(stats['Total kBytes']), f'{stats}'
+ assert 1 == int(stats['Total Accesses'])
+ assert 1 == int(stats['Total kBytes'])
count = 10
url = env.mkurl("https", "test1", f'/data-100m?[0-{count-1}]')
r = env.curl_get(url, 5, options=['--http2', '-H', f'Range: bytes=0-{4096}'])
assert r.exit_code == 0, f'{r}'
- for _ in range(10):
- # slow cpu might not success on first read
- stats = self.get_server_status(env)
- if (4*count)+1 <= int(stats['Total kBytes']):
- break
- time.sleep(0.1)
+ stats = self.get_server_status(env)
# amount reported is larger than (count *4k), the net payload
# but does not exceed an additional 4k
assert (4*count)+1 <= int(stats['Total kBytes'])
diff --git a/test/modules/http2/test_100_conn_reuse.py b/test/modules/http2/test_100_conn_reuse.py
index 3ebac24..103166f 100644
--- a/test/modules/http2/test_100_conn_reuse.py
+++ b/test/modules/http2/test_100_conn_reuse.py
@@ -48,6 +48,12 @@ class TestConnReuse:
hostname = ("noh2.%s" % env.http_tld)
r = env.curl_get(url, 5, options=[ "-H", "Host:%s" % hostname ])
assert 421 == r.response["status"]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02032" # Hostname provided via SNI and hostname provided via HTTP have no compatible SSL setup
+ ]
+ )
# access an unknown vhost, after using ServerName in SNI
def test_h2_100_05(self, env):
@@ -55,3 +61,9 @@ class TestConnReuse:
hostname = ("unknown.%s" % env.http_tld)
r = env.curl_get(url, 5, options=[ "-H", "Host:%s" % hostname ])
assert 421 == r.response["status"]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02032" # Hostname provided via SNI and hostname provided via HTTP have no compatible SSL setup
+ ]
+ )
diff --git a/test/modules/http2/test_101_ssl_reneg.py b/test/modules/http2/test_101_ssl_reneg.py
index 528002f..d278af2 100644
--- a/test/modules/http2/test_101_ssl_reneg.py
+++ b/test/modules/http2/test_101_ssl_reneg.py
@@ -56,6 +56,12 @@ class TestSslRenegotiation:
assert 0 == r.exit_code, f"{r}"
assert r.response
assert 403 == r.response["status"]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH01276" # No matching DirectoryIndex found
+ ]
+ )
# try to renegotiate the cipher, should fail with correct code
def test_h2_101_02(self, env):
@@ -68,6 +74,16 @@ class TestSslRenegotiation:
assert 0 != r.exit_code
assert not r.response
assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02261" # Re-negotiation handshake failed
+ ],
+ matches = [
+ r'.*:tls_post_process_client_hello:.*',
+ r'.*SSL Library Error:.*:SSL routines::no shared cipher.*'
+ ]
+ )
# try to renegotiate a client certificate from Location
# needs to fail with correct code
@@ -79,6 +95,16 @@ class TestSslRenegotiation:
assert 0 != r.exit_code
assert not r.response
assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02261" # Re-negotiation handshake failed
+ ],
+ matches = [
+ r'.*:tls_process_client_certificate:.*',
+ r'.*SSL Library Error:.*:SSL routines::peer did not return a certificate.*'
+ ]
+ )
# try to renegotiate a client certificate from Directory
# needs to fail with correct code
@@ -90,6 +116,16 @@ class TestSslRenegotiation:
assert 0 != r.exit_code, f"{r}"
assert not r.response
assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02261" # Re-negotiation handshake failed
+ ],
+ matches = [
+ r'.*:tls_process_client_certificate:.*',
+ r'.*SSL Library Error:.*:SSL routines::peer did not return a certificate.*'
+ ]
+ )
# make 10 requests on the same connection, none should produce a status code
# reported by erki@example.ee
@@ -136,3 +172,13 @@ class TestSslRenegotiation:
assert 0 != r.exit_code
assert not r.response
assert re.search(r'HTTP_1_1_REQUIRED \(err 13\)', r.stderr)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02261" # Re-negotiation handshake failed
+ ],
+ matches = [
+ r'.*:tls_post_process_client_hello:.*',
+ r'.*SSL Library Error:.*:SSL routines::no shared cipher.*'
+ ]
+ )
diff --git a/test/modules/http2/test_102_require.py b/test/modules/http2/test_102_require.py
index b7e4eae..4b0cad5 100644
--- a/test/modules/http2/test_102_require.py
+++ b/test/modules/http2/test_102_require.py
@@ -39,3 +39,9 @@ class TestRequire:
assert 0 == r.exit_code
assert r.response
assert 403 == r.response["status"]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH01630" # client denied by server configuration
+ ]
+ )
diff --git a/test/modules/http2/test_103_upgrade.py b/test/modules/http2/test_103_upgrade.py
index 2fa7d1d..1542450 100644
--- a/test/modules/http2/test_103_upgrade.py
+++ b/test/modules/http2/test_103_upgrade.py
@@ -90,6 +90,9 @@ class TestUpgrade:
url = env.mkurl("http", "test1", "/index.html")
r = env.nghttp().get(url, options=["-u"])
assert r.response["status"] == 200
+ # check issue #272
+ assert 'date' in r.response["header"], f'{r.response}'
+ assert r.response["header"]["date"] != 'Sun, 00 Jan 1900 00:00:00 GMT', f'{r.response}'
# upgrade to h2c for a request where http/1.1 is preferred, but the clients upgrade
# wish is honored nevertheless
diff --git a/test/modules/http2/test_105_timeout.py b/test/modules/http2/test_105_timeout.py
index f7d3859..22160b4 100644
--- a/test/modules/http2/test_105_timeout.py
+++ b/test/modules/http2/test_105_timeout.py
@@ -42,6 +42,13 @@ class TestTimeout:
except Exception as ex:
print(f"as expected: {ex}")
sock.close()
+ #
+ time.sleep(1) # let the log flush
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10373" # SSL handshake was not completed
+ ]
+ )
# Check that mod_reqtimeout handshake setting takes effect
def test_h2_105_02(self, env):
@@ -77,6 +84,13 @@ class TestTimeout:
except Exception as ex:
print(f"as expected: {ex}")
sock.close()
+ #
+ time.sleep(1) # let the log flush
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10373" # SSL handshake was not completed
+ ]
+ )
# Check that mod_reqtimeout handshake setting do no longer apply to handshaked
# connections. See <https://github.com/icing/mod_h2/issues/196>.
diff --git a/test/modules/http2/test_106_shutdown.py b/test/modules/http2/test_106_shutdown.py
index 83e143c..fab881b 100644
--- a/test/modules/http2/test_106_shutdown.py
+++ b/test/modules/http2/test_106_shutdown.py
@@ -72,4 +72,10 @@ class TestShutdown:
else:
assert r.exit_code == 0, f"failed on {i}. request: {r.stdout} {r.stderr}"
assert r.response["status"] == 200
- assert "HTTP/2" == r.response["protocol"] \ No newline at end of file
+ assert "HTTP/2" == r.response["protocol"]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH03490" # scoreboard is full, not at MaxRequestWorkers
+ ]
+ ) \ No newline at end of file
diff --git a/test/modules/http2/test_200_header_invalid.py b/test/modules/http2/test_200_header_invalid.py
index 5b3aafd..04c022c 100644
--- a/test/modules/http2/test_200_header_invalid.py
+++ b/test/modules/http2/test_200_header_invalid.py
@@ -28,6 +28,15 @@ class TestInvalidHeaders:
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}'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02429" # Response header name contains invalid characters
+ ],
+ matches = [
+ r'.*malformed header from script \'hecho.py\': Bad header: x.*'
+ ]
+ )
# let the hecho.py CGI echo chars < 0x20 in field value
# for almost all such characters, the stream returns a 500
@@ -46,6 +55,12 @@ class TestInvalidHeaders:
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
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02430" # Response header value contains invalid characters
+ ]
+ )
# let the hecho.py CGI echo 0x10 and 0x7f in field name and value
def test_h2_200_03(self, env):
@@ -63,6 +78,13 @@ class TestInvalidHeaders:
assert 500 == r.response["status"], f"unexpected exit code for char 0x{h:02}"
else:
assert 0 != r.exit_code
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH02429", # Response header name contains invalid characters
+ "AH02430" # Response header value contains invalid characters
+ ]
+ )
# test header field lengths check, LimitRequestLine
def test_h2_200_10(self, env):
diff --git a/test/modules/http2/test_203_rfc9113.py b/test/modules/http2/test_203_rfc9113.py
index 9fc8f3b..1fe3e13 100644
--- a/test/modules/http2/test_203_rfc9113.py
+++ b/test/modules/http2/test_203_rfc9113.py
@@ -1,4 +1,5 @@
import pytest
+from typing import List, Optional
from pyhttpd.env import HttpdTestEnv
from .env import H2Conf
@@ -22,17 +23,17 @@ class TestRfc9113:
assert r.response["status"] == 200, f'curl output: {r.stdout}'
# response header are also handled, but we strip ws before sending
- @pytest.mark.parametrize(["hvalue", "expvalue", "status"], [
- ['"123"', '123', 200],
- ['"123 "', '123', 200], # trailing space stripped
- ['"123\t"', '123', 200], # trailing tab stripped
- ['" 123"', '123', 200], # leading space is stripped
- ['" 123"', '123', 200], # leading spaces are stripped
- ['"\t123"', '123', 200], # leading tab is stripped
- ['"expr=%{unescape:123%0A 123}"', '', 500], # illegal char
- ['" \t "', '', 200], # just ws
+ @pytest.mark.parametrize(["hvalue", "expvalue", "status", "lognos"], [
+ ['"123"', '123', 200, None],
+ ['"123 "', '123', 200, None], # trailing space stripped
+ ['"123\t"', '123', 200, None], # trailing tab stripped
+ ['" 123"', '123', 200, None], # leading space is stripped
+ ['" 123"', '123', 200, None], # leading spaces are stripped
+ ['"\t123"', '123', 200, None], # leading tab is stripped
+ ['"expr=%{unescape:123%0A 123}"', '', 500, ["AH02430"]], # illegal char
+ ['" \t "', '', 200, None], # just ws
])
- def test_h2_203_02(self, env, hvalue, expvalue, status):
+ def test_h2_203_02(self, env, hvalue, expvalue, status, lognos: Optional[List[str]]):
hname = 'ap-test-007'
conf = H2Conf(env, extras={
f'test1.{env.http_tld}': [
@@ -53,4 +54,7 @@ class TestRfc9113:
assert r.response["status"] == status
if int(status) < 400:
assert r.response["header"][hname] == expvalue
+ #
+ if lognos is not None:
+ env.httpd_error_log.ignore_recent(lognos = lognos)
diff --git a/test/modules/http2/test_500_proxy.py b/test/modules/http2/test_500_proxy.py
index 88a8ece..87e523c 100644
--- a/test/modules/http2/test_500_proxy.py
+++ b/test/modules/http2/test_500_proxy.py
@@ -149,9 +149,21 @@ class TestProxy:
url = env.mkurl("https", "cgi", "/proxy/h2test/error?body_error=timeout")
r = env.curl_get(url)
assert r.exit_code != 0, r
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH01110" # Network error reading response
+ ]
+ )
# produce an error, fail to generate an error bucket
def test_h2_500_32(self, env, repeat):
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
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH01110" # Network error reading response
+ ]
+ )
diff --git a/test/modules/http2/test_600_h2proxy.py b/test/modules/http2/test_600_h2proxy.py
index 18d5d1d..18a528e 100644
--- a/test/modules/http2/test_600_h2proxy.py
+++ b/test/modules/http2/test_600_h2proxy.py
@@ -78,8 +78,8 @@ class TestH2Proxy:
conf.install()
assert env.apache_restart() == 0
url = env.mkurl("https", "cgi", f"/h2proxy/{env.http_port}/hello.py")
- # httpd 2.4.59 disables reuse, not matter the config
- if enable_reuse == "on" and not env.httpd_is_at_least("2.4.59"):
+ # httpd 2.5.0 disables reuse, not matter the config
+ if enable_reuse == "on" and not env.httpd_is_at_least("2.4.60"):
# reuse is not guaranteed for each request, but we expect some
# to do it and run on a h2 stream id > 1
reused = False
@@ -132,7 +132,7 @@ class TestH2Proxy:
assert int(r.json[0]["port"]) == env.http_port
assert r.response["status"] == 200
exp_port = env.http_port if enable_reuse == "on" \
- and not env.httpd_is_at_least("2.4.59")\
+ and not env.httpd_is_at_least("2.4.60")\
else env.http_port2
assert int(r.json[1]["port"]) == exp_port
@@ -188,7 +188,6 @@ class TestH2Proxy:
# produce an error, fail to generate an error bucket
def test_h2_600_32(self, env, repeat):
- pytest.skip('only works reliable with r1911964 from trunk')
conf = H2Conf(env)
conf.add_vhost_cgi(h2proxy_self=True)
conf.install()
diff --git a/test/modules/http2/test_700_load_get.py b/test/modules/http2/test_700_load_get.py
index 78760fb..138e74c 100644
--- a/test/modules/http2/test_700_load_get.py
+++ b/test/modules/http2/test_700_load_get.py
@@ -61,3 +61,37 @@ class TestLoadGet:
args.append(env.mkurl("https", "cgi", ("/mnot164.py?count=%d&text=%s" % (start+(n*chunk)+i, text))))
r = env.run(args)
self.check_h2load_ok(env, r, chunk)
+
+ # test window sizes, connection and stream
+ @pytest.mark.parametrize("connbits,streambits", [
+ [10, 16], # 1k connection window, 64k stream windows
+ [10, 30], # 1k connection window, huge stream windows
+ [30, 8], # huge conn window, 256 bytes stream windows
+ ])
+ @pytest.mark.skip('awaiting mpm_event improvements')
+ def test_h2_700_20(self, env, connbits, streambits):
+ 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, extras={
+ 'base': [
+ 'StartServers 1',
+ ]
+ })
+ conf.add_vhost_cgi().add_vhost_test1().install()
+ assert env.apache_restart() == 0
+ assert env.is_live()
+ n = 2000
+ conns = 50
+ parallel = 10
+ args = [
+ env.h2load,
+ '-n', f'{n}', '-t', '1',
+ '-c', f'{conns}', '-m', f'{parallel}',
+ '-W', f'{connbits}', # connection window bits
+ '-w', f'{streambits}', # stream window bits
+ f'--connect-to=localhost:{env.https_port}',
+ f'--base-uri={env.mkurl("https", "test1", "/")}',
+ "/data-100k"
+ ]
+ r = env.run(args)
+ self.check_h2load_ok(env, r, n) \ No newline at end of file
diff --git a/test/modules/http2/test_712_buffering.py b/test/modules/http2/test_712_buffering.py
index 6658441..0a6978b 100644
--- a/test/modules/http2/test_712_buffering.py
+++ b/test/modules/http2/test_712_buffering.py
@@ -33,7 +33,7 @@ class TestBuffering:
url = env.mkurl("https", "cgi", "/h2test/echo")
base_chunk = "0123456789"
chunks = ["chunk-{0:03d}-{1}\n".format(i, base_chunk) for i in range(5)]
- stutter = timedelta(seconds=0.2) # this is short, but works on my machine (tm)
+ stutter = timedelta(seconds=0.2)
piper = CurlPiper(env=env, url=url)
piper.stutter_check(chunks, stutter)
@@ -43,6 +43,16 @@ class TestBuffering:
url = env.mkurl("https", "cgi", "/h2proxy/h2test/echo")
base_chunk = "0123456789"
chunks = ["chunk-{0:03d}-{1}\n".format(i, base_chunk) for i in range(3)]
- stutter = timedelta(seconds=1) # need a bit more delay since we have the extra connection
+ stutter = timedelta(seconds=0.4) # need a bit more delay since we have the extra connection
+ piper = CurlPiper(env=env, url=url)
+ piper.stutter_check(chunks, stutter)
+
+ def test_h2_712_03(self, env):
+ # same as 712_02 but with smaller chunks
+ #
+ url = env.mkurl("https", "cgi", "/h2proxy/h2test/echo")
+ base_chunk = "0"
+ chunks = ["ck{0}-{1}\n".format(i, base_chunk) for i in range(3)]
+ stutter = timedelta(seconds=0.4) # need a bit more delay since we have the extra connection
piper = CurlPiper(env=env, url=url)
piper.stutter_check(chunks, stutter)
diff --git a/test/modules/http2/test_800_websockets.py b/test/modules/http2/test_800_websockets.py
index 52af1a3..c0fc0c2 100644
--- a/test/modules/http2/test_800_websockets.py
+++ b/test/modules/http2/test_800_websockets.py
@@ -84,8 +84,8 @@ def ws_run(env: H2TestEnv, path, authority=None, do_input=None, inbytes=None,
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
-@pytest.mark.skipif(condition=not H2TestEnv().httpd_is_at_least("2.4.58"),
- reason=f'need at least httpd 2.4.58 for this')
+@pytest.mark.skipif(condition=not H2TestEnv().httpd_is_at_least("2.4.60"),
+ reason=f'need at least httpd 2.4.60 for this')
@pytest.mark.skipif(condition=ws_version < ws_version_min,
reason=f'websockets is {ws_version}, need at least {ws_version_min}')
class TestWebSockets:
@@ -154,7 +154,6 @@ class TestWebSockets:
r, infos, frames = ws_run(env, path='/ws/echo/', scenario='fail-proto')
assert r.exit_code == 0, f'{r}'
assert infos == ['[1] :status: 501', '[1] EOF'], f'{r}'
- env.httpd_error_log.ignore_recent()
# a correct CONNECT, send CLOSE, expect CLOSE, basic success
def test_h2_800_02_ws_empty(self, env: H2TestEnv, ws_server):
diff --git a/test/modules/md/conftest.py b/test/modules/md/conftest.py
index 04165a2..0f9e4a9 100755
--- a/test/modules/md/conftest.py
+++ b/test/modules/md/conftest.py
@@ -1,6 +1,5 @@
import logging
import os
-import re
import sys
import pytest
@@ -33,48 +32,18 @@ def env(pytestconfig) -> MDTestEnv:
env.setup_httpd()
env.apache_access_log_clear()
env.httpd_error_log.clear_log()
- return env
+ yield env
+ env.apache_stop()
@pytest.fixture(autouse=True, scope="package")
-def _session_scope(env):
- # we'd like to check the httpd error logs after the test suite has
- # run to catch anything unusual. For this, we setup the ignore list
- # of errors and warnings that we do expect.
- env.httpd_error_log.set_ignored_lognos([
- 'AH10040', # mod_md, setup complain
- 'AH10045', # mod_md complains that there is no vhost for an MDomain
- 'AH10056', # mod_md, invalid params
- 'AH10105', # mod_md does not find a vhost with SSL enabled for an MDomain
- 'AH10085', # mod_ssl complains about fallback certificates
- 'AH01909', # mod_ssl, cert alt name complains
- 'AH10170', # mod_md, wrong config, tested
- 'AH10171', # mod_md, wrong config, tested
- 'AH10373', # SSL errors on uncompleted handshakes
- 'AH10398', # test on global store lock
+def _md_package_scope(env):
+ env.httpd_error_log.add_ignored_lognos([
+ "AH10085", # There are no SSL certificates configured and no other module contributed any
+ "AH10045", # No VirtualHost matches Managed Domain
+ "AH10105", # MDomain does not match any VirtualHost with 'SSLEngine on'
])
- env.httpd_error_log.add_ignored_patterns([
- re.compile(r'.*urn:ietf:params:acme:error:.*'),
- re.compile(r'.*None of the ACME challenge methods configured for this domain are suitable.*'),
- re.compile(r'.*problem\[(challenge-mismatch|challenge-setup-failure|apache:eab-hmac-invalid)].*'),
- re.compile(r'.*CA considers answer to challenge invalid.].*'),
- re.compile(r'.*problem\[urn:org:apache:httpd:log:AH\d+:].*'),
- re.compile(r'.*Unsuccessful in contacting ACME server at :*'),
- re.compile(r'.*test-md-720-002-\S+.org: dns-01 setup command failed .*'),
- re.compile(r'.*AH\d*: unable to obtain global registry lock, .*'),
- ])
- if env.lacks_ocsp():
- env.httpd_error_log.add_ignored_patterns([
- re.compile(r'.*certificate with serial \S+ has no OCSP responder URL.*'),
- ])
- yield
- assert env.apache_stop() == 0
- errors, warnings = env.httpd_error_log.get_missed()
- assert (len(errors), len(warnings)) == (0, 0),\
- f"apache logged {len(errors)} errors and {len(warnings)} warnings: \n"\
- "{0}\n{1}\n".format("\n".join(errors), "\n".join(warnings))
-
@pytest.fixture(scope="package")
def acme(env):
diff --git a/test/modules/md/test_300_conf_validate.py b/test/modules/md/test_300_conf_validate.py
index 85371ba..88df168 100644
--- a/test/modules/md/test_300_conf_validate.py
+++ b/test/modules/md/test_300_conf_validate.py
@@ -15,7 +15,8 @@ from .md_env import MDTestEnv
class TestConf:
@pytest.fixture(autouse=True, scope='class')
- def _class_scope(self, env):
+ def _class_scope(self, env, acme):
+ acme.start(config='default')
env.clear_store()
# test case: just one MDomain definition
@@ -24,6 +25,12 @@ class TestConf:
MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain
+ ]
+ )
# test case: two MDomain definitions, non-overlapping
def test_md_300_002(self, env):
@@ -32,6 +39,12 @@ class TestConf:
MDomain example2.org www.example2.org mail.example2.org
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain
+ ]
+ )
# test case: two MDomain definitions, exactly the same
def test_md_300_003(self, env):
@@ -41,6 +54,12 @@ class TestConf:
MDomain not-forbidden.org www.not-forbidden.org mail.not-forbidden.org test3.not-forbidden.org
""").install()
assert env.apache_fail() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10038" # two Managed Domains have an overlap in domain
+ ]
+ )
# test case: two MDomain definitions, overlapping
def test_md_300_004(self, env):
@@ -50,6 +69,12 @@ class TestConf:
MDomain example2.org test3.not-forbidden.org www.example2.org mail.example2.org
""").install()
assert env.apache_fail() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10038" # two Managed Domains have an overlap in domain
+ ]
+ )
# test case: two MDomains, one inside a virtual host
def test_md_300_005(self, env):
@@ -60,6 +85,12 @@ class TestConf:
</VirtualHost>
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain
+ ]
+ )
# test case: two MDomains, one correct vhost name
def test_md_300_006(self, env):
@@ -71,6 +102,12 @@ class TestConf:
</VirtualHost>
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain
+ ]
+ )
# test case: two MDomains, two correct vhost names
def test_md_300_007(self, env):
@@ -85,6 +122,12 @@ class TestConf:
</VirtualHost>
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain
+ ]
+ )
# test case: two MDomains, overlapping vhosts
def test_md_300_008(self, env):
@@ -102,6 +145,12 @@ class TestConf:
</VirtualHost>
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain
+ ]
+ )
# test case: vhosts with overlapping MDs
def test_md_300_009(self, env):
@@ -118,7 +167,12 @@ class TestConf:
conf.install()
assert env.apache_fail() == 0
env.apache_stop()
- env.httpd_error_log.ignore_recent()
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10238" # 2 MDs match Virtualhost
+ ]
+ )
# test case: MDomain, vhost with matching ServerAlias
def test_md_300_010(self, env):
@@ -146,6 +200,9 @@ class TestConf:
conf.install()
assert env.apache_fail() == 0
env.apache_stop()
+ env.httpd_error_log.ignore_recent([
+ "AH10040" # A requested MD certificate will not match ServerName
+ ])
# test case: MDomain, misses one ServerAlias, but auto add enabled
def test_md_300_011b(self, env):
@@ -171,6 +228,12 @@ class TestConf:
</VirtualHost>
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain
+ ]
+ )
# test case: one md covers two vhosts
def test_md_300_013(self, env):
@@ -261,7 +324,6 @@ class TestConf:
MDConf(env, text=line).install()
assert env.apache_fail() == 0, "Server accepted test config {}".format(line)
assert exp_err_msg in env.apachectl_stderr
- env.httpd_error_log.ignore_recent()
# test case: alt-names incomplete detection, github isse #68
def test_md_300_021(self, env):
@@ -294,6 +356,12 @@ class TestConf:
</VirtualHost>
""").install()
assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10105" # MD secret.com does not match any VirtualHost with 'SSLEngine on'
+ ]
+ )
# test case: use MDRequireHttps not in <Directory
def test_md_300_023(self, env):
@@ -346,7 +414,7 @@ class TestConf:
def test_md_300_026(self, env):
assert env.apache_stop() == 0
conf = MDConf(env)
- domain = f"t300_026.{env.http_tld}"
+ domain = f"t300-026.{env.http_tld}"
conf.add(f"""
MDomain {domain}
""")
@@ -388,3 +456,92 @@ class TestConf:
assert len(md['ca']['urls']) == len(cas)
else:
assert rv != 0, "Server should not have accepted CAs '{}'".format(cas)
+
+ # messy ServerAliases, see #301
+ def test_md_300_028(self, env):
+ assert env.apache_stop() == 0
+ conf = MDConf(env)
+ domaina = f"t300-028a.{env.http_tld}"
+ domainb = f"t300-028b.{env.http_tld}"
+ dalias = f"t300-028alias.{env.http_tld}"
+ conf.add_vhost(port=env.http_port, domains=[domaina, domainb, dalias], with_ssl=False)
+ conf.add(f"""
+ MDMembers manual
+ MDomain {domaina}
+ MDomain {domainb} {dalias}
+ """)
+ conf.add(f"""
+ <VirtualHost 10.0.0.1:{env.https_port}>
+ ServerName {domaina}
+ ServerAlias {dalias}
+ SSLEngine on
+ </VirtualHost>
+ <VirtualHost 10.0.0.1:{env.https_port}>
+ ServerName {domainb}
+ ServerAlias {dalias}
+ SSLEngine on
+ </VirtualHost>
+ """)
+ conf.install()
+ # This does not work as we have both MDs match domain's vhost
+ assert env.apache_fail() == 0
+ env.httpd_error_log.ignore_recent(
+ lognos=[
+ "AH10238", # 2 MDs match the same vhost
+ ]
+ )
+ # It works, if we only match on ServerNames
+ conf.add("MDMatchNames servernames")
+ conf.install()
+ assert env.apache_restart() == 0
+ env.httpd_error_log.ignore_recent(
+ lognos=[
+ "AH10040", # ServerAlias not covered
+ ]
+ )
+
+ # wildcard and specfic MD overlaps
+ def test_md_300_029(self, env):
+ assert env.apache_stop() == 0
+ conf = MDConf(env)
+ domain = f"t300-029.{env.http_tld}"
+ subdomain = f"sub.{domain}"
+ conf.add_vhost(port=env.http_port, domains=[domain, subdomain], with_ssl=False)
+ conf.add(f"""
+ MDMembers manual
+ MDomain {domain} *.{domain}
+ MDomain {subdomain}
+ """)
+ conf.add(f"""
+ <VirtualHost 10.0.0.1:{env.https_port}>
+ ServerName {domain}
+ SSLEngine on
+ </VirtualHost>
+ <VirtualHost 10.0.0.1:{env.https_port}>
+ ServerName another.{domain}
+ SSLEngine on
+ </VirtualHost>
+ <VirtualHost 10.0.0.1:{env.https_port}>
+ ServerName {subdomain}
+ SSLEngine on
+ </VirtualHost>
+ """)
+ conf.install()
+ # This does not work as we have overlapping names in MDs
+ assert env.apache_fail() == 0
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10038" # 2 MDs overlap
+ ]
+ )
+ # It works, if we only match on ServerNames
+ conf.add("MDMatchNames servernames")
+ conf.install()
+ assert env.apache_restart() == 0
+ time.sleep(2)
+ assert env.apache_stop() == 0
+ # we need dns-01 challenge for the wildcard, which is not configured
+ env.httpd_error_log.ignore_recent(matches=[
+ r'.*None of offered challenge types.*are supported.*'
+ ])
+
diff --git a/test/modules/md/test_702_auto.py b/test/modules/md/test_702_auto.py
index 8e8f5f1..04a9c75 100644
--- a/test/modules/md/test_702_auto.py
+++ b/test/modules/md/test_702_auto.py
@@ -64,6 +64,12 @@ class TestAutov2:
# file system needs to have correct permissions
env.check_dir_empty(env.store_challenges())
env.check_file_permissions(domain)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10045" # No VirtualHost matches Managed Domain test-md-702-001-1688648129.org
+ ]
+ )
# test case: same as test_702_001, but with two parallel managed domains
def test_md_702_002(self, env):
@@ -234,6 +240,15 @@ class TestAutov2:
cert = env.get_cert(name_a)
assert name_a in cert.get_san_list()
assert env.get_http_status(name_a, "/name.txt") == 503
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # None of offered challenge types
+ ],
+ matches = [
+ r'.*problem\[challenge-mismatch\].*'
+ ]
+ )
# Specify a non-working http proxy
def test_md_702_008(self, env):
@@ -254,6 +269,15 @@ class TestAutov2:
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['status-description'] == 'Connection refused'
assert 'account' not in md['ca']
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # Unsuccessful in contacting ACME server
+ ],
+ matches = [
+ r'.*Unsuccessful in contacting ACME server at .*'
+ ]
+ )
# Specify a valid http proxy
def test_md_702_008a(self, env):
@@ -335,6 +359,16 @@ class TestAutov2:
assert env.apache_restart() == 0
env.check_md(domains)
assert env.await_completion([domain])
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10173", # None of the ACME challenge methods configured for this domain are suitable
+ "AH10056" # None of the ACME challenge methods configured for this domain are suitable
+ ],
+ matches = [
+ r'.*None of the ACME challenge methods configured for this domain are suitable.*'
+ ]
+ )
def test_md_702_011(self, env):
domain = self.test_domain
@@ -364,6 +398,16 @@ class TestAutov2:
assert env.apache_restart() == 0
env.check_md(domains)
assert env.await_completion([domain])
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10173", # None of the ACME challenge methods configured for this domain are suitable
+ "AH10056" # None of the ACME challenge methods configured for this domain are suitable
+ ],
+ matches = [
+ r'.*None of the ACME challenge methods configured for this domain are suitable.*'
+ ]
+ )
# test case: one MD with several dns names. sign up. remove the *first* name
# in the MD. restart. should find and keep the existing MD.
@@ -648,6 +692,16 @@ class TestAutov2:
conf.install()
assert env.apache_restart() == 0
assert env.await_error(domain)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10173", # None of the ACME challenge methods configured for this domain are suitable
+ "AH10056" # None of the ACME challenge methods configured for this domain are suitable
+ ],
+ matches = [
+ r'.*None of the ACME challenge methods configured for this domain are suitable.*'
+ ]
+ )
# Make a setup using the base server without http:, but with acme-tls/1, should work.
def test_md_702_052(self, env):
diff --git a/test/modules/md/test_720_wildcard.py b/test/modules/md/test_720_wildcard.py
index 23b311c..916c47a 100644
--- a/test/modules/md/test_720_wildcard.py
+++ b/test/modules/md/test_720_wildcard.py
@@ -44,6 +44,15 @@ class TestWildcard:
assert md
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'challenge-mismatch'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # None of offered challenge types
+ ],
+ matches = [
+ r'.*problem\[challenge-mismatch\].*'
+ ]
+ )
# test case: a wildcard certificate with ACMEv2, only dns-01 configured, invalid command path
def test_md_720_002(self, env):
@@ -67,6 +76,16 @@ class TestWildcard:
assert md
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'challenge-setup-failure'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # None of offered challenge types
+ ],
+ matches = [
+ r'.*problem\[challenge-setup-failure\].*',
+ r'.*setup command failed to execute.*'
+ ]
+ )
# variation, invalid cmd path, other challenges still get certificate for non-wildcard
def test_md_720_002b(self, env):
@@ -113,6 +132,15 @@ class TestWildcard:
assert md
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'challenge-setup-failure'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # None of offered challenge types
+ ],
+ matches = [
+ r'.*problem\[challenge-setup-failure\].*'
+ ]
+ )
# test case: a wildcard name certificate with ACMEv2, only dns-01 configured
def test_md_720_004(self, env):
diff --git a/test/modules/md/test_730_static.py b/test/modules/md/test_730_static.py
index f7f7b4b..891ae62 100644
--- a/test/modules/md/test_730_static.py
+++ b/test/modules/md/test_730_static.py
@@ -115,3 +115,10 @@ class TestStatic:
conf.add_vhost(domain)
conf.install()
assert env.apache_fail() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10170", # Managed Domain needs one MDCertificateKeyFile for each MDCertificateFile
+ "AH10171" # Managed Domain has MDCertificateKeyFile(s) but no MDCertificateFile
+ ]
+ )
diff --git a/test/modules/md/test_740_acme_errors.py b/test/modules/md/test_740_acme_errors.py
index 670c9ab..364aaca 100644
--- a/test/modules/md/test_740_acme_errors.py
+++ b/test/modules/md/test_740_acme_errors.py
@@ -46,6 +46,15 @@ class TestAcmeErrors:
assert md['renewal']['last']['detail'] == (
"Error creating new order :: Cannot issue for "
"\"%s\": Domain name contains an invalid character" % domains[1])
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # Order included DNS identifier with a value containing an illegal character
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:malformed.*'
+ ]
+ )
# test case: MD with 3 names, 2 invalid
#
@@ -70,3 +79,12 @@ class TestAcmeErrors:
"Error creating new order :: Cannot issue for")
assert md['renewal']['last']['subproblems']
assert len(md['renewal']['last']['subproblems']) == 2
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # Order included DNS identifier with a value containing an illegal character
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:malformed.*'
+ ]
+ )
diff --git a/test/modules/md/test_741_setup_errors.py b/test/modules/md/test_741_setup_errors.py
index 49b4e78..9ad79f0 100644
--- a/test/modules/md/test_741_setup_errors.py
+++ b/test/modules/md/test_741_setup_errors.py
@@ -46,3 +46,13 @@ class TestSetupErrors:
md = env.await_error(domain, errors=2, timeout=10)
assert md
assert md['renewal']['errors'] > 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # CA considers answer to challenge invalid
+ ],
+ matches = [
+ r'.*The key authorization file from the server did not match this challenge.*',
+ r'.*CA considers answer to challenge invalid.*'
+ ]
+ )
diff --git a/test/modules/md/test_750_eab.py b/test/modules/md/test_750_eab.py
index af1be95..aec7e89 100644
--- a/test/modules/md/test_750_eab.py
+++ b/test/modules/md/test_750_eab.py
@@ -37,6 +37,15 @@ class TestEab:
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:externalAccountRequired'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # ACME server policy requires newAccount requests must include a value for the 'externalAccountBinding' field
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:externalAccountRequired.*'
+ ]
+ )
def test_md_750_002(self, env):
# md with known EAB KID and non base64 hmac key configured
@@ -51,6 +60,15 @@ class TestEab:
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'apache:eab-hmac-invalid'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # external account binding HMAC value is not valid base64
+ ],
+ matches = [
+ r'.*problem\[apache:eab-hmac-invalid\].*'
+ ]
+ )
def test_md_750_003(self, env):
# md with empty EAB KID configured
@@ -64,7 +82,19 @@ class TestEab:
assert env.apache_restart() == 0
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
- assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:unauthorized'
+ assert md['renewal']['last']['problem'] in [
+ 'urn:ietf:params:acme:error:unauthorized',
+ 'urn:ietf:params:acme:error:malformed',
+ ]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # the field 'kid' references a key that is not known to the ACME server
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:(unauthorized|malformed).*'
+ ]
+ )
def test_md_750_004(self, env):
# md with unknown EAB KID configured
@@ -78,7 +108,19 @@ class TestEab:
assert env.apache_restart() == 0
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
- assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:unauthorized'
+ assert md['renewal']['last']['problem'] in [
+ 'urn:ietf:params:acme:error:unauthorized',
+ 'urn:ietf:params:acme:error:malformed',
+ ]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # the field 'kid' references a key that is not known to the ACME server
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:(unauthorized|malformed).*'
+ ]
+ )
def test_md_750_005(self, env):
# md with known EAB KID but wrong HMAC configured
@@ -92,7 +134,19 @@ class TestEab:
assert env.apache_restart() == 0
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
- assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:unauthorized'
+ assert md['renewal']['last']['problem'] in [
+ 'urn:ietf:params:acme:error:unauthorized',
+ 'urn:ietf:params:acme:error:malformed',
+ ]
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # external account binding JWS verification error: square/go-jose: error in cryptographic primitive
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:(unauthorized|malformed).*'
+ ]
+ )
def test_md_750_010(self, env):
# md with correct EAB configured
@@ -125,6 +179,15 @@ class TestEab:
md = env.await_error(domain_b)
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:externalAccountRequired'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # ACME server policy requires newAccount requests must include a value for the 'externalAccountBinding' field
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:externalAccountRequired.*'
+ ]
+ )
def test_md_750_012(self, env):
# first one md without EAB, then one with
@@ -144,6 +207,15 @@ class TestEab:
md = env.await_error(domain_a)
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:externalAccountRequired'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # ACME server policy requires newAccount requests must include a value for the 'externalAccountBinding' field
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:externalAccountRequired.*'
+ ]
+ )
def test_md_750_013(self, env):
# 2 mds with the same EAB, should one create a single account
@@ -215,6 +287,15 @@ class TestEab:
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:externalAccountRequired'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # ACME server policy requires newAccount requests must include a value for the 'externalAccountBinding' field
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:externalAccountRequired.*'
+ ]
+ )
def test_md_750_016(self, env):
# md with correct EAB, get cert, change to invalid EAB
@@ -241,6 +322,15 @@ class TestEab:
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:unauthorized'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # the field 'kid' references a key that is not known to the ACME server
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:unauthorized.*'
+ ]
+ )
def test_md_750_017(self, env):
# md without EAB explicitly set to none
@@ -257,6 +347,15 @@ class TestEab:
md = env.await_error(domain)
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:externalAccountRequired'
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # ACME server policy requires newAccount requests must include a value for the 'externalAccountBinding' field
+ ],
+ matches = [
+ r'.*urn:ietf:params:acme:error:externalAccountRequired.*'
+ ]
+ )
def test_md_750_018(self, env):
# md with EAB file that does not exist
diff --git a/test/modules/md/test_780_tailscale.py b/test/modules/md/test_780_tailscale.py
index 84a266b..27a2df4 100644
--- a/test/modules/md/test_780_tailscale.py
+++ b/test/modules/md/test_780_tailscale.py
@@ -140,6 +140,12 @@ class TestTailscale:
assert md['renewal']['last']['status-description'] == 'No such file or directory'
assert md['renewal']['last']['detail'] == \
f"tailscale socket not available, may not be up: {socket_path}"
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # retrieving certificate from tailscale
+ ]
+ )
# create a MD using `tailscale` as protocol, path to faker, should succeed
def test_md_780_002(self, env):
@@ -184,3 +190,9 @@ class TestTailscale:
assert md['renewal']['errors'] > 0
assert md['renewal']['last']['status-description'] == 'No such file or directory'
assert md['renewal']['last']['detail'] == "retrieving certificate from tailscale"
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # retrieving certificate from tailscale
+ ]
+ )
diff --git a/test/modules/md/test_790_failover.py b/test/modules/md/test_790_failover.py
index a939912..696161f 100644
--- a/test/modules/md/test_790_failover.py
+++ b/test/modules/md/test_790_failover.py
@@ -63,6 +63,15 @@ class TestFailover:
assert env.apache_restart() == 0
assert env.await_completion([domain])
env.check_md_complete(domain)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # Unsuccessful in contacting ACME server
+ ],
+ matches = [
+ r'.*Unsuccessful in contacting ACME server at .*'
+ ]
+ )
# set 3 ACME certificata authority, invalid + invalid + valid
def test_md_790_003(self, env):
@@ -85,3 +94,12 @@ class TestFailover:
assert env.apache_restart() == 0
assert env.await_completion([domain])
env.check_md_complete(domain)
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # Unsuccessful in contacting ACME server
+ ],
+ matches = [
+ r'.*Unsuccessful in contacting ACME server at .*'
+ ]
+ )
diff --git a/test/modules/md/test_900_notify.py b/test/modules/md/test_900_notify.py
index 30e0742..9d18da5 100644
--- a/test/modules/md/test_900_notify.py
+++ b/test/modules/md/test_900_notify.py
@@ -49,6 +49,12 @@ class TestNotify:
assert env.await_error(self.domain)
stat = env.get_md_status(self.domain)
assert stat["renewal"]["last"]["problem"] == "urn:org:apache:httpd:log:AH10108:"
+ #
+ env.httpd_error_log.ignore_recent(
+ matches = [
+ r'.*urn:org:apache:httpd:log:AH10108:.*'
+ ]
+ )
# test: valid notify cmd that fails, check error
def test_md_900_002(self, env):
@@ -61,6 +67,14 @@ class TestNotify:
assert env.await_error(self.domain)
stat = env.get_md_status(self.domain)
assert stat["renewal"]["last"]["problem"] == "urn:org:apache:httpd:log:AH10108:"
+ #
+ env.httpd_error_log.ignore_recent(
+ matches = [
+ r'.*urn:org:apache:httpd:log:AH10108:.*',
+ r'.*urn:org:apache:httpd:log:AH10109:.*'
+ r'.*problem\[challenge-setup-failure\].*',
+ ]
+ )
# test: valid notify that logs to file
def test_md_900_010(self, env):
diff --git a/test/modules/md/test_901_message.py b/test/modules/md/test_901_message.py
index 8d03bfd..b18cfd3 100644
--- a/test/modules/md/test_901_message.py
+++ b/test/modules/md/test_901_message.py
@@ -46,6 +46,16 @@ class TestMessage:
stat = env.get_md_status(domain)
# this command should have failed and logged an error
assert stat["renewal"]["last"]["problem"] == "urn:org:apache:httpd:log:AH10109:"
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # None of the offered challenge types
+ ],
+ matches = [
+ r'.*urn:org:apache:httpd:log:AH10109:.*',
+ r'.*problem\[challenge-setup-failure\].*'
+ ]
+ )
# test: signup with configured message cmd that is valid but returns != 0
def test_md_901_002(self, env):
@@ -63,6 +73,16 @@ class TestMessage:
stat = env.get_md_status(domain)
# this command should have failed and logged an error
assert stat["renewal"]["last"]["problem"] == "urn:org:apache:httpd:log:AH10109:"
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # None of the offered challenge types
+ ],
+ matches = [
+ r'.*urn:org:apache:httpd:log:AH10109:.*',
+ r'.*problem\[challenge-setup-failure\].*'
+ ]
+ )
# test: signup with working message cmd and see that it logs the right things
def test_md_901_003(self, env):
@@ -247,7 +267,6 @@ class TestMessage:
assert job["last"]["problem"] == "urn:org:apache:httpd:log:AH10109:"
break
time.sleep(0.1)
- env.httpd_error_log.ignore_recent()
# reconfigure to a working notification command and restart
conf = MDConf(env)
@@ -294,4 +313,13 @@ class TestMessage:
stat = env.get_md_status(domain)
# this command should have failed and logged an error
assert stat["renewal"]["last"]["problem"] == "challenge-setup-failure"
-
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10056" # None of the offered challenge types
+ ],
+ matches = [
+ r'.*urn:org:apache:httpd:log:AH10109:.*',
+ r'.*problem\[challenge-setup-failure\].*'
+ ]
+ )
diff --git a/test/modules/md/test_920_status.py b/test/modules/md/test_920_status.py
index c89ce6d..6ad7087 100644
--- a/test/modules/md/test_920_status.py
+++ b/test/modules/md/test_920_status.py
@@ -243,3 +243,9 @@ Protocols h2 http/1.1 acme-tls/1
assert ktype in stat['cert']
if env.acme_server == 'boulder':
assert 'ocsp' in stat['cert'][ktype]
+ #
+ env.httpd_error_log.ignore_recent(
+ matches = [
+ r'.*certificate with serial \w+ has no OCSP responder URL.*'
+ ]
+ )
diff --git a/test/modules/proxy/conftest.py b/test/modules/proxy/conftest.py
index 23c5f14..7e6f4e7 100644
--- a/test/modules/proxy/conftest.py
+++ b/test/modules/proxy/conftest.py
@@ -29,23 +29,3 @@ def env(pytestconfig) -> ProxyTestEnv:
env.apache_access_log_clear()
env.httpd_error_log.clear_log()
return env
-
-
-@pytest.fixture(autouse=True, scope="package")
-def _session_scope(env):
- # we'd like to check the httpd error logs after the test suite has
- # run to catch anything unusual. For this, we setup the ignore list
- # of errors and warnings that we do expect.
- env.httpd_error_log.set_ignored_lognos([
- 'AH01144', # No protocol handler was valid for the URL
- ])
-
- env.httpd_error_log.add_ignored_patterns([
- #re.compile(r'.*urn:ietf:params:acme:error:.*'),
- ])
- yield
- assert env.apache_stop() == 0
- errors, warnings = env.httpd_error_log.get_missed()
- assert (len(errors), len(warnings)) == (0, 0),\
- f"apache logged {len(errors)} errors and {len(warnings)} warnings: \n"\
- "{0}\n{1}\n".format("\n".join(errors), "\n".join(warnings))
diff --git a/test/modules/proxy/env.py b/test/modules/proxy/env.py
index 9ed635c..098d4d4 100644
--- a/test/modules/proxy/env.py
+++ b/test/modules/proxy/env.py
@@ -1,7 +1,6 @@
import inspect
import logging
import os
-import re
import subprocess
from typing import Dict, Any
diff --git a/test/modules/proxy/test_02_unix.py b/test/modules/proxy/test_02_unix.py
index 7f3d4d5..0c39bc9 100644
--- a/test/modules/proxy/test_02_unix.py
+++ b/test/modules/proxy/test_02_unix.py
@@ -153,6 +153,12 @@ Host: {domain}
r2 = self.parse_response(rlines)
assert r2.response
assert r2.response['status'] == exp_status
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH01144" # No protocol handler was valid for the URL
+ ]
+ )
def parse_response(self, lines) -> ExecResult:
exp_body = False
diff --git a/test/modules/tls/conf.py b/test/modules/tls/conf.py
index ddeb91f..b34f746 100644
--- a/test/modules/tls/conf.py
+++ b/test/modules/tls/conf.py
@@ -13,7 +13,10 @@ class TlsTestConf(HttpdConf):
def start_tls_vhost(self, domains: List[str], port=None, ssl_module=None):
if ssl_module is None:
- ssl_module = 'mod_tls'
+ if not self.env.has_shared_module("tls"):
+ ssl_module = "mod_ssl"
+ else:
+ ssl_module = 'mod_tls'
super().start_vhost(domains=domains, port=port, doc_root=f"htdocs/{domains[0]}", ssl_module=ssl_module)
def end_tls_vhost(self):
@@ -39,8 +42,12 @@ class TlsTestConf(HttpdConf):
f" MDCertificateKeyFile {pkey_file}",
])
self.add("</MDomain>")
+ if self.env.has_shared_module("tls"):
+ ssl_module= "mod_tls"
+ else:
+ ssl_module= "mod_ssl"
super().add_vhost(domains=[domain], port=port, doc_root=f"htdocs/{domain}",
- with_ssl=True, with_certificates=False, ssl_module='mod_tls')
+ with_ssl=True, with_certificates=False, ssl_module=ssl_module)
def add_md_base(self, domain: str):
self.add([
diff --git a/test/modules/tls/conftest.py b/test/modules/tls/conftest.py
index cde4be6..c7cb858 100644
--- a/test/modules/tls/conftest.py
+++ b/test/modules/tls/conftest.py
@@ -31,9 +31,3 @@ def env(pytestconfig) -> TlsTestEnv:
env.apache_access_log_clear()
env.httpd_error_log.clear_log()
return env
-
-
-@pytest.fixture(autouse=True, scope="package")
-def _session_scope(env):
- yield
- assert env.apache_stop() == 0
diff --git a/test/modules/tls/env.py b/test/modules/tls/env.py
index 0e457bf..6afc472 100644
--- a/test/modules/tls/env.py
+++ b/test/modules/tls/env.py
@@ -129,7 +129,10 @@ class TlsTestEnv(HttpdTestEnv):
]),
CertificateSpec(name="user1", client=True, single_file=True),
])
- self.add_httpd_log_modules(['tls'])
+ if not HttpdTestEnv.has_shared_module("tls"):
+ self.add_httpd_log_modules(['ssl'])
+ else:
+ self.add_httpd_log_modules(['tls'])
def setup_httpd(self, setup: TlsTestSetup = None):
diff --git a/test/modules/tls/test_02_conf.py b/test/modules/tls/test_02_conf.py
index 4d6aa60..88be80c 100644
--- a/test/modules/tls/test_02_conf.py
+++ b/test/modules/tls/test_02_conf.py
@@ -64,9 +64,15 @@ class TestConf:
])
def test_tls_02_conf_cert_listen_valid(self, env, listen: str):
conf = TlsTestConf(env=env)
- conf.add("TLSEngine {listen}".format(listen=listen))
- conf.install()
- assert env.apache_restart() == 0
+ if not env.has_shared_module("tls"):
+ # Without cert/key openssl will complain
+ conf.add("SSLEngine on");
+ conf.install()
+ assert env.apache_restart() == 1
+ else:
+ conf.add("TLSEngine {listen}".format(listen=listen))
+ conf.install()
+ assert env.apache_restart() == 0
def test_tls_02_conf_cert_listen_cert(self, env):
domain = env.domain_a
diff --git a/test/modules/tls/test_03_sni.py b/test/modules/tls/test_03_sni.py
index cf421c0..cbd142a 100644
--- a/test/modules/tls/test_03_sni.py
+++ b/test/modules/tls/test_03_sni.py
@@ -34,6 +34,12 @@ class TestSni:
domain_unknown = "unknown.test"
r = env.tls_get(domain_unknown, "/index.json")
assert r.exit_code != 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10353" # cannot decrypt peer's message
+ ]
+ )
def test_tls_03_sni_request_other_same_config(self, env):
# do we see the first vhost response for another domain with different certs?
@@ -44,6 +50,12 @@ class TestSni:
assert r.exit_code == 0
assert r.json is None
assert r.response['status'] == 421
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10345" # Connection host selected via SNI and request have incompatible TLS configurations
+ ]
+ )
def test_tls_03_sni_request_other_other_honor(self, env):
# do we see the first vhost response for an unknown domain?
@@ -60,6 +72,12 @@ class TestSni:
# request denied
assert r.exit_code == 0
assert r.json is None
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10345" # Connection host selected via SNI and request have incompatible TLS configurations
+ ]
+ )
@pytest.mark.skip('openssl behaviour changed on ventura, unreliable')
def test_tls_03_sni_bad_hostname(self, env):
diff --git a/test/modules/tls/test_06_ciphers.py b/test/modules/tls/test_06_ciphers.py
index 2e60bdd..4bedd69 100644
--- a/test/modules/tls/test_06_ciphers.py
+++ b/test/modules/tls/test_06_ciphers.py
@@ -176,16 +176,21 @@ class TestCiphers:
def test_tls_06_ciphers_pref_unsupported(self, env):
# a warning on preferring a known, but not supported cipher
- env.httpd_error_log.ignore_recent()
conf = TlsTestConf(env=env, extras={
env.domain_b: "TLSCiphersPrefer TLS_NULL_WITH_NULL_NULL"
})
conf.add_tls_vhosts(domains=[env.domain_a, env.domain_b])
conf.install()
- assert env.apache_restart() == 0
- (errors, warnings) = env.httpd_error_log.get_recent_count()
- assert errors == 0
- assert warnings == 2 # once on dry run, once on start
+ if not conf.env.has_shared_module("tls"):
+ assert env.apache_restart() != 0
+ else:
+ assert env.apache_restart() == 0
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH10319" # Server has TLSCiphersPrefer configured that are not supported by rustls
+ ]
+ )
def test_tls_06_ciphers_supp_unknown(self, env):
conf = TlsTestConf(env=env, extras={
@@ -197,13 +202,11 @@ class TestCiphers:
def test_tls_06_ciphers_supp_unsupported(self, env):
# no warnings on suppressing known, but not supported ciphers
- env.httpd_error_log.ignore_recent()
conf = TlsTestConf(env=env, extras={
env.domain_b: "TLSCiphersSuppress TLS_NULL_WITH_NULL_NULL"
})
conf.add_tls_vhosts(domains=[env.domain_a, env.domain_b])
conf.install()
+ if not conf.env.has_shared_module("tls"):
+ return
assert env.apache_restart() == 0
- (errors, warnings) = env.httpd_error_log.get_recent_count()
- assert errors == 0
- assert warnings == 0
diff --git a/test/modules/tls/test_08_vars.py b/test/modules/tls/test_08_vars.py
index a8df99a..0e3ee74 100644
--- a/test/modules/tls/test_08_vars.py
+++ b/test/modules/tls/test_08_vars.py
@@ -23,7 +23,10 @@ class TestVars:
def test_tls_08_vars_root(self, env):
# in domain_b root, the StdEnvVars is switch on
exp_proto = "TLSv1.2"
- exp_cipher = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
+ if env.has_shared_module("tls"):
+ exp_cipher = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
+ else:
+ exp_cipher = "ECDHE-ECDSA-AES256-GCM-SHA384"
options = [ '--tls-max', '1.2']
r = env.tls_get(env.domain_b, "/vars.py", options=options)
assert r.exit_code == 0, r.stderr
@@ -47,7 +50,12 @@ class TestVars:
def test_tls_08_vars_const(self, env, name: str, value: str):
r = env.tls_get(env.domain_b, f"/vars.py?name={name}")
assert r.exit_code == 0, r.stderr
- assert r.json == {name: value}, r.stdout
+ if env.has_shared_module("tls"):
+ assert r.json == {name: value}, r.stdout
+ else:
+ if name == "SSL_SECURE_RENEG":
+ value = "true"
+ assert r.json == {name: value}, r.stdout
@pytest.mark.parametrize("name, pattern", [
("SSL_VERSION_INTERFACE", r'mod_tls/\d+\.\d+\.\d+'),
@@ -57,4 +65,11 @@ class TestVars:
r = env.tls_get(env.domain_b, f"/vars.py?name={name}")
assert r.exit_code == 0, r.stderr
assert name in r.json
- assert re.match(pattern, r.json[name]), r.json
+ if env.has_shared_module("tls"):
+ assert re.match(pattern, r.json[name]), r.json
+ else:
+ if name == "SSL_VERSION_INTERFACE":
+ pattern = r'mod_ssl/\d+\.\d+\.\d+'
+ else:
+ pattern = r'OpenSSL/\d+\.\d+\.\d+'
+ assert re.match(pattern, r.json[name]), r.json
diff --git a/test/modules/tls/test_14_proxy_ssl.py b/test/modules/tls/test_14_proxy_ssl.py
index cefcbf6..87e04c2 100644
--- a/test/modules/tls/test_14_proxy_ssl.py
+++ b/test/modules/tls/test_14_proxy_ssl.py
@@ -2,6 +2,7 @@ import re
import pytest
from .conf import TlsTestConf
+from pyhttpd.env import HttpdTestEnv
class TestProxySSL:
@@ -9,6 +10,12 @@ class TestProxySSL:
@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
# add vhosts a+b and a ssl proxy from a to b
+ if not HttpdTestEnv.has_shared_module("tls"):
+ myoptions="SSLOptions +StdEnvVars"
+ myssl="mod_ssl"
+ else:
+ myoptions="TLSOptions +StdEnvVars"
+ myssl="mod_tls"
conf = TlsTestConf(env=env, extras={
'base': [
"LogLevel proxy:trace1 proxy_http:trace1 ssl:trace1 proxy_http2:trace1",
@@ -33,10 +40,10 @@ class TestProxySSL:
f'ProxyPass /proxy-ssl/ https://127.0.0.1:{env.https_port}/',
f'ProxyPass /proxy-local/ https://localhost:{env.https_port}/',
f'ProxyPass /proxy-h2-ssl/ h2://127.0.0.1:{env.https_port}/',
- "TLSOptions +StdEnvVars",
+ myoptions,
],
})
- conf.add_tls_vhosts(domains=[env.domain_a, env.domain_b])
+ conf.add_tls_vhosts(domains=[env.domain_a, env.domain_b], ssl_module=myssl)
conf.install()
assert env.apache_restart() == 0
@@ -48,6 +55,13 @@ class TestProxySSL:
# does not work, since SSLProxy* not configured
data = env.tls_get_json(env.domain_b, "/proxy-local/index.json")
assert data is None
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH01961", # failed to enable ssl support [Hint: if using mod_ssl, see SSLProxyEngine]
+ "AH00961" # failed to enable ssl support (mod_proxy)
+ ]
+ )
def test_tls_14_proxy_ssl_h2_get(self, env):
r = env.tls_get(env.domain_b, "/proxy-h2-ssl/index.json")
@@ -62,7 +76,24 @@ class TestProxySSL:
("SSL_CIPHER_EXPORT", "false"),
("SSL_CLIENT_VERIFY", "NONE"),
])
+ def test_tls_14_proxy_tsl_vars_const(self, env, name: str, value: str):
+ if not HttpdTestEnv.has_shared_module("tls"):
+ return
+ r = env.tls_get(env.domain_b, f"/proxy-ssl/vars.py?name={name}")
+ assert r.exit_code == 0, r.stderr
+ assert r.json == {name: value}, r.stdout
+
+ @pytest.mark.parametrize("name, value", [
+ ("SERVER_NAME", "b.mod-tls.test"),
+ ("SSL_SESSION_RESUMED", "Initial"),
+ ("SSL_SECURE_RENEG", "true"),
+ ("SSL_COMPRESS_METHOD", "NULL"),
+ ("SSL_CIPHER_EXPORT", "false"),
+ ("SSL_CLIENT_VERIFY", "NONE"),
+ ])
def test_tls_14_proxy_ssl_vars_const(self, env, name: str, value: str):
+ if HttpdTestEnv.has_shared_module("tls"):
+ return
r = env.tls_get(env.domain_b, f"/proxy-ssl/vars.py?name={name}")
assert r.exit_code == 0, r.stderr
assert r.json == {name: value}, r.stdout
@@ -71,7 +102,21 @@ class TestProxySSL:
("SSL_VERSION_INTERFACE", r'mod_tls/\d+\.\d+\.\d+'),
("SSL_VERSION_LIBRARY", r'rustls-ffi/\d+\.\d+\.\d+/rustls/\d+\.\d+(\.\d+)?'),
])
+ def test_tls_14_proxy_tsl_vars_match(self, env, name: str, pattern: str):
+ if not HttpdTestEnv.has_shared_module("tls"):
+ return
+ r = env.tls_get(env.domain_b, f"/proxy-ssl/vars.py?name={name}")
+ assert r.exit_code == 0, r.stderr
+ assert name in r.json
+ assert re.match(pattern, r.json[name]), r.json
+
+ @pytest.mark.parametrize("name, pattern", [
+ ("SSL_VERSION_INTERFACE", r'mod_ssl/\d+\.\d+\.\d+'),
+ ("SSL_VERSION_LIBRARY", r'OpenSSL/\d+\.\d+\.\d+'),
+ ])
def test_tls_14_proxy_ssl_vars_match(self, env, name: str, pattern: str):
+ if HttpdTestEnv.has_shared_module("tls"):
+ return
r = env.tls_get(env.domain_b, f"/proxy-ssl/vars.py?name={name}")
assert r.exit_code == 0, r.stderr
assert name in r.json
diff --git a/test/modules/tls/test_15_proxy_tls.py b/test/modules/tls/test_15_proxy_tls.py
index f2f670d..e7eb103 100644
--- a/test/modules/tls/test_15_proxy_tls.py
+++ b/test/modules/tls/test_15_proxy_tls.py
@@ -1,10 +1,11 @@
-import re
from datetime import timedelta
import pytest
from .conf import TlsTestConf
+from pyhttpd.env import HttpdTestEnv
+@pytest.mark.skipif(condition=not HttpdTestEnv.has_shared_module("tls"), reason="no mod_tls available")
class TestProxyTLS:
@@ -53,6 +54,13 @@ class TestProxyTLS:
# does not work, since SSLProxy* not configured
data = env.tls_get_json(env.domain_b, "/proxy-local/index.json")
assert data is None
+ #
+ env.httpd_error_log.ignore_recent(
+ lognos = [
+ "AH01961", # failed to enable ssl support [Hint: if using mod_ssl, see SSLProxyEngine]
+ "AH00961" # failed to enable ssl support (mod_proxy)
+ ]
+ )
def test_tls_15_proxy_tls_h2_get(self, env):
r = env.tls_get(env.domain_b, "/proxy-h2-tls/index.json")
diff --git a/test/modules/tls/test_16_proxy_mixed.py b/test/modules/tls/test_16_proxy_mixed.py
index ca08236..88b351f 100644
--- a/test/modules/tls/test_16_proxy_mixed.py
+++ b/test/modules/tls/test_16_proxy_mixed.py
@@ -3,6 +3,9 @@ import time
import pytest
from .conf import TlsTestConf
+from pyhttpd.env import HttpdTestEnv
+
+@pytest.mark.skipif(condition=not HttpdTestEnv.has_shared_module("tls"), reason="no mod_tls available")
class TestProxyMixed:
diff --git a/test/modules/tls/test_17_proxy_machine_cert.py b/test/modules/tls/test_17_proxy_machine_cert.py
index 7b5ef44..a5410d6 100644
--- a/test/modules/tls/test_17_proxy_machine_cert.py
+++ b/test/modules/tls/test_17_proxy_machine_cert.py
@@ -3,8 +3,9 @@ import os
import pytest
from .conf import TlsTestConf
+from pyhttpd.env import HttpdTestEnv
-
+@pytest.mark.skipif(condition=not HttpdTestEnv.has_shared_module("tls"), reason="no mod_tls available")
class TestProxyMachineCert:
@pytest.fixture(autouse=True, scope='class')