测试gitnore
This commit is contained in:
@@ -1,51 +1,103 @@
|
||||
import base64
|
||||
import calendar
|
||||
import datetime
|
||||
import re
|
||||
import unicodedata
|
||||
import warnings
|
||||
from binascii import Error as BinasciiError
|
||||
from email.utils import formatdate
|
||||
from urllib.parse import (
|
||||
ParseResult,
|
||||
SplitResult,
|
||||
_coerce_args,
|
||||
_splitnetloc,
|
||||
_splitparams,
|
||||
scheme_chars,
|
||||
ParseResult, SplitResult, _coerce_args, _splitnetloc, _splitparams, quote,
|
||||
quote_plus, scheme_chars, unquote, unquote_plus,
|
||||
urlencode as original_urlencode, uses_params,
|
||||
)
|
||||
from urllib.parse import urlencode as original_urlencode
|
||||
from urllib.parse import uses_params
|
||||
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.deprecation import RemovedInDjango40Warning
|
||||
from django.utils.functional import keep_lazy_text
|
||||
from django.utils.regex_helper import _lazy_re_compile
|
||||
|
||||
# based on RFC 7232, Appendix C
|
||||
ETAG_MATCH = _lazy_re_compile(
|
||||
r"""
|
||||
ETAG_MATCH = _lazy_re_compile(r'''
|
||||
\A( # start of string and capture group
|
||||
(?:W/)? # optional weak indicator
|
||||
" # opening quote
|
||||
[^"]* # any sequence of non-quote characters
|
||||
" # end quote
|
||||
)\Z # end of string and capture group
|
||||
""",
|
||||
re.X,
|
||||
)
|
||||
''', re.X)
|
||||
|
||||
MONTHS = "jan feb mar apr may jun jul aug sep oct nov dec".split()
|
||||
__D = r"(?P<day>\d{2})"
|
||||
__D2 = r"(?P<day>[ \d]\d)"
|
||||
__M = r"(?P<mon>\w{3})"
|
||||
__Y = r"(?P<year>\d{4})"
|
||||
__Y2 = r"(?P<year>\d{2})"
|
||||
__T = r"(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})"
|
||||
RFC1123_DATE = _lazy_re_compile(r"^\w{3}, %s %s %s %s GMT$" % (__D, __M, __Y, __T))
|
||||
RFC850_DATE = _lazy_re_compile(r"^\w{6,9}, %s-%s-%s %s GMT$" % (__D, __M, __Y2, __T))
|
||||
ASCTIME_DATE = _lazy_re_compile(r"^\w{3} %s %s %s %s$" % (__M, __D2, __T, __Y))
|
||||
MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
|
||||
__D = r'(?P<day>\d{2})'
|
||||
__D2 = r'(?P<day>[ \d]\d)'
|
||||
__M = r'(?P<mon>\w{3})'
|
||||
__Y = r'(?P<year>\d{4})'
|
||||
__Y2 = r'(?P<year>\d{2})'
|
||||
__T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
|
||||
RFC1123_DATE = _lazy_re_compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
|
||||
RFC850_DATE = _lazy_re_compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
|
||||
ASCTIME_DATE = _lazy_re_compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
|
||||
|
||||
RFC3986_GENDELIMS = ":/?#[]@"
|
||||
RFC3986_SUBDELIMS = "!$&'()*+,;="
|
||||
|
||||
|
||||
@keep_lazy_text
|
||||
def urlquote(url, safe='/'):
|
||||
"""
|
||||
A legacy compatibility wrapper to Python's urllib.parse.quote() function.
|
||||
(was used for unicode handling on Python 2)
|
||||
"""
|
||||
warnings.warn(
|
||||
'django.utils.http.urlquote() is deprecated in favor of '
|
||||
'urllib.parse.quote().',
|
||||
RemovedInDjango40Warning, stacklevel=2,
|
||||
)
|
||||
return quote(url, safe)
|
||||
|
||||
|
||||
@keep_lazy_text
|
||||
def urlquote_plus(url, safe=''):
|
||||
"""
|
||||
A legacy compatibility wrapper to Python's urllib.parse.quote_plus()
|
||||
function. (was used for unicode handling on Python 2)
|
||||
"""
|
||||
warnings.warn(
|
||||
'django.utils.http.urlquote_plus() is deprecated in favor of '
|
||||
'urllib.parse.quote_plus(),',
|
||||
RemovedInDjango40Warning, stacklevel=2,
|
||||
)
|
||||
return quote_plus(url, safe)
|
||||
|
||||
|
||||
@keep_lazy_text
|
||||
def urlunquote(quoted_url):
|
||||
"""
|
||||
A legacy compatibility wrapper to Python's urllib.parse.unquote() function.
|
||||
(was used for unicode handling on Python 2)
|
||||
"""
|
||||
warnings.warn(
|
||||
'django.utils.http.urlunquote() is deprecated in favor of '
|
||||
'urllib.parse.unquote().',
|
||||
RemovedInDjango40Warning, stacklevel=2,
|
||||
)
|
||||
return unquote(quoted_url)
|
||||
|
||||
|
||||
@keep_lazy_text
|
||||
def urlunquote_plus(quoted_url):
|
||||
"""
|
||||
A legacy compatibility wrapper to Python's urllib.parse.unquote_plus()
|
||||
function. (was used for unicode handling on Python 2)
|
||||
"""
|
||||
warnings.warn(
|
||||
'django.utils.http.urlunquote_plus() is deprecated in favor of '
|
||||
'urllib.parse.unquote_plus().',
|
||||
RemovedInDjango40Warning, stacklevel=2,
|
||||
)
|
||||
return unquote_plus(quoted_url)
|
||||
|
||||
|
||||
def urlencode(query, doseq=False):
|
||||
"""
|
||||
A version of Python's urllib.parse.urlencode() function that can operate on
|
||||
@@ -53,7 +105,7 @@ def urlencode(query, doseq=False):
|
||||
"""
|
||||
if isinstance(query, MultiValueDict):
|
||||
query = query.lists()
|
||||
elif hasattr(query, "items"):
|
||||
elif hasattr(query, 'items'):
|
||||
query = query.items()
|
||||
query_params = []
|
||||
for key, value in query:
|
||||
@@ -120,10 +172,9 @@ def parse_http_date(date):
|
||||
else:
|
||||
raise ValueError("%r is not in a valid HTTP date format" % date)
|
||||
try:
|
||||
tz = datetime.timezone.utc
|
||||
year = int(m["year"])
|
||||
year = int(m['year'])
|
||||
if year < 100:
|
||||
current_year = datetime.datetime.now(tz=tz).year
|
||||
current_year = datetime.datetime.utcnow().year
|
||||
current_century = current_year - (current_year % 100)
|
||||
if year - (current_year % 100) > 50:
|
||||
# year that appears to be more than 50 years in the future are
|
||||
@@ -131,13 +182,13 @@ def parse_http_date(date):
|
||||
year += current_century - 100
|
||||
else:
|
||||
year += current_century
|
||||
month = MONTHS.index(m["mon"].lower()) + 1
|
||||
day = int(m["day"])
|
||||
hour = int(m["hour"])
|
||||
min = int(m["min"])
|
||||
sec = int(m["sec"])
|
||||
result = datetime.datetime(year, month, day, hour, min, sec, tzinfo=tz)
|
||||
return int(result.timestamp())
|
||||
month = MONTHS.index(m['mon'].lower()) + 1
|
||||
day = int(m['day'])
|
||||
hour = int(m['hour'])
|
||||
min = int(m['min'])
|
||||
sec = int(m['sec'])
|
||||
result = datetime.datetime(year, month, day, hour, min, sec)
|
||||
return calendar.timegm(result.utctimetuple())
|
||||
except Exception as exc:
|
||||
raise ValueError("%r is not a valid date" % date) from exc
|
||||
|
||||
@@ -154,7 +205,6 @@ def parse_http_date_safe(date):
|
||||
|
||||
# Base 36 functions: useful for generating compact URLs
|
||||
|
||||
|
||||
def base36_to_int(s):
|
||||
"""
|
||||
Convert a base 36 string to an int. Raise ValueError if the input won't fit
|
||||
@@ -170,12 +220,12 @@ def base36_to_int(s):
|
||||
|
||||
def int_to_base36(i):
|
||||
"""Convert an integer to a base36 string."""
|
||||
char_set = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
char_set = '0123456789abcdefghijklmnopqrstuvwxyz'
|
||||
if i < 0:
|
||||
raise ValueError("Negative base36 conversion input.")
|
||||
if i < 36:
|
||||
return char_set[i]
|
||||
b36 = ""
|
||||
b36 = ''
|
||||
while i != 0:
|
||||
i, n = divmod(i, 36)
|
||||
b36 = char_set[n] + b36
|
||||
@@ -187,7 +237,7 @@ def urlsafe_base64_encode(s):
|
||||
Encode a bytestring to a base64 string for use in URLs. Strip any trailing
|
||||
equal signs.
|
||||
"""
|
||||
return base64.urlsafe_b64encode(s).rstrip(b"\n=").decode("ascii")
|
||||
return base64.urlsafe_b64encode(s).rstrip(b'\n=').decode('ascii')
|
||||
|
||||
|
||||
def urlsafe_base64_decode(s):
|
||||
@@ -197,7 +247,7 @@ def urlsafe_base64_decode(s):
|
||||
"""
|
||||
s = s.encode()
|
||||
try:
|
||||
return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b"="))
|
||||
return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b'='))
|
||||
except (LookupError, BinasciiError) as e:
|
||||
raise ValueError(e)
|
||||
|
||||
@@ -208,11 +258,11 @@ def parse_etags(etag_str):
|
||||
defined by RFC 7232. Return a list of quoted ETags, or ['*'] if all ETags
|
||||
should be matched.
|
||||
"""
|
||||
if etag_str.strip() == "*":
|
||||
return ["*"]
|
||||
if etag_str.strip() == '*':
|
||||
return ['*']
|
||||
else:
|
||||
# Parse each ETag individually, and return any that are valid.
|
||||
etag_matches = (ETAG_MATCH.match(etag.strip()) for etag in etag_str.split(","))
|
||||
etag_matches = (ETAG_MATCH.match(etag.strip()) for etag in etag_str.split(','))
|
||||
return [match[1] for match in etag_matches if match]
|
||||
|
||||
|
||||
@@ -241,9 +291,8 @@ def is_same_domain(host, pattern):
|
||||
|
||||
pattern = pattern.lower()
|
||||
return (
|
||||
pattern[0] == "."
|
||||
and (host.endswith(pattern) or host == pattern[1:])
|
||||
or pattern == host
|
||||
pattern[0] == '.' and (host.endswith(pattern) or host == pattern[1:]) or
|
||||
pattern == host
|
||||
)
|
||||
|
||||
|
||||
@@ -270,15 +319,23 @@ def url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
||||
allowed_hosts = {allowed_hosts}
|
||||
# Chrome treats \ completely as / in paths but it could be part of some
|
||||
# basic auth credentials so we need to check both URLs.
|
||||
return _url_has_allowed_host_and_scheme(
|
||||
url, allowed_hosts, require_https=require_https
|
||||
) and _url_has_allowed_host_and_scheme(
|
||||
url.replace("\\", "/"), allowed_hosts, require_https=require_https
|
||||
return (
|
||||
_url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=require_https) and
|
||||
_url_has_allowed_host_and_scheme(url.replace('\\', '/'), allowed_hosts, require_https=require_https)
|
||||
)
|
||||
|
||||
|
||||
def is_safe_url(url, allowed_hosts, require_https=False):
|
||||
warnings.warn(
|
||||
'django.utils.http.is_safe_url() is deprecated in favor of '
|
||||
'url_has_allowed_host_and_scheme().',
|
||||
RemovedInDjango40Warning, stacklevel=2,
|
||||
)
|
||||
return url_has_allowed_host_and_scheme(url, allowed_hosts, require_https)
|
||||
|
||||
|
||||
# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function.
|
||||
def _urlparse(url, scheme="", allow_fragments=True):
|
||||
def _urlparse(url, scheme='', allow_fragments=True):
|
||||
"""Parse a URL into 6 components:
|
||||
<scheme>://<netloc>/<path>;<params>?<query>#<fragment>
|
||||
Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
|
||||
@@ -287,42 +344,41 @@ def _urlparse(url, scheme="", allow_fragments=True):
|
||||
url, scheme, _coerce_result = _coerce_args(url, scheme)
|
||||
splitresult = _urlsplit(url, scheme, allow_fragments)
|
||||
scheme, netloc, url, query, fragment = splitresult
|
||||
if scheme in uses_params and ";" in url:
|
||||
if scheme in uses_params and ';' in url:
|
||||
url, params = _splitparams(url)
|
||||
else:
|
||||
params = ""
|
||||
params = ''
|
||||
result = ParseResult(scheme, netloc, url, params, query, fragment)
|
||||
return _coerce_result(result)
|
||||
|
||||
|
||||
# Copied from urllib.parse.urlsplit() with
|
||||
# https://github.com/python/cpython/pull/661 applied.
|
||||
def _urlsplit(url, scheme="", allow_fragments=True):
|
||||
def _urlsplit(url, scheme='', allow_fragments=True):
|
||||
"""Parse a URL into 5 components:
|
||||
<scheme>://<netloc>/<path>?<query>#<fragment>
|
||||
Return a 5-tuple: (scheme, netloc, path, query, fragment).
|
||||
Note that we don't break the components up in smaller bits
|
||||
(e.g. netloc is a single string) and we don't expand % escapes."""
|
||||
url, scheme, _coerce_result = _coerce_args(url, scheme)
|
||||
netloc = query = fragment = ""
|
||||
i = url.find(":")
|
||||
netloc = query = fragment = ''
|
||||
i = url.find(':')
|
||||
if i > 0:
|
||||
for c in url[:i]:
|
||||
if c not in scheme_chars:
|
||||
break
|
||||
else:
|
||||
scheme, url = url[:i].lower(), url[i + 1 :]
|
||||
scheme, url = url[:i].lower(), url[i + 1:]
|
||||
|
||||
if url[:2] == "//":
|
||||
if url[:2] == '//':
|
||||
netloc, url = _splitnetloc(url, 2)
|
||||
if ("[" in netloc and "]" not in netloc) or (
|
||||
"]" in netloc and "[" not in netloc
|
||||
):
|
||||
if (('[' in netloc and ']' not in netloc) or
|
||||
(']' in netloc and '[' not in netloc)):
|
||||
raise ValueError("Invalid IPv6 URL")
|
||||
if allow_fragments and "#" in url:
|
||||
url, fragment = url.split("#", 1)
|
||||
if "?" in url:
|
||||
url, query = url.split("?", 1)
|
||||
if allow_fragments and '#' in url:
|
||||
url, fragment = url.split('#', 1)
|
||||
if '?' in url:
|
||||
url, query = url.split('?', 1)
|
||||
v = SplitResult(scheme, netloc, url, query, fragment)
|
||||
return _coerce_result(v)
|
||||
|
||||
@@ -330,7 +386,7 @@ def _urlsplit(url, scheme="", allow_fragments=True):
|
||||
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
|
||||
# urlparse is not so flexible. Treat any url with three slashes as unsafe.
|
||||
if url.startswith("///"):
|
||||
if url.startswith('///'):
|
||||
return False
|
||||
try:
|
||||
url_info = _urlparse(url)
|
||||
@@ -345,16 +401,93 @@ def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
||||
# Forbid URLs that start with control characters. Some browsers (like
|
||||
# Chrome) ignore quite a few control characters at the start of a
|
||||
# URL and might consider the URL as scheme relative.
|
||||
if unicodedata.category(url[0])[0] == "C":
|
||||
if unicodedata.category(url[0])[0] == 'C':
|
||||
return False
|
||||
scheme = url_info.scheme
|
||||
# Consider URLs without a scheme (e.g. //example.com/p) to be http.
|
||||
if not url_info.scheme and url_info.netloc:
|
||||
scheme = "http"
|
||||
valid_schemes = ["https"] if require_https else ["http", "https"]
|
||||
return (not url_info.netloc or url_info.netloc in allowed_hosts) and (
|
||||
not scheme or scheme in valid_schemes
|
||||
)
|
||||
scheme = 'http'
|
||||
valid_schemes = ['https'] if require_https else ['http', 'https']
|
||||
return ((not url_info.netloc or url_info.netloc in allowed_hosts) and
|
||||
(not scheme or scheme in valid_schemes))
|
||||
|
||||
|
||||
# TODO: Remove when dropping support for PY37.
|
||||
def parse_qsl(
|
||||
qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8',
|
||||
errors='replace', max_num_fields=None, separator='&',
|
||||
):
|
||||
"""
|
||||
Return a list of key/value tuples parsed from query string.
|
||||
|
||||
Backport of urllib.parse.parse_qsl() from Python 3.8.8.
|
||||
Copyright (C) 2021 Python Software Foundation (see LICENSE.python).
|
||||
|
||||
----
|
||||
|
||||
Parse a query given as a string argument.
|
||||
|
||||
Arguments:
|
||||
|
||||
qs: percent-encoded query string to be parsed
|
||||
|
||||
keep_blank_values: flag indicating whether blank values in
|
||||
percent-encoded queries should be treated as blank strings. A
|
||||
true value indicates that blanks should be retained as blank
|
||||
strings. The default false value indicates that blank values
|
||||
are to be ignored and treated as if they were not included.
|
||||
|
||||
strict_parsing: flag indicating what to do with parsing errors. If false
|
||||
(the default), errors are silently ignored. If true, errors raise a
|
||||
ValueError exception.
|
||||
|
||||
encoding and errors: specify how to decode percent-encoded sequences
|
||||
into Unicode characters, as accepted by the bytes.decode() method.
|
||||
|
||||
max_num_fields: int. If set, then throws a ValueError if there are more
|
||||
than n fields read by parse_qsl().
|
||||
|
||||
separator: str. The symbol to use for separating the query arguments.
|
||||
Defaults to &.
|
||||
|
||||
Returns a list, as G-d intended.
|
||||
"""
|
||||
qs, _coerce_result = _coerce_args(qs)
|
||||
|
||||
if not separator or not isinstance(separator, (str, bytes)):
|
||||
raise ValueError('Separator must be of type string or bytes.')
|
||||
|
||||
# If max_num_fields is defined then check that the number of fields is less
|
||||
# than max_num_fields. This prevents a memory exhaustion DOS attack via
|
||||
# post bodies with many fields.
|
||||
if max_num_fields is not None:
|
||||
num_fields = 1 + qs.count(separator)
|
||||
if max_num_fields < num_fields:
|
||||
raise ValueError('Max number of fields exceeded')
|
||||
|
||||
pairs = [s1 for s1 in qs.split(separator)]
|
||||
r = []
|
||||
for name_value in pairs:
|
||||
if not name_value and not strict_parsing:
|
||||
continue
|
||||
nv = name_value.split('=', 1)
|
||||
if len(nv) != 2:
|
||||
if strict_parsing:
|
||||
raise ValueError("bad query field: %r" % (name_value,))
|
||||
# Handle case of a control-name with no equal sign.
|
||||
if keep_blank_values:
|
||||
nv.append('')
|
||||
else:
|
||||
continue
|
||||
if len(nv[1]) or keep_blank_values:
|
||||
name = nv[0].replace('+', ' ')
|
||||
name = unquote(name, encoding=encoding, errors=errors)
|
||||
name = _coerce_result(name)
|
||||
value = nv[1].replace('+', ' ')
|
||||
value = unquote(value, encoding=encoding, errors=errors)
|
||||
value = _coerce_result(value)
|
||||
r.append((name, value))
|
||||
return r
|
||||
|
||||
|
||||
def escape_leading_slashes(url):
|
||||
@@ -363,6 +496,6 @@ def escape_leading_slashes(url):
|
||||
escaped to prevent browsers from handling the path as schemaless and
|
||||
redirecting to another host.
|
||||
"""
|
||||
if url.startswith("//"):
|
||||
url = "/%2F{}".format(url[2:])
|
||||
if url.startswith('//'):
|
||||
url = '/%2F{}'.format(url[2:])
|
||||
return url
|
||||
|
||||
Reference in New Issue
Block a user