Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 45 additions & 36 deletions tests/test_email.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,54 @@
"""Test eMail."""
# -*- coding: utf-8 -*-

# standard
import pytest

# local
from validators import email, ValidationFailure


@pytest.mark.parametrize(('value', 'whitelist'), [
('email@here.com', None),
('weirder-email@here.and.there.com', None),
('email@[127.0.0.1]', None),
('example@valid-----hyphens.com', None),
('example@valid-with-hyphens.com', None),
('test@domain.with.idn.tld.उदाहरण.परीक्षा', None),
('email@localhost', None),
('email@localdomain', ['localdomain']),
('"test@test"@example.com', None),
('"\\\011"@here.com', None),
])
def test_returns_true_on_valid_email(value, whitelist):
assert email(value, whitelist=whitelist)
@pytest.mark.parametrize(
("value",),
[
("email@here.com",),
("weirder-email@here.and.there.com",),
("email@127.local.home.arpa",),
("example@valid-----hyphens.com",),
("example@valid-with-hyphens.com",),
("test@domain.with.idn.tld.उदाहरण.परीक्षा",),
("email@localhost.in",),
("email@localdomain.org",),
('"\\\011"@here.com',),
],
)
def test_returns_true_on_valid_email(value: str):
"""Test returns true on valid email."""
assert email(value)


@pytest.mark.parametrize(('value',), [
(None,),
('',),
('abc',),
('abc@',),
('abc@bar',),
('a @x.cz',),
('abc@.com',),
('something@@somewhere.com',),
('email@127.0.0.1',),
('example@invalid-.com',),
('example@-invalid.com',),
('example@inv-.alid-.com',),
('example@inv-.-alid.com',),
(
'john56789.john56789.john56789.john56789.john56789.john56789.john5'
'@example.com',
),
# Quoted-string format (CR not allowed)
('"\\\012"@here.com',),
])
def test_returns_failed_validation_on_invalid_email(value):
@pytest.mark.parametrize(
("value",),
[
(None,),
("",),
("abc",),
("abc@",),
("abc@bar",),
("a @x.cz",),
("abc@.com",),
("something@@somewhere.com",),
("email@127.0.0.1",),
("example@invalid-.com",),
("example@-invalid.com",),
("example@inv-.alid-.com",),
("example@inv-.-alid.com",),
("john56789.john56789.john56789.john56789.john56789.john56789.john5@example.com",),
('"test@test"@example.com',),
# Quoted-string format (CR not allowed)
('"\\\012"@here.com',),
],
)
def test_returns_failed_validation_on_invalid_email(value: str):
"""Test returns failed validation on invalid email."""
assert isinstance(email(value), ValidationFailure)
96 changes: 41 additions & 55 deletions validators/email.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,61 @@
"""eMail."""
# -*- coding: utf-8 -*-

# standard
import re

# local
from .utils import validator

user_regex = re.compile(
# dot-atom
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+"
r"(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$"
# quoted-string
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|'
r"""\\[\001-\011\013\014\016-\177])*"$)""",
re.IGNORECASE
)
domain_regex = re.compile(
# domain
r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?$)'
# literal form, ipv4 address (SMTP 4.1.3)
r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)'
r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$',
re.IGNORECASE)
domain_whitelist = ['localhost']
from .domain import domain


@validator
def email(value, whitelist=None):
"""
Validate an email address.
def email(value: str, /):
"""Validate an email address.
This validator is based on `Django's email validator`_. Returns
``True`` on success and :class:`~validators.utils.ValidationFailure`
when validation fails.
This was inspired from [Django's email validator][1].
Also ref: [RFC 1034][2], [RFC 5321][3] and [RFC 5322][4].
Examples::
[1]: https://github.com/django/django/blob/main/django/core/validators.py#L174
[2]: https://www.rfc-editor.org/rfc/rfc1034
[3]: https://www.rfc-editor.org/rfc/rfc5321
[4]: https://www.rfc-editor.org/rfc/rfc5322
Examples:
>>> email('someone@example.com')
True
# Output: True
>>> email('bogus@@')
ValidationFailure(func=email, ...)
.. _Django's email validator:
https://github.com/django/django/blob/master/django/core/validators.py
# Output: ValidationFailure(email=email, args={'value': 'bogus@@'})
.. versionadded:: 0.1
Args:
value:
eMail string to validate.
:param value: value to validate
:param whitelist: domain names to whitelist
Returns:
(Literal[True]):
If `value` is a valid eMail.
(ValidationFailure):
If `value` is an invalid eMail.
:copyright: (c) Django Software Foundation and individual contributors.
:license: BSD
> *New in version 0.1.0*.
"""

if whitelist is None:
whitelist = domain_whitelist

if not value or '@' not in value:
if not value or value.count("@") != 1:
return False

user_part, domain_part = value.rsplit('@', 1)

if not user_regex.match(user_part):
return False
username_part, domain_part = value.rsplit("@", 1)

if len(user_part.encode("utf-8")) > 64:
if len(username_part) > 64 or len(domain_part) > 253:
# ref: RFC 1034 and 5231
return False

if domain_part not in whitelist and not domain_regex.match(domain_part):
# Try for possible IDN domain-part
try:
domain_part = domain_part.encode('idna').decode('ascii')
return domain_regex.match(domain_part)
except UnicodeError:
return False
return True
return (
bool(domain(domain_part))
if re.compile(
# dot-atom
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$"
# quoted-string
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)',
re.IGNORECASE,
).match(username_part)
else False
)