Skip to content

Commit 324be05

Browse files
committed
added an XMPP URL validator
- Legacy-Id: 17872
1 parent 8a05780 commit 324be05

1 file changed

Lines changed: 73 additions & 7 deletions

File tree

ietf/utils/validators.py

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
import os
66
import re
77
from pyquery import PyQuery
8-
from urllib.parse import urlparse
8+
from urllib.parse import urlparse, urlsplit
99

1010

1111
from django.conf import settings
1212
from django.core.exceptions import ValidationError
13-
from django.core.validators import RegexValidator, URLValidator, EmailValidator
13+
from django.core.validators import RegexValidator, URLValidator, EmailValidator, _lazy_re_compile
1414
from django.template.defaultfilters import filesizeformat
1515
from django.utils.deconstruct import deconstructible
16+
from django.utils.translation import gettext_lazy as _
1617

1718
import debug # pyflakes:ignore
1819

@@ -92,6 +93,75 @@ def validate_no_html_frame(file):
9293
validate_http_url = URLValidator(schemes=['http','https'])
9394
validate_email = EmailValidator()
9495

96+
@deconstructible
97+
class XMPPURLValidator(RegexValidator):
98+
ul = '\u00a1-\uffff' # unicode letters range (must not be a raw string)
99+
100+
# IP patterns
101+
ipv4_re = r'(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
102+
ipv6_re = r'\[[0-9a-f:\.]+\]' # (simple regex, validated later)
103+
104+
# Host patterns
105+
hostname_re = r'[a-z' + ul + r'0-9](?:[a-z' + ul + r'0-9-]{0,61}[a-z' + ul + r'0-9])?'
106+
# Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1
107+
domain_re = r'(?:\.(?!-)[a-z' + ul + r'0-9-]{1,63}(?<!-))*'
108+
tld_re = (
109+
r'\.' # dot
110+
r'(?!-)' # can't start with a dash
111+
r'(?:[a-z' + ul + '-]{2,63}' # domain label
112+
r'|xn--[a-z0-9]{1,59})' # or punycode label
113+
r'(?<!-)' # can't end with a dash
114+
r'\.?' # may have a trailing dot
115+
)
116+
host_re = '(' + hostname_re + domain_re + tld_re + '|localhost)'
117+
118+
regex = _lazy_re_compile(
119+
r'^(?:xmpp:)' # Note there is no '//'
120+
r'(?:[^\s:@/]+(?::[^\s:@/]*)?@)?' # user:pass authentication
121+
r'(?:' + ipv4_re + '|' + ipv6_re + '|' + host_re + ')'
122+
r'(?::\d{2,5})?' # port
123+
r'(?:[/?#][^\s]*)?' # resource path
124+
r'\Z', re.IGNORECASE)
125+
message = _('Enter a valid URL.')
126+
schemes = ['http', 'https', 'ftp', 'ftps']
127+
128+
def __call__(self, value):
129+
try:
130+
super().__call__(value)
131+
except ValidationError as e:
132+
# Trivial case failed. Try for possible IDN domain
133+
if value:
134+
try:
135+
scheme, netloc, path, query, fragment = urlsplit(value)
136+
except ValueError: # for example, "Invalid IPv6 URL"
137+
raise ValidationError(self.message, code=self.code)
138+
try:
139+
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
140+
except UnicodeError: # invalid domain part
141+
raise e
142+
url = urlunsplit((scheme, netloc, path, query, fragment))
143+
super().__call__(url)
144+
else:
145+
raise
146+
else:
147+
# Now verify IPv6 in the netloc part
148+
host_match = re.search(r'^\[(.+)\](?::\d{2,5})?$', urlsplit(value).netloc)
149+
if host_match:
150+
potential_ip = host_match.groups()[0]
151+
try:
152+
validate_ipv6_address(potential_ip)
153+
except ValidationError:
154+
raise ValidationError(self.message, code=self.code)
155+
156+
# The maximum length of a full host name is 253 characters per RFC 1034
157+
# section 3.1. It's defined to be 255 bytes or less, but this includes
158+
# one byte for the length of the name and one byte for the trailing dot
159+
# that's used to indicate absolute names in DNS.
160+
if len(urlsplit(value).netloc) > 253:
161+
raise ValidationError(self.message, code=self.code)
162+
163+
validate_xmpp = XMPPURLValidator()
164+
95165
def validate_external_resource_value(name, value):
96166
""" validate a resource value using its name's properties """
97167

@@ -102,11 +172,7 @@ def validate_external_resource_value(name, value):
102172
if urlparse(value).netloc.lower() != 'github.com':
103173
raise ValidationError('URL must be a github url')
104174
elif name.slug == 'jabber_room':
105-
pass
106-
# TODO - build a xmpp URL validator. See XEP-0032.
107-
# It should be easy to build one by copyhacking URLValidator,
108-
# but reading source says it would be better to wait to do that
109-
# until after we make the Django 2 transition
175+
validate_xmpp(value)
110176
else:
111177
validate_url(value)
112178

0 commit comments

Comments
 (0)