Repository: django/django
Issue category: Bug Report

Dead code in URLValidator
Description
	 
There's some dead code in django.core.validators.URLValidator which could be removed:
The entire ​Trivial case failed. Try for possible IDN domain section is no longer useful. This code attempts to re-validate a failed URL after encoding an international domain name using IDNA 2003 ("punycode"). But the URLValidator regular expressions have allowed all IDNs since ​Commit 2e65d56 (for #20003, in 2015), so the super call will never fail with a validation error that switching to IDNA 2003 would let pass.
The ​`ul` unicode letters property is no longer used. The regular expressions that had used it were moved into DomainNameValidator in ​Commit 4971a9a (for #18119, in Django 5.1).
For the first case, one way to verify the code is no longer in use is to run URLValidator on https://މިހާރު.com, which is a domain allowed by IDNA 2008 but prohibited by IDNA 2003. If the punycode() branch were coming into play, that URL would be rejected:
from django.core.validators import URLValidator
URLValidator()("https://މިހާރު.com")
# (No error)
from django.utils.encoding import punycode
punycode("މިހާރު.com")
# UnicodeError: Violation of BIDI requirement 3
# encoding with 'idna' codec failed


Functions to edit:
django/core/validators.py:URLValidator
django/core/validators.py:URLValidator.__call__

Patch:
diff --git a/django/core/validators.py b/django/core/validators.py
index 8732ddf7adbf..c4e734c1d82a 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -2,7 +2,7 @@
 import math
 import re
 from pathlib import Path
-from urllib.parse import urlsplit, urlunsplit
+from urllib.parse import urlsplit
 
 from django.core.exceptions import ValidationError
 from django.utils.deconstruct import deconstructible
@@ -128,8 +128,6 @@ def __call__(self, value):
 
 @deconstructible
 class URLValidator(RegexValidator):
-    ul = "\u00a1-\uffff"  # Unicode letters range (must not be a raw string).
-
     # IP patterns
     ipv4_re = (
         r"(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)"
@@ -177,31 +175,17 @@ def __call__(self, value):
             splitted_url = urlsplit(value)
         except ValueError:
             raise ValidationError(self.message, code=self.code, params={"value": value})
-        try:
-            super().__call__(value)
-        except ValidationError as e:
-            # Trivial case failed. Try for possible IDN domain
-            if value:
-                scheme, netloc, path, query, fragment = splitted_url
-                try:
-                    netloc = punycode(netloc)  # IDN -> ACE
-                except UnicodeError:  # invalid domain part
-                    raise e
-                url = urlunsplit((scheme, netloc, path, query, fragment))
-                super().__call__(url)
-            else:
-                raise
-        else:
-            # Now verify IPv6 in the netloc part
-            host_match = re.search(r"^\[(.+)\](?::[0-9]{1,5})?$", splitted_url.netloc)
-            if host_match:
-                potential_ip = host_match[1]
-                try:
-                    validate_ipv6_address(potential_ip)
-                except ValidationError:
-                    raise ValidationError(
-                        self.message, code=self.code, params={"value": value}
-                    )
+        super().__call__(value)
+        # Now verify IPv6 in the netloc part
+        host_match = re.search(r"^\[(.+)\](?::[0-9]{1,5})?$", splitted_url.netloc)
+        if host_match:
+            potential_ip = host_match[1]
+            try:
+                validate_ipv6_address(potential_ip)
+            except ValidationError:
+                raise ValidationError(
+                    self.message, code=self.code, params={"value": value}
+                )
 
         # The maximum length of a full host name is 253 characters per RFC 1034
         # section 3.1. It's defined to be 255 bytes or less, but this includes

Test patch: diff --git a/tests/validators/tests.py b/tests/validators/tests.py
index 4ae0f6413e5e..7455c93d407e 100644
--- a/tests/validators/tests.py
+++ b/tests/validators/tests.py
@@ -126,6 +126,7 @@
     "http://مثال.إختبار",
     "http://例子.测试",
     "http://उदाहरण.परीक्षा",
+    "https://މިހާރު.com",  # (valid in IDNA 2008 but not IDNA 2003)
     "http://-.~_!$&'()*+,;=%40:80%2f@example.com",
     "http://xn--7sbb4ac0ad0be6cf.xn--p1ai",
     "http://1337.net",