Fixed #24496 -- Added CSRF Referer checking against CSRF_COOKIE_DOMAIN.
Thanks Seth Gottlieb for help with the documentation and Carl Meyer and Joshua Kehn for reviews.
This commit is contained in:
parent
535809e121
commit
b0c56b895f
|
@ -16,6 +16,7 @@ from django.utils.datastructures import ImmutableList, MultiValueDict
|
||||||
from django.utils.encoding import (
|
from django.utils.encoding import (
|
||||||
escape_uri_path, force_bytes, force_str, force_text, iri_to_uri,
|
escape_uri_path, force_bytes, force_str, force_text, iri_to_uri,
|
||||||
)
|
)
|
||||||
|
from django.utils.http import is_same_domain
|
||||||
from django.utils.six.moves.urllib.parse import (
|
from django.utils.six.moves.urllib.parse import (
|
||||||
parse_qsl, quote, urlencode, urljoin, urlsplit,
|
parse_qsl, quote, urlencode, urljoin, urlsplit,
|
||||||
)
|
)
|
||||||
|
@ -546,15 +547,7 @@ def validate_host(host, allowed_hosts):
|
||||||
host = host[:-1] if host.endswith('.') else host
|
host = host[:-1] if host.endswith('.') else host
|
||||||
|
|
||||||
for pattern in allowed_hosts:
|
for pattern in allowed_hosts:
|
||||||
pattern = pattern.lower()
|
if pattern == '*' or is_same_domain(host, pattern):
|
||||||
match = (
|
|
||||||
pattern == '*' or
|
|
||||||
pattern.startswith('.') and (
|
|
||||||
host.endswith(pattern) or host == pattern[1:]
|
|
||||||
) or
|
|
||||||
pattern == host
|
|
||||||
)
|
|
||||||
if match:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -14,7 +14,8 @@ from django.core.urlresolvers import get_callable
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
from django.utils.crypto import constant_time_compare, get_random_string
|
from django.utils.crypto import constant_time_compare, get_random_string
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.http import same_origin
|
from django.utils.http import is_same_domain
|
||||||
|
from django.utils.six.moves.urllib.parse import urlparse
|
||||||
|
|
||||||
logger = logging.getLogger('django.request')
|
logger = logging.getLogger('django.request')
|
||||||
|
|
||||||
|
@ -22,6 +23,8 @@ REASON_NO_REFERER = "Referer checking failed - no Referer."
|
||||||
REASON_BAD_REFERER = "Referer checking failed - %s does not match any trusted origins."
|
REASON_BAD_REFERER = "Referer checking failed - %s does not match any trusted origins."
|
||||||
REASON_NO_CSRF_COOKIE = "CSRF cookie not set."
|
REASON_NO_CSRF_COOKIE = "CSRF cookie not set."
|
||||||
REASON_BAD_TOKEN = "CSRF token missing or incorrect."
|
REASON_BAD_TOKEN = "CSRF token missing or incorrect."
|
||||||
|
REASON_MALFORMED_REFERER = "Referer checking failed - Referer is malformed."
|
||||||
|
REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while host is secure."
|
||||||
|
|
||||||
CSRF_KEY_LENGTH = 32
|
CSRF_KEY_LENGTH = 32
|
||||||
|
|
||||||
|
@ -154,15 +157,35 @@ class CsrfViewMiddleware(object):
|
||||||
if referer is None:
|
if referer is None:
|
||||||
return self._reject(request, REASON_NO_REFERER)
|
return self._reject(request, REASON_NO_REFERER)
|
||||||
|
|
||||||
|
referer = urlparse(referer)
|
||||||
|
|
||||||
|
# Make sure we have a valid URL for Referer.
|
||||||
|
if '' in (referer.scheme, referer.netloc):
|
||||||
|
return self._reject(request, REASON_MALFORMED_REFERER)
|
||||||
|
|
||||||
|
# Ensure that our Referer is also secure.
|
||||||
|
if referer.scheme != 'https':
|
||||||
|
return self._reject(request, REASON_INSECURE_REFERER)
|
||||||
|
|
||||||
|
# If there isn't a CSRF_COOKIE_DOMAIN, assume we need an exact
|
||||||
|
# match on host:port. If not, obey the cookie rules.
|
||||||
|
if settings.CSRF_COOKIE_DOMAIN is None:
|
||||||
|
# request.get_host() includes the port.
|
||||||
|
good_referer = request.get_host()
|
||||||
|
else:
|
||||||
|
good_referer = settings.CSRF_COOKIE_DOMAIN
|
||||||
|
server_port = request.META['SERVER_PORT']
|
||||||
|
if server_port not in ('443', '80'):
|
||||||
|
good_referer = '%s:%s' % (good_referer, server_port)
|
||||||
|
|
||||||
# Here we generate a list of all acceptable HTTP referers,
|
# Here we generate a list of all acceptable HTTP referers,
|
||||||
# including the current host since that has been validated
|
# including the current host since that has been validated
|
||||||
# upstream.
|
# upstream.
|
||||||
good_hosts = list(settings.CSRF_TRUSTED_ORIGINS)
|
good_hosts = list(settings.CSRF_TRUSTED_ORIGINS)
|
||||||
# Note that request.get_host() includes the port.
|
good_hosts.append(good_referer)
|
||||||
good_hosts.append(request.get_host())
|
|
||||||
good_referers = ['https://{0}/'.format(host) for host in good_hosts]
|
if not any(is_same_domain(referer.netloc, host) for host in good_hosts):
|
||||||
if not any(same_origin(referer, host) for host in good_referers):
|
reason = REASON_BAD_REFERER % referer.geturl()
|
||||||
reason = REASON_BAD_REFERER % referer
|
|
||||||
return self._reject(request, reason)
|
return self._reject(request, reason)
|
||||||
|
|
||||||
if csrf_token is None:
|
if csrf_token is None:
|
||||||
|
|
|
@ -253,18 +253,24 @@ def quote_etag(etag):
|
||||||
return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"')
|
return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"')
|
||||||
|
|
||||||
|
|
||||||
def same_origin(url1, url2):
|
def is_same_domain(host, pattern):
|
||||||
"""
|
"""
|
||||||
Checks if two URLs are 'same-origin'
|
Return ``True`` if the host is either an exact match or a match
|
||||||
|
to the wildcard pattern.
|
||||||
|
|
||||||
|
Any pattern beginning with a period matches a domain and all of its
|
||||||
|
subdomains. (e.g. ``.example.com`` matches ``example.com`` and
|
||||||
|
``foo.example.com``). Anything else is an exact string match.
|
||||||
"""
|
"""
|
||||||
p1, p2 = urlparse(url1), urlparse(url2)
|
if not pattern:
|
||||||
try:
|
|
||||||
o1 = (p1.scheme, p1.hostname, p1.port or PROTOCOL_TO_PORT[p1.scheme])
|
|
||||||
o2 = (p2.scheme, p2.hostname, p2.port or PROTOCOL_TO_PORT[p2.scheme])
|
|
||||||
return o1 == o2
|
|
||||||
except (ValueError, KeyError):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
pattern = pattern.lower()
|
||||||
|
return (
|
||||||
|
pattern[0] == '.' and (host.endswith(pattern) or host == pattern[1:]) or
|
||||||
|
pattern == host
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_safe_url(url, host=None):
|
def is_safe_url(url, host=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -257,11 +257,19 @@ The CSRF protection is based on the following things:
|
||||||
due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted
|
due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted
|
||||||
by clients that are talking to a site under HTTPS. (Referer checking is not
|
by clients that are talking to a site under HTTPS. (Referer checking is not
|
||||||
done for HTTP requests because the presence of the Referer header is not
|
done for HTTP requests because the presence of the Referer header is not
|
||||||
reliable enough under HTTP.) Expanding the accepted referers beyond the
|
reliable enough under HTTP.)
|
||||||
current host can be done with the :setting:`CSRF_TRUSTED_ORIGINS` setting.
|
|
||||||
|
|
||||||
This ensures that only forms that have originated from your Web site can be used
|
If the :setting:`CSRF_COOKIE_DOMAIN` setting is set, the referer is compared
|
||||||
to POST data back.
|
against it. This setting supports subdomains. For example,
|
||||||
|
``CSRF_COOKIE_DOMAIN = '.example.com'`` will allow POST requests from
|
||||||
|
``www.example.com`` and ``api.example.com``. If the setting is not set, then
|
||||||
|
the referer must match the HTTP ``Host`` header.
|
||||||
|
|
||||||
|
Expanding the accepted referers beyond the current host or cookie domain can
|
||||||
|
be done with the :setting:`CSRF_TRUSTED_ORIGINS` setting.
|
||||||
|
|
||||||
|
This ensures that only forms that have originated from trusted domains can be
|
||||||
|
used to POST data back.
|
||||||
|
|
||||||
It deliberately ignores GET requests (and other requests that are defined as
|
It deliberately ignores GET requests (and other requests that are defined as
|
||||||
'safe' by :rfc:`2616`). These requests ought never to have any potentially
|
'safe' by :rfc:`2616`). These requests ought never to have any potentially
|
||||||
|
@ -269,6 +277,10 @@ dangerous side effects , and so a CSRF attack with a GET request ought to be
|
||||||
harmless. :rfc:`2616` defines POST, PUT and DELETE as 'unsafe', and all other
|
harmless. :rfc:`2616` defines POST, PUT and DELETE as 'unsafe', and all other
|
||||||
methods are assumed to be unsafe, for maximum protection.
|
methods are assumed to be unsafe, for maximum protection.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.9
|
||||||
|
|
||||||
|
Checking against the :setting:`CSRF_COOKIE_DOMAIN` setting was added.
|
||||||
|
|
||||||
Caching
|
Caching
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|
|
@ -444,6 +444,8 @@ header that matches the origin present in the ``Host`` header. This prevents,
|
||||||
for example, a ``POST`` request from ``subdomain.example.com`` from succeeding
|
for example, a ``POST`` request from ``subdomain.example.com`` from succeeding
|
||||||
against ``api.example.com``. If you need cross-origin unsafe requests over
|
against ``api.example.com``. If you need cross-origin unsafe requests over
|
||||||
HTTPS, continuing the example, add ``"subdomain.example.com"`` to this list.
|
HTTPS, continuing the example, add ``"subdomain.example.com"`` to this list.
|
||||||
|
The setting also supports subdomains, so you could add ``".example.com"``, for
|
||||||
|
example, to allow access from all subdomains of ``example.com``.
|
||||||
|
|
||||||
.. setting:: DATABASES
|
.. setting:: DATABASES
|
||||||
|
|
||||||
|
|
|
@ -516,6 +516,10 @@ CSRF
|
||||||
* The request header's name used for CSRF authentication can be customized
|
* The request header's name used for CSRF authentication can be customized
|
||||||
with :setting:`CSRF_HEADER_NAME`.
|
with :setting:`CSRF_HEADER_NAME`.
|
||||||
|
|
||||||
|
* The CSRF referer header is now validated against the
|
||||||
|
:setting:`CSRF_COOKIE_DOMAIN` setting if set. See :ref:`how-csrf-works` for
|
||||||
|
details.
|
||||||
|
|
||||||
* The new :setting:`CSRF_TRUSTED_ORIGINS` setting provides a way to allow
|
* The new :setting:`CSRF_TRUSTED_ORIGINS` setting provides a way to allow
|
||||||
cross-origin unsafe requests (e.g. ``POST``) over HTTPS.
|
cross-origin unsafe requests (e.g. ``POST``) over HTTPS.
|
||||||
|
|
||||||
|
|
|
@ -295,7 +295,7 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
||||||
csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME]
|
csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME]
|
||||||
self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
||||||
|
|
||||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
@override_settings(DEBUG=True)
|
||||||
def test_https_bad_referer(self):
|
def test_https_bad_referer(self):
|
||||||
"""
|
"""
|
||||||
Test that a POST HTTPS request with a bad referer is rejected
|
Test that a POST HTTPS request with a bad referer is rejected
|
||||||
|
@ -304,27 +304,50 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
||||||
req._is_secure_override = True
|
req._is_secure_override = True
|
||||||
req.META['HTTP_HOST'] = 'www.example.com'
|
req.META['HTTP_HOST'] = 'www.example.com'
|
||||||
req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage'
|
req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage'
|
||||||
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
req.META['SERVER_PORT'] = '443'
|
||||||
self.assertIsNotNone(req2)
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
self.assertEqual(403, req2.status_code)
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'Referer checking failed - https://www.evil.org/somepage does not '
|
||||||
|
'match any trusted origins.',
|
||||||
|
status_code=403,
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
@override_settings(DEBUG=True)
|
||||||
def test_https_malformed_referer(self):
|
def test_https_malformed_referer(self):
|
||||||
"""
|
"""
|
||||||
A POST HTTPS request with a bad referer is rejected.
|
A POST HTTPS request with a bad referer is rejected.
|
||||||
"""
|
"""
|
||||||
|
malformed_referer_msg = 'Referer checking failed - Referer is malformed.'
|
||||||
req = self._get_POST_request_with_token()
|
req = self._get_POST_request_with_token()
|
||||||
req._is_secure_override = True
|
req._is_secure_override = True
|
||||||
req.META['HTTP_HOST'] = 'www.example.com'
|
|
||||||
req.META['HTTP_REFERER'] = 'http://http://www.example.com/'
|
req.META['HTTP_REFERER'] = 'http://http://www.example.com/'
|
||||||
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
self.assertIsNotNone(req2)
|
self.assertContains(
|
||||||
self.assertEqual(403, req2.status_code)
|
response,
|
||||||
|
'Referer checking failed - Referer is insecure while host is secure.',
|
||||||
|
status_code=403,
|
||||||
|
)
|
||||||
|
# Empty
|
||||||
|
req.META['HTTP_REFERER'] = ''
|
||||||
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
|
self.assertContains(response, malformed_referer_msg, status_code=403)
|
||||||
# Non-ASCII
|
# Non-ASCII
|
||||||
req.META['HTTP_REFERER'] = b'\xd8B\xf6I\xdf'
|
req.META['HTTP_REFERER'] = b'\xd8B\xf6I\xdf'
|
||||||
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
self.assertIsNotNone(req2)
|
self.assertContains(response, malformed_referer_msg, status_code=403)
|
||||||
self.assertEqual(403, req2.status_code)
|
# missing scheme
|
||||||
|
# >>> urlparse('//example.com/')
|
||||||
|
# ParseResult(scheme='', netloc='example.com', path='/', params='', query='', fragment='')
|
||||||
|
req.META['HTTP_REFERER'] = '//example.com/'
|
||||||
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
|
self.assertContains(response, malformed_referer_msg, status_code=403)
|
||||||
|
# missing netloc
|
||||||
|
# >>> urlparse('https://')
|
||||||
|
# ParseResult(scheme='https', netloc='', path='', params='', query='', fragment='')
|
||||||
|
req.META['HTTP_REFERER'] = 'https://'
|
||||||
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
|
self.assertContains(response, malformed_referer_msg, status_code=403)
|
||||||
|
|
||||||
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||||
def test_https_good_referer(self):
|
def test_https_good_referer(self):
|
||||||
|
@ -365,6 +388,62 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
||||||
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
self.assertIsNone(req2)
|
self.assertIsNone(req2)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['.example.com'])
|
||||||
|
def test_https_csrf_wildcard_trusted_origin_allowed(self):
|
||||||
|
"""
|
||||||
|
A POST HTTPS request with a referer that matches a CSRF_TRUSTED_ORIGINS
|
||||||
|
wilcard is accepted.
|
||||||
|
"""
|
||||||
|
req = self._get_POST_request_with_token()
|
||||||
|
req._is_secure_override = True
|
||||||
|
req.META['HTTP_HOST'] = 'www.example.com'
|
||||||
|
req.META['HTTP_REFERER'] = 'https://dashboard.example.com'
|
||||||
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
|
self.assertIsNone(response)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com')
|
||||||
|
def test_https_good_referer_matches_cookie_domain(self):
|
||||||
|
"""
|
||||||
|
A POST HTTPS request with a good referer should be accepted from a
|
||||||
|
subdomain that's allowed by CSRF_COOKIE_DOMAIN.
|
||||||
|
"""
|
||||||
|
req = self._get_POST_request_with_token()
|
||||||
|
req._is_secure_override = True
|
||||||
|
req.META['HTTP_REFERER'] = 'https://foo.example.com/'
|
||||||
|
req.META['SERVER_PORT'] = '443'
|
||||||
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
|
self.assertIsNone(response)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com')
|
||||||
|
def test_https_good_referer_matches_cookie_domain_with_different_port(self):
|
||||||
|
"""
|
||||||
|
A POST HTTPS request with a good referer should be accepted from a
|
||||||
|
subdomain that's allowed by CSRF_COOKIE_DOMAIN and a non-443 port.
|
||||||
|
"""
|
||||||
|
req = self._get_POST_request_with_token()
|
||||||
|
req._is_secure_override = True
|
||||||
|
req.META['HTTP_HOST'] = 'www.example.com'
|
||||||
|
req.META['HTTP_REFERER'] = 'https://foo.example.com:4443/'
|
||||||
|
req.META['SERVER_PORT'] = '4443'
|
||||||
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
|
self.assertIsNone(response)
|
||||||
|
|
||||||
|
@override_settings(CSRF_COOKIE_DOMAIN='.example.com', DEBUG=True)
|
||||||
|
def test_https_reject_insecure_referer(self):
|
||||||
|
"""
|
||||||
|
A POST HTTPS request from an insecure referer should be rejected.
|
||||||
|
"""
|
||||||
|
req = self._get_POST_request_with_token()
|
||||||
|
req._is_secure_override = True
|
||||||
|
req.META['HTTP_REFERER'] = 'http://example.com/'
|
||||||
|
req.META['SERVER_PORT'] = '443'
|
||||||
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
'Referer checking failed - Referer is insecure while host is secure.',
|
||||||
|
status_code=403,
|
||||||
|
)
|
||||||
|
|
||||||
def test_ensures_csrf_cookie_no_middleware(self):
|
def test_ensures_csrf_cookie_no_middleware(self):
|
||||||
"""
|
"""
|
||||||
The ensure_csrf_cookie() decorator works without middleware.
|
The ensure_csrf_cookie() decorator works without middleware.
|
||||||
|
|
|
@ -10,31 +10,6 @@ from django.utils.datastructures import MultiValueDict
|
||||||
|
|
||||||
class TestUtilsHttp(unittest.TestCase):
|
class TestUtilsHttp(unittest.TestCase):
|
||||||
|
|
||||||
def test_same_origin_true(self):
|
|
||||||
# Identical
|
|
||||||
self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com/'))
|
|
||||||
# One with trailing slash - see #15617
|
|
||||||
self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com/'))
|
|
||||||
self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com'))
|
|
||||||
# With port
|
|
||||||
self.assertTrue(http.same_origin('https://foo.com:8000', 'https://foo.com:8000/'))
|
|
||||||
# No port given but according to RFC6454 still the same origin
|
|
||||||
self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com:80/'))
|
|
||||||
self.assertTrue(http.same_origin('https://foo.com', 'https://foo.com:443/'))
|
|
||||||
|
|
||||||
def test_same_origin_false(self):
|
|
||||||
# Different scheme
|
|
||||||
self.assertFalse(http.same_origin('http://foo.com', 'https://foo.com'))
|
|
||||||
# Different host
|
|
||||||
self.assertFalse(http.same_origin('http://foo.com', 'http://goo.com'))
|
|
||||||
# Different host again
|
|
||||||
self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com.evil.com'))
|
|
||||||
# Different port
|
|
||||||
self.assertFalse(http.same_origin('http://foo.com:8000', 'http://foo.com:8001'))
|
|
||||||
# No port given
|
|
||||||
self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com:8000/'))
|
|
||||||
self.assertFalse(http.same_origin('https://foo.com', 'https://foo.com:8000/'))
|
|
||||||
|
|
||||||
def test_urlencode(self):
|
def test_urlencode(self):
|
||||||
# 2-tuples (the norm)
|
# 2-tuples (the norm)
|
||||||
result = http.urlencode((('a', 1), ('b', 2), ('c', 3)))
|
result = http.urlencode((('a', 1), ('b', 2), ('c', 3)))
|
||||||
|
@ -157,6 +132,25 @@ class TestUtilsHttp(unittest.TestCase):
|
||||||
http.urlunquote_plus('Paris+&+Orl%C3%A9ans'),
|
http.urlunquote_plus('Paris+&+Orl%C3%A9ans'),
|
||||||
'Paris & Orl\xe9ans')
|
'Paris & Orl\xe9ans')
|
||||||
|
|
||||||
|
def test_is_same_domain_good(self):
|
||||||
|
for pair in (
|
||||||
|
('example.com', 'example.com'),
|
||||||
|
('example.com', '.example.com'),
|
||||||
|
('foo.example.com', '.example.com'),
|
||||||
|
('example.com:8888', 'example.com:8888'),
|
||||||
|
('example.com:8888', '.example.com:8888'),
|
||||||
|
('foo.example.com:8888', '.example.com:8888'),
|
||||||
|
):
|
||||||
|
self.assertTrue(http.is_same_domain(*pair))
|
||||||
|
|
||||||
|
def test_is_same_domain_bad(self):
|
||||||
|
for pair in (
|
||||||
|
('example2.com', 'example.com'),
|
||||||
|
('foo.example.com', 'example.com'),
|
||||||
|
('example.com:9999', 'example.com:8888'),
|
||||||
|
):
|
||||||
|
self.assertFalse(http.is_same_domain(*pair))
|
||||||
|
|
||||||
|
|
||||||
class ETagProcessingTests(unittest.TestCase):
|
class ETagProcessingTests(unittest.TestCase):
|
||||||
def test_parsing(self):
|
def test_parsing(self):
|
||||||
|
|
Loading…
Reference in New Issue