mirror of https://github.com/django/django.git
Fixed 35467 -- Replaced urlparse with urlsplit where appropriate.
This work should not generate any change of functionality, and `urlsplit` is approximately 6x faster. Most use cases of `urlparse` didn't touch the path, so they can be converted to `urlsplit` without any issue. Most of those which do use `.path`, simply parse the URL, mutate the querystring, then put them back together, which is also fine (so long as urlunsplit is used).
This commit is contained in:
parent
02dab94c7b
commit
ff308a0604
|
@ -6,7 +6,7 @@ import warnings
|
||||||
from functools import partial, update_wrapper
|
from functools import partial, update_wrapper
|
||||||
from urllib.parse import parse_qsl
|
from urllib.parse import parse_qsl
|
||||||
from urllib.parse import quote as urlquote
|
from urllib.parse import quote as urlquote
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -1384,7 +1384,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_preserved_qsl(self, request, preserved_filters):
|
def _get_preserved_qsl(self, request, preserved_filters):
|
||||||
query_string = urlparse(request.build_absolute_uri()).query
|
query_string = urlsplit(request.build_absolute_uri()).query
|
||||||
return parse_qsl(query_string.replace(preserved_filters, ""))
|
return parse_qsl(query_string.replace(preserved_filters, ""))
|
||||||
|
|
||||||
def response_add(self, request, obj, post_url_continue=None):
|
def response_add(self, request, obj, post_url_continue=None):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from urllib.parse import parse_qsl, unquote, urlparse, urlunparse
|
from urllib.parse import parse_qsl, unquote, urlsplit, urlunsplit
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.contrib.admin.utils import quote
|
from django.contrib.admin.utils import quote
|
||||||
|
@ -24,8 +24,8 @@ def add_preserved_filters(context, url, popup=False, to_field=None):
|
||||||
preserved_filters = context.get("preserved_filters")
|
preserved_filters = context.get("preserved_filters")
|
||||||
preserved_qsl = context.get("preserved_qsl")
|
preserved_qsl = context.get("preserved_qsl")
|
||||||
|
|
||||||
parsed_url = list(urlparse(url))
|
parsed_url = list(urlsplit(url))
|
||||||
parsed_qs = dict(parse_qsl(parsed_url[4]))
|
parsed_qs = dict(parse_qsl(parsed_url[3]))
|
||||||
merged_qs = {}
|
merged_qs = {}
|
||||||
|
|
||||||
if preserved_qsl:
|
if preserved_qsl:
|
||||||
|
@ -66,5 +66,5 @@ def add_preserved_filters(context, url, popup=False, to_field=None):
|
||||||
|
|
||||||
merged_qs.update(parsed_qs)
|
merged_qs.update(parsed_qs)
|
||||||
|
|
||||||
parsed_url[4] = urlencode(merged_qs)
|
parsed_url[3] = urlencode(merged_qs)
|
||||||
return urlunparse(parsed_url)
|
return urlunsplit(parsed_url)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync, sync_to_async
|
from asgiref.sync import async_to_sync, sync_to_async
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ def user_passes_test(
|
||||||
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
|
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
|
||||||
# If the login url is the same scheme and net location then just
|
# If the login url is the same scheme and net location then just
|
||||||
# use the path as the "next" url.
|
# use the path as the "next" url.
|
||||||
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
|
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
|
||||||
current_scheme, current_netloc = urlparse(path)[:2]
|
current_scheme, current_netloc = urlsplit(path)[:2]
|
||||||
if (not login_scheme or login_scheme == current_scheme) and (
|
if (not login_scheme or login_scheme == current_scheme) and (
|
||||||
not login_netloc or login_netloc == current_netloc
|
not login_netloc or login_netloc == current_netloc
|
||||||
):
|
):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
|
@ -74,8 +74,8 @@ class LoginRequiredMiddleware(MiddlewareMixin):
|
||||||
resolved_login_url = resolve_url(self.get_login_url(view_func))
|
resolved_login_url = resolve_url(self.get_login_url(view_func))
|
||||||
# If the login url is the same scheme and net location then use the
|
# If the login url is the same scheme and net location then use the
|
||||||
# path as the "next" url.
|
# path as the "next" url.
|
||||||
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
|
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
|
||||||
current_scheme, current_netloc = urlparse(path)[:2]
|
current_scheme, current_netloc = urlsplit(path)[:2]
|
||||||
if (not login_scheme or login_scheme == current_scheme) and (
|
if (not login_scheme or login_scheme == current_scheme) and (
|
||||||
not login_netloc or login_netloc == current_netloc
|
not login_netloc or login_netloc == current_netloc
|
||||||
):
|
):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
|
@ -51,8 +51,8 @@ class AccessMixin:
|
||||||
resolved_login_url = resolve_url(self.get_login_url())
|
resolved_login_url = resolve_url(self.get_login_url())
|
||||||
# If the login url is the same scheme and net location then use the
|
# If the login url is the same scheme and net location then use the
|
||||||
# path as the "next" url.
|
# path as the "next" url.
|
||||||
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
|
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
|
||||||
current_scheme, current_netloc = urlparse(path)[:2]
|
current_scheme, current_netloc = urlsplit(path)[:2]
|
||||||
if (not login_scheme or login_scheme == current_scheme) and (
|
if (not login_scheme or login_scheme == current_scheme) and (
|
||||||
not login_netloc or login_netloc == current_netloc
|
not login_netloc or login_netloc == current_netloc
|
||||||
):
|
):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from urllib.parse import urlparse, urlunparse
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -183,13 +183,13 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
|
||||||
"""
|
"""
|
||||||
resolved_url = resolve_url(login_url or settings.LOGIN_URL)
|
resolved_url = resolve_url(login_url or settings.LOGIN_URL)
|
||||||
|
|
||||||
login_url_parts = list(urlparse(resolved_url))
|
login_url_parts = list(urlsplit(resolved_url))
|
||||||
if redirect_field_name:
|
if redirect_field_name:
|
||||||
querystring = QueryDict(login_url_parts[4], mutable=True)
|
querystring = QueryDict(login_url_parts[3], mutable=True)
|
||||||
querystring[redirect_field_name] = next
|
querystring[redirect_field_name] = next
|
||||||
login_url_parts[4] = querystring.urlencode(safe="/")
|
login_url_parts[3] = querystring.urlencode(safe="/")
|
||||||
|
|
||||||
return HttpResponseRedirect(urlunparse(login_url_parts))
|
return HttpResponseRedirect(urlunsplit(login_url_parts))
|
||||||
|
|
||||||
|
|
||||||
# Class-based password reset views
|
# Class-based password reset views
|
||||||
|
|
|
@ -36,13 +36,13 @@ class StaticFilesHandlerMixin:
|
||||||
* the host is provided as part of the base_url
|
* the host is provided as part of the base_url
|
||||||
* the request's path isn't under the media path (or equal)
|
* the request's path isn't under the media path (or equal)
|
||||||
"""
|
"""
|
||||||
return path.startswith(self.base_url[2]) and not self.base_url[1]
|
return path.startswith(self.base_url.path) and not self.base_url.netloc
|
||||||
|
|
||||||
def file_path(self, url):
|
def file_path(self, url):
|
||||||
"""
|
"""
|
||||||
Return the relative path to the media file on disk for the given URL.
|
Return the relative path to the media file on disk for the given URL.
|
||||||
"""
|
"""
|
||||||
relative_url = url.removeprefix(self.base_url[2])
|
relative_url = url.removeprefix(self.base_url.path)
|
||||||
return url2pathname(relative_url)
|
return url2pathname(relative_url)
|
||||||
|
|
||||||
def serve(self, request):
|
def serve(self, request):
|
||||||
|
|
|
@ -792,13 +792,13 @@ class URLField(CharField):
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
def split_url(url):
|
def split_url(url):
|
||||||
"""
|
"""
|
||||||
Return a list of url parts via urlparse.urlsplit(), or raise
|
Return a list of url parts via urlsplit(), or raise
|
||||||
ValidationError for some malformed URLs.
|
ValidationError for some malformed URLs.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return list(urlsplit(url))
|
return list(urlsplit(url))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# urlparse.urlsplit can raise a ValueError with some
|
# urlsplit can raise a ValueError with some
|
||||||
# misformatted URLs.
|
# misformatted URLs.
|
||||||
raise ValidationError(self.error_messages["invalid"], code="invalid")
|
raise ValidationError(self.error_messages["invalid"], code="invalid")
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import time
|
||||||
import warnings
|
import warnings
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
from http.client import responses
|
from http.client import responses
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync, sync_to_async
|
from asgiref.sync import async_to_sync, sync_to_async
|
||||||
|
|
||||||
|
@ -616,7 +616,7 @@ class HttpResponseRedirectBase(HttpResponse):
|
||||||
def __init__(self, redirect_to, *args, **kwargs):
|
def __init__(self, redirect_to, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self["Location"] = iri_to_uri(redirect_to)
|
self["Location"] = iri_to_uri(redirect_to)
|
||||||
parsed = urlparse(str(redirect_to))
|
parsed = urlsplit(str(redirect_to))
|
||||||
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
|
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
|
||||||
raise DisallowedRedirect(
|
raise DisallowedRedirect(
|
||||||
"Unsafe redirect to URL with protocol '%s'" % parsed.scheme
|
"Unsafe redirect to URL with protocol '%s'" % parsed.scheme
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import re
|
import re
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
@ -171,7 +171,7 @@ class BrokenLinkEmailsMiddleware(MiddlewareMixin):
|
||||||
|
|
||||||
# The referer is equal to the current URL, ignoring the scheme (assumed
|
# The referer is equal to the current URL, ignoring the scheme (assumed
|
||||||
# to be a poorly implemented bot).
|
# to be a poorly implemented bot).
|
||||||
parsed_referer = urlparse(referer)
|
parsed_referer = urlsplit(referer)
|
||||||
if parsed_referer.netloc in ["", domain] and parsed_referer.path == uri:
|
if parsed_referer.netloc in ["", domain] and parsed_referer.path == uri:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ against request forgeries from other sites.
|
||||||
import logging
|
import logging
|
||||||
import string
|
import string
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import DisallowedHost, ImproperlyConfigured
|
from django.core.exceptions import DisallowedHost, ImproperlyConfigured
|
||||||
|
@ -174,7 +174,7 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
@cached_property
|
@cached_property
|
||||||
def csrf_trusted_origins_hosts(self):
|
def csrf_trusted_origins_hosts(self):
|
||||||
return [
|
return [
|
||||||
urlparse(origin).netloc.lstrip("*")
|
urlsplit(origin).netloc.lstrip("*")
|
||||||
for origin in settings.CSRF_TRUSTED_ORIGINS
|
for origin in settings.CSRF_TRUSTED_ORIGINS
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
allowed_origin_subdomains = defaultdict(list)
|
allowed_origin_subdomains = defaultdict(list)
|
||||||
for parsed in (
|
for parsed in (
|
||||||
urlparse(origin)
|
urlsplit(origin)
|
||||||
for origin in settings.CSRF_TRUSTED_ORIGINS
|
for origin in settings.CSRF_TRUSTED_ORIGINS
|
||||||
if "*" in origin
|
if "*" in origin
|
||||||
):
|
):
|
||||||
|
@ -284,7 +284,7 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
if request_origin in self.allowed_origins_exact:
|
if request_origin in self.allowed_origins_exact:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
parsed_origin = urlparse(request_origin)
|
parsed_origin = urlsplit(request_origin)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
request_scheme = parsed_origin.scheme
|
request_scheme = parsed_origin.scheme
|
||||||
|
@ -300,7 +300,7 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||||
raise RejectRequest(REASON_NO_REFERER)
|
raise RejectRequest(REASON_NO_REFERER)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
referer = urlparse(referer)
|
referer = urlsplit(referer)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RejectRequest(REASON_MALFORMED_REFERER)
|
raise RejectRequest(REASON_MALFORMED_REFERER)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from functools import partial
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from io import BytesIO, IOBase
|
from io import BytesIO, IOBase
|
||||||
from urllib.parse import unquote_to_bytes, urljoin, urlparse, urlsplit
|
from urllib.parse import unquote_to_bytes, urljoin, urlsplit
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
|
@ -458,11 +458,7 @@ class RequestFactory:
|
||||||
return json.dumps(data, cls=self.json_encoder) if should_encode else data
|
return json.dumps(data, cls=self.json_encoder) if should_encode else data
|
||||||
|
|
||||||
def _get_path(self, parsed):
|
def _get_path(self, parsed):
|
||||||
path = parsed.path
|
path = unquote_to_bytes(parsed.path)
|
||||||
# If there are parameters, add them
|
|
||||||
if parsed.params:
|
|
||||||
path += ";" + parsed.params
|
|
||||||
path = unquote_to_bytes(path)
|
|
||||||
# Replace the behavior where non-ASCII values in the WSGI environ are
|
# Replace the behavior where non-ASCII values in the WSGI environ are
|
||||||
# arbitrarily decoded with ISO-8859-1.
|
# arbitrarily decoded with ISO-8859-1.
|
||||||
# Refs comment in `get_bytes_from_wsgi()`.
|
# Refs comment in `get_bytes_from_wsgi()`.
|
||||||
|
@ -647,7 +643,7 @@ class RequestFactory:
|
||||||
**extra,
|
**extra,
|
||||||
):
|
):
|
||||||
"""Construct an arbitrary HTTP request."""
|
"""Construct an arbitrary HTTP request."""
|
||||||
parsed = urlparse(str(path)) # path can be lazy
|
parsed = urlsplit(str(path)) # path can be lazy
|
||||||
data = force_bytes(data, settings.DEFAULT_CHARSET)
|
data = force_bytes(data, settings.DEFAULT_CHARSET)
|
||||||
r = {
|
r = {
|
||||||
"PATH_INFO": self._get_path(parsed),
|
"PATH_INFO": self._get_path(parsed),
|
||||||
|
@ -671,8 +667,7 @@ class RequestFactory:
|
||||||
# If QUERY_STRING is absent or empty, we want to extract it from the URL.
|
# If QUERY_STRING is absent or empty, we want to extract it from the URL.
|
||||||
if not r.get("QUERY_STRING"):
|
if not r.get("QUERY_STRING"):
|
||||||
# WSGI requires latin-1 encoded strings. See get_path_info().
|
# WSGI requires latin-1 encoded strings. See get_path_info().
|
||||||
query_string = parsed[4].encode().decode("iso-8859-1")
|
r["QUERY_STRING"] = parsed.query.encode().decode("iso-8859-1")
|
||||||
r["QUERY_STRING"] = query_string
|
|
||||||
return self.request(**r)
|
return self.request(**r)
|
||||||
|
|
||||||
|
|
||||||
|
@ -748,7 +743,7 @@ class AsyncRequestFactory(RequestFactory):
|
||||||
**extra,
|
**extra,
|
||||||
):
|
):
|
||||||
"""Construct an arbitrary HTTP request."""
|
"""Construct an arbitrary HTTP request."""
|
||||||
parsed = urlparse(str(path)) # path can be lazy.
|
parsed = urlsplit(str(path)) # path can be lazy.
|
||||||
data = force_bytes(data, settings.DEFAULT_CHARSET)
|
data = force_bytes(data, settings.DEFAULT_CHARSET)
|
||||||
s = {
|
s = {
|
||||||
"method": method,
|
"method": method,
|
||||||
|
@ -772,7 +767,7 @@ class AsyncRequestFactory(RequestFactory):
|
||||||
else:
|
else:
|
||||||
# If QUERY_STRING is absent or empty, we want to extract it from
|
# If QUERY_STRING is absent or empty, we want to extract it from
|
||||||
# the URL.
|
# the URL.
|
||||||
s["query_string"] = parsed[4]
|
s["query_string"] = parsed.query
|
||||||
if headers:
|
if headers:
|
||||||
extra.update(HttpHeaders.to_asgi_names(headers))
|
extra.update(HttpHeaders.to_asgi_names(headers))
|
||||||
s["headers"] += [
|
s["headers"] += [
|
||||||
|
|
|
@ -21,7 +21,7 @@ from urllib.parse import (
|
||||||
urljoin,
|
urljoin,
|
||||||
urlparse,
|
urlparse,
|
||||||
urlsplit,
|
urlsplit,
|
||||||
urlunparse,
|
urlunsplit,
|
||||||
)
|
)
|
||||||
from urllib.request import url2pathname
|
from urllib.request import url2pathname
|
||||||
|
|
||||||
|
@ -541,11 +541,9 @@ class SimpleTestCase(unittest.TestCase):
|
||||||
def normalize(url):
|
def normalize(url):
|
||||||
"""Sort the URL's query string parameters."""
|
"""Sort the URL's query string parameters."""
|
||||||
url = str(url) # Coerce reverse_lazy() URLs.
|
url = str(url) # Coerce reverse_lazy() URLs.
|
||||||
scheme, netloc, path, params, query, fragment = urlparse(url)
|
scheme, netloc, path, query, fragment = urlsplit(url)
|
||||||
query_parts = sorted(parse_qsl(query))
|
query_parts = sorted(parse_qsl(query))
|
||||||
return urlunparse(
|
return urlunsplit((scheme, netloc, path, urlencode(query_parts), fragment))
|
||||||
(scheme, netloc, path, params, urlencode(query_parts), fragment)
|
|
||||||
)
|
|
||||||
|
|
||||||
if msg_prefix:
|
if msg_prefix:
|
||||||
msg_prefix += ": "
|
msg_prefix += ": "
|
||||||
|
@ -1637,11 +1635,11 @@ class FSFilesHandler(WSGIHandler):
|
||||||
* the host is provided as part of the base_url
|
* the host is provided as part of the base_url
|
||||||
* the request's path isn't under the media path (or equal)
|
* the request's path isn't under the media path (or equal)
|
||||||
"""
|
"""
|
||||||
return path.startswith(self.base_url[2]) and not self.base_url[1]
|
return path.startswith(self.base_url.path) and not self.base_url.netloc
|
||||||
|
|
||||||
def file_path(self, url):
|
def file_path(self, url):
|
||||||
"""Return the relative path to the file on disk for the given URL."""
|
"""Return the relative path to the file on disk for the given URL."""
|
||||||
relative_url = url.removeprefix(self.base_url[2])
|
relative_url = url.removeprefix(self.base_url.path)
|
||||||
return url2pathname(relative_url)
|
return url2pathname(relative_url)
|
||||||
|
|
||||||
def get_response(self, request):
|
def get_response(self, request):
|
||||||
|
|
|
@ -6,7 +6,7 @@ from datetime import datetime, timezone
|
||||||
from email.utils import formatdate
|
from email.utils import formatdate
|
||||||
from urllib.parse import quote, unquote
|
from urllib.parse import quote, unquote
|
||||||
from urllib.parse import urlencode as original_urlencode
|
from urllib.parse import urlencode as original_urlencode
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
from django.utils.datastructures import MultiValueDict
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.regex_helper import _lazy_re_compile
|
from django.utils.regex_helper import _lazy_re_compile
|
||||||
|
@ -271,11 +271,11 @@ def url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
||||||
|
|
||||||
def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
||||||
# Chrome considers any URL with more than two slashes to be absolute, but
|
# Chrome considers any URL with more than two slashes to be absolute, but
|
||||||
# urlparse is not so flexible. Treat any url with three slashes as unsafe.
|
# urlsplit is not so flexible. Treat any url with three slashes as unsafe.
|
||||||
if url.startswith("///"):
|
if url.startswith("///"):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
url_info = urlparse(url)
|
url_info = urlsplit(url)
|
||||||
except ValueError: # e.g. invalid IPv6 addresses
|
except ValueError: # e.g. invalid IPv6 addresses
|
||||||
return False
|
return False
|
||||||
# Forbid URLs like http:///example.com - with a scheme, but without a hostname.
|
# Forbid URLs like http:///example.com - with a scheme, but without a hostname.
|
||||||
|
|
|
@ -203,7 +203,7 @@ A :class:`ResolverMatch` object can also be assigned to a triple::
|
||||||
One possible use of :func:`~django.urls.resolve` would be to test whether a
|
One possible use of :func:`~django.urls.resolve` would be to test whether a
|
||||||
view would raise a ``Http404`` error before redirecting to it::
|
view would raise a ``Http404`` error before redirecting to it::
|
||||||
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlsplit
|
||||||
from django.urls import resolve
|
from django.urls import resolve
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ view would raise a ``Http404`` error before redirecting to it::
|
||||||
# modify the request and response as required, e.g. change locale
|
# modify the request and response as required, e.g. change locale
|
||||||
# and set corresponding locale cookie
|
# and set corresponding locale cookie
|
||||||
|
|
||||||
view, args, kwargs = resolve(urlparse(next)[2])
|
view, args, kwargs = resolve(urlsplit(next).path)
|
||||||
kwargs["request"] = request
|
kwargs["request"] = request
|
||||||
try:
|
try:
|
||||||
view(*args, **kwargs)
|
view(*args, **kwargs)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import re
|
||||||
import unittest
|
import unittest
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import parse_qsl, urljoin, urlparse
|
from urllib.parse import parse_qsl, urljoin, urlsplit
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -357,7 +357,7 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
||||||
**save_option,
|
**save_option,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
parsed_url = urlparse(response.url)
|
parsed_url = urlsplit(response.url)
|
||||||
self.assertEqual(parsed_url.query, qsl)
|
self.assertEqual(parsed_url.query, qsl)
|
||||||
|
|
||||||
def test_change_query_string_persists(self):
|
def test_change_query_string_persists(self):
|
||||||
|
@ -386,7 +386,7 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
||||||
**save_option,
|
**save_option,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
parsed_url = urlparse(response.url)
|
parsed_url = urlsplit(response.url)
|
||||||
self.assertEqual(parsed_url.query, qsl)
|
self.assertEqual(parsed_url.query, qsl)
|
||||||
|
|
||||||
def test_basic_edit_GET(self):
|
def test_basic_edit_GET(self):
|
||||||
|
@ -8032,11 +8032,11 @@ class AdminKeepChangeListFiltersTests(TestCase):
|
||||||
Assert that two URLs are equal despite the ordering
|
Assert that two URLs are equal despite the ordering
|
||||||
of their querystring. Refs #22360.
|
of their querystring. Refs #22360.
|
||||||
"""
|
"""
|
||||||
parsed_url1 = urlparse(url1)
|
parsed_url1 = urlsplit(url1)
|
||||||
path1 = parsed_url1.path
|
path1 = parsed_url1.path
|
||||||
parsed_qs1 = dict(parse_qsl(parsed_url1.query))
|
parsed_qs1 = dict(parse_qsl(parsed_url1.query))
|
||||||
|
|
||||||
parsed_url2 = urlparse(url2)
|
parsed_url2 = urlsplit(url2)
|
||||||
path2 = parsed_url2.path
|
path2 = parsed_url2.path
|
||||||
parsed_qs2 = dict(parse_qsl(parsed_url2.query))
|
parsed_qs2 = dict(parse_qsl(parsed_url2.query))
|
||||||
|
|
||||||
|
|
|
@ -709,25 +709,21 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
response = mw.process_view(req, post_form_view, (), {})
|
response = mw.process_view(req, post_form_view, (), {})
|
||||||
self.assertContains(response, malformed_referer_msg, status_code=403)
|
self.assertContains(response, malformed_referer_msg, status_code=403)
|
||||||
# missing scheme
|
# missing scheme
|
||||||
# >>> urlparse('//example.com/')
|
# >>> urlsplit('//example.com/')
|
||||||
# ParseResult(
|
# SplitResult(scheme='', netloc='example.com', path='/', query='', fragment='')
|
||||||
# scheme='', netloc='example.com', path='/', params='', query='', fragment='',
|
|
||||||
# )
|
|
||||||
req.META["HTTP_REFERER"] = "//example.com/"
|
req.META["HTTP_REFERER"] = "//example.com/"
|
||||||
self._check_referer_rejects(mw, req)
|
self._check_referer_rejects(mw, req)
|
||||||
response = mw.process_view(req, post_form_view, (), {})
|
response = mw.process_view(req, post_form_view, (), {})
|
||||||
self.assertContains(response, malformed_referer_msg, status_code=403)
|
self.assertContains(response, malformed_referer_msg, status_code=403)
|
||||||
# missing netloc
|
# missing netloc
|
||||||
# >>> urlparse('https://')
|
# >>> urlsplit('https://')
|
||||||
# ParseResult(
|
# SplitResult(scheme='https', netloc='', path='', query='', fragment='')
|
||||||
# scheme='https', netloc='', path='', params='', query='', fragment='',
|
|
||||||
# )
|
|
||||||
req.META["HTTP_REFERER"] = "https://"
|
req.META["HTTP_REFERER"] = "https://"
|
||||||
self._check_referer_rejects(mw, req)
|
self._check_referer_rejects(mw, req)
|
||||||
response = mw.process_view(req, post_form_view, (), {})
|
response = mw.process_view(req, post_form_view, (), {})
|
||||||
self.assertContains(response, malformed_referer_msg, status_code=403)
|
self.assertContains(response, malformed_referer_msg, status_code=403)
|
||||||
# Invalid URL
|
# Invalid URL
|
||||||
# >>> urlparse('https://[')
|
# >>> urlsplit('https://[')
|
||||||
# ValueError: Invalid IPv6 URL
|
# ValueError: Invalid IPv6 URL
|
||||||
req.META["HTTP_REFERER"] = "https://["
|
req.META["HTTP_REFERER"] = "https://["
|
||||||
self._check_referer_rejects(mw, req)
|
self._check_referer_rejects(mw, req)
|
||||||
|
@ -979,7 +975,7 @@ class CsrfViewMiddlewareTestMixin(CsrfFunctionTestMixin):
|
||||||
@override_settings(ALLOWED_HOSTS=["www.example.com"])
|
@override_settings(ALLOWED_HOSTS=["www.example.com"])
|
||||||
def test_bad_origin_cannot_be_parsed(self):
|
def test_bad_origin_cannot_be_parsed(self):
|
||||||
"""
|
"""
|
||||||
A POST request with an origin that can't be parsed by urlparse() is
|
A POST request with an origin that can't be parsed by urlsplit() is
|
||||||
rejected.
|
rejected.
|
||||||
"""
|
"""
|
||||||
req = self._get_POST_request_with_token()
|
req = self._get_POST_request_with_token()
|
||||||
|
|
Loading…
Reference in New Issue