blob: 690db2c22d34d358b6707d6befa0fcf96e6373bc [file] [log] [blame]
Barry Warsawcbbc3f12007-03-12 03:20:01 +00001# Copyright (C) 2002-2007 Python Software Foundation
Barry Warsawbb113862004-10-03 03:16:19 +00002# Contact: email-sig@python.org
Barry Warsaw030ddf72002-11-05 19:54:52 +00003
4"""Email address parsing code.
5
6Lifted directly from rfc822.py. This should eventually be rewritten.
7"""
8
Barry Warsaw40ef0062006-03-18 15:41:53 +00009__all__ = [
10 'mktime_tz',
11 'parsedate',
12 'parsedate_tz',
13 'quote',
14 ]
15
Alexander Belopolskye99d3a12012-06-21 20:57:39 -040016import time, calendar
Barry Warsaw5c8fef92002-12-30 16:43:42 +000017
18SPACE = ' '
19EMPTYSTRING = ''
20COMMASPACE = ', '
Barry Warsaw030ddf72002-11-05 19:54:52 +000021
22# Parse a date field
23_monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
24 'aug', 'sep', 'oct', 'nov', 'dec',
25 'january', 'february', 'march', 'april', 'may', 'june', 'july',
26 'august', 'september', 'october', 'november', 'december']
27
28_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
29
30# The timezone table does not include the military time zones defined
31# in RFC822, other than Z. According to RFC1123, the description in
32# RFC822 gets the signs wrong, so we can't rely on any such time
33# zones. RFC1123 recommends that numeric timezone indicators be used
34# instead of timezone names.
35
36_timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
37 'AST': -400, 'ADT': -300, # Atlantic (used in Canada)
38 'EST': -500, 'EDT': -400, # Eastern
39 'CST': -600, 'CDT': -500, # Central
40 'MST': -700, 'MDT': -600, # Mountain
41 'PST': -800, 'PDT': -700 # Pacific
42 }
43
44
45def parsedate_tz(data):
46 """Convert a date string to a time tuple.
47
48 Accounts for military timezones.
49 """
50 data = data.split()
Barry Warsawba976592002-12-30 17:21:36 +000051 # The FWS after the comma after the day-of-week is optional, so search and
52 # adjust for this.
53 if data[0].endswith(',') or data[0].lower() in _daynames:
Barry Warsaw030ddf72002-11-05 19:54:52 +000054 # There's a dayname here. Skip it
55 del data[0]
Barry Warsawba976592002-12-30 17:21:36 +000056 else:
57 i = data[0].rfind(',')
Barry Warsawb5dc39f2003-05-08 03:33:15 +000058 if i >= 0:
59 data[0] = data[0][i+1:]
Barry Warsaw030ddf72002-11-05 19:54:52 +000060 if len(data) == 3: # RFC 850 date, deprecated
61 stuff = data[0].split('-')
62 if len(stuff) == 3:
63 data = stuff + data[1:]
64 if len(data) == 4:
65 s = data[3]
66 i = s.find('+')
67 if i > 0:
68 data[3:] = [s[:i], s[i+1:]]
69 else:
70 data.append('') # Dummy tz
71 if len(data) < 5:
72 return None
73 data = data[:5]
74 [dd, mm, yy, tm, tz] = data
75 mm = mm.lower()
Barry Warsaw5c8fef92002-12-30 16:43:42 +000076 if mm not in _monthnames:
Barry Warsaw030ddf72002-11-05 19:54:52 +000077 dd, mm = mm, dd.lower()
Barry Warsaw5c8fef92002-12-30 16:43:42 +000078 if mm not in _monthnames:
Barry Warsaw030ddf72002-11-05 19:54:52 +000079 return None
Barry Warsaw5c8fef92002-12-30 16:43:42 +000080 mm = _monthnames.index(mm) + 1
81 if mm > 12:
82 mm -= 12
Barry Warsaw030ddf72002-11-05 19:54:52 +000083 if dd[-1] == ',':
84 dd = dd[:-1]
85 i = yy.find(':')
86 if i > 0:
87 yy, tm = tm, yy
88 if yy[-1] == ',':
89 yy = yy[:-1]
90 if not yy[0].isdigit():
91 yy, tz = tz, yy
92 if tm[-1] == ',':
93 tm = tm[:-1]
94 tm = tm.split(':')
95 if len(tm) == 2:
96 [thh, tmm] = tm
97 tss = '0'
98 elif len(tm) == 3:
99 [thh, tmm, tss] = tm
100 else:
101 return None
102 try:
103 yy = int(yy)
104 dd = int(dd)
105 thh = int(thh)
106 tmm = int(tmm)
107 tss = int(tss)
108 except ValueError:
109 return None
R. David Murray7c4bf552010-08-25 01:45:57 +0000110 # Check for a yy specified in two-digit format, then convert it to the
111 # appropriate four-digit format, according to the POSIX standard. RFC 822
112 # calls for a two-digit yy, but RFC 2822 (which obsoletes RFC 822)
113 # mandates a 4-digit yy. For more information, see the documentation for
114 # the time module.
115 if yy < 100:
116 # The year is between 1969 and 1999 (inclusive).
117 if yy > 68:
118 yy += 1900
119 # The year is between 2000 and 2068 (inclusive).
120 else:
121 yy += 2000
Barry Warsaw030ddf72002-11-05 19:54:52 +0000122 tzoffset = None
123 tz = tz.upper()
Brett Cannon81614982008-08-03 23:40:13 +0000124 if tz in _timezones:
Barry Warsaw030ddf72002-11-05 19:54:52 +0000125 tzoffset = _timezones[tz]
126 else:
127 try:
128 tzoffset = int(tz)
129 except ValueError:
130 pass
131 # Convert a timezone offset into seconds ; -0500 -> -18000
132 if tzoffset:
133 if tzoffset < 0:
134 tzsign = -1
135 tzoffset = -tzoffset
136 else:
137 tzsign = 1
Barry Warsawbb113862004-10-03 03:16:19 +0000138 tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
Anthony Baxter93f5b932006-04-03 08:05:07 +0000139 # Daylight Saving Time flag is set to -1, since DST is unknown.
140 return yy, mm, dd, thh, tmm, tss, 0, 1, -1, tzoffset
Barry Warsaw030ddf72002-11-05 19:54:52 +0000141
142
143def parsedate(data):
144 """Convert a time string to a time tuple."""
145 t = parsedate_tz(data)
Barry Warsaw24f79762004-05-09 03:55:11 +0000146 if isinstance(t, tuple):
Barry Warsaw030ddf72002-11-05 19:54:52 +0000147 return t[:9]
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000148 else:
149 return t
Barry Warsaw030ddf72002-11-05 19:54:52 +0000150
151
152def mktime_tz(data):
Alexander Belopolskye99d3a12012-06-21 20:57:39 -0400153 """Turn a 10-tuple as returned by parsedate_tz() into a POSIX timestamp."""
Barry Warsaw030ddf72002-11-05 19:54:52 +0000154 if data[9] is None:
155 # No zone info, so localtime is better assumption than GMT
156 return time.mktime(data[:8] + (-1,))
157 else:
Alexander Belopolskye99d3a12012-06-21 20:57:39 -0400158 t = calendar.timegm(data)
159 return t - data[9]
Barry Warsaw030ddf72002-11-05 19:54:52 +0000160
161
162def quote(str):
R. David Murray10a86762010-10-02 16:26:05 +0000163 """Prepare string to be used in a quoted string.
164
165 Turns backslash and double quote characters into quoted pairs. These
166 are the only characters that need to be quoted inside a quoted string.
167 Does not add the surrounding double quotes.
168 """
Barry Warsaw030ddf72002-11-05 19:54:52 +0000169 return str.replace('\\', '\\\\').replace('"', '\\"')
170
171
172class AddrlistClass:
173 """Address parser class by Ben Escoto.
174
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000175 To understand what this class does, it helps to have a copy of RFC 2822 in
176 front of you.
Barry Warsaw030ddf72002-11-05 19:54:52 +0000177
178 Note: this class interface is deprecated and may be removed in the future.
179 Use rfc822.AddressList instead.
180 """
181
182 def __init__(self, field):
183 """Initialize a new instance.
184
185 `field' is an unparsed address header field, containing
186 one or more addresses.
187 """
188 self.specials = '()<>@,:;.\"[]'
189 self.pos = 0
190 self.LWS = ' \t'
191 self.CR = '\r\n'
Barry Warsawcbbc3f12007-03-12 03:20:01 +0000192 self.FWS = self.LWS + self.CR
Barry Warsaw030ddf72002-11-05 19:54:52 +0000193 self.atomends = self.specials + self.LWS + self.CR
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000194 # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
195 # is obsolete syntax. RFC 2822 requires that we recognize obsolete
196 # syntax, so allow dots in phrases.
197 self.phraseends = self.atomends.replace('.', '')
Barry Warsaw030ddf72002-11-05 19:54:52 +0000198 self.field = field
199 self.commentlist = []
200
201 def gotonext(self):
202 """Parse up to the start of the next address."""
203 while self.pos < len(self.field):
204 if self.field[self.pos] in self.LWS + '\n\r':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000205 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000206 elif self.field[self.pos] == '(':
207 self.commentlist.append(self.getcomment())
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000208 else:
209 break
Barry Warsaw030ddf72002-11-05 19:54:52 +0000210
211 def getaddrlist(self):
212 """Parse all addresses.
213
214 Returns a list containing all of the addresses.
215 """
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000216 result = []
Barry Warsawfa348c82003-03-17 18:35:42 +0000217 while self.pos < len(self.field):
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000218 ad = self.getaddress()
219 if ad:
220 result += ad
221 else:
Barry Warsawfa348c82003-03-17 18:35:42 +0000222 result.append(('', ''))
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000223 return result
Barry Warsaw030ddf72002-11-05 19:54:52 +0000224
225 def getaddress(self):
226 """Parse the next address."""
227 self.commentlist = []
228 self.gotonext()
229
230 oldpos = self.pos
231 oldcl = self.commentlist
232 plist = self.getphraselist()
233
234 self.gotonext()
235 returnlist = []
236
237 if self.pos >= len(self.field):
238 # Bad email address technically, no domain.
239 if plist:
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000240 returnlist = [(SPACE.join(self.commentlist), plist[0])]
Barry Warsaw030ddf72002-11-05 19:54:52 +0000241
242 elif self.field[self.pos] in '.@':
243 # email address is just an addrspec
244 # this isn't very efficient since we start over
245 self.pos = oldpos
246 self.commentlist = oldcl
247 addrspec = self.getaddrspec()
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000248 returnlist = [(SPACE.join(self.commentlist), addrspec)]
Barry Warsaw030ddf72002-11-05 19:54:52 +0000249
250 elif self.field[self.pos] == ':':
251 # address is a group
252 returnlist = []
253
254 fieldlen = len(self.field)
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000255 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000256 while self.pos < len(self.field):
257 self.gotonext()
258 if self.pos < fieldlen and self.field[self.pos] == ';':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000259 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000260 break
261 returnlist = returnlist + self.getaddress()
262
263 elif self.field[self.pos] == '<':
264 # Address is a phrase then a route addr
265 routeaddr = self.getrouteaddr()
266
267 if self.commentlist:
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000268 returnlist = [(SPACE.join(plist) + ' (' +
269 ' '.join(self.commentlist) + ')', routeaddr)]
270 else:
271 returnlist = [(SPACE.join(plist), routeaddr)]
Barry Warsaw030ddf72002-11-05 19:54:52 +0000272
273 else:
274 if plist:
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000275 returnlist = [(SPACE.join(self.commentlist), plist[0])]
Barry Warsaw030ddf72002-11-05 19:54:52 +0000276 elif self.field[self.pos] in self.specials:
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000277 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000278
279 self.gotonext()
280 if self.pos < len(self.field) and self.field[self.pos] == ',':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000281 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000282 return returnlist
283
284 def getrouteaddr(self):
285 """Parse a route address (Return-path value).
286
287 This method just skips all the route stuff and returns the addrspec.
288 """
289 if self.field[self.pos] != '<':
290 return
291
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000292 expectroute = False
293 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000294 self.gotonext()
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000295 adlist = ''
Barry Warsaw030ddf72002-11-05 19:54:52 +0000296 while self.pos < len(self.field):
297 if expectroute:
298 self.getdomain()
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000299 expectroute = False
Barry Warsaw030ddf72002-11-05 19:54:52 +0000300 elif self.field[self.pos] == '>':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000301 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000302 break
303 elif self.field[self.pos] == '@':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000304 self.pos += 1
305 expectroute = True
Barry Warsaw030ddf72002-11-05 19:54:52 +0000306 elif self.field[self.pos] == ':':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000307 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000308 else:
309 adlist = self.getaddrspec()
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000310 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000311 break
312 self.gotonext()
313
314 return adlist
315
316 def getaddrspec(self):
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000317 """Parse an RFC 2822 addr-spec."""
Barry Warsaw030ddf72002-11-05 19:54:52 +0000318 aslist = []
319
320 self.gotonext()
321 while self.pos < len(self.field):
322 if self.field[self.pos] == '.':
323 aslist.append('.')
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000324 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000325 elif self.field[self.pos] == '"':
R. David Murray10a86762010-10-02 16:26:05 +0000326 aslist.append('"%s"' % quote(self.getquote()))
Barry Warsaw030ddf72002-11-05 19:54:52 +0000327 elif self.field[self.pos] in self.atomends:
328 break
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000329 else:
330 aslist.append(self.getatom())
Barry Warsaw030ddf72002-11-05 19:54:52 +0000331 self.gotonext()
332
333 if self.pos >= len(self.field) or self.field[self.pos] != '@':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000334 return EMPTYSTRING.join(aslist)
Barry Warsaw030ddf72002-11-05 19:54:52 +0000335
336 aslist.append('@')
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000337 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000338 self.gotonext()
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000339 return EMPTYSTRING.join(aslist) + self.getdomain()
Barry Warsaw030ddf72002-11-05 19:54:52 +0000340
341 def getdomain(self):
342 """Get the complete domain name from an address."""
343 sdlist = []
344 while self.pos < len(self.field):
345 if self.field[self.pos] in self.LWS:
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000346 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000347 elif self.field[self.pos] == '(':
348 self.commentlist.append(self.getcomment())
349 elif self.field[self.pos] == '[':
350 sdlist.append(self.getdomainliteral())
351 elif self.field[self.pos] == '.':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000352 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000353 sdlist.append('.')
354 elif self.field[self.pos] in self.atomends:
355 break
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000356 else:
357 sdlist.append(self.getatom())
358 return EMPTYSTRING.join(sdlist)
Barry Warsaw030ddf72002-11-05 19:54:52 +0000359
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000360 def getdelimited(self, beginchar, endchars, allowcomments=True):
Barry Warsaw030ddf72002-11-05 19:54:52 +0000361 """Parse a header fragment delimited by special characters.
362
363 `beginchar' is the start character for the fragment.
364 If self is not looking at an instance of `beginchar' then
365 getdelimited returns the empty string.
366
367 `endchars' is a sequence of allowable end-delimiting characters.
368 Parsing stops when one of these is encountered.
369
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000370 If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
371 within the parsed fragment.
Barry Warsaw030ddf72002-11-05 19:54:52 +0000372 """
373 if self.field[self.pos] != beginchar:
374 return ''
375
376 slist = ['']
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000377 quote = False
378 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000379 while self.pos < len(self.field):
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000380 if quote:
Barry Warsaw030ddf72002-11-05 19:54:52 +0000381 slist.append(self.field[self.pos])
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000382 quote = False
Barry Warsaw030ddf72002-11-05 19:54:52 +0000383 elif self.field[self.pos] in endchars:
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000384 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000385 break
386 elif allowcomments and self.field[self.pos] == '(':
387 slist.append(self.getcomment())
Barry Warsawdbcc8d92006-05-01 03:03:02 +0000388 continue # have already advanced pos from getcomment
Barry Warsaw030ddf72002-11-05 19:54:52 +0000389 elif self.field[self.pos] == '\\':
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000390 quote = True
Barry Warsaw030ddf72002-11-05 19:54:52 +0000391 else:
392 slist.append(self.field[self.pos])
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000393 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000394
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000395 return EMPTYSTRING.join(slist)
Barry Warsaw030ddf72002-11-05 19:54:52 +0000396
397 def getquote(self):
398 """Get a quote-delimited fragment from self's field."""
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000399 return self.getdelimited('"', '"\r', False)
Barry Warsaw030ddf72002-11-05 19:54:52 +0000400
401 def getcomment(self):
402 """Get a parenthesis-delimited fragment from self's field."""
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000403 return self.getdelimited('(', ')\r', True)
Barry Warsaw030ddf72002-11-05 19:54:52 +0000404
405 def getdomainliteral(self):
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000406 """Parse an RFC 2822 domain-literal."""
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000407 return '[%s]' % self.getdelimited('[', ']\r', False)
Barry Warsaw030ddf72002-11-05 19:54:52 +0000408
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000409 def getatom(self, atomends=None):
410 """Parse an RFC 2822 atom.
411
412 Optional atomends specifies a different set of end token delimiters
413 (the default is to use self.atomends). This is used e.g. in
414 getphraselist() since phrase endings must not include the `.' (which
415 is legal in phrases)."""
Barry Warsaw030ddf72002-11-05 19:54:52 +0000416 atomlist = ['']
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000417 if atomends is None:
418 atomends = self.atomends
Barry Warsaw030ddf72002-11-05 19:54:52 +0000419
420 while self.pos < len(self.field):
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000421 if self.field[self.pos] in atomends:
Barry Warsaw030ddf72002-11-05 19:54:52 +0000422 break
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000423 else:
424 atomlist.append(self.field[self.pos])
425 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000426
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000427 return EMPTYSTRING.join(atomlist)
Barry Warsaw030ddf72002-11-05 19:54:52 +0000428
429 def getphraselist(self):
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000430 """Parse a sequence of RFC 2822 phrases.
Barry Warsaw030ddf72002-11-05 19:54:52 +0000431
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000432 A phrase is a sequence of words, which are in turn either RFC 2822
433 atoms or quoted-strings. Phrases are canonicalized by squeezing all
434 runs of continuous whitespace into one space.
Barry Warsaw030ddf72002-11-05 19:54:52 +0000435 """
436 plist = []
437
438 while self.pos < len(self.field):
Barry Warsawcbbc3f12007-03-12 03:20:01 +0000439 if self.field[self.pos] in self.FWS:
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000440 self.pos += 1
Barry Warsaw030ddf72002-11-05 19:54:52 +0000441 elif self.field[self.pos] == '"':
442 plist.append(self.getquote())
443 elif self.field[self.pos] == '(':
444 self.commentlist.append(self.getcomment())
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000445 elif self.field[self.pos] in self.phraseends:
Barry Warsaw030ddf72002-11-05 19:54:52 +0000446 break
Barry Warsaw5c8fef92002-12-30 16:43:42 +0000447 else:
448 plist.append(self.getatom(self.phraseends))
Barry Warsaw030ddf72002-11-05 19:54:52 +0000449
450 return plist
451
452class AddressList(AddrlistClass):
Barry Warsaw1fb22bb2002-12-30 16:21:07 +0000453 """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
Barry Warsaw030ddf72002-11-05 19:54:52 +0000454 def __init__(self, field):
455 AddrlistClass.__init__(self, field)
456 if field:
457 self.addresslist = self.getaddrlist()
458 else:
459 self.addresslist = []
460
461 def __len__(self):
462 return len(self.addresslist)
463
Barry Warsaw030ddf72002-11-05 19:54:52 +0000464 def __add__(self, other):
465 # Set union
466 newaddr = AddressList(None)
467 newaddr.addresslist = self.addresslist[:]
468 for x in other.addresslist:
469 if not x in self.addresslist:
470 newaddr.addresslist.append(x)
471 return newaddr
472
473 def __iadd__(self, other):
474 # Set union, in-place
475 for x in other.addresslist:
476 if not x in self.addresslist:
477 self.addresslist.append(x)
478 return self
479
480 def __sub__(self, other):
481 # Set difference
482 newaddr = AddressList(None)
483 for x in self.addresslist:
484 if not x in other.addresslist:
485 newaddr.addresslist.append(x)
486 return newaddr
487
488 def __isub__(self, other):
489 # Set difference, in-place
490 for x in other.addresslist:
491 if x in self.addresslist:
492 self.addresslist.remove(x)
493 return self
494
495 def __getitem__(self, index):
496 # Make indexing, slices, and 'in' work
497 return self.addresslist[index]