From a110914328811488c49be1d8deb5d10a58054167 Mon Sep 17 00:00:00 2001 From: Ben Baert Date: Thu, 31 May 2018 14:58:35 +0200 Subject: [PATCH] Reorganised code; mx check in separate file --- AUTHORS | 3 +- INSTALL.txt | 6 -- README.rst | 19 ++----- email_regex.py | 14 ++++- mx_check.py | 37 +++++++++++++ test_email_regex.py | 29 ++++++++-- vagrant_setup.md | 51 ----------------- validate_email.py | 132 ++++---------------------------------------- 8 files changed, 92 insertions(+), 199 deletions(-) delete mode 100644 INSTALL.txt create mode 100644 mx_check.py delete mode 100644 vagrant_setup.md diff --git a/AUTHORS b/AUTHORS index a3d8ce1..0b56cf3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,5 @@ -validate_email was extended and updated for use with Python 3 by Ben Baert in May 2018. +validate_email was extended and updated for use with Python 3 +by Ben Baert in May 2018. validate_email was created by Syrus Akbary in April 2012. This package is based on the work of Noel Bush diff --git a/INSTALL.txt b/INSTALL.txt deleted file mode 100644 index 993c123..0000000 --- a/INSTALL.txt +++ /dev/null @@ -1,6 +0,0 @@ -Thanks for downloading validate_email. - -To install it, make sure you have Python 3 and dnspython installed. Then run -this command from the command prompt: - - python setup.py install diff --git a/README.rst b/README.rst index c49f681..c080af2 100644 --- a/README.rst +++ b/README.rst @@ -1,24 +1,17 @@ ============== -Validate_email +pyemailval ============== -Validate_email is a package for Python that check if an email is valid, properly formatted and really exists. +pyemailval is a package for Python that check if an email is valid, properly formatted and really exists. INSTALLATION ============ -First, you must do:: +You can install the package with pip: - pip install validate_email - -Extra ------- - -For check the domain mx and verify email exits you must have the `pyDNS` package installed:: - - pip install pyDNS + pip install pyemailval USAGE @@ -26,7 +19,7 @@ USAGE Basic usage:: - from validate_email import validate_email + from pyemailval import validate_email is_valid = validate_email('example@example.com') @@ -50,4 +43,4 @@ Check if the host has SMTP Server and the email really exists:: TODOs and BUGS ============== -See: http://github.com/syrusakbary/validate_email/issues \ No newline at end of file +See: http://github.com/Ben-Baert/pyemailval/issues \ No newline at end of file diff --git a/email_regex.py b/email_regex.py index 65f88c9..1f103af 100644 --- a/email_regex.py +++ b/email_regex.py @@ -56,10 +56,18 @@ ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1 VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$' +def get_domain_from_email_address(email_address): + try: + return re.search(r"(?<=@)\[?([^\[\]]+)", email_address)[1] + except TypeError: + raise ValueError("Invalid email address") + except IndexError: + raise ValueError("Invalid email address") + + def email_has_valid_structure(email_address): + if any(ord(char) > 127 for char in email_address): + return False if re.match(VALID_ADDRESS_REGEXP, email_address): return True return False - - - diff --git a/mx_check.py b/mx_check.py new file mode 100644 index 0000000..253cb6e --- /dev/null +++ b/mx_check.py @@ -0,0 +1,37 @@ +import socket +import smtplib +import dns.resolver as dns + +from email_regex import get_domain_from_email_address + + +def get_mx_records(domain): + try: + records = dns.query(domain, 'MX') + except dns.NXDOMAIN: + raise ValueError("Domain {} does not seem to exist") + except: + raise NotImplementedError("Feature not yet implemented") + return [str(x.exchange) for x in records] + + +def mx_check(email_address): + host = socket.gethostname() + + smtp = smtplib.SMTP() + smtp.set_debuglevel(0) + + domain = get_domain_from_email_address(email_address) + mx_records = get_mx_records(domain) + print(mx_records) + + for mx_record in mx_records: + smtp.connect(mx_record) + smtp.helo(host) + smtp.mail(email_address) + code, message = smtp.rcpt(email_address) + smtp.quit() + return True + if code == 250: + return True + return False diff --git a/test_email_regex.py b/test_email_regex.py index 8c30804..8c833dd 100644 --- a/test_email_regex.py +++ b/test_email_regex.py @@ -1,5 +1,15 @@ +from email_regex import get_domain_from_email_address from email_regex import email_has_valid_structure +DOMAINS = { + "email@domain.com": "domain.com", + "email@subdomain.domain.com": "subdomain.domain.com", + "email@123.123.123.123": "123.123.123.123", + "email@[123.123.123.123]": "123.123.123.123", + "email@domain-one.com": "domain-one.com", + "email@domain.co.jp": "domain.co.jp", +} + VALID_EMAIL_ADDRESS_EXAMPLES = [ "email@domain.com", # basic valid email "firstname.lastname@domain.com", # dot in address field @@ -36,14 +46,25 @@ INVALID_EMAIL_ADDRESS_EXAMPLES = [ ] +def test_domain_from_email_address(): + for email_address, domain in DOMAINS.items(): + try: + domain_from_function = get_domain_from_email_address(email_address) + assert domain_from_function == domain + except AssertionError: + raise AssertionError( + "Email address {} should result in domain {} but resulted in domain {}" + .format(email_address, domain, domain_from_function)) + + def test_valid_email_structure_regex(): for index, valid_email_address in enumerate(VALID_EMAIL_ADDRESS_EXAMPLES): try: assert email_has_valid_structure(valid_email_address) is True except AssertionError: raise AssertionError( - ("{} should be valid ({}th email address in the list)" - .format(valid_email_address, index))) + "{} should be valid ({}th email address in the list)" + .format(valid_email_address, index)) def test_invalid_email_structure_regex(): for index, invalid_email_address in enumerate(INVALID_EMAIL_ADDRESS_EXAMPLES): @@ -51,5 +72,5 @@ def test_invalid_email_structure_regex(): assert email_has_valid_structure(invalid_email_address) is False except AssertionError: raise AssertionError( - ("{} should be invalid ({}th email address in the list)" - .format(invalid_email_address, index))) + "{} should be invalid ({}th email address in the list)" + .format(invalid_email_address, index)) diff --git a/vagrant_setup.md b/vagrant_setup.md deleted file mode 100644 index e44ba31..0000000 --- a/vagrant_setup.md +++ /dev/null @@ -1,51 +0,0 @@ -How to Set Up Validate Email Using Vagrant on PC -------------------------------------------------- - -1. Download Vagrant & Virtual Box - * http://www.vagrantup.com/downloads - * http://www.virtualbox.org/ -2. Look at http://docs.vagrantup.com/v2/getting-started/ to make a linux virtual machine via vagrant - * tl;dr using bash (Can use regular Windows Terminal) - * Make a folder first, whenever you want to access the vm you will need to cd into that folder - ``` - $ vagrant init hashicorp/precise32 - - $ vagrant up - - $ vagrant ssh - ``` - * This last command is used to go into your vm -3. Install pipin the vm - * http://www.saltycrane.com/blog/2010/02/how-install-pip-ubuntu/ - * tl;dr - ``` - $ sudo apt-get install python-pip - - ``` -4. Install pyDNS - ``` - This is a package dependency of validate email - - $ sudo pip install pydns - ``` -5. Install git - ``` - $ sudo apt-get install git - ``` -6. Clone the validate_email repo to your vm - * (Since its a new machine you will need to clone using the https url) - * ```$ git clone git@github.com:efagerberg/validate_email.git``` - * If you want to use your ssh keys on your machine you will need to add this line to the vagrant file under the config - * Looks somthing like this: - Vagrant::Config.run do |config| - # stuff - config.ssh.forward_agent = true - end -7. cd into validate_email and run script - ``` - $ cd validate_email - - $ python validate_email.py - ``` - - diff --git a/validate_email.py b/validate_email.py index e812b78..fc1ac23 100644 --- a/validate_email.py +++ b/validate_email.py @@ -17,131 +17,21 @@ # exception of a circular definition (see comments below), and # with the omission of the pattern components marked as "obsolete". -import smtplib -import logging -import socket -class ServerError(Exception): - pass +from email_regex import email_has_valid_structure +from mx_check import mx_check +def validate_email( + email_address, + check_structure=True, + check_mx=True, + smtp_timeout=10): -MX_DNS_CACHE = {} -MX_CHECK_CACHE = {} - - -def get_mx_ip(hostname): - if hostname not in MX_DNS_CACHE: - try: - MX_DNS_CACHE[hostname] = DNS.mxlookup(hostname) - except ServerError as e: - if e.rcode == 3 or e.rcode == 2: # NXDOMAIN (Non-Existent Domain) or SERVFAIL - MX_DNS_CACHE[hostname] = None - else: - raise - - return MX_DNS_CACHE[hostname] - - -def validate_email(email, check_mx=False, verify=False, debug=False, smtp_timeout=10): - """Indicate whether the given string is a valid email address - according to the 'addr-spec' portion of RFC 2822 (see section - 3.4.1). Parts of the spec that are marked obsolete are *not* - included in this test, and certain arcane constructions that - depend on circular definitions in the spec may not pass, but in - general this should correctly identify any email address likely - to be in use as of 2011.""" - if debug: - logger = logging.getLogger('validate_email') - logger.setLevel(logging.DEBUG) - else: - logger = None - - if not email_has_valid_structure(email_address): + if check_structure and not email_has_valid_structure(email_address): return False - check_mx |= verify - if check_mx: - if not DNS: - raise Exception('For check the mx records or check if the email exists you must ' - 'have installed pyDNS python package') - hostname = email[email.find('@') + 1:] - mx_hosts = get_mx_ip(hostname) - if mx_hosts is None: - return False - for mx in mx_hosts: - try: - if not verify and mx[1] in MX_CHECK_CACHE: - return MX_CHECK_CACHE[mx[1]] - smtp = smtplib.SMTP(timeout=smtp_timeout) - smtp.connect(mx[1]) - MX_CHECK_CACHE[mx[1]] = True - if not verify: - try: - smtp.quit() - except smtplib.SMTPServerDisconnected: - pass - return True - status, _ = smtp.helo() - if status != 250: - smtp.quit() - if debug: - logger.debug(u'%s answer: %s - %s', mx[1], status, _) - continue - smtp.mail('') - status, _ = smtp.rcpt(email) - if status == 250: - smtp.quit() - return True - if debug: - logger.debug(u'%s answer: %s - %s', mx[1], status, _) - smtp.quit() - except smtplib.SMTPServerDisconnected: # Server not permits verify user - if debug: - logger.debug(u'%s disconected.', mx[1]) - except smtplib.SMTPConnectError: - if debug: - logger.debug(u'Unable to connect to %s.', mx[1]) - return None - except AssertionError: - return False - except (ServerError, socket.error) as e: - if debug: - logger.debug('ServerError or socket.error exception raised (%s).', e) - return None - return True + if not check_mx: + return True -if __name__ == "__main__": - import time - while True: - email = raw_input('Enter email for validation: ') - - mx = raw_input('Validate MX record? [yN] ') - if mx.strip().lower() == 'y': - mx = True - else: - mx = False - - validate = raw_input('Try to contact server for address validation? [yN] ') - if validate.strip().lower() == 'y': - validate = True - else: - validate = False - - logging.basicConfig() - - result = validate_email(email, mx, validate, debug=True, smtp_timeout=1) - if result: - print("Valid!") - elif result is None: - print("I'm not sure.") - else: - print("Invalid!") - - time.sleep(1) - - -# import sys - -# sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__] -# from validate_email_module import * + return mx_check(email_address)