1# coding:utf-8 2 3''' 4 Email address validation plugin for gmail.com email addresses. 5 6 Notes: 7 8 must be between 6-30 characters 9 must start with letter or number 10 must end with letter or number 11 must use letters, numbers, or dots (.) 12 consecutive dots (..) are not permitted 13 dots (.) at the beginning or end are not permitted 14 case is ignored 15 plus (+) is allowed, everything after + is ignored 16 1. All characters prefixing the plus symbol (+) and stripping all dot 17 symbol (.) must be between 6-30 characters. 18 19 20 Grammar: 21 22 local-part -> main-part [ tags ] 23 main-part -> alphanum { [dot] alphanum } 24 tags -> { + [ dot-atom ] } 25 dot-atom -> atom { [ dot atom ] } 26 atom -> { A-Za-z0-9!#$%&'*+\-/=?^_`{|}~ } 27 alphanum -> alpha | num 28 dot -> . 29''' 30import re 31from flanker.addresslib.plugins._tokenizer import TokenStream 32from flanker.addresslib._parser.lexer import t_ATOM, _UNICODE_CHAR 33 34ATOM = re.compile(t_ATOM, re.MULTILINE | re.VERBOSE) 35 36ALPHANUM = re.compile(r''' 37 ( [A-Za-z0-9] 38 | {unicode_char} 39 )+ 40 '''.format(unicode_char=_UNICODE_CHAR), 41 re.MULTILINE | re.VERBOSE) 42 43PLUS = '+' 44DOT = '.' 45 46 47def validate(email_addr): 48 # Setup for handling EmailAddress type instead of literal string 49 localpart = email_addr.mailbox 50 51 # check string exists and not empty 52 if not localpart: 53 return False 54 55 lparts = localpart.split('+') 56 real_localpart = lparts[0] 57 stripped_localpart = real_localpart.replace('.', '') 58 59 # length check 60 l = len(stripped_localpart) 61 if l < 6 or l > 30: 62 return False 63 64 # must start with letter or num 65 if ALPHANUM.match(real_localpart[0]) is None: 66 return False 67 # must end with letter or num 68 if ALPHANUM.match(real_localpart[-1]) is None: 69 return False 70 # grammar check 71 return _validate(real_localpart) 72 73 74def _validate(localpart): 75 stream = TokenStream(localpart) 76 77 while True: 78 # get alphanumeric portion 79 mpart = stream.get_token(ALPHANUM) 80 if mpart is None: 81 return False 82 # get optional dot, must be followed by more alphanumerics 83 mpart = stream.get_token(DOT) 84 if mpart is None: 85 break 86 87 # optional tags 88 tgs = _tags(stream) 89 90 if not stream.end_of_stream(): 91 return False 92 93 return True 94 95 96def _tags(stream): 97 while True: 98 # plus sign 99 pls = stream.get_token(PLUS) 100 101 # optional atom 102 if pls: 103 stream.get_token(ATOM) 104 else: 105 break 106 107 return True 108