annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/email/_parseaddr.py @ 68:5028fdace37b

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