diff --git a/CHANGELOG.md b/CHANGELOG.md index edabdec..90fdcd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Using the following categories, list your changes in this order: ### Added - Support Python 3.13. +- Django `settings.py:SERVESTATIC_PRESERVE_QUERY_STRING_ON_REDIRECT` will forward the request query string when issuing redirects. ## [2.0.1] - 2024-09-13 diff --git a/docs/src/django-settings.md b/docs/src/django-settings.md index 054f7d4..e9e67ec 100644 --- a/docs/src/django-settings.md +++ b/docs/src/django-settings.md @@ -204,6 +204,16 @@ This setting is only effective if the `ServeStatic` storage backend is being use --- +## `SERVESTATIC_PRESERVE_QUERY_STRING_ON_REDIRECT` + +**Default:** `False` + +Set to `True` to preserve the request query string when issuing redirects. + +By default, ServeStatic will strip the query string when issuing a redirect. If this setting is enabled, the query string will instead be forwarded to the redirect location. + +--- + ## `SERVESTATIC_MANIFEST_STRICT` **Default:** `True` diff --git a/src/servestatic/base.py b/src/servestatic/base.py index 2c16e66..f48be2c 100644 --- a/src/servestatic/base.py +++ b/src/servestatic/base.py @@ -48,6 +48,7 @@ def __init__( add_headers_function: Callable[[Headers, str, str], None] | None = None, index_file: str | bool | None = None, immutable_file_test: Callable | str | None = None, + preserve_query_string_on_redirect: bool | None = None, ): self.autorefresh = autorefresh self.max_age = max_age @@ -60,6 +61,7 @@ def __init__( self.application = application self.files = {} self.directories = [] + self.preserve_query_string_on_redirect = preserve_query_string_on_redirect if index_file is True: self.index_file: str | None = "index.html" @@ -236,4 +238,8 @@ def redirect(self, from_url, to_url): msg = f"Cannot handle redirect: {from_url} > {to_url}" raise ValueError(msg) headers = {"Cache-Control": f"max-age={self.max_age}, public"} if self.max_age is not None else {} - return Redirect(relative_url, headers=headers) + return Redirect( + relative_url, + headers=headers, + preserve_query_string=self.preserve_query_string_on_redirect, + ) diff --git a/src/servestatic/middleware.py b/src/servestatic/middleware.py index b513d63..f5fbcfd 100644 --- a/src/servestatic/middleware.py +++ b/src/servestatic/middleware.py @@ -73,6 +73,11 @@ def __init__(self, get_response=None, settings=django_settings): force_script_name = getattr(settings, "FORCE_SCRIPT_NAME", None) static_url = getattr(settings, "STATIC_URL", None) root = getattr(settings, "SERVESTATIC_ROOT", None) + preserve_query_string_on_redirect = getattr( + django_settings, + "SERVESTATIC_PRESERVE_QUERY_STRING_ON_REDIRECT", + False, + ) super().__init__( application=None, @@ -84,6 +89,7 @@ def __init__(self, get_response=None, settings=django_settings): add_headers_function=add_headers_function, index_file=self.index_file, immutable_file_test=immutable_file_test, + preserve_query_string_on_redirect=preserve_query_string_on_redirect, ) if self.static_prefix is None: diff --git a/src/servestatic/responders.py b/src/servestatic/responders.py index 7b4e8a0..f1b8c80 100644 --- a/src/servestatic/responders.py +++ b/src/servestatic/responders.py @@ -315,12 +315,20 @@ def get_path_and_headers(self, request_headers): class Redirect: - def __init__(self, location, headers=None): + def __init__(self, location, headers=None, preserve_query_string=False): headers = list(headers.items()) if headers else [] headers.append(("Location", quote(location.encode("utf8")))) self.response = Response(HTTPStatus.FOUND, headers, None) + self.preserve_query_string = preserve_query_string def get_response(self, method, request_headers): + query_string = request_headers.get("QUERY_STRING") + if query_string and self.preserve_query_string: + headers = list(self.response.headers) + name, value = headers[-1] + value = "{}?{}".format(value, query_string) + headers[-1] = (name, value) + return Response(self.response.status, headers, None) return self.response async def aget_response(self, method, request_headers): diff --git a/tests/test_servestatic.py b/tests/test_servestatic.py index dd9c311..992dbb3 100644 --- a/tests/test_servestatic.py +++ b/tests/test_servestatic.py @@ -16,7 +16,7 @@ import pytest from servestatic import ServeStatic -from servestatic.responders import StaticFile +from servestatic.responders import Redirect, StaticFile from .utils import AppServer, Files @@ -376,3 +376,15 @@ def test_chunked_file_size_matches_range_with_range_header(): while response.file.read(1): file_size += 1 assert file_size == 14 + + +def test_redirect_strips_query_string_by_default(): + responder = Redirect("/redirect/to/here/") + response = responder.get_response("GET", {"QUERY_STRING": "foo=1&bar=2"}) + assert response.headers[0] == ("Location", "/redirect/to/here/") + + +def test_redirect_preserves_query_string_if_configured(): + responder = Redirect("/redirect/to/here/", preserve_query_string=True) + response = responder.get_response("GET", {"QUERY_STRING": "foo=1&bar=2"}) + assert response.headers[0] == ("Location", "/redirect/to/here/?foo=1&bar=2")