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