From 967995834096315215ab94e3f7ec276434a49821 Mon Sep 17 00:00:00 2001 From: Paulo Scardine Date: Sat, 1 Jun 2013 16:54:39 -0300 Subject: [PATCH 1/2] Suggestions for #4 (returning None) and #2 (logging server responses) * removed unused "import socket" * changed broad "except" clause to more specific "except ImportError" * general PEP8 reformat * added "debug" option for logging server responses/errors * return None instead of True/False on several errors that may be temporary --- validate_email.py | 76 ++++++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/validate_email.py b/validate_email.py index 3624bd1..d44c63b 100644 --- a/validate_email.py +++ b/validate_email.py @@ -19,14 +19,17 @@ import re import smtplib -import socket +import logging 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 +48,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 +93,20 @@ 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') + 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,20 +118,30 @@ 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: + if debug: + logger.debug(u'ServerError exception raised.') + return None return True # import sys From cbba38aaeb077aae26f98c1b52813be667ae56fb Mon Sep 17 00:00:00 2001 From: Paulo Scardine Date: Sat, 1 Jun 2013 18:15:37 -0300 Subject: [PATCH 2/2] Suggestions for #4 (returning None) and #2 (logging server responses) * removed unused "import socket" * changed broad "except" clause to more specific "except ImportError" * general PEP8 reformat * added "debug" option for logging server responses/errors * return None instead of True/False on several errors that may be temporary --- validate_email.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/validate_email.py b/validate_email.py index d44c63b..ebff013 100644 --- a/validate_email.py +++ b/validate_email.py @@ -20,6 +20,7 @@ import re import smtplib import logging +import socket try: import DNS @@ -95,6 +96,7 @@ def validate_email(email, check_mx=False, verify=False, debug=False): to be in use as of 2011.""" if debug: logger = logging.getLogger('validate_email') + logger.setLevel(logging.DEBUG) else: logger = None @@ -138,12 +140,42 @@ def validate_email(email, check_mx=False, verify=False, debug=False): return None except AssertionError: return False - except ServerError: + except (ServerError, socket.error) as e: if debug: - logger.debug(u'ServerError exception raised.') + 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__]