mirror of
https://github.com/apache/httpd.git
synced 2026-10-23 22:49:01 +03:00
Based on [PR665] install what is needed to build Apache httpd with vcpkg in window and fix some tests.
Done with the help of Claude Code Opus 4.6 this closes #731 M test/modules/http1/env.py M test/modules/http2/env.py M test/modules/http2/test_006_assets.py M test/modules/http2/test_103_upgrade.py M test/modules/http2/test_104_padding.py M test/modules/http2/test_107_frame_lengths.py M test/modules/http2/test_202_trailer.py M test/modules/http2/test_400_push.py M test/modules/http2/test_401_early_hints.py M test/modules/http2/test_710_load_post_static.py M test/modules/http2/test_711_load_post_cgi.py M test/pyhttpd/certs.py M test/pyhttpd/conf/httpd.conf.template M test/pyhttpd/env.py git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1937863 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@@ -1047,6 +1047,42 @@ IF(OPENSSL_FOUND)
|
||||
TARGET_LINK_LIBRARIES(abs ${EXTRA_LIBS} ${APR_LIBRARIES} ${OPENSSL_LIBRARIES} Ws2_32.lib)
|
||||
ENDIF()
|
||||
|
||||
IF(OPENSSL_FOUND AND CURL_FOUND AND JANSSON_FOUND)
|
||||
SET(a2md_md_lib_sources
|
||||
modules/md/md_acme.c modules/md/md_acme_acct.c
|
||||
modules/md/md_acme_authz.c modules/md/md_acme_drive.c
|
||||
modules/md/md_acmev2_drive.c modules/md/md_event.c
|
||||
modules/md/md_acme_order.c modules/md/md_core.c
|
||||
modules/md/md_curl.c modules/md/md_crypt.c
|
||||
modules/md/md_http.c modules/md/md_json.c
|
||||
modules/md/md_jws.c modules/md/md_log.c
|
||||
modules/md/md_result.c modules/md/md_reg.c
|
||||
modules/md/md_status.c modules/md/md_store.c
|
||||
modules/md/md_store_fs.c modules/md/md_time.c
|
||||
modules/md/md_ocsp.c modules/md/md_util.c
|
||||
)
|
||||
SET(a2md_cmd_sources
|
||||
support/a2md/md_cmd_main.c
|
||||
support/a2md/md_cmd_acme.c
|
||||
support/a2md/md_cmd_reg.c
|
||||
support/a2md/md_cmd_store.c
|
||||
)
|
||||
ADD_EXECUTABLE(a2md ${a2md_cmd_sources} ${a2md_md_lib_sources} build/win32/httpd.rc)
|
||||
SET(install_targets ${install_targets} a2md)
|
||||
SET(install_bin_pdb ${install_bin_pdb} $<TARGET_PDB_FILE:a2md>)
|
||||
SET(tmp_includes ${HTTPD_INCLUDE_DIRECTORIES}
|
||||
${OPENSSL_INCLUDE_DIR} ${CURL_INCLUDE_DIR} ${JANSSON_INCLUDE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/modules/md ${CMAKE_SOURCE_DIR}/support/a2md)
|
||||
SET_TARGET_PROPERTIES(a2md PROPERTIES INCLUDE_DIRECTORIES "${tmp_includes}")
|
||||
TARGET_COMPILE_DEFINITIONS(a2md PRIVATE
|
||||
"APP_FILE"
|
||||
"LONG_NAME=Apache HTTP Server a2md program"
|
||||
"BIN_NAME=a2md.exe"
|
||||
)
|
||||
TARGET_LINK_LIBRARIES(a2md ${EXTRA_LIBS} ${APR_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} ${JANSSON_LIBRARIES})
|
||||
ENDIF()
|
||||
|
||||
# Unit Test Suite
|
||||
IF(CHECK_FOUND)
|
||||
# Get all of the test cases.
|
||||
@@ -1161,22 +1197,45 @@ ENDIF()
|
||||
INSTALL(DIRECTORY DESTINATION logs)
|
||||
INSTALL(DIRECTORY DESTINATION cgi-bin)
|
||||
|
||||
INSTALL(CODE "EXECUTE_PROCESS(COMMAND perl \"${CMAKE_CURRENT_SOURCE_DIR}/build/cpR_noreplace.pl\" \"${CMAKE_CURRENT_SOURCE_DIR}/docs/error\" \"${CMAKE_INSTALL_PREFIX}/error\" ifdestmissing)")
|
||||
|
||||
INSTALL(CODE "EXECUTE_PROCESS(COMMAND perl \"${CMAKE_CURRENT_SOURCE_DIR}/build/cpR_noreplace.pl\" \"${CMAKE_CURRENT_SOURCE_DIR}/docs/docroot\" \"${CMAKE_INSTALL_PREFIX}/htdocs\" ifdestmissing)")
|
||||
|
||||
INSTALL(CODE "EXECUTE_PROCESS(COMMAND perl \"${CMAKE_CURRENT_SOURCE_DIR}/build/cpR_noreplace.pl\" \"${CMAKE_CURRENT_SOURCE_DIR}/docs/icons\" \"${CMAKE_INSTALL_PREFIX}/icons\" ifdestmissing)")
|
||||
# Copy error, htdocs, icons only if the destination dir doesn't exist yet
|
||||
# (same semantics as cpR_noreplace.pl ifdestmissing, without Perl)
|
||||
INSTALL(CODE "
|
||||
if(NOT IS_DIRECTORY \"${CMAKE_INSTALL_PREFIX}/error\")
|
||||
file(INSTALL \"${CMAKE_CURRENT_SOURCE_DIR}/docs/error/\" DESTINATION \"${CMAKE_INSTALL_PREFIX}/error\")
|
||||
endif()
|
||||
")
|
||||
INSTALL(CODE "
|
||||
if(NOT IS_DIRECTORY \"${CMAKE_INSTALL_PREFIX}/htdocs\")
|
||||
file(INSTALL \"${CMAKE_CURRENT_SOURCE_DIR}/docs/docroot/\" DESTINATION \"${CMAKE_INSTALL_PREFIX}/htdocs\")
|
||||
endif()
|
||||
")
|
||||
INSTALL(CODE "
|
||||
if(NOT IS_DIRECTORY \"${CMAKE_INSTALL_PREFIX}/icons\")
|
||||
file(INSTALL \"${CMAKE_CURRENT_SOURCE_DIR}/docs/icons/\" DESTINATION \"${CMAKE_INSTALL_PREFIX}/icons\")
|
||||
endif()
|
||||
")
|
||||
|
||||
# Copy generated .conf files from the build directory to the install,
|
||||
# without overwriting stuff already there.
|
||||
INSTALL(CODE "EXECUTE_PROCESS(COMMAND perl \"${CMAKE_CURRENT_SOURCE_DIR}/build/cpR_noreplace.pl\" \"${CMAKE_BINARY_DIR}/conf\" \"${CMAKE_INSTALL_PREFIX}/conf\")")
|
||||
# without overwriting stuff already there (no Perl dependency).
|
||||
INSTALL(CODE "
|
||||
file(GLOB_RECURSE _conf_files \"${CMAKE_BINARY_DIR}/conf/*\")
|
||||
foreach(_f \${_conf_files})
|
||||
# Skip conf/original/ — handled separately below
|
||||
string(FIND \"\${_f}\" \"/conf/original/\" _is_original)
|
||||
if(NOT \${_is_original} EQUAL -1)
|
||||
continue()
|
||||
endif()
|
||||
file(RELATIVE_PATH _rel \"${CMAKE_BINARY_DIR}/conf\" \"\${_f}\")
|
||||
set(_dest \"${CMAKE_INSTALL_PREFIX}/conf/\${_rel}\")
|
||||
if(NOT EXISTS \"\${_dest}\")
|
||||
get_filename_component(_dest_dir \"\${_dest}\" DIRECTORY)
|
||||
file(COPY \"\${_f}\" DESTINATION \"\${_dest_dir}\")
|
||||
endif()
|
||||
endforeach()
|
||||
")
|
||||
# But conf/original is supposed to be overwritten.
|
||||
# Note: FILE(TO_NATIVE_PATH ...) leaves the backslashes unescaped, which
|
||||
# generates warnings. Just do it manually since this build only supports
|
||||
# Windows anyway.
|
||||
STRING(REPLACE "/" "\\\\" native_src ${CMAKE_BINARY_DIR}/conf/original)
|
||||
STRING(REPLACE "/" "\\\\" native_dest ${CMAKE_INSTALL_PREFIX}/conf/original)
|
||||
INSTALL(CODE "EXECUTE_PROCESS(COMMAND xcopy \"${native_src}\" \"${native_dest}\" /Q /S /Y)")
|
||||
INSTALL(DIRECTORY "${CMAKE_BINARY_DIR}/conf/original/"
|
||||
DESTINATION conf/original)
|
||||
|
||||
STRING(TOUPPER "${CMAKE_BUILD_TYPE}" buildtype)
|
||||
MESSAGE(STATUS "")
|
||||
|
||||
125
test/conftest.py
125
test/conftest.py
@@ -1,5 +1,7 @@
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
@@ -8,6 +10,11 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '.'))
|
||||
|
||||
from pyhttpd.env import HttpdTestEnv
|
||||
|
||||
|
||||
def pytest_ignore_collect(collection_path, config):
|
||||
if "pytest_suite" in collection_path.parts:
|
||||
return True
|
||||
|
||||
def pytest_report_header(config, start_path):
|
||||
env = HttpdTestEnv()
|
||||
return f"[apache httpd: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
|
||||
@@ -19,6 +26,115 @@ def pytest_addoption(parser):
|
||||
parser.addoption("--archive", action="store", default=None,
|
||||
help='Archive the server dir after each test package '
|
||||
'to the specified folder')
|
||||
parser.addoption("--httpd", action="store", default=None,
|
||||
help="path to httpd binary (used to derive prefix, version, etc.)")
|
||||
parser.addoption("--conf", action="store", default=None,
|
||||
help="path to the installed httpd.conf (for LoadModule discovery "
|
||||
"when apxs is not available, e.g. on Windows)")
|
||||
|
||||
|
||||
def _generate_config_ini(config):
|
||||
"""Generate a config.ini from --httpd/--conf when no config.ini exists."""
|
||||
httpd_opt = config.getoption("--httpd")
|
||||
conf_opt = config.getoption("--conf")
|
||||
if not httpd_opt:
|
||||
return
|
||||
|
||||
httpd_path = os.path.abspath(httpd_opt)
|
||||
prefix = os.path.dirname(os.path.dirname(httpd_path))
|
||||
bindir = os.path.join(prefix, "bin")
|
||||
libexecdir = os.path.join(prefix, "modules")
|
||||
is_win = sys.platform == "win32"
|
||||
|
||||
# version from httpd -v
|
||||
version = "2.5.0"
|
||||
try:
|
||||
p = subprocess.run([httpd_path, "-v"], capture_output=True, text=True)
|
||||
m = re.search(r"Apache/(\S+)", p.stdout)
|
||||
if m:
|
||||
version = m.group(1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# modules and mpm from conf file
|
||||
dso_modules = []
|
||||
mpm_modules = []
|
||||
if conf_opt:
|
||||
conf_path = os.path.abspath(conf_opt)
|
||||
try:
|
||||
with open(conf_path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("#"):
|
||||
continue
|
||||
m = re.match(r"LoadModule\s+(\S+)_module", line)
|
||||
if m:
|
||||
name = m.group(1)
|
||||
if name.startswith("mpm_"):
|
||||
mpm_modules.append(name)
|
||||
else:
|
||||
dso_modules.append(name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not mpm_modules:
|
||||
mpm_modules = ["mpm_winnt"] if is_win else ["mpm_event"]
|
||||
if is_win and "cgid" in dso_modules and "cgi" not in dso_modules:
|
||||
dso_modules = ["cgi" if m == "cgid" else m for m in dso_modules]
|
||||
|
||||
apachectl = httpd_path if is_win else os.path.join(bindir, "apachectl")
|
||||
if is_win:
|
||||
prefix_curl = os.path.join(prefix, "bin", "curl.exe")
|
||||
curl_bin = prefix_curl if os.path.isfile(prefix_curl) else "curl.exe"
|
||||
else:
|
||||
curl_bin = "curl"
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
gen_dir = os.path.join(test_dir, "gen")
|
||||
|
||||
ini_content = f"""[global]
|
||||
prefix = {prefix}
|
||||
exec_prefix = ${{prefix}}
|
||||
bindir = ${{prefix}}/bin
|
||||
sbindir = ${{exec_prefix}}/sbin
|
||||
libdir = ${{exec_prefix}}/lib
|
||||
libexecdir = ${{exec_prefix}}/modules
|
||||
|
||||
apr_bindir = ${{prefix}}
|
||||
apxs = ${{prefix}}/bin/apxs
|
||||
apachectl = {apachectl}
|
||||
|
||||
curl_bin = {curl_bin}
|
||||
nghttp = nghttp
|
||||
h2load = h2load
|
||||
|
||||
pre_built_test_binaries_dir = ${{libexecdir}}
|
||||
|
||||
[httpd]
|
||||
version = {version}
|
||||
name = httpd
|
||||
dso_modules = {' '.join(dso_modules)}
|
||||
mpm_modules = {' '.join(mpm_modules)}
|
||||
|
||||
[test]
|
||||
gen_dir = {gen_dir}
|
||||
http_port = 5002
|
||||
https_port = 5001
|
||||
proxy_port = 5003
|
||||
http_port2 = 5004
|
||||
ws_port = 5100
|
||||
http_tld = tests.httpd.apache.org
|
||||
test_dir = {test_dir}
|
||||
test_src_dir = {test_dir}
|
||||
"""
|
||||
ini_path = os.path.join(test_dir, "pyhttpd", "config.ini")
|
||||
with open(ini_path, "w") as f:
|
||||
f.write(ini_content)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
httpd_opt = config.getoption("--httpd", default=None)
|
||||
if httpd_opt and sys.platform == "win32":
|
||||
_generate_config_ini(config)
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
@@ -52,6 +168,15 @@ def _module_scope(env):
|
||||
def _package_scope(env, request):
|
||||
env.httpd_error_log.clear_ignored_matches()
|
||||
env.httpd_error_log.clear_ignored_lognos()
|
||||
if sys.platform == "win32":
|
||||
env.httpd_error_log.add_ignored_lognos([
|
||||
"AH00098",
|
||||
"AH10373",
|
||||
"AH00336",
|
||||
"AH00337",
|
||||
"AH00338",
|
||||
"AH00341",
|
||||
])
|
||||
yield
|
||||
assert env.apache_stop() == 0
|
||||
env.check_error_log()
|
||||
|
||||
@@ -30,7 +30,7 @@ class AAATestEnv(HttpdTestEnv):
|
||||
def __init__(self, pytestconfig=None):
|
||||
super().__init__(pytestconfig=pytestconfig)
|
||||
self.add_httpd_log_modules(["auth_digest", "authn_file", "authz_core"])
|
||||
self._digest_pwfile = os.path.join(self.server_dir, "digest.passwd")
|
||||
self._digest_pwfile = f"{self.server_dir}/digest.passwd"
|
||||
|
||||
def setup_httpd(self, setup: HttpdTestSetup = None):
|
||||
super().setup_httpd(setup=AAATestSetup(env=self))
|
||||
|
||||
@@ -24,19 +24,24 @@ class H1TestSetup(HttpdTestSetup):
|
||||
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}")
|
||||
module_dir = self.env.test_modules_dir
|
||||
if not self.env.isWindows:
|
||||
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}")
|
||||
|
||||
module_dir = f"{local_dir}/mod_h1test/.libs"
|
||||
|
||||
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")
|
||||
fd.write(f"LoadModule h1test_module \"{module_dir}/mod_h1test.so\"\n")
|
||||
|
||||
def _setup_data_1k_1m(self):
|
||||
s90 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"
|
||||
|
||||
@@ -25,19 +25,24 @@ class H2TestSetup(HttpdTestSetup):
|
||||
self._setup_data_1k_1m()
|
||||
|
||||
def _add_h2test(self):
|
||||
local_dir = os.path.dirname(inspect.getfile(H2TestSetup))
|
||||
p = subprocess.run([self.env.apxs, '-c', 'mod_h2test.c'],
|
||||
capture_output=True,
|
||||
cwd=os.path.join(local_dir, 'mod_h2test'))
|
||||
rv = p.returncode
|
||||
if rv != 0:
|
||||
log.error(f"compiling md_h2test failed: {p.stderr}")
|
||||
raise Exception(f"compiling md_h2test failed: {p.stderr}")
|
||||
module_dir = self.env.test_modules_dir
|
||||
if not self.env.isWindows:
|
||||
local_dir = os.path.dirname(inspect.getfile(H2TestSetup))
|
||||
p = subprocess.run([self.env.apxs, '-c', 'mod_h2test.c'],
|
||||
capture_output=True,
|
||||
cwd=os.path.join(local_dir, 'mod_h2test'))
|
||||
|
||||
rv = p.returncode
|
||||
if rv != 0:
|
||||
log.error(f"compiling md_h2test failed: {p.stderr}")
|
||||
raise Exception(f"compiling md_h2test failed: {p.stderr}")
|
||||
|
||||
module_dir = f"{local_dir}/mod_h2test/.libs"
|
||||
|
||||
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 h2test_module \"{local_dir}/mod_h2test/.libs/mod_h2test.so\"\n")
|
||||
fd.write(f"LoadModule h2test_module \"{module_dir}/mod_h2test.so\"\n")
|
||||
|
||||
def _setup_data_1k_1m(self):
|
||||
s90 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"
|
||||
|
||||
@@ -4,6 +4,7 @@ from .env import H2Conf, H2TestEnv
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_nghttp(), reason="nghttp not available")
|
||||
class TestAssets:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -4,6 +4,7 @@ from .env import H2Conf, H2TestEnv
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_nghttp(), reason="nghttp not available")
|
||||
class TestUpgrade:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -9,6 +9,7 @@ def frame_padding(payload, padbits):
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_nghttp(), reason="nghttp not available")
|
||||
class TestPadding:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -16,6 +16,7 @@ def mk_text_file(fpath: str, lines: int):
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_nghttp(), reason="nghttp not available")
|
||||
class TestFrameLengths:
|
||||
|
||||
URI_PATHS = []
|
||||
|
||||
@@ -14,6 +14,7 @@ def setup_data(env):
|
||||
# The trailer tests depend on "nghttp" as no other client seems to be able to send those
|
||||
# rare things.
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_nghttp(), reason="nghttp not available")
|
||||
class TestTrailers:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -6,6 +6,7 @@ from .env import H2Conf, H2TestEnv
|
||||
|
||||
# The push tests depend on "nghttp"
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_nghttp(), reason="nghttp not available")
|
||||
class TestPush:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -5,6 +5,7 @@ from .env import H2Conf, H2TestEnv
|
||||
|
||||
# The push tests depend on "nghttp"
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_nghttp(), reason="nghttp not available")
|
||||
class TestEarlyHints:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -5,6 +5,7 @@ from .env import H2Conf, H2TestEnv
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_h2load(), reason="h2load not available")
|
||||
class TestLoadPostStatic:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -5,6 +5,7 @@ from .env import H2Conf, H2TestEnv
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=H2TestEnv.is_unsupported(), reason="mod_http2 not supported here")
|
||||
@pytest.mark.skipif(not H2TestEnv().has_h2load(), reason="h2load not available")
|
||||
class TestLoadCgi:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
|
||||
@@ -174,7 +174,7 @@ class Credentials:
|
||||
|
||||
if spec.sub_specs:
|
||||
if self._store:
|
||||
sub_store = CertStore(fpath=os.path.join(self._store.path, creds.name))
|
||||
sub_store = CertStore(fpath=f"{self._store.path}/{creds.name}")
|
||||
creds.set_store(sub_store)
|
||||
subchain = chain.copy() if chain else []
|
||||
subchain.append(self)
|
||||
@@ -231,7 +231,7 @@ class CertStore:
|
||||
chain.append(creds)
|
||||
if not with_root and len(chain) > 1:
|
||||
chain = chain[:-1]
|
||||
chain_file = os.path.join(self._store_dir, f'{name}-{infix}.pem')
|
||||
chain_file = f"{self._store_dir}/{name}-{infix}.pem"
|
||||
with open(chain_file, "wb") as fd:
|
||||
for c in chain:
|
||||
fd.write(c.cert_pem)
|
||||
@@ -246,11 +246,11 @@ class CertStore:
|
||||
|
||||
def get_cert_file(self, name: str, key_type=None) -> str:
|
||||
key_infix = ".{0}".format(key_type) if key_type is not None else ""
|
||||
return os.path.join(self._store_dir, f'{name}{key_infix}.cert.pem')
|
||||
return f"{self._store_dir}/{name}{key_infix}.cert.pem"
|
||||
|
||||
def get_pkey_file(self, name: str, key_type=None) -> str:
|
||||
key_infix = ".{0}".format(key_type) if key_type is not None else ""
|
||||
return os.path.join(self._store_dir, f'{name}{key_infix}.pkey.pem')
|
||||
return f"{self._store_dir}/{name}{key_infix}.pkey.pem"
|
||||
|
||||
def load_pem_cert(self, fpath: str) -> x509.Certificate:
|
||||
with open(fpath) as fd:
|
||||
|
||||
@@ -60,4 +60,10 @@ RequestReadTimeout header=10 body=10
|
||||
Options +ExecCGI
|
||||
</Directory>
|
||||
|
||||
<IfModule mpm_winnt_module>
|
||||
<IfModule mod_cgi.c>
|
||||
ScriptInterpreterSource Script
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ import inspect
|
||||
import logging
|
||||
import re
|
||||
import os
|
||||
import signal
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
from datetime import datetime, timedelta
|
||||
from string import Template
|
||||
from typing import List, Optional
|
||||
@@ -92,6 +94,7 @@ class HttpdTestSetup:
|
||||
self.add_modules([self.env.ssl_module])
|
||||
self._make_modules_conf()
|
||||
self._make_htdocs()
|
||||
|
||||
self._add_aptest()
|
||||
self._build_clients()
|
||||
self.env.clear_curl_headerfiles()
|
||||
@@ -101,12 +104,13 @@ class HttpdTestSetup:
|
||||
os.makedirs(self.env.gen_dir)
|
||||
# wipe logs from any previous module run to avoid stale files in the archive
|
||||
if os.path.isdir(self.env.server_logs_dir):
|
||||
shutil.rmtree(self.env.server_logs_dir)
|
||||
os.makedirs(self.env.server_logs_dir)
|
||||
shutil.rmtree(self.env.server_logs_dir,
|
||||
ignore_errors=(sys.platform == "win32"))
|
||||
os.makedirs(self.env.server_logs_dir, exist_ok=True)
|
||||
|
||||
def _make_conf(self):
|
||||
# remove anything from another run/test suite
|
||||
conf_dest_dir = os.path.join(self.env.server_dir, 'conf')
|
||||
conf_dest_dir = f"{self.env.server_dir}/conf"
|
||||
if os.path.isdir(conf_dest_dir):
|
||||
shutil.rmtree(conf_dest_dir)
|
||||
for d in self._source_dirs:
|
||||
@@ -148,13 +152,16 @@ class HttpdTestSetup:
|
||||
with open(modules_conf, 'w') as fd:
|
||||
# issue load directives for all modules we want that are shared
|
||||
missing_mods = list()
|
||||
for m in self._modules:
|
||||
modules = self._modules
|
||||
if sys.platform == "win32":
|
||||
modules = ["cgi" if m == "cgid" else m for m in modules]
|
||||
for m in modules:
|
||||
match = re.match(r'^mod_(.+)$', m)
|
||||
if match:
|
||||
m = match.group(1)
|
||||
if m in loaded:
|
||||
continue
|
||||
mod_path = os.path.join(self.env.libexec_dir, f"mod_{m}.so")
|
||||
mod_path = f"{self.env.libexec_dir}/mod_{m}.so"
|
||||
if m in builtin_modules:
|
||||
fd.write(f"#built-in: LoadModule {m}_module \"{mod_path}\"\n")
|
||||
elif os.path.isfile(mod_path):
|
||||
@@ -170,7 +177,7 @@ class HttpdTestSetup:
|
||||
m = match.group(1)
|
||||
if m in loaded:
|
||||
continue
|
||||
mod_path = os.path.join(self.env.libexec_dir, f"mod_{m}.so")
|
||||
mod_path = f"{self.env.libexec_dir}/mod_{m}.so"
|
||||
if m not in builtin_modules and os.path.isfile(mod_path):
|
||||
fd.write(f"LoadModule {m}_module \"{mod_path}\"\n")
|
||||
loaded.add(m)
|
||||
@@ -181,7 +188,7 @@ class HttpdTestSetup:
|
||||
def _make_htdocs(self):
|
||||
if not os.path.exists(self.env.server_docs_dir):
|
||||
os.makedirs(self.env.server_docs_dir)
|
||||
dest_dir = os.path.join(self.env.server_dir, 'htdocs')
|
||||
dest_dir = f"{self.env.server_dir}/htdocs"
|
||||
# remove anything from another run/test suite
|
||||
if os.path.isdir(dest_dir):
|
||||
shutil.rmtree(dest_dir)
|
||||
@@ -189,30 +196,49 @@ class HttpdTestSetup:
|
||||
srcdocs = os.path.join(d, 'htdocs')
|
||||
if os.path.isdir(srcdocs):
|
||||
shutil.copytree(srcdocs, dest_dir, dirs_exist_ok=True)
|
||||
# make all contained .py scripts executable
|
||||
# make all contained .py scripts executable and fix shebangs on Windows
|
||||
for dirpath, _dirnames, filenames in os.walk(dest_dir):
|
||||
for fname in filenames:
|
||||
if re.match(r'.+\.py', fname):
|
||||
py_file = os.path.join(dirpath, fname)
|
||||
if sys.platform == "win32":
|
||||
self._fix_shebang(py_file)
|
||||
st = os.stat(py_file)
|
||||
os.chmod(py_file, st.st_mode | stat.S_IEXEC)
|
||||
|
||||
@staticmethod
|
||||
def _fix_shebang(py_file):
|
||||
python_path = sys.executable
|
||||
python_path = python_path.replace("\\", "/")
|
||||
with open(py_file, 'r') as f:
|
||||
lines = f.readlines()
|
||||
if lines and re.match(r'#!\s*/usr/bin/env\s+python', lines[0]):
|
||||
lines[0] = f"#!{python_path}\n"
|
||||
with open(py_file, 'w', newline='\n') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def _add_aptest(self):
|
||||
local_dir = os.path.dirname(inspect.getfile(HttpdTestSetup))
|
||||
p = subprocess.run([self.env.apxs, '-c', 'mod_aptest.c'],
|
||||
capture_output=True,
|
||||
cwd=os.path.join(local_dir, 'mod_aptest'))
|
||||
rv = p.returncode
|
||||
if rv != 0:
|
||||
log.error(f"compiling mod_aptest failed: {p.stderr}")
|
||||
raise Exception(f"compiling mod_aptest failed: {p.stderr}")
|
||||
module_dir = self.env.test_modules_dir
|
||||
if not self.env.isWindows:
|
||||
local_dir = os.path.dirname(inspect.getfile(HttpdTestSetup))
|
||||
p = subprocess.run([self.env.apxs, '-c', 'mod_aptest.c'],
|
||||
capture_output=True,
|
||||
cwd=os.path.join(local_dir, 'mod_aptest'))
|
||||
rv = p.returncode
|
||||
if rv != 0:
|
||||
log.error(f"compiling mod_aptest failed: {p.stderr}")
|
||||
raise Exception(f"compiling mod_aptest failed: {p.stderr}")
|
||||
|
||||
module_dir = f"{local_dir}/mod_aptest/.libs/"
|
||||
|
||||
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 aptest_module \"{local_dir}/mod_aptest/.libs/mod_aptest.so\"\n")
|
||||
# load our test module
|
||||
fd.write(f"LoadModule aptest_module \"{module_dir}/mod_aptest.so\"\n")
|
||||
|
||||
def _build_clients(self):
|
||||
if self.env.test_modules_dir:
|
||||
return
|
||||
clients_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(inspect.getfile(HttpdTestSetup))),
|
||||
'clients')
|
||||
@@ -269,8 +295,15 @@ class HttpdTestEnv:
|
||||
self._apxs = self.config.get('global', 'apxs')
|
||||
self._prefix = self.config.get('global', 'prefix')
|
||||
self._apachectl = self.config.get('global', 'apachectl')
|
||||
|
||||
self._libexec_dir = self.config.get('global', 'libexecdir')
|
||||
HttpdTestEnv.LIBEXEC_DIR = self._libexec_dir
|
||||
|
||||
if HttpdTestEnv.LIBEXEC_DIR is None:
|
||||
HttpdTestEnv.LIBEXEC_DIR = self._libexec_dir = self.get_apxs_var('LIBEXECDIR')
|
||||
|
||||
self._httpd_ver = self.config.get('httpd', 'version')
|
||||
|
||||
self._curl = self.config.get('global', 'curl_bin')
|
||||
if 'CURL' in os.environ:
|
||||
self._curl = os.environ['CURL']
|
||||
@@ -280,6 +313,8 @@ class HttpdTestEnv:
|
||||
self._h2load = self.config.get('global', 'h2load')
|
||||
if self._h2load is None:
|
||||
self._h2load = 'h2load'
|
||||
self._test_modules_dir = self.config.get('global', 'pre_built_test_binaries_dir',
|
||||
fallback=None)
|
||||
|
||||
self._http_port = int(self.config.get('test', 'http_port'))
|
||||
self._http_port2 = int(self.config.get('test', 'http_port2'))
|
||||
@@ -288,19 +323,29 @@ class HttpdTestEnv:
|
||||
self._ws_port = int(self.config.get('test', 'ws_port'))
|
||||
self._http_tld = self.config.get('test', 'http_tld')
|
||||
self._test_dir = self.config.get('test', 'test_dir')
|
||||
|
||||
# In cmake determining the test directory is a headache and this is a simple workaround
|
||||
if not self._test_dir:
|
||||
self._test_dir = os.getcwd() + "/pyhttpd" if "pyhttpd" not in os.getcwd() else os.getcwd()
|
||||
|
||||
self._clients_dir = os.path.join(os.path.dirname(self._test_dir), 'clients')
|
||||
self._gen_dir = self.config.get('test', 'gen_dir')
|
||||
|
||||
self._server_dir = os.path.join(self._gen_dir, 'apache')
|
||||
self._server_conf_dir = os.path.join(self._server_dir, "conf")
|
||||
self._server_docs_dir = os.path.join(self._server_dir, "htdocs")
|
||||
self._server_logs_dir = os.path.join(self.server_dir, "logs")
|
||||
self._server_access_log = os.path.join(self._server_logs_dir, "access_log")
|
||||
self._error_log = HttpdErrorLog(os.path.join(self._server_logs_dir, "error_log"))
|
||||
self._server_dir = self._server_dir.replace("\\","/")
|
||||
self._server_conf_dir = f"{self._server_dir}/conf"
|
||||
self._server_docs_dir = f"{self._server_dir}/htdocs"
|
||||
self._server_logs_dir = f"{self._server_dir}/logs"
|
||||
self._server_run_dir = f"{self._server_dir}/run"
|
||||
self._server_access_log = f"{self._server_logs_dir}/access_log"
|
||||
error_log_sep = "." if self.isWindows else "_"
|
||||
self._error_log = HttpdErrorLog(f"{self._server_logs_dir}/error{error_log_sep}log")
|
||||
self._apachectl_stderr = None
|
||||
self._httpd_proc = None
|
||||
|
||||
self._dso_modules = self.config.get('httpd', 'dso_modules').split(' ')
|
||||
self._mpm_modules = self.config.get('httpd', 'mpm_modules').split(' ')
|
||||
self._mpm_module = f"mpm_{os.environ['MPM']}" if 'MPM' in os.environ else 'mpm_event'
|
||||
self._mpm_module = f"mpm_{os.environ['MPM']}" if 'MPM' in os.environ else 'mpm_winnt' if self.isWindows else 'mpm_event'
|
||||
self._ssl_module = self.get_ssl_module()
|
||||
if len(self._ssl_module.strip()) == 0:
|
||||
self._ssl_module = None
|
||||
@@ -310,7 +355,7 @@ class HttpdTestEnv:
|
||||
self._https_base = f"https://{self._httpd_addr}:{self.https_port}"
|
||||
|
||||
self._verbosity = pytestconfig.option.verbose if pytestconfig is not None else 0
|
||||
self._test_conf = os.path.join(self._server_conf_dir, "test.conf")
|
||||
self._test_conf = f"{self._server_conf_dir}/test.conf"
|
||||
self._httpd_base_conf = []
|
||||
self._httpd_env = {}
|
||||
self._httpd_log_modules = ['aptest']
|
||||
@@ -353,7 +398,7 @@ class HttpdTestEnv:
|
||||
def issue_certs(self):
|
||||
if self._ca is None:
|
||||
self._ca = HttpdTestCA.create_root(name=self.http_tld,
|
||||
store_dir=os.path.join(self.server_dir, 'ca'),
|
||||
store_dir=f"{self.server_dir}/ca",
|
||||
key_type="rsa4096")
|
||||
self._ca.issue_certs(self._cert_specs)
|
||||
|
||||
@@ -538,6 +583,10 @@ class HttpdTestEnv:
|
||||
def server_logs_dir(self) -> str:
|
||||
return self._server_logs_dir
|
||||
|
||||
@property
|
||||
def server_run_dir(self) -> str:
|
||||
return self._server_run_dir
|
||||
|
||||
@property
|
||||
def libexec_dir(self) -> str:
|
||||
return HttpdTestEnv.LIBEXEC_DIR
|
||||
@@ -581,9 +630,17 @@ class HttpdTestEnv:
|
||||
self._current_test = val
|
||||
|
||||
@property
|
||||
def apachectl_stderr(self):
|
||||
def apachectl_stderr(self) -> str:
|
||||
return self._apachectl_stderr
|
||||
|
||||
@property
|
||||
def test_modules_dir(self) -> str:
|
||||
return self._test_modules_dir
|
||||
|
||||
@property
|
||||
def isWindows(self) -> bool:
|
||||
return platform.system().lower() == "windows"
|
||||
|
||||
def add_cert_specs(self, specs: List[CertificateSpec]):
|
||||
self._cert_specs.extend(specs)
|
||||
|
||||
@@ -656,7 +713,11 @@ class HttpdTestEnv:
|
||||
return False
|
||||
|
||||
def has_nghttp(self):
|
||||
return self._nghttp != ""
|
||||
if self._nghttp == "":
|
||||
return False
|
||||
if os.path.dirname(self._nghttp):
|
||||
return os.path.isfile(self._nghttp) and os.access(self._nghttp, os.X_OK)
|
||||
return self.has_tool(self._nghttp)
|
||||
|
||||
def has_nghttp_get_assets(self):
|
||||
if not self.has_nghttp():
|
||||
@@ -675,7 +736,7 @@ class HttpdTestEnv:
|
||||
return p.stdout.strip()
|
||||
|
||||
def get_httpd_version(self) -> str:
|
||||
return self.get_apxs_var("HTTPD_VERSION")
|
||||
return self._httpd_ver
|
||||
|
||||
def mkpath(self, path):
|
||||
if not os.path.exists(path):
|
||||
@@ -803,7 +864,7 @@ class HttpdTestEnv:
|
||||
conf_file = 'stop.conf' if cmd == 'stop' else 'httpd.conf'
|
||||
args = [self._apachectl,
|
||||
"-d", self.server_dir,
|
||||
"-f", os.path.join(self._server_dir, f'conf/{conf_file}'),
|
||||
"-f", f"{self._server_dir}/conf/{conf_file}",
|
||||
"-k", cmd]
|
||||
r = self.run(args, env=self._clean_path_env())
|
||||
self._apachectl_stderr = r.stderr
|
||||
@@ -811,7 +872,51 @@ class HttpdTestEnv:
|
||||
log.warning(f"failed: {r}")
|
||||
return r
|
||||
|
||||
def _win_httpd_args(self):
|
||||
httpd = os.path.join(self._bin_dir, 'httpd')
|
||||
return [httpd,
|
||||
"-d", self.server_dir,
|
||||
"-f", f"{self.server_dir}/conf/httpd.conf",
|
||||
"-DFOREGROUND"]
|
||||
|
||||
def _win_start(self) -> int:
|
||||
self._win_stop()
|
||||
args = self._win_httpd_args()
|
||||
log.debug(f"starting httpd: {args}")
|
||||
self._httpd_proc = subprocess.Popen(args, env=self._clean_path_env())
|
||||
time.sleep(1)
|
||||
rc = self._httpd_proc.poll()
|
||||
if rc is not None:
|
||||
log.error(f"httpd exited immediately with code {rc}")
|
||||
self._httpd_proc = None
|
||||
return -1
|
||||
timeout = timedelta(seconds=30)
|
||||
if self.is_live(self._http_base, timeout=timeout):
|
||||
return 0
|
||||
log.error(f"httpd not responding at {self._http_base}")
|
||||
rc = self._httpd_proc.poll()
|
||||
if rc is not None:
|
||||
log.error(f"httpd process died with code {rc}")
|
||||
return -1
|
||||
|
||||
def _win_stop(self) -> int:
|
||||
if self._httpd_proc is not None:
|
||||
log.debug("stopping httpd (terminate parent)")
|
||||
self._httpd_proc.terminate()
|
||||
try:
|
||||
self._httpd_proc.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
self._httpd_proc.kill()
|
||||
self._httpd_proc.wait(timeout=5)
|
||||
self._httpd_proc = None
|
||||
if not self.is_dead(self._http_base, timeout=timedelta(seconds=60)):
|
||||
log.warning("port still in use after stop")
|
||||
return 0
|
||||
|
||||
def apache_reload(self):
|
||||
if self.isWindows:
|
||||
self._win_stop()
|
||||
return self._win_start()
|
||||
r = self._run_apachectl("graceful")
|
||||
if r.exit_code == 0:
|
||||
timeout = timedelta(seconds=10)
|
||||
@@ -819,14 +924,21 @@ class HttpdTestEnv:
|
||||
return r.exit_code
|
||||
|
||||
def apache_restart(self):
|
||||
if self.isWindows:
|
||||
self._win_stop()
|
||||
return self._win_start()
|
||||
self.apache_stop()
|
||||
r = self._run_apachectl("start")
|
||||
if r.exit_code == 0:
|
||||
timeout = timedelta(seconds=10)
|
||||
return 0 if self.is_live(self._http_base, timeout=timeout) else -1
|
||||
return r.exit_code
|
||||
|
||||
|
||||
def apache_stop(self):
|
||||
if self.isWindows:
|
||||
self._win_stop()
|
||||
timeout = timedelta(seconds=10)
|
||||
return 0 if self.is_dead(self._http_base, timeout=timeout) else -1
|
||||
r = self._run_apachectl("stop")
|
||||
if r.exit_code == 0:
|
||||
timeout = timedelta(seconds=10)
|
||||
@@ -835,11 +947,18 @@ class HttpdTestEnv:
|
||||
|
||||
def apache_graceful_stop(self):
|
||||
log.debug("stop apache")
|
||||
if self.isWindows:
|
||||
self._win_stop()
|
||||
return 0 if self.is_dead() else -1
|
||||
self._run_apachectl("graceful-stop")
|
||||
return 0 if self.is_dead() else -1
|
||||
|
||||
def apache_fail(self):
|
||||
log.debug("expect apache fail")
|
||||
if self.isWindows:
|
||||
self._win_stop()
|
||||
rv = self._win_start()
|
||||
return 0 if rv != 0 else (0 if self.is_dead() else -1)
|
||||
self._run_apachectl("stop")
|
||||
rv = self._run_apachectl("start")
|
||||
if rv == 0:
|
||||
@@ -1029,7 +1148,8 @@ class HttpdTestEnv:
|
||||
def curl_protocol_version(self, url, timeout=5, options=None):
|
||||
if not options:
|
||||
options = []
|
||||
options.extend(["-w", "%{http_version}\n", "-o", "/dev/null"])
|
||||
devnull = "NUL" if sys.platform == "win32" else "/dev/null"
|
||||
options.extend(["-w", "%{http_version}\n", "-o", devnull])
|
||||
r = self.curl_raw(url, timeout=timeout, options=options)
|
||||
if r.exit_code == 0 and r.response:
|
||||
return r.response["body"].decode('utf-8').rstrip()
|
||||
|
||||
Reference in New Issue
Block a user