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