[MERGE] forward port of branch 7.0 up to de07c64
[odoo/odoo.git] / addons / crm / validate_email.py
1 # RFC 2822 - style email validation for Python
2 # (c) 2012 Syrus Akbary <me@syrusakbary.com>
3 # Extended from (c) 2011 Noel Bush <noel@aitools.org>
4 # for support of mx and user check
5 # This code is made available to you under the GNU LGPL v3.
6 #
7 # This module provides a single method, valid_email_address(),
8 # which returns True or False to indicate whether a given address
9 # is valid according to the 'addr-spec' part of the specification
10 # given in RFC 2822.  Ideally, we would like to find this
11 # in some other library, already thoroughly tested and well-
12 # maintained.  The standard Python library email.utils
13 # contains a parse_addr() function, but it is not sufficient
14 # to detect many malformed addresses.
15 #
16 # This implementation aims to be faithful to the RFC, with the
17 # exception of a circular definition (see comments below), and
18 # with the omission of the pattern components marked as "obsolete".
19
20 import re
21 import smtplib
22 import socket
23
24 try:
25     import DNS
26     ServerError = DNS.ServerError
27 except:
28     DNS = None
29     class ServerError(Exception): pass
30 # All we are really doing is comparing the input string to one
31 # gigantic regular expression.  But building that regexp, and
32 # ensuring its correctness, is made much easier by assembling it
33 # from the "tokens" defined by the RFC.  Each of these tokens is
34 # tested in the accompanying unit test file.
35 #
36 # The section of RFC 2822 from which each pattern component is
37 # derived is given in an accompanying comment.
38 #
39 # (To make things simple, every string below is given as 'raw',
40 # even when it's not strictly necessary.  This way we don't forget
41 # when it is necessary.)
42 #
43 WSP = r'[ \t]'                                       # see 2.2.2. Structured Header Field Bodies
44 CRLF = r'(?:\r\n)'                                   # see 2.2.3. Long Header Fields
45 NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f'        # see 3.2.1. Primitive Tokens
46 QUOTED_PAIR = r'(?:\\.)'                             # see 3.2.2. Quoted characters
47 FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + \
48             WSP + r'+)'                                    # see 3.2.3. Folding white space and comments
49 CTEXT = r'[' + NO_WS_CTL + \
50                 r'\x21-\x27\x2a-\x5b\x5d-\x7e]'              # see 3.2.3
51 CCONTENT = r'(?:' + CTEXT + r'|' + \
52                      QUOTED_PAIR + r')'                        # see 3.2.3 (NB: The RFC includes COMMENT here
53                                                                                                          # as well, but that would be circular.)
54 COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + \
55                     r')*' + FWS + r'?\)'                       # see 3.2.3
56 CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + \
57              FWS + '?' + COMMENT + '|' + FWS + ')'         # see 3.2.3
58 ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]'           # see 3.2.4. Atom
59 ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?'      # see 3.2.4
60 DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*'   # see 3.2.4
61 DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4
62 QTEXT = r'[' + NO_WS_CTL + \
63                 r'\x21\x23-\x5b\x5d-\x7e]'                   # see 3.2.5. Quoted strings
64 QCONTENT = r'(?:' + QTEXT + r'|' + \
65                      QUOTED_PAIR + r')'                        # see 3.2.5
66 QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + \
67                                 r'?' + QCONTENT + r')*' + FWS + \
68                                 r'?' + r'"' + CFWS + r'?'
69 LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + \
70                          QUOTED_STRING + r')'                    # see 3.4.1. Addr-spec specification
71 DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]'    # see 3.4.1
72 DCONTENT = r'(?:' + DTEXT + r'|' + \
73                      QUOTED_PAIR + r')'                        # see 3.4.1
74 DOMAIN_LITERAL = CFWS + r'?' + r'\[' + \
75                                  r'(?:' + FWS + r'?' + DCONTENT + \
76                                  r')*' + FWS + r'?\]' + CFWS + r'?'  # see 3.4.1
77 DOMAIN = r'(?:' + DOT_ATOM + r'|' + \
78                  DOMAIN_LITERAL + r')'                       # see 3.4.1
79 ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN               # see 3.4.1
80
81 # A valid address will match exactly the 3.4.1 addr-spec.
82 VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$'
83
84 def validate_email(email, check_mx=False,verify=False):
85
86     """Indicate whether the given string is a valid email address
87     according to the 'addr-spec' portion of RFC 2822 (see section
88     3.4.1).  Parts of the spec that are marked obsolete are *not*
89     included in this test, and certain arcane constructions that
90     depend on circular definitions in the spec may not pass, but in
91     general this should correctly identify any email address likely
92     to be in use as of 2011."""
93     try:
94         assert re.match(VALID_ADDRESS_REGEXP, email) is not None
95         check_mx |= verify
96         if check_mx:
97             if not DNS: raise Exception('For check the mx records or check if the email exists you must have installed pyDNS python package')
98             DNS.DiscoverNameServers()
99             hostname = email[email.find('@')+1:]
100             mx_hosts = DNS.mxlookup(hostname)
101             for mx in mx_hosts:
102                 try:
103                     smtp = smtplib.SMTP()
104                     smtp.connect(mx[1])
105                     if not verify: return True
106                     status, _ = smtp.helo()
107                     if status != 250: continue
108                     smtp.mail('')
109                     status, _ = smtp.rcpt(email)
110                     if status != 250: return False
111                     break
112                 except smtplib.SMTPServerDisconnected: #Server not permits verify user
113                     break
114                 except smtplib.SMTPConnectError:
115                     continue
116     except (AssertionError, ServerError): 
117         return False
118     return True
119
120 # import sys
121
122 # sys.modules[__name__],sys.modules['validate_email_module'] = validate_email,sys.modules[__name__]
123 # from validate_email_module import *