You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
4.5 KiB

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