from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.forms import URLField
from django.test import SimpleTestCase
from . import FormFieldAssertionsMixin
class URLFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
def test_urlfield_widget(self):
f = URLField()
self.assertWidgetRendersTo(f, '')
def test_urlfield_widget_max_min_length(self):
f = URLField(min_length=15, max_length=20)
self.assertEqual("http://example.com", f.clean("http://example.com"))
self.assertWidgetRendersTo(
f,
'',
)
msg = "'Ensure this value has at least 15 characters (it has 12).'"
with self.assertRaisesMessage(ValidationError, msg):
f.clean("http://f.com")
msg = "'Ensure this value has at most 20 characters (it has 37).'"
with self.assertRaisesMessage(ValidationError, msg):
f.clean("http://abcdefghijklmnopqrstuvwxyz.com")
def test_urlfield_clean(self):
f = URLField(required=False)
tests = [
("http://localhost", "http://localhost"),
("http://example.com", "http://example.com"),
("http://example.com/test", "http://example.com/test"),
("http://example.com.", "http://example.com."),
("http://www.example.com", "http://www.example.com"),
("http://www.example.com:8000/test", "http://www.example.com:8000/test"),
(
"http://example.com?some_param=some_value",
"http://example.com?some_param=some_value",
),
("valid-with-hyphens.com", "https://valid-with-hyphens.com"),
("subdomain.domain.com", "https://subdomain.domain.com"),
("http://200.8.9.10", "http://200.8.9.10"),
("http://200.8.9.10:8000/test", "http://200.8.9.10:8000/test"),
("http://valid-----hyphens.com", "http://valid-----hyphens.com"),
(
"http://some.idn.xyzäöüßabc.domain.com:123/blah",
"http://some.idn.xyz\xe4\xf6\xfc\xdfabc.domain.com:123/blah",
),
(
"www.example.com/s/http://code.djangoproject.com/ticket/13804",
"https://www.example.com/s/http://code.djangoproject.com/ticket/13804",
),
# Normalization.
("http://example.com/ ", "http://example.com/"),
# Valid IDN.
("http://עברית.idn.icann.org/", "http://עברית.idn.icann.org/"),
("http://sãopaulo.com/", "http://sãopaulo.com/"),
("http://sãopaulo.com.br/", "http://sãopaulo.com.br/"),
("http://пример.испытание/", "http://пример.испытание/"),
("http://مثال.إختبار/", "http://مثال.إختبار/"),
("http://例子.测试/", "http://例子.测试/"),
("http://例子.測試/", "http://例子.測試/"),
(
"http://उदाहरण.परीक्षा/",
"http://उदाहरण.परीक्षा/",
),
("http://例え.テスト/", "http://例え.テスト/"),
("http://مثال.آزمایشی/", "http://مثال.آزمایشی/"),
("http://실례.테스트/", "http://실례.테스트/"),
("http://العربية.idn.icann.org/", "http://العربية.idn.icann.org/"),
# IPv6.
("http://[12:34::3a53]/", "http://[12:34::3a53]/"),
("http://[a34:9238::]:8080/", "http://[a34:9238::]:8080/"),
# IPv6 without scheme.
("[12:34::3a53]/", "https://[12:34::3a53]/"),
# IDN domain without scheme but with port.
("ñandú.es:8080/", "https://ñandú.es:8080/"),
# Scheme-relative.
("//example.com", "https://example.com"),
("//example.com/path", "https://example.com/path"),
# Whitespace stripped.
("\t\n//example.com \n\t\n", "https://example.com"),
("\t\nhttp://example.com \n\t\n", "http://example.com"),
]
for url, expected in tests:
with self.subTest(url=url):
self.assertEqual(f.clean(url), expected)
def test_urlfield_clean_invalid(self):
f = URLField()
tests = [
"foo",
"com.",
".",
"http://",
"http://example",
"http://example.",
"http://.com",
"http://invalid-.com",
"http://-invalid.com",
"http://inv-.alid-.com",
"http://inv-.-alid.com",
"[a",
"http://[a",
# Non-string.
23,
# Hangs "forever" before fixing a catastrophic backtracking,
# see #11198.
"http://%s" % ("X" * 60,),
# A second example, to make sure the problem is really addressed,
# even on domains that don't fail the domain label length check in
# the regex.
"http://%s" % ("X" * 200,),
# Scheme prepend yields a structurally invalid URL.
"////]@N.AN",
# Scheme prepend yields an empty hostname.
"#@A.bO",
# Known problematic unicode chars.
"http://" + "¾" * 200,
# Non-ASCII character before the first colon.
"¾:example.com",
# ASCII digit before the first colon.
"1http://example.com",
# Empty scheme.
"://example.com",
":example.com",
]
msg = "'Enter a valid URL.'"
for value in tests:
with self.subTest(value=value):
with self.assertRaisesMessage(ValidationError, msg):
f.clean(value)
def test_urlfield_clean_required(self):
f = URLField()
msg = "'This field is required.'"
with self.assertRaisesMessage(ValidationError, msg):
f.clean(None)
with self.assertRaisesMessage(ValidationError, msg):
f.clean("")
def test_urlfield_clean_not_required(self):
f = URLField(required=False)
self.assertEqual(f.clean(None), "")
self.assertEqual(f.clean(""), "")
def test_urlfield_strip_on_none_value(self):
f = URLField(required=False, empty_value=None)
self.assertIsNone(f.clean(""))
self.assertIsNone(f.clean(None))
def test_urlfield_unable_to_set_strip_kwarg(self):
msg = "got multiple values for keyword argument 'strip'"
with self.assertRaisesMessage(TypeError, msg):
URLField(strip=False)
def test_urlfield_assume_scheme(self):
f = URLField()
self.assertEqual(f.clean("example.com"), "https://example.com")
f = URLField(assume_scheme="http")
self.assertEqual(f.clean("example.com"), "http://example.com")
f = URLField(assume_scheme="https")
self.assertEqual(f.clean("example.com"), "https://example.com")
def test_urlfield_assume_scheme_when_colons(self):
f = URLField()
tests = [
# Port number.
("http://example.com:8080/", "http://example.com:8080/"),
("https://example.com:443/path", "https://example.com:443/path"),
# Userinfo with password.
("http://user:pass@example.com", "http://user:pass@example.com"),
(
"http://user:pass@example.com:8080/",
"http://user:pass@example.com:8080/",
),
# Colon in path segment.
("http://example.com/path:segment", "http://example.com/path:segment"),
("http://example.com/a:b/c:d", "http://example.com/a:b/c:d"),
# Colon in query string.
("http://example.com/?key=val:ue", "http://example.com/?key=val:ue"),
# Colon in fragment.
("http://example.com/#section:1", "http://example.com/#section:1"),
# IPv6 -- multiple colons in host.
("http://[::1]/", "http://[::1]/"),
("http://[2001:db8::1]/", "http://[2001:db8::1]/"),
("http://[2001:db8::1]:8080/", "http://[2001:db8::1]:8080/"),
# Colons across multiple components.
(
"http://user:pass@example.com:8080/path:x?q=a:b#id:1",
"http://user:pass@example.com:8080/path:x?q=a:b#id:1",
),
# FTP with port and userinfo.
(
"ftp://user:pass@ftp.example.com:21/file",
"ftp://user:pass@ftp.example.com:21/file",
),
(
"ftps://user:pass@ftp.example.com:990/",
"ftps://user:pass@ftp.example.com:990/",
),
# Scheme-relative URLs, starts with "//".
("//example.com:8080/path", "https://example.com:8080/path"),
("//user:pass@example.com/", "https://user:pass@example.com/"),
]
for value, expected in tests:
with self.subTest(value=value):
self.assertEqual(f.clean(value), expected)
def test_urlfield_non_hierarchical_schemes_unchanged_in_to_python(self):
f = URLField()
tests = [
"mailto:test@example.com",
"mailto:test@example.com?subject=Hello",
"tel:+1-800-555-0100",
"tel:555-0100",
"urn:isbn:0-486-27557-4",
"urn:ietf:rfc:2648",
]
for value in tests:
with self.subTest(value=value):
self.assertEqual(f.to_python(value), value)
def test_custom_validator_longer_max_length(self):
class CustomLongURLValidator(URLValidator):
max_length = 4096
class CustomURLField(URLField):
default_validators = [CustomLongURLValidator()]
field = CustomURLField()
# A URL with 4096 chars is valid given the custom validator.
prefix = "https://example.com/"
url = prefix + "a" * (4096 - len(prefix))
self.assertEqual(len(url), 4096)
# No ValidationError is raised.
field.clean(url)