Author SHA1 Message Date
klemek ada69f773e fix: force upgrade for all http methods
Python Lint CI / ruff-format-check (push) Successful in 1m2s
Python Lint CI / ruff (push) Successful in 1m4s
Python Lint CI / ty (push) Successful in 1m10s
Docker CI / docker-build (push) Successful in 2m32s
Python Test CI / coverage (push) Successful in 3m26s
2026-06-03 18:59:59 +02:00
klemek c5f3e53d88 feat: stapler challenge for self discovery 2026-06-03 18:56:07 +02:00
2 changed files with 86 additions and 16 deletions
+38 -2
View File
@@ -289,6 +289,7 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
protocol_version = "HTTP/1.1"
server_version = "StaplerServer/" + PKG_VERSION
CERTBOT_CHALLENGE_PATH = "/.well-known/acme-challenge"
STAPLER_CHALLENGE_PATH = "/.well-known/stapler"
UPDATE_PATH_REGEX = re.compile(r"^\/([\w-]+)\/?$")
GET_PATH_REGEX = re.compile(r"^\/([\w-]+)($|\/)")
HOST_PART_REGEX = re.compile(r"^([a-z0-9]|[a-z0-9][a-z0-9-]{,61}[a-z0-9])$")
@@ -405,6 +406,8 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
self._pre_log_request()
if self._proxy_or_redirect():
return None
if self._is_stapler_challenge(self.path):
return self.send_status_only(http.HTTPStatus.NO_CONTENT)
if self.path == "/" and self.host == self.default_host:
return self.send_basic_body(self.server_signature())
super().do_HEAD()
@@ -417,6 +420,8 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
self._pre_log_request()
if self._proxy_or_redirect():
return None
if self._is_stapler_challenge(self.path):
return self.send_status_only(http.HTTPStatus.NO_CONTENT)
if self.path == "/" and self.host == self.default_host:
return self.send_basic_body(self.server_signature())
super().do_GET()
@@ -545,7 +550,11 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
return True
def _proxy_or_redirect(self) -> bool:
if self.has_token or self._is_certbot_challenge(self.path):
if (
self.has_token
or self._is_certbot_challenge(self.path)
or self._is_stapler_challenge(self.path)
):
return False
if (page := self.__get_page(self.path)) is None:
return False
@@ -570,6 +579,9 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler, BaseHandler):
self.certbot_www + path
).resolve().is_relative_to(self.certbot_www)
def _is_stapler_challenge(self, path: str) -> bool:
return path.startswith(self.STAPLER_CHALLENGE_PATH)
@typing.override
def translate_path(self, path: str) -> str:
if self._is_certbot_challenge(path):
@@ -664,7 +676,10 @@ class UpgradeHandler(RequestHandler):
def do_HEAD(self) -> None:
with self.handle_errors():
self._pre_log_request()
self.send_redirect(f"https://{self.host}{self.path}")
if self._is_stapler_challenge(self.path):
self.send_status_only(http.HTTPStatus.NO_CONTENT)
else:
self.send_redirect(f"https://{self.host}{self.path}")
self.close_connection = True
def do_GET(self) -> None:
@@ -674,3 +689,24 @@ class UpgradeHandler(RequestHandler):
self.close_connection = True
else:
self.do_HEAD()
def do_POST(self) -> None:
self.do_HEAD()
def do_PUT(self) -> None:
self.do_HEAD()
def do_DELETE(self) -> None:
self.do_HEAD()
def do_OPTIONS(self) -> None:
self.do_HEAD()
def do_PATCH(self) -> None:
self.do_HEAD()
def do_CONNECT(self) -> None:
self.do_HEAD()
def do_TRACE(self) -> None:
self.do_HEAD()
+48 -14
View File
@@ -204,6 +204,17 @@ class TestRequestHandler(BaseHandlerTestCase):
):
handler.do_HEAD()
def test_do_head_stapler(self) -> None:
handler = self._get_handler("/.well-known/stapler/something")
with (
self.expects_status_only(
handler,
http.HTTPStatus.NO_CONTENT,
),
self.seal_mocks(),
):
handler.do_HEAD()
def test_do_head_forward(self) -> None:
handler = self._get_handler("/file")
with (
@@ -222,6 +233,17 @@ class TestRequestHandler(BaseHandlerTestCase):
):
handler.do_GET()
def test_do_get_stapler(self) -> None:
handler = self._get_handler("/.well-known/stapler/something")
with (
self.expects_status_only(
handler,
http.HTTPStatus.NO_CONTENT,
),
self.seal_mocks(),
):
handler.do_GET()
def test_do_get_forward_on_other_path(self) -> None:
handler = self._get_handler("/file")
with (
@@ -1336,21 +1358,22 @@ class TestUpgradeHandler(BaseHandlerTestCase):
handler.data_dir = self.data_dir
return handler
def test_do_get(self) -> None:
def test_do_upgrade(self) -> None:
handler = self._get_handler("/file")
with (
self.expects_status_only(
handler,
http.HTTPStatus.MOVED_PERMANENTLY,
headers={"Location": "https://localhost/file"},
),
self.patch(
"http.server.SimpleHTTPRequestHandler.do_GET",
count=0,
),
self.seal_mocks(),
):
handler.do_GET()
for method in [method.value for method in http.HTTPMethod]:
with (
self.subTest(None, method=method),
self.expects_status_only(
handler,
http.HTTPStatus.MOVED_PERMANENTLY,
headers={"Location": "https://localhost/file"},
),
self.patch(
f"http.server.SimpleHTTPRequestHandler.do_{method}", count=0
),
self.seal_mocks(),
):
getattr(handler, f"do_{method}")()
def test_do_get_certbot(self) -> None:
handler = self._get_handler("/.well-known/acme-challenge/abcde")
@@ -1373,3 +1396,14 @@ class TestUpgradeHandler(BaseHandlerTestCase):
self.seal_mocks(),
):
handler.do_HEAD()
def test_do_head_stapler(self) -> None:
handler = self._get_handler("/.well-known/stapler/something")
with (
self.expects_status_only(
handler,
http.HTTPStatus.NO_CONTENT,
),
self.seal_mocks(),
):
handler.do_HEAD()