From e0ee0b9254553bb8b943e879cb660e42d3f1dc4b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 30 Aug 2017 14:08:39 -0400 Subject: [PATCH] Closes #1460: Hostnames with no domain are now acceptable in custom URL fields --- netbox/utilities/forms.py | 17 ++++++----------- netbox/utilities/validators.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 netbox/utilities/validators.py diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 7004d4260..36894ef26 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -7,9 +7,10 @@ from mptt.forms import TreeNodeMultipleChoiceField from django import forms from django.conf import settings -from django.core.validators import URLValidator from django.urls import reverse_lazy +from .validators import EnhancedURLValidator + COLOR_CHOICES = ( ('aa1409', 'Dark red'), @@ -431,17 +432,11 @@ class FilterTreeNodeMultipleChoiceField(FilterChoiceFieldMixin, TreeNodeMultiple class LaxURLField(forms.URLField): """ - Custom URLField which allows any valid URL scheme + Modifies Django's built-in URLField in two ways: + 1) Allow any valid scheme per RFC 3986 section 3.1 + 2) Remove the requirement for fully-qualified domain names (e.g. http://myserver/ is valid) """ - - class AnyURLScheme(object): - # A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1 - def __contains__(self, item): - if not item or not re.match('^[a-z][0-9a-z+\-.]*$', item.lower()): - return False - return True - - default_validators = [URLValidator(schemes=AnyURLScheme())] + default_validators = [EnhancedURLValidator()] # diff --git a/netbox/utilities/validators.py b/netbox/utilities/validators.py new file mode 100644 index 000000000..b5b058111 --- /dev/null +++ b/netbox/utilities/validators.py @@ -0,0 +1,30 @@ +from __future__ import unicode_literals +import re + +from django.core.validators import _lazy_re_compile, URLValidator + + +class EnhancedURLValidator(URLValidator): + """ + Extends Django's built-in URLValidator to permit the use of hostnames with no domain extension. + """ + + class AnyURLScheme(object): + """ + A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1 + """ + def __contains__(self, item): + if not item or not re.match('^[a-z][0-9a-z+\-.]*$', item.lower()): + return False + return True + + fqdn_re = URLValidator.hostname_re + URLValidator.domain_re + URLValidator.tld_re + host_res = [URLValidator.ipv4_re, URLValidator.ipv6_re, fqdn_re, URLValidator.hostname_re] + regex = _lazy_re_compile( + r'^(?:[a-z0-9\.\-\+]*)://' # Scheme (previously enforced by AnyURLScheme or schemes kwarg) + r'(?:\S+(?::\S*)?@)?' # HTTP basic authentication + r'(?:' + '|'.join(host_res) + ')' # IPv4, IPv6, FQDN, or hostname + r'(?::\d{2,5})?' # Port number + r'(?:[/?#][^\s]*)?' # Path + r'\Z', re.IGNORECASE) + schemes = AnyURLScheme()