Merge pull request #6 from scardine/master

Suggestions for #4 and #2, general code janitoring.
This commit is contained in:
Syrus Akbary
2013-06-03 12:59:06 -07:00

View File

@@ -19,14 +19,18 @@
import re import re
import smtplib import smtplib
import logging
import socket import socket
try: try:
import DNS import DNS
ServerError = DNS.ServerError ServerError = DNS.ServerError
except: except ImportError:
DNS = None DNS = None
class ServerError(Exception): pass
class ServerError(Exception):
pass
# All we are really doing is comparing the input string to one # All we are really doing is comparing the input string to one
# gigantic regular expression. But building that regexp, and # gigantic regular expression. But building that regexp, and
# ensuring its correctness, is made much easier by assembling it # ensuring its correctness, is made much easier by assembling it
@@ -45,44 +49,44 @@ CRLF = r'(?:\r\n)' # see 2.2.3. Long Header Fi
NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f' # see 3.2.1. Primitive Tokens NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f' # see 3.2.1. Primitive Tokens
QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters
FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + \ FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + \
WSP + r'+)' # see 3.2.3. Folding white space and comments WSP + r'+)' # see 3.2.3. Folding white space and comments
CTEXT = r'[' + NO_WS_CTL + \ CTEXT = r'[' + NO_WS_CTL + \
r'\x21-\x27\x2a-\x5b\x5d-\x7e]' # see 3.2.3 r'\x21-\x27\x2a-\x5b\x5d-\x7e]' # see 3.2.3
CCONTENT = r'(?:' + CTEXT + r'|' + \ CCONTENT = r'(?:' + CTEXT + r'|' + \
QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here
# as well, but that would be circular.) # as well, but that would be circular.)
COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \ COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \
r')*' + FWS + r'?\)' # see 3.2.3 r')*' + FWS + r'?\)' # see 3.2.3
CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + \ CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + \
FWS + '?' + COMMENT + '|' + FWS + ')' # see 3.2.3 FWS + '?' + COMMENT + '|' + FWS + ')' # see 3.2.3
ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4. Atom ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4. Atom
ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?' # see 3.2.4 ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?' # see 3.2.4
DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*' # see 3.2.4 DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*' # see 3.2.4
DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4 DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4
QTEXT = r'[' + NO_WS_CTL + \ QTEXT = r'[' + NO_WS_CTL + \
r'\x21\x23-\x5b\x5d-\x7e]' # see 3.2.5. Quoted strings r'\x21\x23-\x5b\x5d-\x7e]' # see 3.2.5. Quoted strings
QCONTENT = r'(?:' + QTEXT + r'|' + \ QCONTENT = r'(?:' + QTEXT + r'|' + \
QUOTED_PAIR + r')' # see 3.2.5 QUOTED_PAIR + r')' # see 3.2.5
QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \ QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \
r'?' + QCONTENT + r')*' + FWS + \ r'?' + QCONTENT + r')*' + FWS + \
r'?' + r'"' + CFWS + r'?' r'?' + r'"' + CFWS + r'?'
LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + \ LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + \
QUOTED_STRING + r')' # see 3.4.1. Addr-spec specification QUOTED_STRING + r')' # see 3.4.1. Addr-spec specification
DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]' # see 3.4.1 DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]' # see 3.4.1
DCONTENT = r'(?:' + DTEXT + r'|' + \ DCONTENT = r'(?:' + DTEXT + r'|' + \
QUOTED_PAIR + r')' # see 3.4.1 QUOTED_PAIR + r')' # see 3.4.1
DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \ DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \
r'(?:' + FWS + r'?' + DCONTENT + \ r'(?:' + FWS + r'?' + DCONTENT + \
r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1 r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1
DOMAIN = r'(?:' + DOT_ATOM + r'|' + \ DOMAIN = r'(?:' + DOT_ATOM + r'|' + \
DOMAIN_LITERAL + r')' # see 3.4.1 DOMAIN_LITERAL + r')' # see 3.4.1
ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1 ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1
# A valid address will match exactly the 3.4.1 addr-spec. # A valid address will match exactly the 3.4.1 addr-spec.
VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$' VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$'
def validate_email(email, check_mx=False,verify=False):
def validate_email(email, check_mx=False, verify=False, debug=False):
"""Indicate whether the given string is a valid email address """Indicate whether the given string is a valid email address
according to the 'addr-spec' portion of RFC 2822 (see section according to the 'addr-spec' portion of RFC 2822 (see section
3.4.1). Parts of the spec that are marked obsolete are *not* 3.4.1). Parts of the spec that are marked obsolete are *not*
@@ -90,13 +94,21 @@ def validate_email(email, check_mx=False,verify=False):
depend on circular definitions in the spec may not pass, but in depend on circular definitions in the spec may not pass, but in
general this should correctly identify any email address likely general this should correctly identify any email address likely
to be in use as of 2011.""" to be in use as of 2011."""
if debug:
logger = logging.getLogger('validate_email')
logger.setLevel(logging.DEBUG)
else:
logger = None
try: try:
assert re.match(VALID_ADDRESS_REGEXP, email) is not None assert re.match(VALID_ADDRESS_REGEXP, email) is not None
check_mx |= verify check_mx |= verify
if check_mx: 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') if not DNS:
raise Exception('For check the mx records or check if the email exists you must '
'have installed pyDNS python package')
DNS.DiscoverNameServers() DNS.DiscoverNameServers()
hostname = email[email.find('@')+1:] hostname = email[email.find('@') + 1:]
mx_hosts = DNS.mxlookup(hostname) mx_hosts = DNS.mxlookup(hostname)
for mx in mx_hosts: for mx in mx_hosts:
try: try:
@@ -108,22 +120,62 @@ def validate_email(email, check_mx=False,verify=False):
status, _ = smtp.helo() status, _ = smtp.helo()
if status != 250: if status != 250:
smtp.quit() smtp.quit()
if debug:
logger.debug(u'%s answer: %s - %s', mx[1], status, _)
continue continue
smtp.mail('') smtp.mail('')
status, _ = smtp.rcpt(email) status, _ = smtp.rcpt(email)
if status != 250: if status == 250:
smtp.quit() smtp.quit()
return False return True
if debug:
logger.debug(u'%s answer: %s - %s', mx[1], status, _)
smtp.quit() smtp.quit()
break except smtplib.SMTPServerDisconnected: # Server not permits verify user
except smtplib.SMTPServerDisconnected: #Server not permits verify user if debug:
break logger.debug(u'%s disconected.', mx[1])
except smtplib.SMTPConnectError: except smtplib.SMTPConnectError:
continue if debug:
except (AssertionError, ServerError): logger.debug(u'Unable to connect to %s.', mx[1])
return None
except AssertionError:
return False return False
except (ServerError, socket.error) as e:
if debug:
logger.debug('ServerError or socket.error exception raised (%s).', e)
return None
return True 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)
if result:
print "Valid!"
elif result is None:
print "I'm not sure."
else:
print "Invalid!"
time.sleep(1)
# import sys # import sys
# sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__] # sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__]