xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/email/_parseaddr.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker# Copyright (C) 2002-2007 Python Software Foundation
2*cda5da8dSAndroid Build Coastguard Worker# Contact: [email protected]
3*cda5da8dSAndroid Build Coastguard Worker
4*cda5da8dSAndroid Build Coastguard Worker"""Email address parsing code.
5*cda5da8dSAndroid Build Coastguard Worker
6*cda5da8dSAndroid Build Coastguard WorkerLifted directly from rfc822.py.  This should eventually be rewritten.
7*cda5da8dSAndroid Build Coastguard Worker"""
8*cda5da8dSAndroid Build Coastguard Worker
9*cda5da8dSAndroid Build Coastguard Worker__all__ = [
10*cda5da8dSAndroid Build Coastguard Worker    'mktime_tz',
11*cda5da8dSAndroid Build Coastguard Worker    'parsedate',
12*cda5da8dSAndroid Build Coastguard Worker    'parsedate_tz',
13*cda5da8dSAndroid Build Coastguard Worker    'quote',
14*cda5da8dSAndroid Build Coastguard Worker    ]
15*cda5da8dSAndroid Build Coastguard Worker
16*cda5da8dSAndroid Build Coastguard Workerimport time, calendar
17*cda5da8dSAndroid Build Coastguard Worker
18*cda5da8dSAndroid Build Coastguard WorkerSPACE = ' '
19*cda5da8dSAndroid Build Coastguard WorkerEMPTYSTRING = ''
20*cda5da8dSAndroid Build Coastguard WorkerCOMMASPACE = ', '
21*cda5da8dSAndroid Build Coastguard Worker
22*cda5da8dSAndroid Build Coastguard Worker# Parse a date field
23*cda5da8dSAndroid Build Coastguard Worker_monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
24*cda5da8dSAndroid Build Coastguard Worker               'aug', 'sep', 'oct', 'nov', 'dec',
25*cda5da8dSAndroid Build Coastguard Worker               'january', 'february', 'march', 'april', 'may', 'june', 'july',
26*cda5da8dSAndroid Build Coastguard Worker               'august', 'september', 'october', 'november', 'december']
27*cda5da8dSAndroid Build Coastguard Worker
28*cda5da8dSAndroid Build Coastguard Worker_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
29*cda5da8dSAndroid Build Coastguard Worker
30*cda5da8dSAndroid Build Coastguard Worker# The timezone table does not include the military time zones defined
31*cda5da8dSAndroid Build Coastguard Worker# in RFC822, other than Z.  According to RFC1123, the description in
32*cda5da8dSAndroid Build Coastguard Worker# RFC822 gets the signs wrong, so we can't rely on any such time
33*cda5da8dSAndroid Build Coastguard Worker# zones.  RFC1123 recommends that numeric timezone indicators be used
34*cda5da8dSAndroid Build Coastguard Worker# instead of timezone names.
35*cda5da8dSAndroid Build Coastguard Worker
36*cda5da8dSAndroid Build Coastguard Worker_timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
37*cda5da8dSAndroid Build Coastguard Worker              'AST': -400, 'ADT': -300,  # Atlantic (used in Canada)
38*cda5da8dSAndroid Build Coastguard Worker              'EST': -500, 'EDT': -400,  # Eastern
39*cda5da8dSAndroid Build Coastguard Worker              'CST': -600, 'CDT': -500,  # Central
40*cda5da8dSAndroid Build Coastguard Worker              'MST': -700, 'MDT': -600,  # Mountain
41*cda5da8dSAndroid Build Coastguard Worker              'PST': -800, 'PDT': -700   # Pacific
42*cda5da8dSAndroid Build Coastguard Worker              }
43*cda5da8dSAndroid Build Coastguard Worker
44*cda5da8dSAndroid Build Coastguard Worker
45*cda5da8dSAndroid Build Coastguard Workerdef parsedate_tz(data):
46*cda5da8dSAndroid Build Coastguard Worker    """Convert a date string to a time tuple.
47*cda5da8dSAndroid Build Coastguard Worker
48*cda5da8dSAndroid Build Coastguard Worker    Accounts for military timezones.
49*cda5da8dSAndroid Build Coastguard Worker    """
50*cda5da8dSAndroid Build Coastguard Worker    res = _parsedate_tz(data)
51*cda5da8dSAndroid Build Coastguard Worker    if not res:
52*cda5da8dSAndroid Build Coastguard Worker        return
53*cda5da8dSAndroid Build Coastguard Worker    if res[9] is None:
54*cda5da8dSAndroid Build Coastguard Worker        res[9] = 0
55*cda5da8dSAndroid Build Coastguard Worker    return tuple(res)
56*cda5da8dSAndroid Build Coastguard Worker
57*cda5da8dSAndroid Build Coastguard Workerdef _parsedate_tz(data):
58*cda5da8dSAndroid Build Coastguard Worker    """Convert date to extended time tuple.
59*cda5da8dSAndroid Build Coastguard Worker
60*cda5da8dSAndroid Build Coastguard Worker    The last (additional) element is the time zone offset in seconds, except if
61*cda5da8dSAndroid Build Coastguard Worker    the timezone was specified as -0000.  In that case the last element is
62*cda5da8dSAndroid Build Coastguard Worker    None.  This indicates a UTC timestamp that explicitly declaims knowledge of
63*cda5da8dSAndroid Build Coastguard Worker    the source timezone, as opposed to a +0000 timestamp that indicates the
64*cda5da8dSAndroid Build Coastguard Worker    source timezone really was UTC.
65*cda5da8dSAndroid Build Coastguard Worker
66*cda5da8dSAndroid Build Coastguard Worker    """
67*cda5da8dSAndroid Build Coastguard Worker    if not data:
68*cda5da8dSAndroid Build Coastguard Worker        return None
69*cda5da8dSAndroid Build Coastguard Worker    data = data.split()
70*cda5da8dSAndroid Build Coastguard Worker    if not data:  # This happens for whitespace-only input.
71*cda5da8dSAndroid Build Coastguard Worker        return None
72*cda5da8dSAndroid Build Coastguard Worker    # The FWS after the comma after the day-of-week is optional, so search and
73*cda5da8dSAndroid Build Coastguard Worker    # adjust for this.
74*cda5da8dSAndroid Build Coastguard Worker    if data[0].endswith(',') or data[0].lower() in _daynames:
75*cda5da8dSAndroid Build Coastguard Worker        # There's a dayname here. Skip it
76*cda5da8dSAndroid Build Coastguard Worker        del data[0]
77*cda5da8dSAndroid Build Coastguard Worker    else:
78*cda5da8dSAndroid Build Coastguard Worker        i = data[0].rfind(',')
79*cda5da8dSAndroid Build Coastguard Worker        if i >= 0:
80*cda5da8dSAndroid Build Coastguard Worker            data[0] = data[0][i+1:]
81*cda5da8dSAndroid Build Coastguard Worker    if len(data) == 3: # RFC 850 date, deprecated
82*cda5da8dSAndroid Build Coastguard Worker        stuff = data[0].split('-')
83*cda5da8dSAndroid Build Coastguard Worker        if len(stuff) == 3:
84*cda5da8dSAndroid Build Coastguard Worker            data = stuff + data[1:]
85*cda5da8dSAndroid Build Coastguard Worker    if len(data) == 4:
86*cda5da8dSAndroid Build Coastguard Worker        s = data[3]
87*cda5da8dSAndroid Build Coastguard Worker        i = s.find('+')
88*cda5da8dSAndroid Build Coastguard Worker        if i == -1:
89*cda5da8dSAndroid Build Coastguard Worker            i = s.find('-')
90*cda5da8dSAndroid Build Coastguard Worker        if i > 0:
91*cda5da8dSAndroid Build Coastguard Worker            data[3:] = [s[:i], s[i:]]
92*cda5da8dSAndroid Build Coastguard Worker        else:
93*cda5da8dSAndroid Build Coastguard Worker            data.append('') # Dummy tz
94*cda5da8dSAndroid Build Coastguard Worker    if len(data) < 5:
95*cda5da8dSAndroid Build Coastguard Worker        return None
96*cda5da8dSAndroid Build Coastguard Worker    data = data[:5]
97*cda5da8dSAndroid Build Coastguard Worker    [dd, mm, yy, tm, tz] = data
98*cda5da8dSAndroid Build Coastguard Worker    if not (dd and mm and yy):
99*cda5da8dSAndroid Build Coastguard Worker        return None
100*cda5da8dSAndroid Build Coastguard Worker    mm = mm.lower()
101*cda5da8dSAndroid Build Coastguard Worker    if mm not in _monthnames:
102*cda5da8dSAndroid Build Coastguard Worker        dd, mm = mm, dd.lower()
103*cda5da8dSAndroid Build Coastguard Worker        if mm not in _monthnames:
104*cda5da8dSAndroid Build Coastguard Worker            return None
105*cda5da8dSAndroid Build Coastguard Worker    mm = _monthnames.index(mm) + 1
106*cda5da8dSAndroid Build Coastguard Worker    if mm > 12:
107*cda5da8dSAndroid Build Coastguard Worker        mm -= 12
108*cda5da8dSAndroid Build Coastguard Worker    if dd[-1] == ',':
109*cda5da8dSAndroid Build Coastguard Worker        dd = dd[:-1]
110*cda5da8dSAndroid Build Coastguard Worker    i = yy.find(':')
111*cda5da8dSAndroid Build Coastguard Worker    if i > 0:
112*cda5da8dSAndroid Build Coastguard Worker        yy, tm = tm, yy
113*cda5da8dSAndroid Build Coastguard Worker    if yy[-1] == ',':
114*cda5da8dSAndroid Build Coastguard Worker        yy = yy[:-1]
115*cda5da8dSAndroid Build Coastguard Worker        if not yy:
116*cda5da8dSAndroid Build Coastguard Worker            return None
117*cda5da8dSAndroid Build Coastguard Worker    if not yy[0].isdigit():
118*cda5da8dSAndroid Build Coastguard Worker        yy, tz = tz, yy
119*cda5da8dSAndroid Build Coastguard Worker    if tm[-1] == ',':
120*cda5da8dSAndroid Build Coastguard Worker        tm = tm[:-1]
121*cda5da8dSAndroid Build Coastguard Worker    tm = tm.split(':')
122*cda5da8dSAndroid Build Coastguard Worker    if len(tm) == 2:
123*cda5da8dSAndroid Build Coastguard Worker        [thh, tmm] = tm
124*cda5da8dSAndroid Build Coastguard Worker        tss = '0'
125*cda5da8dSAndroid Build Coastguard Worker    elif len(tm) == 3:
126*cda5da8dSAndroid Build Coastguard Worker        [thh, tmm, tss] = tm
127*cda5da8dSAndroid Build Coastguard Worker    elif len(tm) == 1 and '.' in tm[0]:
128*cda5da8dSAndroid Build Coastguard Worker        # Some non-compliant MUAs use '.' to separate time elements.
129*cda5da8dSAndroid Build Coastguard Worker        tm = tm[0].split('.')
130*cda5da8dSAndroid Build Coastguard Worker        if len(tm) == 2:
131*cda5da8dSAndroid Build Coastguard Worker            [thh, tmm] = tm
132*cda5da8dSAndroid Build Coastguard Worker            tss = 0
133*cda5da8dSAndroid Build Coastguard Worker        elif len(tm) == 3:
134*cda5da8dSAndroid Build Coastguard Worker            [thh, tmm, tss] = tm
135*cda5da8dSAndroid Build Coastguard Worker        else:
136*cda5da8dSAndroid Build Coastguard Worker            return None
137*cda5da8dSAndroid Build Coastguard Worker    else:
138*cda5da8dSAndroid Build Coastguard Worker        return None
139*cda5da8dSAndroid Build Coastguard Worker    try:
140*cda5da8dSAndroid Build Coastguard Worker        yy = int(yy)
141*cda5da8dSAndroid Build Coastguard Worker        dd = int(dd)
142*cda5da8dSAndroid Build Coastguard Worker        thh = int(thh)
143*cda5da8dSAndroid Build Coastguard Worker        tmm = int(tmm)
144*cda5da8dSAndroid Build Coastguard Worker        tss = int(tss)
145*cda5da8dSAndroid Build Coastguard Worker    except ValueError:
146*cda5da8dSAndroid Build Coastguard Worker        return None
147*cda5da8dSAndroid Build Coastguard Worker    # Check for a yy specified in two-digit format, then convert it to the
148*cda5da8dSAndroid Build Coastguard Worker    # appropriate four-digit format, according to the POSIX standard. RFC 822
149*cda5da8dSAndroid Build Coastguard Worker    # calls for a two-digit yy, but RFC 2822 (which obsoletes RFC 822)
150*cda5da8dSAndroid Build Coastguard Worker    # mandates a 4-digit yy. For more information, see the documentation for
151*cda5da8dSAndroid Build Coastguard Worker    # the time module.
152*cda5da8dSAndroid Build Coastguard Worker    if yy < 100:
153*cda5da8dSAndroid Build Coastguard Worker        # The year is between 1969 and 1999 (inclusive).
154*cda5da8dSAndroid Build Coastguard Worker        if yy > 68:
155*cda5da8dSAndroid Build Coastguard Worker            yy += 1900
156*cda5da8dSAndroid Build Coastguard Worker        # The year is between 2000 and 2068 (inclusive).
157*cda5da8dSAndroid Build Coastguard Worker        else:
158*cda5da8dSAndroid Build Coastguard Worker            yy += 2000
159*cda5da8dSAndroid Build Coastguard Worker    tzoffset = None
160*cda5da8dSAndroid Build Coastguard Worker    tz = tz.upper()
161*cda5da8dSAndroid Build Coastguard Worker    if tz in _timezones:
162*cda5da8dSAndroid Build Coastguard Worker        tzoffset = _timezones[tz]
163*cda5da8dSAndroid Build Coastguard Worker    else:
164*cda5da8dSAndroid Build Coastguard Worker        try:
165*cda5da8dSAndroid Build Coastguard Worker            tzoffset = int(tz)
166*cda5da8dSAndroid Build Coastguard Worker        except ValueError:
167*cda5da8dSAndroid Build Coastguard Worker            pass
168*cda5da8dSAndroid Build Coastguard Worker        if tzoffset==0 and tz.startswith('-'):
169*cda5da8dSAndroid Build Coastguard Worker            tzoffset = None
170*cda5da8dSAndroid Build Coastguard Worker    # Convert a timezone offset into seconds ; -0500 -> -18000
171*cda5da8dSAndroid Build Coastguard Worker    if tzoffset:
172*cda5da8dSAndroid Build Coastguard Worker        if tzoffset < 0:
173*cda5da8dSAndroid Build Coastguard Worker            tzsign = -1
174*cda5da8dSAndroid Build Coastguard Worker            tzoffset = -tzoffset
175*cda5da8dSAndroid Build Coastguard Worker        else:
176*cda5da8dSAndroid Build Coastguard Worker            tzsign = 1
177*cda5da8dSAndroid Build Coastguard Worker        tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
178*cda5da8dSAndroid Build Coastguard Worker    # Daylight Saving Time flag is set to -1, since DST is unknown.
179*cda5da8dSAndroid Build Coastguard Worker    return [yy, mm, dd, thh, tmm, tss, 0, 1, -1, tzoffset]
180*cda5da8dSAndroid Build Coastguard Worker
181*cda5da8dSAndroid Build Coastguard Worker
182*cda5da8dSAndroid Build Coastguard Workerdef parsedate(data):
183*cda5da8dSAndroid Build Coastguard Worker    """Convert a time string to a time tuple."""
184*cda5da8dSAndroid Build Coastguard Worker    t = parsedate_tz(data)
185*cda5da8dSAndroid Build Coastguard Worker    if isinstance(t, tuple):
186*cda5da8dSAndroid Build Coastguard Worker        return t[:9]
187*cda5da8dSAndroid Build Coastguard Worker    else:
188*cda5da8dSAndroid Build Coastguard Worker        return t
189*cda5da8dSAndroid Build Coastguard Worker
190*cda5da8dSAndroid Build Coastguard Worker
191*cda5da8dSAndroid Build Coastguard Workerdef mktime_tz(data):
192*cda5da8dSAndroid Build Coastguard Worker    """Turn a 10-tuple as returned by parsedate_tz() into a POSIX timestamp."""
193*cda5da8dSAndroid Build Coastguard Worker    if data[9] is None:
194*cda5da8dSAndroid Build Coastguard Worker        # No zone info, so localtime is better assumption than GMT
195*cda5da8dSAndroid Build Coastguard Worker        return time.mktime(data[:8] + (-1,))
196*cda5da8dSAndroid Build Coastguard Worker    else:
197*cda5da8dSAndroid Build Coastguard Worker        t = calendar.timegm(data)
198*cda5da8dSAndroid Build Coastguard Worker        return t - data[9]
199*cda5da8dSAndroid Build Coastguard Worker
200*cda5da8dSAndroid Build Coastguard Worker
201*cda5da8dSAndroid Build Coastguard Workerdef quote(str):
202*cda5da8dSAndroid Build Coastguard Worker    """Prepare string to be used in a quoted string.
203*cda5da8dSAndroid Build Coastguard Worker
204*cda5da8dSAndroid Build Coastguard Worker    Turns backslash and double quote characters into quoted pairs.  These
205*cda5da8dSAndroid Build Coastguard Worker    are the only characters that need to be quoted inside a quoted string.
206*cda5da8dSAndroid Build Coastguard Worker    Does not add the surrounding double quotes.
207*cda5da8dSAndroid Build Coastguard Worker    """
208*cda5da8dSAndroid Build Coastguard Worker    return str.replace('\\', '\\\\').replace('"', '\\"')
209*cda5da8dSAndroid Build Coastguard Worker
210*cda5da8dSAndroid Build Coastguard Worker
211*cda5da8dSAndroid Build Coastguard Workerclass AddrlistClass:
212*cda5da8dSAndroid Build Coastguard Worker    """Address parser class by Ben Escoto.
213*cda5da8dSAndroid Build Coastguard Worker
214*cda5da8dSAndroid Build Coastguard Worker    To understand what this class does, it helps to have a copy of RFC 2822 in
215*cda5da8dSAndroid Build Coastguard Worker    front of you.
216*cda5da8dSAndroid Build Coastguard Worker
217*cda5da8dSAndroid Build Coastguard Worker    Note: this class interface is deprecated and may be removed in the future.
218*cda5da8dSAndroid Build Coastguard Worker    Use email.utils.AddressList instead.
219*cda5da8dSAndroid Build Coastguard Worker    """
220*cda5da8dSAndroid Build Coastguard Worker
221*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, field):
222*cda5da8dSAndroid Build Coastguard Worker        """Initialize a new instance.
223*cda5da8dSAndroid Build Coastguard Worker
224*cda5da8dSAndroid Build Coastguard Worker        `field' is an unparsed address header field, containing
225*cda5da8dSAndroid Build Coastguard Worker        one or more addresses.
226*cda5da8dSAndroid Build Coastguard Worker        """
227*cda5da8dSAndroid Build Coastguard Worker        self.specials = '()<>@,:;.\"[]'
228*cda5da8dSAndroid Build Coastguard Worker        self.pos = 0
229*cda5da8dSAndroid Build Coastguard Worker        self.LWS = ' \t'
230*cda5da8dSAndroid Build Coastguard Worker        self.CR = '\r\n'
231*cda5da8dSAndroid Build Coastguard Worker        self.FWS = self.LWS + self.CR
232*cda5da8dSAndroid Build Coastguard Worker        self.atomends = self.specials + self.LWS + self.CR
233*cda5da8dSAndroid Build Coastguard Worker        # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
234*cda5da8dSAndroid Build Coastguard Worker        # is obsolete syntax.  RFC 2822 requires that we recognize obsolete
235*cda5da8dSAndroid Build Coastguard Worker        # syntax, so allow dots in phrases.
236*cda5da8dSAndroid Build Coastguard Worker        self.phraseends = self.atomends.replace('.', '')
237*cda5da8dSAndroid Build Coastguard Worker        self.field = field
238*cda5da8dSAndroid Build Coastguard Worker        self.commentlist = []
239*cda5da8dSAndroid Build Coastguard Worker
240*cda5da8dSAndroid Build Coastguard Worker    def gotonext(self):
241*cda5da8dSAndroid Build Coastguard Worker        """Skip white space and extract comments."""
242*cda5da8dSAndroid Build Coastguard Worker        wslist = []
243*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
244*cda5da8dSAndroid Build Coastguard Worker            if self.field[self.pos] in self.LWS + '\n\r':
245*cda5da8dSAndroid Build Coastguard Worker                if self.field[self.pos] not in '\n\r':
246*cda5da8dSAndroid Build Coastguard Worker                    wslist.append(self.field[self.pos])
247*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
248*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '(':
249*cda5da8dSAndroid Build Coastguard Worker                self.commentlist.append(self.getcomment())
250*cda5da8dSAndroid Build Coastguard Worker            else:
251*cda5da8dSAndroid Build Coastguard Worker                break
252*cda5da8dSAndroid Build Coastguard Worker        return EMPTYSTRING.join(wslist)
253*cda5da8dSAndroid Build Coastguard Worker
254*cda5da8dSAndroid Build Coastguard Worker    def getaddrlist(self):
255*cda5da8dSAndroid Build Coastguard Worker        """Parse all addresses.
256*cda5da8dSAndroid Build Coastguard Worker
257*cda5da8dSAndroid Build Coastguard Worker        Returns a list containing all of the addresses.
258*cda5da8dSAndroid Build Coastguard Worker        """
259*cda5da8dSAndroid Build Coastguard Worker        result = []
260*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
261*cda5da8dSAndroid Build Coastguard Worker            ad = self.getaddress()
262*cda5da8dSAndroid Build Coastguard Worker            if ad:
263*cda5da8dSAndroid Build Coastguard Worker                result += ad
264*cda5da8dSAndroid Build Coastguard Worker            else:
265*cda5da8dSAndroid Build Coastguard Worker                result.append(('', ''))
266*cda5da8dSAndroid Build Coastguard Worker        return result
267*cda5da8dSAndroid Build Coastguard Worker
268*cda5da8dSAndroid Build Coastguard Worker    def getaddress(self):
269*cda5da8dSAndroid Build Coastguard Worker        """Parse the next address."""
270*cda5da8dSAndroid Build Coastguard Worker        self.commentlist = []
271*cda5da8dSAndroid Build Coastguard Worker        self.gotonext()
272*cda5da8dSAndroid Build Coastguard Worker
273*cda5da8dSAndroid Build Coastguard Worker        oldpos = self.pos
274*cda5da8dSAndroid Build Coastguard Worker        oldcl = self.commentlist
275*cda5da8dSAndroid Build Coastguard Worker        plist = self.getphraselist()
276*cda5da8dSAndroid Build Coastguard Worker
277*cda5da8dSAndroid Build Coastguard Worker        self.gotonext()
278*cda5da8dSAndroid Build Coastguard Worker        returnlist = []
279*cda5da8dSAndroid Build Coastguard Worker
280*cda5da8dSAndroid Build Coastguard Worker        if self.pos >= len(self.field):
281*cda5da8dSAndroid Build Coastguard Worker            # Bad email address technically, no domain.
282*cda5da8dSAndroid Build Coastguard Worker            if plist:
283*cda5da8dSAndroid Build Coastguard Worker                returnlist = [(SPACE.join(self.commentlist), plist[0])]
284*cda5da8dSAndroid Build Coastguard Worker
285*cda5da8dSAndroid Build Coastguard Worker        elif self.field[self.pos] in '.@':
286*cda5da8dSAndroid Build Coastguard Worker            # email address is just an addrspec
287*cda5da8dSAndroid Build Coastguard Worker            # this isn't very efficient since we start over
288*cda5da8dSAndroid Build Coastguard Worker            self.pos = oldpos
289*cda5da8dSAndroid Build Coastguard Worker            self.commentlist = oldcl
290*cda5da8dSAndroid Build Coastguard Worker            addrspec = self.getaddrspec()
291*cda5da8dSAndroid Build Coastguard Worker            returnlist = [(SPACE.join(self.commentlist), addrspec)]
292*cda5da8dSAndroid Build Coastguard Worker
293*cda5da8dSAndroid Build Coastguard Worker        elif self.field[self.pos] == ':':
294*cda5da8dSAndroid Build Coastguard Worker            # address is a group
295*cda5da8dSAndroid Build Coastguard Worker            returnlist = []
296*cda5da8dSAndroid Build Coastguard Worker
297*cda5da8dSAndroid Build Coastguard Worker            fieldlen = len(self.field)
298*cda5da8dSAndroid Build Coastguard Worker            self.pos += 1
299*cda5da8dSAndroid Build Coastguard Worker            while self.pos < len(self.field):
300*cda5da8dSAndroid Build Coastguard Worker                self.gotonext()
301*cda5da8dSAndroid Build Coastguard Worker                if self.pos < fieldlen and self.field[self.pos] == ';':
302*cda5da8dSAndroid Build Coastguard Worker                    self.pos += 1
303*cda5da8dSAndroid Build Coastguard Worker                    break
304*cda5da8dSAndroid Build Coastguard Worker                returnlist = returnlist + self.getaddress()
305*cda5da8dSAndroid Build Coastguard Worker
306*cda5da8dSAndroid Build Coastguard Worker        elif self.field[self.pos] == '<':
307*cda5da8dSAndroid Build Coastguard Worker            # Address is a phrase then a route addr
308*cda5da8dSAndroid Build Coastguard Worker            routeaddr = self.getrouteaddr()
309*cda5da8dSAndroid Build Coastguard Worker
310*cda5da8dSAndroid Build Coastguard Worker            if self.commentlist:
311*cda5da8dSAndroid Build Coastguard Worker                returnlist = [(SPACE.join(plist) + ' (' +
312*cda5da8dSAndroid Build Coastguard Worker                               ' '.join(self.commentlist) + ')', routeaddr)]
313*cda5da8dSAndroid Build Coastguard Worker            else:
314*cda5da8dSAndroid Build Coastguard Worker                returnlist = [(SPACE.join(plist), routeaddr)]
315*cda5da8dSAndroid Build Coastguard Worker
316*cda5da8dSAndroid Build Coastguard Worker        else:
317*cda5da8dSAndroid Build Coastguard Worker            if plist:
318*cda5da8dSAndroid Build Coastguard Worker                returnlist = [(SPACE.join(self.commentlist), plist[0])]
319*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] in self.specials:
320*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
321*cda5da8dSAndroid Build Coastguard Worker
322*cda5da8dSAndroid Build Coastguard Worker        self.gotonext()
323*cda5da8dSAndroid Build Coastguard Worker        if self.pos < len(self.field) and self.field[self.pos] == ',':
324*cda5da8dSAndroid Build Coastguard Worker            self.pos += 1
325*cda5da8dSAndroid Build Coastguard Worker        return returnlist
326*cda5da8dSAndroid Build Coastguard Worker
327*cda5da8dSAndroid Build Coastguard Worker    def getrouteaddr(self):
328*cda5da8dSAndroid Build Coastguard Worker        """Parse a route address (Return-path value).
329*cda5da8dSAndroid Build Coastguard Worker
330*cda5da8dSAndroid Build Coastguard Worker        This method just skips all the route stuff and returns the addrspec.
331*cda5da8dSAndroid Build Coastguard Worker        """
332*cda5da8dSAndroid Build Coastguard Worker        if self.field[self.pos] != '<':
333*cda5da8dSAndroid Build Coastguard Worker            return
334*cda5da8dSAndroid Build Coastguard Worker
335*cda5da8dSAndroid Build Coastguard Worker        expectroute = False
336*cda5da8dSAndroid Build Coastguard Worker        self.pos += 1
337*cda5da8dSAndroid Build Coastguard Worker        self.gotonext()
338*cda5da8dSAndroid Build Coastguard Worker        adlist = ''
339*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
340*cda5da8dSAndroid Build Coastguard Worker            if expectroute:
341*cda5da8dSAndroid Build Coastguard Worker                self.getdomain()
342*cda5da8dSAndroid Build Coastguard Worker                expectroute = False
343*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '>':
344*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
345*cda5da8dSAndroid Build Coastguard Worker                break
346*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '@':
347*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
348*cda5da8dSAndroid Build Coastguard Worker                expectroute = True
349*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == ':':
350*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
351*cda5da8dSAndroid Build Coastguard Worker            else:
352*cda5da8dSAndroid Build Coastguard Worker                adlist = self.getaddrspec()
353*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
354*cda5da8dSAndroid Build Coastguard Worker                break
355*cda5da8dSAndroid Build Coastguard Worker            self.gotonext()
356*cda5da8dSAndroid Build Coastguard Worker
357*cda5da8dSAndroid Build Coastguard Worker        return adlist
358*cda5da8dSAndroid Build Coastguard Worker
359*cda5da8dSAndroid Build Coastguard Worker    def getaddrspec(self):
360*cda5da8dSAndroid Build Coastguard Worker        """Parse an RFC 2822 addr-spec."""
361*cda5da8dSAndroid Build Coastguard Worker        aslist = []
362*cda5da8dSAndroid Build Coastguard Worker
363*cda5da8dSAndroid Build Coastguard Worker        self.gotonext()
364*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
365*cda5da8dSAndroid Build Coastguard Worker            preserve_ws = True
366*cda5da8dSAndroid Build Coastguard Worker            if self.field[self.pos] == '.':
367*cda5da8dSAndroid Build Coastguard Worker                if aslist and not aslist[-1].strip():
368*cda5da8dSAndroid Build Coastguard Worker                    aslist.pop()
369*cda5da8dSAndroid Build Coastguard Worker                aslist.append('.')
370*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
371*cda5da8dSAndroid Build Coastguard Worker                preserve_ws = False
372*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '"':
373*cda5da8dSAndroid Build Coastguard Worker                aslist.append('"%s"' % quote(self.getquote()))
374*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] in self.atomends:
375*cda5da8dSAndroid Build Coastguard Worker                if aslist and not aslist[-1].strip():
376*cda5da8dSAndroid Build Coastguard Worker                    aslist.pop()
377*cda5da8dSAndroid Build Coastguard Worker                break
378*cda5da8dSAndroid Build Coastguard Worker            else:
379*cda5da8dSAndroid Build Coastguard Worker                aslist.append(self.getatom())
380*cda5da8dSAndroid Build Coastguard Worker            ws = self.gotonext()
381*cda5da8dSAndroid Build Coastguard Worker            if preserve_ws and ws:
382*cda5da8dSAndroid Build Coastguard Worker                aslist.append(ws)
383*cda5da8dSAndroid Build Coastguard Worker
384*cda5da8dSAndroid Build Coastguard Worker        if self.pos >= len(self.field) or self.field[self.pos] != '@':
385*cda5da8dSAndroid Build Coastguard Worker            return EMPTYSTRING.join(aslist)
386*cda5da8dSAndroid Build Coastguard Worker
387*cda5da8dSAndroid Build Coastguard Worker        aslist.append('@')
388*cda5da8dSAndroid Build Coastguard Worker        self.pos += 1
389*cda5da8dSAndroid Build Coastguard Worker        self.gotonext()
390*cda5da8dSAndroid Build Coastguard Worker        domain = self.getdomain()
391*cda5da8dSAndroid Build Coastguard Worker        if not domain:
392*cda5da8dSAndroid Build Coastguard Worker            # Invalid domain, return an empty address instead of returning a
393*cda5da8dSAndroid Build Coastguard Worker            # local part to denote failed parsing.
394*cda5da8dSAndroid Build Coastguard Worker            return EMPTYSTRING
395*cda5da8dSAndroid Build Coastguard Worker        return EMPTYSTRING.join(aslist) + domain
396*cda5da8dSAndroid Build Coastguard Worker
397*cda5da8dSAndroid Build Coastguard Worker    def getdomain(self):
398*cda5da8dSAndroid Build Coastguard Worker        """Get the complete domain name from an address."""
399*cda5da8dSAndroid Build Coastguard Worker        sdlist = []
400*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
401*cda5da8dSAndroid Build Coastguard Worker            if self.field[self.pos] in self.LWS:
402*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
403*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '(':
404*cda5da8dSAndroid Build Coastguard Worker                self.commentlist.append(self.getcomment())
405*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '[':
406*cda5da8dSAndroid Build Coastguard Worker                sdlist.append(self.getdomainliteral())
407*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '.':
408*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
409*cda5da8dSAndroid Build Coastguard Worker                sdlist.append('.')
410*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '@':
411*cda5da8dSAndroid Build Coastguard Worker                # bpo-34155: Don't parse domains with two `@` like
412*cda5da8dSAndroid Build Coastguard Worker                # `[email protected]@important.com`.
413*cda5da8dSAndroid Build Coastguard Worker                return EMPTYSTRING
414*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] in self.atomends:
415*cda5da8dSAndroid Build Coastguard Worker                break
416*cda5da8dSAndroid Build Coastguard Worker            else:
417*cda5da8dSAndroid Build Coastguard Worker                sdlist.append(self.getatom())
418*cda5da8dSAndroid Build Coastguard Worker        return EMPTYSTRING.join(sdlist)
419*cda5da8dSAndroid Build Coastguard Worker
420*cda5da8dSAndroid Build Coastguard Worker    def getdelimited(self, beginchar, endchars, allowcomments=True):
421*cda5da8dSAndroid Build Coastguard Worker        """Parse a header fragment delimited by special characters.
422*cda5da8dSAndroid Build Coastguard Worker
423*cda5da8dSAndroid Build Coastguard Worker        `beginchar' is the start character for the fragment.
424*cda5da8dSAndroid Build Coastguard Worker        If self is not looking at an instance of `beginchar' then
425*cda5da8dSAndroid Build Coastguard Worker        getdelimited returns the empty string.
426*cda5da8dSAndroid Build Coastguard Worker
427*cda5da8dSAndroid Build Coastguard Worker        `endchars' is a sequence of allowable end-delimiting characters.
428*cda5da8dSAndroid Build Coastguard Worker        Parsing stops when one of these is encountered.
429*cda5da8dSAndroid Build Coastguard Worker
430*cda5da8dSAndroid Build Coastguard Worker        If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
431*cda5da8dSAndroid Build Coastguard Worker        within the parsed fragment.
432*cda5da8dSAndroid Build Coastguard Worker        """
433*cda5da8dSAndroid Build Coastguard Worker        if self.field[self.pos] != beginchar:
434*cda5da8dSAndroid Build Coastguard Worker            return ''
435*cda5da8dSAndroid Build Coastguard Worker
436*cda5da8dSAndroid Build Coastguard Worker        slist = ['']
437*cda5da8dSAndroid Build Coastguard Worker        quote = False
438*cda5da8dSAndroid Build Coastguard Worker        self.pos += 1
439*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
440*cda5da8dSAndroid Build Coastguard Worker            if quote:
441*cda5da8dSAndroid Build Coastguard Worker                slist.append(self.field[self.pos])
442*cda5da8dSAndroid Build Coastguard Worker                quote = False
443*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] in endchars:
444*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
445*cda5da8dSAndroid Build Coastguard Worker                break
446*cda5da8dSAndroid Build Coastguard Worker            elif allowcomments and self.field[self.pos] == '(':
447*cda5da8dSAndroid Build Coastguard Worker                slist.append(self.getcomment())
448*cda5da8dSAndroid Build Coastguard Worker                continue        # have already advanced pos from getcomment
449*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '\\':
450*cda5da8dSAndroid Build Coastguard Worker                quote = True
451*cda5da8dSAndroid Build Coastguard Worker            else:
452*cda5da8dSAndroid Build Coastguard Worker                slist.append(self.field[self.pos])
453*cda5da8dSAndroid Build Coastguard Worker            self.pos += 1
454*cda5da8dSAndroid Build Coastguard Worker
455*cda5da8dSAndroid Build Coastguard Worker        return EMPTYSTRING.join(slist)
456*cda5da8dSAndroid Build Coastguard Worker
457*cda5da8dSAndroid Build Coastguard Worker    def getquote(self):
458*cda5da8dSAndroid Build Coastguard Worker        """Get a quote-delimited fragment from self's field."""
459*cda5da8dSAndroid Build Coastguard Worker        return self.getdelimited('"', '"\r', False)
460*cda5da8dSAndroid Build Coastguard Worker
461*cda5da8dSAndroid Build Coastguard Worker    def getcomment(self):
462*cda5da8dSAndroid Build Coastguard Worker        """Get a parenthesis-delimited fragment from self's field."""
463*cda5da8dSAndroid Build Coastguard Worker        return self.getdelimited('(', ')\r', True)
464*cda5da8dSAndroid Build Coastguard Worker
465*cda5da8dSAndroid Build Coastguard Worker    def getdomainliteral(self):
466*cda5da8dSAndroid Build Coastguard Worker        """Parse an RFC 2822 domain-literal."""
467*cda5da8dSAndroid Build Coastguard Worker        return '[%s]' % self.getdelimited('[', ']\r', False)
468*cda5da8dSAndroid Build Coastguard Worker
469*cda5da8dSAndroid Build Coastguard Worker    def getatom(self, atomends=None):
470*cda5da8dSAndroid Build Coastguard Worker        """Parse an RFC 2822 atom.
471*cda5da8dSAndroid Build Coastguard Worker
472*cda5da8dSAndroid Build Coastguard Worker        Optional atomends specifies a different set of end token delimiters
473*cda5da8dSAndroid Build Coastguard Worker        (the default is to use self.atomends).  This is used e.g. in
474*cda5da8dSAndroid Build Coastguard Worker        getphraselist() since phrase endings must not include the `.' (which
475*cda5da8dSAndroid Build Coastguard Worker        is legal in phrases)."""
476*cda5da8dSAndroid Build Coastguard Worker        atomlist = ['']
477*cda5da8dSAndroid Build Coastguard Worker        if atomends is None:
478*cda5da8dSAndroid Build Coastguard Worker            atomends = self.atomends
479*cda5da8dSAndroid Build Coastguard Worker
480*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
481*cda5da8dSAndroid Build Coastguard Worker            if self.field[self.pos] in atomends:
482*cda5da8dSAndroid Build Coastguard Worker                break
483*cda5da8dSAndroid Build Coastguard Worker            else:
484*cda5da8dSAndroid Build Coastguard Worker                atomlist.append(self.field[self.pos])
485*cda5da8dSAndroid Build Coastguard Worker            self.pos += 1
486*cda5da8dSAndroid Build Coastguard Worker
487*cda5da8dSAndroid Build Coastguard Worker        return EMPTYSTRING.join(atomlist)
488*cda5da8dSAndroid Build Coastguard Worker
489*cda5da8dSAndroid Build Coastguard Worker    def getphraselist(self):
490*cda5da8dSAndroid Build Coastguard Worker        """Parse a sequence of RFC 2822 phrases.
491*cda5da8dSAndroid Build Coastguard Worker
492*cda5da8dSAndroid Build Coastguard Worker        A phrase is a sequence of words, which are in turn either RFC 2822
493*cda5da8dSAndroid Build Coastguard Worker        atoms or quoted-strings.  Phrases are canonicalized by squeezing all
494*cda5da8dSAndroid Build Coastguard Worker        runs of continuous whitespace into one space.
495*cda5da8dSAndroid Build Coastguard Worker        """
496*cda5da8dSAndroid Build Coastguard Worker        plist = []
497*cda5da8dSAndroid Build Coastguard Worker
498*cda5da8dSAndroid Build Coastguard Worker        while self.pos < len(self.field):
499*cda5da8dSAndroid Build Coastguard Worker            if self.field[self.pos] in self.FWS:
500*cda5da8dSAndroid Build Coastguard Worker                self.pos += 1
501*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '"':
502*cda5da8dSAndroid Build Coastguard Worker                plist.append(self.getquote())
503*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] == '(':
504*cda5da8dSAndroid Build Coastguard Worker                self.commentlist.append(self.getcomment())
505*cda5da8dSAndroid Build Coastguard Worker            elif self.field[self.pos] in self.phraseends:
506*cda5da8dSAndroid Build Coastguard Worker                break
507*cda5da8dSAndroid Build Coastguard Worker            else:
508*cda5da8dSAndroid Build Coastguard Worker                plist.append(self.getatom(self.phraseends))
509*cda5da8dSAndroid Build Coastguard Worker
510*cda5da8dSAndroid Build Coastguard Worker        return plist
511*cda5da8dSAndroid Build Coastguard Worker
512*cda5da8dSAndroid Build Coastguard Workerclass AddressList(AddrlistClass):
513*cda5da8dSAndroid Build Coastguard Worker    """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
514*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, field):
515*cda5da8dSAndroid Build Coastguard Worker        AddrlistClass.__init__(self, field)
516*cda5da8dSAndroid Build Coastguard Worker        if field:
517*cda5da8dSAndroid Build Coastguard Worker            self.addresslist = self.getaddrlist()
518*cda5da8dSAndroid Build Coastguard Worker        else:
519*cda5da8dSAndroid Build Coastguard Worker            self.addresslist = []
520*cda5da8dSAndroid Build Coastguard Worker
521*cda5da8dSAndroid Build Coastguard Worker    def __len__(self):
522*cda5da8dSAndroid Build Coastguard Worker        return len(self.addresslist)
523*cda5da8dSAndroid Build Coastguard Worker
524*cda5da8dSAndroid Build Coastguard Worker    def __add__(self, other):
525*cda5da8dSAndroid Build Coastguard Worker        # Set union
526*cda5da8dSAndroid Build Coastguard Worker        newaddr = AddressList(None)
527*cda5da8dSAndroid Build Coastguard Worker        newaddr.addresslist = self.addresslist[:]
528*cda5da8dSAndroid Build Coastguard Worker        for x in other.addresslist:
529*cda5da8dSAndroid Build Coastguard Worker            if not x in self.addresslist:
530*cda5da8dSAndroid Build Coastguard Worker                newaddr.addresslist.append(x)
531*cda5da8dSAndroid Build Coastguard Worker        return newaddr
532*cda5da8dSAndroid Build Coastguard Worker
533*cda5da8dSAndroid Build Coastguard Worker    def __iadd__(self, other):
534*cda5da8dSAndroid Build Coastguard Worker        # Set union, in-place
535*cda5da8dSAndroid Build Coastguard Worker        for x in other.addresslist:
536*cda5da8dSAndroid Build Coastguard Worker            if not x in self.addresslist:
537*cda5da8dSAndroid Build Coastguard Worker                self.addresslist.append(x)
538*cda5da8dSAndroid Build Coastguard Worker        return self
539*cda5da8dSAndroid Build Coastguard Worker
540*cda5da8dSAndroid Build Coastguard Worker    def __sub__(self, other):
541*cda5da8dSAndroid Build Coastguard Worker        # Set difference
542*cda5da8dSAndroid Build Coastguard Worker        newaddr = AddressList(None)
543*cda5da8dSAndroid Build Coastguard Worker        for x in self.addresslist:
544*cda5da8dSAndroid Build Coastguard Worker            if not x in other.addresslist:
545*cda5da8dSAndroid Build Coastguard Worker                newaddr.addresslist.append(x)
546*cda5da8dSAndroid Build Coastguard Worker        return newaddr
547*cda5da8dSAndroid Build Coastguard Worker
548*cda5da8dSAndroid Build Coastguard Worker    def __isub__(self, other):
549*cda5da8dSAndroid Build Coastguard Worker        # Set difference, in-place
550*cda5da8dSAndroid Build Coastguard Worker        for x in other.addresslist:
551*cda5da8dSAndroid Build Coastguard Worker            if x in self.addresslist:
552*cda5da8dSAndroid Build Coastguard Worker                self.addresslist.remove(x)
553*cda5da8dSAndroid Build Coastguard Worker        return self
554*cda5da8dSAndroid Build Coastguard Worker
555*cda5da8dSAndroid Build Coastguard Worker    def __getitem__(self, index):
556*cda5da8dSAndroid Build Coastguard Worker        # Make indexing, slices, and 'in' work
557*cda5da8dSAndroid Build Coastguard Worker        return self.addresslist[index]
558