mirror of
https://github.com/django/django.git
synced 2025-11-17 10:43:25 +00:00
Merge 25352e72ba into 5c60763561
This commit is contained in:
commit
5fd7d7cc47
9 changed files with 130 additions and 28 deletions
|
|
@ -57,6 +57,7 @@ class ASGIRequest(HttpRequest):
|
|||
self.path_info = scope["path"].removeprefix(self.script_name)
|
||||
else:
|
||||
self.path_info = scope["path"]
|
||||
self.path_info_is_empty = not bool(self.path_info)
|
||||
# HTTP basics.
|
||||
self.method = self.scope["method"].upper()
|
||||
# Ensure query string is encoded correctly.
|
||||
|
|
|
|||
|
|
@ -58,7 +58,16 @@ class WSGIRequest(HttpRequest):
|
|||
script_name = get_script_name(environ)
|
||||
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
|
||||
# trailing slash), operate as if '/' was requested.
|
||||
path_info = get_path_info(environ) or "/"
|
||||
path_info = environ_path_info = get_path_info(environ)
|
||||
if not path_info:
|
||||
# Sometimes PATH_INFO exists, but is empty (e.g. accessing
|
||||
# the SCRIPT_NAME URL without a trailing slash). We really need to
|
||||
# operate as if they'd requested '/'. Not amazingly nice to force
|
||||
# the path like this, but should be harmless.
|
||||
self.path_info_is_empty = True
|
||||
path_info = "/"
|
||||
else:
|
||||
self.path_info_is_empty = False
|
||||
self.environ = environ
|
||||
self.path_info = path_info
|
||||
# be careful to only replace the first slash in the path because of
|
||||
|
|
@ -66,7 +75,7 @@ class WSGIRequest(HttpRequest):
|
|||
# stated in RFC 3986.
|
||||
self.path = "%s/%s" % (script_name.rstrip("/"), path_info.replace("/", "", 1))
|
||||
self.META = environ
|
||||
self.META["PATH_INFO"] = path_info
|
||||
self.META["PATH_INFO"] = environ_path_info
|
||||
self.META["SCRIPT_NAME"] = script_name
|
||||
self.method = environ["REQUEST_METHOD"].upper()
|
||||
# Set content_type, content_params, and encoding.
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class HttpRequest:
|
|||
|
||||
self.path = ""
|
||||
self.path_info = ""
|
||||
self.path_info_is_empty = True
|
||||
self.method = None
|
||||
self.resolver_match = None
|
||||
self.content_type = None
|
||||
|
|
|
|||
|
|
@ -46,28 +46,35 @@ class CommonMiddleware(MiddlewareMixin):
|
|||
|
||||
# Check for a redirect based on settings.PREPEND_WWW
|
||||
host = request.get_host()
|
||||
must_prepend = settings.PREPEND_WWW and host and not host.startswith("www.")
|
||||
redirect_url = f"{request.scheme}://www.{host}" if must_prepend else ""
|
||||
|
||||
if settings.PREPEND_WWW and host and not host.startswith("www."):
|
||||
# Check if we also need to append a slash so we can do it all
|
||||
# with a single redirect. (This check may be somewhat expensive,
|
||||
# so we only do it if we already know we're sending a redirect,
|
||||
# or in process_response if we get a 404.)
|
||||
if self.should_redirect_with_slash(request):
|
||||
path = self.get_full_path_with_slash(request)
|
||||
else:
|
||||
path = request.get_full_path()
|
||||
# Check if a slash should be appended to the URL
|
||||
should_redirect_with_slash = self.should_redirect_with_slash(request)
|
||||
|
||||
return self.response_redirect_class(f"{request.scheme}://www.{host}{path}")
|
||||
# If a slash should be appended, use the full path with a slash.
|
||||
# Otherwise, just get the full path without forcing a slash.
|
||||
if should_redirect_with_slash:
|
||||
path = self.get_full_path_with_slash(request)
|
||||
else:
|
||||
path = request.get_full_path()
|
||||
|
||||
# If it's needed to redirect either based on settings.PREPEND_WWW
|
||||
# or to append a slash, do so.
|
||||
if redirect_url or should_redirect_with_slash:
|
||||
redirect_url = f"{redirect_url}{path}"
|
||||
return self.response_redirect_class(redirect_url)
|
||||
|
||||
def should_redirect_with_slash(self, request):
|
||||
"""
|
||||
Return True if settings.APPEND_SLASH is True and appending a slash to
|
||||
the request path turns an invalid path into a valid one.
|
||||
"""
|
||||
if settings.APPEND_SLASH and not request.path_info.endswith("/"):
|
||||
path_info = "" if request.path_info_is_empty else request.path_info
|
||||
if settings.APPEND_SLASH and not path_info.endswith("/"):
|
||||
urlconf = getattr(request, "urlconf", None)
|
||||
if not is_valid_path(request.path_info, urlconf):
|
||||
match = is_valid_path("%s/" % request.path_info, urlconf)
|
||||
if not is_valid_path(path_info, urlconf):
|
||||
match = is_valid_path("%s/" % path_info, urlconf)
|
||||
if match:
|
||||
view = match.func
|
||||
return getattr(view, "should_append_slash", True)
|
||||
|
|
|
|||
|
|
@ -93,6 +93,52 @@ class HandlerTests(SimpleTestCase):
|
|||
# Expect "bad request" response
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@override_settings(ROOT_URLCONF="handlers.urls")
|
||||
def test_root_path_info_with_slash(self):
|
||||
"""
|
||||
If PATH_INFO is '/' and APPEND_SLASH is True, and a URL pattern
|
||||
is defined for '^/$', then Django should render a response
|
||||
from the corresponding view.
|
||||
"""
|
||||
environ = RequestFactory().get("/").environ
|
||||
handler = WSGIHandler()
|
||||
response = handler(environ, lambda *a, **k: None)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b"Index")
|
||||
|
||||
@override_settings(ROOT_URLCONF="handlers.urls")
|
||||
def test_root_path_info_without_slash(self):
|
||||
"""
|
||||
If PATH_INFO is empty and APPEND_SLASH is True, and a url pattern
|
||||
is defined for '^/$' but not for '^$', then CommonMiddleware should
|
||||
issue a redirect.
|
||||
"""
|
||||
environ = RequestFactory().get("").environ
|
||||
handler = WSGIHandler()
|
||||
response = handler(environ, lambda *a, **k: None)
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertEqual(response.url, "/")
|
||||
|
||||
@override_settings(APPEND_SLASH=False, ROOT_URLCONF="handlers.urls")
|
||||
def test_root_path_info_nonempty_script_name_no_append_slash(self):
|
||||
environ = RequestFactory().get("").environ
|
||||
environ["SCRIPT_NAME"] = "site-root"
|
||||
environ["PATH_INFO"] = ""
|
||||
handler = WSGIHandler()
|
||||
response = handler(environ, lambda *a, **k: None)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b"Index")
|
||||
|
||||
@override_settings(APPEND_SLASH=True, ROOT_URLCONF="handlers.urls")
|
||||
def test_root_path_info_nonempty_script_name_with_append_slash(self):
|
||||
environ = RequestFactory().get("").environ
|
||||
environ["SCRIPT_NAME"] = "site-root"
|
||||
environ["PATH_INFO"] = ""
|
||||
handler = WSGIHandler()
|
||||
response = handler(environ, lambda *a, **k: None)
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertEqual(response.url, "site-root/")
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF="handlers.urls", MIDDLEWARE=[])
|
||||
class TransactionsPerRequestTests(TransactionTestCase):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from django.urls import path
|
||||
from django.urls import path, re_path
|
||||
|
||||
from . import views
|
||||
|
||||
|
|
@ -18,4 +18,5 @@ urlpatterns = [
|
|||
path("malformed_post/", views.malformed_post),
|
||||
path("httpstatus_enum/", views.httpstatus_enum),
|
||||
path("unawaited/", views.async_unawaited),
|
||||
re_path("^$", views.index),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ from django.http import HttpResponse, StreamingHttpResponse
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
|
||||
def index(request):
|
||||
return HttpResponse(b"Index")
|
||||
|
||||
|
||||
def regular(request):
|
||||
return HttpResponse(b"regular content")
|
||||
|
||||
|
|
|
|||
|
|
@ -171,15 +171,15 @@ class CommonMiddlewareTest(SimpleTestCase):
|
|||
"""
|
||||
# Use 4 slashes because of RequestFactory behavior.
|
||||
request = self.rf.get("////evil.com/security")
|
||||
r = CommonMiddleware(get_response_404).process_request(request)
|
||||
self.assertIsNone(r)
|
||||
res = CommonMiddleware(get_response_404).process_request(request)
|
||||
self.assertIsNone(res)
|
||||
response = HttpResponseNotFound()
|
||||
r = CommonMiddleware(get_response_404).process_response(request, response)
|
||||
self.assertEqual(r.status_code, 301)
|
||||
self.assertEqual(r.url, "/%2Fevil.com/security/")
|
||||
r = CommonMiddleware(get_response_404)(request)
|
||||
self.assertEqual(r.status_code, 301)
|
||||
self.assertEqual(r.url, "/%2Fevil.com/security/")
|
||||
res = CommonMiddleware(get_response_404).process_response(request, response)
|
||||
self.assertEqual(res.status_code, 301)
|
||||
self.assertEqual(res.url, "/%2Fevil.com/security/")
|
||||
res = CommonMiddleware(get_response_404)(request)
|
||||
self.assertEqual(res.status_code, 301)
|
||||
self.assertEqual(res.url, "/%2Fevil.com/security/")
|
||||
|
||||
@override_settings(APPEND_SLASH=False, PREPEND_WWW=True)
|
||||
def test_prepend_www(self):
|
||||
|
|
@ -320,6 +320,38 @@ class CommonMiddlewareTest(SimpleTestCase):
|
|||
self.assertEqual(r.status_code, 301)
|
||||
self.assertEqual(r.url, "http://www.testserver/customurlconf/slash/")
|
||||
|
||||
@override_settings(APPEND_SLASH=True)
|
||||
def test_empty_path_info_not_found_with_append_slash(self):
|
||||
req = HttpRequest()
|
||||
req.urlconf = "middleware.urls"
|
||||
res = HttpResponseNotFound()
|
||||
middleware_res = CommonMiddleware(get_response_empty).process_response(req, res)
|
||||
self.assertEqual(middleware_res.status_code, 301)
|
||||
|
||||
@override_settings(APPEND_SLASH=False)
|
||||
def test_empty_path_info_not_found_without_append_slash(self):
|
||||
req = HttpRequest()
|
||||
req.urlconf = "middleware.urls"
|
||||
res = HttpResponseNotFound()
|
||||
middleware_res = CommonMiddleware(get_response_empty).process_response(req, res)
|
||||
self.assertEqual(middleware_res.status_code, 404)
|
||||
|
||||
@override_settings(APPEND_SLASH=True)
|
||||
def test_empty_path_info_200_with_append_slash(self):
|
||||
req = HttpRequest()
|
||||
req.urlconf = "middleware.urls"
|
||||
res = HttpResponse("content")
|
||||
middleware_res = CommonMiddleware(get_response_empty).process_response(req, res)
|
||||
self.assertEqual(middleware_res.status_code, 200)
|
||||
|
||||
@override_settings(APPEND_SLASH=False)
|
||||
def test_empty_path_info_200_without_append_slash(self):
|
||||
req = HttpRequest()
|
||||
req.urlconf = "middleware.urls"
|
||||
res = HttpResponse("content")
|
||||
middleware_res = CommonMiddleware(get_response_empty).process_response(req, res)
|
||||
self.assertEqual(middleware_res.status_code, 200)
|
||||
|
||||
# Tests for the Content-Length header
|
||||
|
||||
def test_content_length_header_added(self):
|
||||
|
|
@ -377,11 +409,11 @@ class CommonMiddlewareTest(SimpleTestCase):
|
|||
"""Regression test for #15152"""
|
||||
request = self.rf.get("/slash")
|
||||
request.META["QUERY_STRING"] = "drink=café"
|
||||
r = CommonMiddleware(get_response_empty).process_request(request)
|
||||
self.assertIsNone(r)
|
||||
res = CommonMiddleware(get_response_empty).process_request(request)
|
||||
self.assertEqual(res.status_code, 301)
|
||||
response = HttpResponseNotFound()
|
||||
r = CommonMiddleware(get_response_empty).process_response(request, response)
|
||||
self.assertEqual(r.status_code, 301)
|
||||
res = CommonMiddleware(get_response_empty).process_response(request, response)
|
||||
self.assertEqual(res.status_code, 301)
|
||||
|
||||
def test_response_redirect_class(self):
|
||||
request = self.rf.get("/slash")
|
||||
|
|
|
|||
|
|
@ -24,4 +24,5 @@ urlpatterns = [
|
|||
path("csp-override-enforced/", views.csp_override_enforced),
|
||||
path("csp-override-report-only/", views.csp_override_report_only),
|
||||
path("csp-500/", views.csp_500),
|
||||
re_path(r"^$", views.empty_view),
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue