The challenge
Create a domain name validator mostly compliant with RFC 1035, RFC 1123, and RFC 2181
The following rules apply:
- Domain name may contain subdomains (levels), hierarchically separated by . (period) character
- Domain name must not contain more than 127 levels, including top level (TLD)
- Domain name must not be longer than 253 characters (RFC specifies 255, but 2 characters are reserved for trailing dot and null character for root level)
- Level names must be composed out of lowercase and uppercase ASCII letters, digits and – (minus sign) character
- Level names must not start or end with – (minus sign) character
- Level names must not be longer than 63 characters
- Top level (TLD) must not be fully numerical
Additionally:
- Domain name must contain at least one subdomain (level) apart from TLD
- Top level validation must be naive – ie. TLDs nonexistent in IANA register are still considered valid as long as they adhere to the rules given above.
The validation function accepts a string with the full domain name and returns a boolean value indicating whether the domain name is valid or not.
Examples:
validate('aoms') == False
validate('andrewodendaal.com') == True
validate('amazon.com') == True
validate('AMAZON.COM') == True
validate('sub.amazon.com') == True
validate('amazon.com-') == False
validate('.amazon.com') == False
validate('[email protected]') == False
validate('127.0.0.1') == False
The solution in Python
Option 1:
import re
def validate(domain):
return re.match('''
(?=^.{,253}$) # max. length 253 chars
(?!^.+\.\d+$) # TLD is not fully numerical
(?=^[^-.].+[^-.]$) # doesn't start/end with '-' or '.'
(?!^.+(\.-|-\.).+$) # levels don't start/end with '-'
(?:[a-z\d-] # uses only allowed chars
{1,63}(\.|$)) # max. level length 63 chars
{2,127} # max. 127 levels
''', domain, re.X | re.I)
Option 2:
def validate(domain):
print(domain)
if len(domain) > 253 or len(domain) == 0:
print(1)
return False
els = domain.split('.')
if len(els) > 127 or len(els) < 2:
print(2)
return False
for x in els:
if len(x) > 63 or len(x) == 0:
print(3)
return False
if not x[0].isalnum() or not x[-1].isalnum():
print(4)
return False
for l in x:
if (not all(ord(c) < 128 for c in l) or not l.isalnum()) and l != '-':
print(5)
return False
if els[-1].isnumeric():
return False
print(True)
return True
Option 3:
import re
def validLevel(lvl):
return not bool(re.search(r'^-|-$', lvl)) and bool(re.match(r'[a-zA-Z0-9-]{1,63}$', lvl))
def validate(domain):
lst = domain.split('.')
return len(domain) <= 253 \
and 2 <= len(lst) <= 127 \
and not lst[-1].isdigit() \
and all( validLevel(lvl) for lvl in lst )
Test cases to validate our solution
test.describe('Domain name validator tests')
test.expect(not validate('aoms'))
test.expect(validate('andrewodendaal.com'))
test.expect(validate('amazon.com'))
test.expect(validate('AMAZON.COM'))
test.expect(validate('sub.amazon.com'))
test.expect(not validate('amazon.com-'))
test.expect(not validate('.amazon.com'))
test.expect(not validate('[email protected]'))
test.expect(not validate('127.0.0.1'))