diff --git a/validate_email.py b/validate_email.py index 3624bd1..ebff013 100644 --- a/validate_email.py +++ b/validate_email.py @@ -19,14 +19,18 @@ import re import smtplib +import logging import socket try: import DNS ServerError = DNS.ServerError -except: +except ImportError: DNS = None - class ServerError(Exception): pass + + class ServerError(Exception): + pass + # All we are really doing is comparing the input string to one # gigantic regular expression. But building that regexp, and # 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 QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters 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 + \ - 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'|' + \ - QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here - # as well, but that would be circular.) + QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here +# as well, but that would be circular.) COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \ - r')*' + FWS + r'?\)' # see 3.2.3 + r')*' + FWS + r'?\)' # see 3.2.3 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 ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + 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 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'|' + \ - QUOTED_PAIR + r')' # see 3.2.5 + QUOTED_PAIR + r')' # see 3.2.5 QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \ - r'?' + QCONTENT + r')*' + FWS + \ - r'?' + r'"' + CFWS + r'?' + r'?' + QCONTENT + r')*' + FWS + \ + r'?' + r'"' + CFWS + 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 DCONTENT = r'(?:' + DTEXT + r'|' + \ - QUOTED_PAIR + r')' # see 3.4.1 + QUOTED_PAIR + r')' # see 3.4.1 DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \ - r'(?:' + FWS + r'?' + DCONTENT + \ - r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1 + r'(?:' + FWS + r'?' + DCONTENT + \ + r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1 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 # A valid address will match exactly the 3.4.1 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 according to the 'addr-spec' portion of RFC 2822 (see section 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 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 + try: assert re.match(VALID_ADDRESS_REGEXP, email) is not None 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') + 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() - hostname = email[email.find('@')+1:] + hostname = email[email.find('@') + 1:] mx_hosts = DNS.mxlookup(hostname) for mx in mx_hosts: try: @@ -108,22 +120,62 @@ def validate_email(email, check_mx=False,verify=False): 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: + if status == 250: smtp.quit() - return False + return True + if debug: + logger.debug(u'%s answer: %s - %s', mx[1], status, _) smtp.quit() - break - except smtplib.SMTPServerDisconnected: #Server not permits verify user - break + except smtplib.SMTPServerDisconnected: # Server not permits verify user + if debug: + logger.debug(u'%s disconected.', mx[1]) except smtplib.SMTPConnectError: - continue - except (AssertionError, ServerError): + 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 __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 # sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__]