From 0a55fcbd6afd17db6e8fcf081adcc8843e7f9369 Mon Sep 17 00:00:00 2001 From: TowyTowy Date: Wed, 15 Jul 2026 11:31:37 +0200 Subject: [PATCH] Tilde-decode custom page path variables, refs #1732 Wildcard custom pages (e.g. templates/pages/{topic}.html) passed the raw tilde-encoded path segment straight into the template context, so a URL like /topic_hello~20world rendered "hello~20world" instead of "hello world" - and ids containing spaces or slashes never matched user data. Tilde-decode the captured groups, matching how the database/table/row url_vars are already decoded elsewhere in the router. Co-Authored-By: Claude --- datasette/app.py | 10 +++++++++- tests/test_custom_pages.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/datasette/app.py b/datasette/app.py index 0e31273d6c..677c28ad26 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -3001,7 +3001,15 @@ async def handle_404(self, request, send, exception=None): for regex, wildcard_template in page_routes: match = regex.match(route_path) if match is not None: - context.update(match.groupdict()) + # Tilde-decode captured path variables so templates see + # the real value - e.g. "hello world" not "hello~20world" + # https://github.com/simonw/datasette/issues/1732 + context.update( + { + key: tilde_decode(value) + for key, value in match.groupdict().items() + } + ) template = wildcard_template break diff --git a/tests/test_custom_pages.py b/tests/test_custom_pages.py index 86cdcc6bda..d93d31316c 100644 --- a/tests/test_custom_pages.py +++ b/tests/test_custom_pages.py @@ -92,6 +92,24 @@ def test_custom_route_pattern(custom_pages_client, path, expected): assert response.text.strip() == expected +@pytest.mark.parametrize( + "path,expected", + [ + # Tilde-encoded path variables should reach the template decoded + # https://github.com/simonw/datasette/issues/1732 + ("/topic_hello~20world", "Topic page for hello world"), + ("/topic_caf~C3~A9", "Topic page for café"), + ("/topic_a~2Fb/x~20y", "Slug: x y, Topic: a/b"), + ], +) +def test_custom_route_pattern_decodes_path_variables( + 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