datasette/tests/test_custom_pages.py
ATOM00blue 9fbd31df00 Block open redirect via backslash in redirect Location
The leading-slash collapse added for #2429 only stripped forward
slashes, so a path like /\example.com/ (or its %5C-encoded form) was
redirected to Location: /\example.com. Browsers treat a backslash as a
slash, so that resolves to //example.com and then https://example.com,
re-opening the open redirect.

Collapse leading backslashes as well as slashes. Extends the existing
slash-slash test to cover /\example.com/ and /\/example.com/.

Fixes #2680
2026-05-22 06:34:22 +05:30

116 lines
3.5 KiB
Python

import pathlib
import pytest
from .fixtures import make_app_client
TEST_TEMPLATE_DIRS = str(pathlib.Path(__file__).parent / "test_templates")
@pytest.fixture(scope="session")
def custom_pages_client():
with make_app_client(template_dir=TEST_TEMPLATE_DIRS) as client:
yield client
@pytest.fixture(scope="session")
def custom_pages_client_with_base_url():
with make_app_client(
template_dir=TEST_TEMPLATE_DIRS, settings={"base_url": "/prefix/"}
) as client:
yield client
def test_custom_pages_view_name(custom_pages_client):
response = custom_pages_client.get("/about")
assert response.status == 200
assert response.text == "ABOUT! view_name:page"
def test_request_is_available(custom_pages_client):
response = custom_pages_client.get("/request")
assert response.status == 200
assert response.text == "path:/request"
def test_custom_pages_with_base_url(custom_pages_client_with_base_url):
response = custom_pages_client_with_base_url.get("/prefix/request")
assert response.status == 200
assert response.text == "path:/prefix/request"
def test_custom_pages_nested(custom_pages_client):
response = custom_pages_client.get("/nested/nest")
assert response.status == 200
assert response.text == "Nest!"
response = custom_pages_client.get("/nested/nest2")
assert response.status == 404
def test_custom_status(custom_pages_client):
response = custom_pages_client.get("/202")
assert response.status == 202
assert response.text == "202!"
def test_custom_headers(custom_pages_client):
response = custom_pages_client.get("/headers")
assert response.status == 200
assert response.headers["x-this-is-foo"] == "foo"
assert response.headers["x-this-is-bar"] == "bar"
assert response.text == "FOOBAR"
def test_custom_content_type(custom_pages_client):
response = custom_pages_client.get("/atom")
assert response.status == 200
assert response.headers["content-type"] == "application/xml"
assert response.text == "<?xml ...>"
def test_redirect(custom_pages_client):
response = custom_pages_client.get("/redirect")
assert response.status == 302
assert response.headers["Location"] == "/example"
def test_redirect2(custom_pages_client):
response = custom_pages_client.get("/redirect2")
assert response.status == 301
assert response.headers["Location"] == "/example"
@pytest.mark.parametrize(
"path,expected",
[
("/route_Sally", "<p>Hello from Sally</p>"),
("/topic_python", "Topic page for python"),
("/topic_python/info", "Slug: info, Topic: python"),
],
)
def test_custom_route_pattern(custom_pages_client, path, expected):
response = custom_pages_client.get(path)
assert response.status == 200
assert response.text.strip() == expected
def test_custom_route_pattern_404(custom_pages_client):
response = custom_pages_client.get("/route_OhNo")
assert response.status == 404
assert "<h1>Error 404</h1>" in response.text
assert ">Oh no</" in response.text
@pytest.mark.parametrize(
"path",
(
# https://github.com/simonw/datasette/issues/2429
"//example.com/",
# https://github.com/simonw/datasette/issues/2680
# Browsers treat backslashes as slashes, so these are also open redirects
"/\\example.com/",
"/\\/example.com/",
),
)
def test_custom_route_pattern_open_redirect_302(custom_pages_client, path):
response = custom_pages_client.get(path)
assert response.status == 302
assert response.headers["location"] == "/example.com"