14710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Copyright (C) 2002-2007 Python Software Foundation
24710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Contact: email-sig@python.org
34710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
44710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm"""Email address parsing code.
54710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
64710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmLifted directly from rfc822.py.  This should eventually be rewritten.
74710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm"""
84710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
94710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm__all__ = [
104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    'mktime_tz',
114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    'parsedate',
124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    'parsedate_tz',
134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    'quote',
144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    ]
154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport time
174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmSPACE = ' '
194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmEMPTYSTRING = ''
204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmCOMMASPACE = ', '
214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Parse a date field
234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm_monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm               'aug', 'sep', 'oct', 'nov', 'dec',
254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm               'january', 'february', 'march', 'april', 'may', 'june', 'july',
264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm               'august', 'september', 'october', 'november', 'december']
274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# The timezone table does not include the military time zones defined
314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# in RFC822, other than Z.  According to RFC1123, the description in
324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# RFC822 gets the signs wrong, so we can't rely on any such time
334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# zones.  RFC1123 recommends that numeric timezone indicators be used
344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# instead of timezone names.
354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm_timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm              'AST': -400, 'ADT': -300,  # Atlantic (used in Canada)
384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm              'EST': -500, 'EDT': -400,  # Eastern
394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm              'CST': -600, 'CDT': -500,  # Central
404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm              'MST': -700, 'MDT': -600,  # Mountain
414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm              'PST': -800, 'PDT': -700   # Pacific
424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm              }
434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef parsedate_tz(data):
464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Convert a date string to a time tuple.
474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Accounts for military timezones.
494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """
504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    data = data.split()
514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # The FWS after the comma after the day-of-week is optional, so search and
524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # adjust for this.
534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if data[0].endswith(',') or data[0].lower() in _daynames:
544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # There's a dayname here. Skip it
554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        del data[0]
564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    else:
574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        i = data[0].rfind(',')
584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if i >= 0:
594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            data[0] = data[0][i+1:]
604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if len(data) == 3: # RFC 850 date, deprecated
614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        stuff = data[0].split('-')
624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if len(stuff) == 3:
634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            data = stuff + data[1:]
644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if len(data) == 4:
654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        s = data[3]
664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        i = s.find('+')
674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if i > 0:
684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            data[3:] = [s[:i], s[i+1:]]
694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            data.append('') # Dummy tz
714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if len(data) < 5:
724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return None
734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    data = data[:5]
744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    [dd, mm, yy, tm, tz] = data
754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    mm = mm.lower()
764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if mm not in _monthnames:
774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        dd, mm = mm, dd.lower()
784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if mm not in _monthnames:
794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return None
804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    mm = _monthnames.index(mm) + 1
814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if mm > 12:
824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        mm -= 12
834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if dd[-1] == ',':
844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        dd = dd[:-1]
854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    i = yy.find(':')
864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if i > 0:
874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        yy, tm = tm, yy
884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if yy[-1] == ',':
894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        yy = yy[:-1]
904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if not yy[0].isdigit():
914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        yy, tz = tz, yy
924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if tm[-1] == ',':
934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        tm = tm[:-1]
944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    tm = tm.split(':')
954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if len(tm) == 2:
964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        [thh, tmm] = tm
974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        tss = '0'
984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    elif len(tm) == 3:
994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        [thh, tmm, tss] = tm
1004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    else:
1014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return None
1024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
1034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        yy = int(yy)
1044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        dd = int(dd)
1054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        thh = int(thh)
1064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        tmm = int(tmm)
1074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        tss = int(tss)
1084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except ValueError:
1094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return None
1104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Check for a yy specified in two-digit format, then convert it to the
1114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # appropriate four-digit format, according to the POSIX standard. RFC 822
1124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # calls for a two-digit yy, but RFC 2822 (which obsoletes RFC 822)
1134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # mandates a 4-digit yy. For more information, see the documentation for
1144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # the time module.
1154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if yy < 100:
1164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # The year is between 1969 and 1999 (inclusive).
1174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if yy > 68:
1184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            yy += 1900
1194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # The year is between 2000 and 2068 (inclusive).
1204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
1214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            yy += 2000
1224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    tzoffset = None
1234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    tz = tz.upper()
1244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if tz in _timezones:
1254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        tzoffset = _timezones[tz]
1264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    else:
1274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
1284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            tzoffset = int(tz)
1294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except ValueError:
1304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            pass
1314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Convert a timezone offset into seconds ; -0500 -> -18000
1324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if tzoffset:
1334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if tzoffset < 0:
1344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            tzsign = -1
1354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            tzoffset = -tzoffset
1364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
1374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            tzsign = 1
1384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
1394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    # Daylight Saving Time flag is set to -1, since DST is unknown.
1404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return yy, mm, dd, thh, tmm, tss, 0, 1, -1, tzoffset
1414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef parsedate(data):
1444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Convert a time string to a time tuple."""
1454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    t = parsedate_tz(data)
1464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if isinstance(t, tuple):
1474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return t[:9]
1484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    else:
1494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return t
1504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef mktime_tz(data):
1534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp."""
1544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if data[9] is None:
1554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # No zone info, so localtime is better assumption than GMT
1564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return time.mktime(data[:8] + (-1,))
1574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    else:
1584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        t = time.mktime(data[:8] + (0,))
1594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return t - data[9] - time.timezone
1604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef quote(str):
1634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Prepare string to be used in a quoted string.
1644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Turns backslash and double quote characters into quoted pairs.  These
1664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    are the only characters that need to be quoted inside a quoted string.
1674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Does not add the surrounding double quotes.
1684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """
1694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return str.replace('\\', '\\\\').replace('"', '\\"')
1704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass AddrlistClass:
1734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """Address parser class by Ben Escoto.
1744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    To understand what this class does, it helps to have a copy of RFC 2822 in
1764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    front of you.
1774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Note: this class interface is deprecated and may be removed in the future.
1794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    Use rfc822.AddressList instead.
1804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """
1814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self, field):
1834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Initialize a new instance.
1844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        `field' is an unparsed address header field, containing
1864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        one or more addresses.
1874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
1884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.specials = '()<>@,:;.\"[]'
1894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.pos = 0
1904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.LWS = ' \t'
1914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.CR = '\r\n'
1924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.FWS = self.LWS + self.CR
1934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.atomends = self.specials + self.LWS + self.CR
1944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
1954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # is obsolete syntax.  RFC 2822 requires that we recognize obsolete
1964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # syntax, so allow dots in phrases.
1974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.phraseends = self.atomends.replace('.', '')
1984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.field = field
1994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.commentlist = []
2004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def gotonext(self):
2024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse up to the start of the next address."""
2034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
2044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self.field[self.pos] in self.LWS + '\n\r':
2054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
2064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '(':
2074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.commentlist.append(self.getcomment())
2084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
2094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
2104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getaddrlist(self):
2124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse all addresses.
2134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Returns a list containing all of the addresses.
2154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        result = []
2174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
2184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            ad = self.getaddress()
2194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if ad:
2204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                result += ad
2214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
2224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                result.append(('', ''))
2234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return result
2244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getaddress(self):
2264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse the next address."""
2274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.commentlist = []
2284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.gotonext()
2294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        oldpos = self.pos
2314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        oldcl = self.commentlist
2324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        plist = self.getphraselist()
2334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.gotonext()
2354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        returnlist = []
2364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self.pos >= len(self.field):
2384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # Bad email address technically, no domain.
2394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if plist:
2404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                returnlist = [(SPACE.join(self.commentlist), plist[0])]
2414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        elif self.field[self.pos] in '.@':
2434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # email address is just an addrspec
2444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # this isn't very efficient since we start over
2454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.pos = oldpos
2464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.commentlist = oldcl
2474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            addrspec = self.getaddrspec()
2484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            returnlist = [(SPACE.join(self.commentlist), addrspec)]
2494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        elif self.field[self.pos] == ':':
2514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # address is a group
2524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            returnlist = []
2534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            fieldlen = len(self.field)
2554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.pos += 1
2564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            while self.pos < len(self.field):
2574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.gotonext()
2584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                if self.pos < fieldlen and self.field[self.pos] == ';':
2594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    self.pos += 1
2604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                    break
2614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                returnlist = returnlist + self.getaddress()
2624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        elif self.field[self.pos] == '<':
2644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            # Address is a phrase then a route addr
2654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            routeaddr = self.getrouteaddr()
2664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self.commentlist:
2684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                returnlist = [(SPACE.join(plist) + ' (' +
2694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                               ' '.join(self.commentlist) + ')', routeaddr)]
2704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
2714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                returnlist = [(SPACE.join(plist), routeaddr)]
2724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
2744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if plist:
2754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                returnlist = [(SPACE.join(self.commentlist), plist[0])]
2764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] in self.specials:
2774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
2784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.gotonext()
2804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self.pos < len(self.field) and self.field[self.pos] == ',':
2814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.pos += 1
2824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return returnlist
2834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getrouteaddr(self):
2854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse a route address (Return-path value).
2864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        This method just skips all the route stuff and returns the addrspec.
2884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
2894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self.field[self.pos] != '<':
2904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return
2914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        expectroute = False
2934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.pos += 1
2944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.gotonext()
2954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        adlist = ''
2964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
2974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if expectroute:
2984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.getdomain()
2994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                expectroute = False
3004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '>':
3014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
3034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '@':
3044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                expectroute = True
3064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == ':':
3074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
3094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                adlist = self.getaddrspec()
3104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
3124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.gotonext()
3134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return adlist
3154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getaddrspec(self):
3174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse an RFC 2822 addr-spec."""
3184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        aslist = []
3194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.gotonext()
3214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
3224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self.field[self.pos] == '.':
3234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                aslist.append('.')
3244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '"':
3264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                aslist.append('"%s"' % quote(self.getquote()))
3274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] in self.atomends:
3284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
3294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
3304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                aslist.append(self.getatom())
3314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.gotonext()
3324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self.pos >= len(self.field) or self.field[self.pos] != '@':
3344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return EMPTYSTRING.join(aslist)
3354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        aslist.append('@')
3374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.pos += 1
3384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.gotonext()
3394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return EMPTYSTRING.join(aslist) + self.getdomain()
3404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getdomain(self):
3424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Get the complete domain name from an address."""
3434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sdlist = []
3444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
3454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self.field[self.pos] in self.LWS:
3464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '(':
3484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.commentlist.append(self.getcomment())
3494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '[':
3504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                sdlist.append(self.getdomainliteral())
3514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '.':
3524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                sdlist.append('.')
3544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] in self.atomends:
3554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
3564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
3574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                sdlist.append(self.getatom())
3584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return EMPTYSTRING.join(sdlist)
3594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getdelimited(self, beginchar, endchars, allowcomments=True):
3614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse a header fragment delimited by special characters.
3624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        `beginchar' is the start character for the fragment.
3644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        If self is not looking at an instance of `beginchar' then
3654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        getdelimited returns the empty string.
3664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        `endchars' is a sequence of allowable end-delimiting characters.
3684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Parsing stops when one of these is encountered.
3694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
3714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        within the parsed fragment.
3724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
3734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if self.field[self.pos] != beginchar:
3744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return ''
3754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        slist = ['']
3774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        quote = False
3784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.pos += 1
3794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
3804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if quote:
3814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                slist.append(self.field[self.pos])
3824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                quote = False
3834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] in endchars:
3844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
3854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
3864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif allowcomments and self.field[self.pos] == '(':
3874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                slist.append(self.getcomment())
3884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                continue        # have already advanced pos from getcomment
3894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '\\':
3904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                quote = True
3914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
3924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                slist.append(self.field[self.pos])
3934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.pos += 1
3944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return EMPTYSTRING.join(slist)
3964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getquote(self):
3984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Get a quote-delimited fragment from self's field."""
3994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self.getdelimited('"', '"\r', False)
4004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getcomment(self):
4024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Get a parenthesis-delimited fragment from self's field."""
4034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self.getdelimited('(', ')\r', True)
4044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getdomainliteral(self):
4064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse an RFC 2822 domain-literal."""
4074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return '[%s]' % self.getdelimited('[', ']\r', False)
4084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getatom(self, atomends=None):
4104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse an RFC 2822 atom.
4114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        Optional atomends specifies a different set of end token delimiters
4134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        (the default is to use self.atomends).  This is used e.g. in
4144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        getphraselist() since phrase endings must not include the `.' (which
4154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        is legal in phrases)."""
4164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        atomlist = ['']
4174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if atomends is None:
4184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            atomends = self.atomends
4194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
4214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self.field[self.pos] in atomends:
4224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
4234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
4244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                atomlist.append(self.field[self.pos])
4254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.pos += 1
4264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return EMPTYSTRING.join(atomlist)
4284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def getphraselist(self):
4304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """Parse a sequence of RFC 2822 phrases.
4314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        A phrase is a sequence of words, which are in turn either RFC 2822
4334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        atoms or quoted-strings.  Phrases are canonicalized by squeezing all
4344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        runs of continuous whitespace into one space.
4354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        """
4364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        plist = []
4374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while self.pos < len(self.field):
4394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if self.field[self.pos] in self.FWS:
4404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.pos += 1
4414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '"':
4424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                plist.append(self.getquote())
4434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] == '(':
4444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.commentlist.append(self.getcomment())
4454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            elif self.field[self.pos] in self.phraseends:
4464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                break
4474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
4484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                plist.append(self.getatom(self.phraseends))
4494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return plist
4514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass AddressList(AddrlistClass):
4534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
4544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self, field):
4554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        AddrlistClass.__init__(self, field)
4564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if field:
4574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.addresslist = self.getaddrlist()
4584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
4594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            self.addresslist = []
4604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __len__(self):
4624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return len(self.addresslist)
4634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __add__(self, other):
4654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Set union
4664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        newaddr = AddressList(None)
4674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        newaddr.addresslist = self.addresslist[:]
4684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for x in other.addresslist:
4694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if not x in self.addresslist:
4704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                newaddr.addresslist.append(x)
4714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return newaddr
4724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __iadd__(self, other):
4744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Set union, in-place
4754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for x in other.addresslist:
4764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if not x in self.addresslist:
4774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.addresslist.append(x)
4784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self
4794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __sub__(self, other):
4814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Set difference
4824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        newaddr = AddressList(None)
4834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for x in self.addresslist:
4844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if not x in other.addresslist:
4854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                newaddr.addresslist.append(x)
4864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return newaddr
4874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __isub__(self, other):
4894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Set difference, in-place
4904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for x in other.addresslist:
4914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if x in self.addresslist:
4924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                self.addresslist.remove(x)
4934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self
4944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
4954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __getitem__(self, index):
4964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        # Make indexing, slices, and 'in' work
4974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return self.addresslist[index]
498