Загрузить файлы в «venv/Lib/site-packages/email_validator»
This commit is contained in:
103
venv/Lib/site-packages/email_validator/__init__.py
Normal file
103
venv/Lib/site-packages/email_validator/__init__.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
# Export the main method, helper methods, and the public data types.
|
||||||
|
from .exceptions import EmailNotValidError, EmailSyntaxError, EmailUndeliverableError
|
||||||
|
from .types import ValidatedEmail
|
||||||
|
from .validate_email import validate_email
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
|
__all__ = ["validate_email",
|
||||||
|
"ValidatedEmail", "EmailNotValidError",
|
||||||
|
"EmailSyntaxError", "EmailUndeliverableError",
|
||||||
|
"caching_resolver", "__version__"]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .deliverability import caching_resolver
|
||||||
|
else:
|
||||||
|
def caching_resolver(*args, **kwargs):
|
||||||
|
# Lazy load `deliverability` as it is slow to import (due to dns.resolver)
|
||||||
|
from .deliverability import caching_resolver
|
||||||
|
|
||||||
|
return caching_resolver(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# These global attributes are a part of the library's API and can be
|
||||||
|
# changed by library users.
|
||||||
|
|
||||||
|
# Default values for keyword arguments.
|
||||||
|
|
||||||
|
ALLOW_SMTPUTF8 = True
|
||||||
|
ALLOW_EMPTY_LOCAL = False
|
||||||
|
ALLOW_QUOTED_LOCAL = False
|
||||||
|
ALLOW_DOMAIN_LITERAL = False
|
||||||
|
ALLOW_DISPLAY_NAME = False
|
||||||
|
STRICT = False
|
||||||
|
GLOBALLY_DELIVERABLE = True
|
||||||
|
CHECK_DELIVERABILITY = True
|
||||||
|
TEST_ENVIRONMENT = False
|
||||||
|
DEFAULT_TIMEOUT = 15 # secs
|
||||||
|
|
||||||
|
# IANA Special Use Domain Names
|
||||||
|
# Last Updated 2021-09-21
|
||||||
|
# https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.txt
|
||||||
|
#
|
||||||
|
# The domain names without dots would be caught by the check that the domain
|
||||||
|
# name in an email address must have a period, but this list will also catch
|
||||||
|
# subdomains of these domains, which are also reserved.
|
||||||
|
SPECIAL_USE_DOMAIN_NAMES = [
|
||||||
|
# The "arpa" entry here is consolidated from a lot of arpa subdomains
|
||||||
|
# for private address (i.e. non-routable IP addresses like 172.16.x.x)
|
||||||
|
# reverse mapping, plus some other subdomains. Although RFC 6761 says
|
||||||
|
# that application software should not treat these domains as special,
|
||||||
|
# they are private-use domains and so cannot have globally deliverable
|
||||||
|
# email addresses, which is an assumption of this library, and probably
|
||||||
|
# all of arpa is similarly special-use, so we reject it all.
|
||||||
|
"arpa",
|
||||||
|
|
||||||
|
# RFC 6761 says applications "SHOULD NOT" treat the "example" domains
|
||||||
|
# as special, i.e. applications should accept these domains.
|
||||||
|
#
|
||||||
|
# The domain "example" alone fails our syntax validation because it
|
||||||
|
# lacks a dot (we assume no one has an email address on a TLD directly).
|
||||||
|
# "@example.com/net/org" will currently fail DNS-based deliverability
|
||||||
|
# checks because IANA publishes a NULL MX for these domains, and
|
||||||
|
# "@mail.example[.com/net/org]" and other subdomains will fail DNS-
|
||||||
|
# based deliverability checks because IANA does not publish MX or A
|
||||||
|
# DNS records for these subdomains.
|
||||||
|
# "example", # i.e. "wwww.example"
|
||||||
|
# "example.com",
|
||||||
|
# "example.net",
|
||||||
|
# "example.org",
|
||||||
|
|
||||||
|
# RFC 6761 says that applications are permitted to treat this domain
|
||||||
|
# as special and that DNS should return an immediate negative response,
|
||||||
|
# so we also immediately reject this domain, which also follows the
|
||||||
|
# purpose of the domain.
|
||||||
|
"invalid",
|
||||||
|
|
||||||
|
# RFC 6762 says that applications "may" treat ".local" as special and
|
||||||
|
# that "name resolution APIs and libraries SHOULD recognize these names
|
||||||
|
# as special," and since ".local" has no global definition, we reject
|
||||||
|
# it, as we expect email addresses to be gloally routable.
|
||||||
|
"local",
|
||||||
|
|
||||||
|
# RFC 6761 says that applications (like this library) are permitted
|
||||||
|
# to treat "localhost" as special, and since it cannot have a globally
|
||||||
|
# deliverable email address, we reject it.
|
||||||
|
"localhost",
|
||||||
|
|
||||||
|
# RFC 7686 says "applications that do not implement the Tor protocol
|
||||||
|
# SHOULD generate an error upon the use of .onion and SHOULD NOT
|
||||||
|
# perform a DNS lookup.
|
||||||
|
"onion",
|
||||||
|
|
||||||
|
# Although RFC 6761 says that application software should not treat
|
||||||
|
# these domains as special, it also warns users that the address may
|
||||||
|
# resolve differently in different systems, and therefore it cannot
|
||||||
|
# have a globally routable email address, which is an assumption of
|
||||||
|
# this library, so we reject "@test" and "@*.test" addresses, unless
|
||||||
|
# the test_environment keyword argument is given, to allow their use
|
||||||
|
# in application-level test environments. These domains will generally
|
||||||
|
# fail deliverability checks because "test" is not an actual TLD.
|
||||||
|
"test",
|
||||||
|
]
|
||||||
61
venv/Lib/site-packages/email_validator/__main__.py
Normal file
61
venv/Lib/site-packages/email_validator/__main__.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# A command-line tool for testing.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# python -m email_validator test@example.org
|
||||||
|
# python -m email_validator < LIST_OF_ADDRESSES.TXT
|
||||||
|
#
|
||||||
|
# Provide email addresses to validate either as a command-line argument
|
||||||
|
# or in STDIN separated by newlines. Validation errors will be printed for
|
||||||
|
# invalid email addresses. When passing an email address on the command
|
||||||
|
# line, if the email address is valid, information about it will be printed.
|
||||||
|
# When using STDIN, no output will be given for valid email addresses.
|
||||||
|
#
|
||||||
|
# Keyword arguments to validate_email can be set in environment variables
|
||||||
|
# of the same name but uppercase (see below).
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from .validate_email import validate_email, _Resolver
|
||||||
|
from .deliverability import caching_resolver
|
||||||
|
from .exceptions import EmailNotValidError
|
||||||
|
|
||||||
|
|
||||||
|
def main(dns_resolver: Optional[_Resolver] = None) -> None:
|
||||||
|
# The dns_resolver argument is for tests.
|
||||||
|
|
||||||
|
# Set options from environment variables.
|
||||||
|
options: Dict[str, Any] = {}
|
||||||
|
for varname in ('ALLOW_SMTPUTF8', 'ALLOW_EMPTY_LOCAL', 'ALLOW_QUOTED_LOCAL', 'ALLOW_DOMAIN_LITERAL',
|
||||||
|
'ALLOW_DISPLAY_NAME',
|
||||||
|
'GLOBALLY_DELIVERABLE', 'CHECK_DELIVERABILITY', 'TEST_ENVIRONMENT'):
|
||||||
|
if varname in os.environ:
|
||||||
|
options[varname.lower()] = bool(os.environ[varname])
|
||||||
|
for varname in ('DEFAULT_TIMEOUT',):
|
||||||
|
if varname in os.environ:
|
||||||
|
options[varname.lower()] = float(os.environ[varname])
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
# Validate the email addresses passed line-by-line on STDIN.
|
||||||
|
dns_resolver = dns_resolver or caching_resolver()
|
||||||
|
for line in sys.stdin:
|
||||||
|
email = line.strip()
|
||||||
|
try:
|
||||||
|
validate_email(email, dns_resolver=dns_resolver, **options)
|
||||||
|
except EmailNotValidError as e:
|
||||||
|
print(f"{email} {e}")
|
||||||
|
else:
|
||||||
|
# Validate the email address passed on the command line.
|
||||||
|
email = sys.argv[1]
|
||||||
|
try:
|
||||||
|
result = validate_email(email, dns_resolver=dns_resolver, **options)
|
||||||
|
print(json.dumps(result.as_dict(), indent=2, sort_keys=True, ensure_ascii=False))
|
||||||
|
except EmailNotValidError as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
159
venv/Lib/site-packages/email_validator/deliverability.py
Normal file
159
venv/Lib/site-packages/email_validator/deliverability.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
from typing import Any, List, Optional, Tuple, TypedDict
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
from .exceptions import EmailUndeliverableError
|
||||||
|
|
||||||
|
import dns.resolver
|
||||||
|
import dns.exception
|
||||||
|
|
||||||
|
|
||||||
|
def caching_resolver(*, timeout: Optional[int] = None, cache: Any = None, dns_resolver: Optional[dns.resolver.Resolver] = None) -> dns.resolver.Resolver:
|
||||||
|
if timeout is None:
|
||||||
|
from . import DEFAULT_TIMEOUT
|
||||||
|
timeout = DEFAULT_TIMEOUT
|
||||||
|
resolver = dns_resolver or dns.resolver.Resolver()
|
||||||
|
resolver.cache = cache or dns.resolver.LRUCache()
|
||||||
|
resolver.lifetime = timeout # timeout, in seconds
|
||||||
|
return resolver
|
||||||
|
|
||||||
|
|
||||||
|
DeliverabilityInfo = TypedDict("DeliverabilityInfo", {
|
||||||
|
"mx": List[Tuple[int, str]],
|
||||||
|
"mx_fallback_type": Optional[str],
|
||||||
|
"unknown-deliverability": str,
|
||||||
|
}, total=False)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Optional[int] = None, dns_resolver: Optional[dns.resolver.Resolver] = None) -> DeliverabilityInfo:
|
||||||
|
# Check that the domain resolves to an MX record. If there is no MX record,
|
||||||
|
# try an A or AAAA record which is a deprecated fallback for deliverability.
|
||||||
|
# Raises an EmailUndeliverableError on failure. On success, returns a dict
|
||||||
|
# with deliverability information.
|
||||||
|
|
||||||
|
# If no dns.resolver.Resolver was given, get dnspython's default resolver.
|
||||||
|
# Override the default resolver's timeout. This may affect other uses of
|
||||||
|
# dnspython in this process.
|
||||||
|
if dns_resolver is None:
|
||||||
|
from . import DEFAULT_TIMEOUT
|
||||||
|
if timeout is None:
|
||||||
|
timeout = DEFAULT_TIMEOUT
|
||||||
|
dns_resolver = dns.resolver.get_default_resolver()
|
||||||
|
dns_resolver.lifetime = timeout
|
||||||
|
elif timeout is not None:
|
||||||
|
raise ValueError("It's not valid to pass both timeout and dns_resolver.")
|
||||||
|
|
||||||
|
deliverability_info: DeliverabilityInfo = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
# Try resolving for MX records (RFC 5321 Section 5).
|
||||||
|
response = dns_resolver.resolve(domain, "MX")
|
||||||
|
|
||||||
|
# For reporting, put them in priority order and remove the trailing dot in the qnames.
|
||||||
|
mtas = sorted([(r.preference, str(r.exchange).rstrip('.')) for r in response])
|
||||||
|
|
||||||
|
# RFC 7505: Null MX (0, ".") records signify the domain does not accept email.
|
||||||
|
# Remove null MX records from the mtas list (but we've stripped trailing dots,
|
||||||
|
# so the 'exchange' is just "") so we can check if there are no non-null MX
|
||||||
|
# records remaining.
|
||||||
|
mtas = [(preference, exchange) for preference, exchange in mtas
|
||||||
|
if exchange != ""]
|
||||||
|
if len(mtas) == 0: # null MX only, if there were no MX records originally a NoAnswer exception would have occurred
|
||||||
|
raise EmailUndeliverableError(f"The domain name {domain_i18n} does not accept email.")
|
||||||
|
|
||||||
|
deliverability_info["mx"] = mtas
|
||||||
|
deliverability_info["mx_fallback_type"] = None
|
||||||
|
|
||||||
|
except dns.resolver.NoAnswer:
|
||||||
|
# If there was no MX record, fall back to an A or AAA record
|
||||||
|
# (RFC 5321 Section 5). Check A first since it's more common.
|
||||||
|
|
||||||
|
# If the A/AAAA response has no Globally Reachable IP address,
|
||||||
|
# treat the response as if it were NoAnswer, i.e., the following
|
||||||
|
# address types are not allowed fallbacks: Private-Use, Loopback,
|
||||||
|
# Link-Local, and some other obscure ranges. See
|
||||||
|
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
||||||
|
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
||||||
|
# (Issue #134.)
|
||||||
|
def is_global_addr(address: Any) -> bool:
|
||||||
|
try:
|
||||||
|
ipaddr = ipaddress.ip_address(address)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
return ipaddr.is_global
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = dns_resolver.resolve(domain, "A")
|
||||||
|
|
||||||
|
if not any(is_global_addr(r.address) for r in response):
|
||||||
|
raise dns.resolver.NoAnswer # fall back to AAAA
|
||||||
|
|
||||||
|
deliverability_info["mx"] = [(0, domain)]
|
||||||
|
deliverability_info["mx_fallback_type"] = "A"
|
||||||
|
|
||||||
|
except dns.resolver.NoAnswer:
|
||||||
|
|
||||||
|
# If there was no A record, fall back to an AAAA record.
|
||||||
|
# (It's unclear if SMTP servers actually do this.)
|
||||||
|
try:
|
||||||
|
response = dns_resolver.resolve(domain, "AAAA")
|
||||||
|
|
||||||
|
if not any(is_global_addr(r.address) for r in response):
|
||||||
|
raise dns.resolver.NoAnswer
|
||||||
|
|
||||||
|
deliverability_info["mx"] = [(0, domain)]
|
||||||
|
deliverability_info["mx_fallback_type"] = "AAAA"
|
||||||
|
|
||||||
|
except dns.resolver.NoAnswer as e:
|
||||||
|
# If there was no MX, A, or AAAA record, then mail to
|
||||||
|
# this domain is not deliverable, although the domain
|
||||||
|
# name has other records (otherwise NXDOMAIN would
|
||||||
|
# have been raised).
|
||||||
|
raise EmailUndeliverableError(f"The domain name {domain_i18n} does not accept email.") from e
|
||||||
|
|
||||||
|
# Check for a SPF (RFC 7208) reject-all record ("v=spf1 -all") which indicates
|
||||||
|
# no emails are sent from this domain (similar to a Null MX record
|
||||||
|
# but for sending rather than receiving). In combination with the
|
||||||
|
# absence of an MX record, this is probably a good sign that the
|
||||||
|
# domain is not used for email.
|
||||||
|
try:
|
||||||
|
response = dns_resolver.resolve(domain, "TXT")
|
||||||
|
for rec in response:
|
||||||
|
value = b"".join(rec.strings)
|
||||||
|
if value.startswith(b"v=spf1 "):
|
||||||
|
if value == b"v=spf1 -all":
|
||||||
|
raise EmailUndeliverableError(f"The domain name {domain_i18n} does not send email.")
|
||||||
|
except dns.resolver.NoAnswer:
|
||||||
|
# No TXT records means there is no SPF policy, so we cannot take any action.
|
||||||
|
pass
|
||||||
|
|
||||||
|
except dns.resolver.NXDOMAIN as e:
|
||||||
|
# The domain name does not exist --- there are no records of any sort
|
||||||
|
# for the domain name.
|
||||||
|
raise EmailUndeliverableError(f"The domain name {domain_i18n} does not exist.") from e
|
||||||
|
|
||||||
|
except dns.resolver.NoNameservers:
|
||||||
|
# All nameservers failed to answer the query. This might be a problem
|
||||||
|
# with local nameservers, maybe? We'll allow the domain to go through.
|
||||||
|
return {
|
||||||
|
"unknown-deliverability": "no_nameservers",
|
||||||
|
}
|
||||||
|
|
||||||
|
except dns.exception.Timeout:
|
||||||
|
# A timeout could occur for various reasons, so don't treat it as a failure.
|
||||||
|
return {
|
||||||
|
"unknown-deliverability": "timeout",
|
||||||
|
}
|
||||||
|
|
||||||
|
except EmailUndeliverableError:
|
||||||
|
# Don't let these get clobbered by the wider except block below.
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Unhandled conditions should not propagate.
|
||||||
|
raise EmailUndeliverableError(
|
||||||
|
"There was an error while checking if the domain name in the email address is deliverable: " + str(e)
|
||||||
|
) from e
|
||||||
|
|
||||||
|
return deliverability_info
|
||||||
13
venv/Lib/site-packages/email_validator/exceptions.py
Normal file
13
venv/Lib/site-packages/email_validator/exceptions.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class EmailNotValidError(ValueError):
|
||||||
|
"""Parent class of all exceptions raised by this module."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EmailSyntaxError(EmailNotValidError):
|
||||||
|
"""Exception raised when an email address fails validation because of its form."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EmailUndeliverableError(EmailNotValidError):
|
||||||
|
"""Exception raised when an email address fails validation because its domain name does not appear deliverable."""
|
||||||
|
pass
|
||||||
BIN
venv/Lib/site-packages/email_validator/py.typed
Normal file
BIN
venv/Lib/site-packages/email_validator/py.typed
Normal file
Binary file not shown.
Reference in New Issue
Block a user