from urllib.parse import quote import pytest from sanic.response import redirect, text @pytest.fixture def redirect_app(app): @app.route("/redirect_init") async def redirect_init(request): return redirect("/redirect_target") @app.route("/redirect_init_with_301") async def redirect_init_with_301(request): return redirect("/redirect_target", status=301) @app.route("/redirect_target") async def redirect_target(request): return text("OK") @app.route("/1") def handler1(request): return redirect("/2") @app.route("/2") def handler2(request): return redirect("/3") @app.route("/3") def handler3(request): return text("OK") @app.route("/redirect_with_header_injection") async def redirect_with_header_injection(request): return redirect("/unsafe\ntest-header: test-value\n\ntest-body") return app def test_redirect_default_302(redirect_app): """ We expect a 302 default status code and the headers to be set. """ request, response = redirect_app.test_client.get( "/redirect_init", allow_redirects=False ) assert response.status == 302 assert response.headers["Location"] == "/redirect_target" assert response.headers["Content-Type"] == "text/html; charset=utf-8" def test_redirect_headers_none(redirect_app): request, response = redirect_app.test_client.get( uri="/redirect_init", headers=None, allow_redirects=False ) assert response.status == 302 assert response.headers["Location"] == "/redirect_target" def test_redirect_with_301(redirect_app): """ Test redirection with a different status code. """ request, response = redirect_app.test_client.get( "/redirect_init_with_301", allow_redirects=False ) assert response.status == 301 assert response.headers["Location"] == "/redirect_target" def test_get_then_redirect_follow_redirect(redirect_app): """ With `allow_redirects` we expect a 200. """ request, response = redirect_app.test_client.get( "/redirect_init", allow_redirects=True ) assert response.status == 200 assert response.text == "OK" def test_chained_redirect(redirect_app): """Test test_client is working for redirection""" request, response = redirect_app.test_client.get("/1") assert request.url.endswith("/1") assert response.status == 200 assert response.text == "OK" try: assert response.url.endswith("/3") except AttributeError: assert response.url.path.endswith("/3") def test_redirect_with_header_injection(redirect_app): """ Test redirection to a URL with header and body injections. """ request, response = redirect_app.test_client.get( "/redirect_with_header_injection", allow_redirects=False ) assert response.status == 302 assert "test-header" not in response.headers assert not response.text.startswith("test-body") @pytest.mark.parametrize( "test_str", [ "sanic-test", "sanictest", "sanic test", ], ) def test_redirect_with_params(app, test_str): use_in_uri = quote(test_str) @app.route("/api/v1/test//") async def init_handler(request, test): return redirect(f"/api/v2/test/{use_in_uri}/") @app.route("/api/v2/test//", unquote=True) async def target_handler(request, test): assert test == quote(test_str) return text("OK") _, response = app.test_client.get(f"/api/v1/test/{use_in_uri}/") assert response.status == 200 assert response.body == b"OK"