From 1a5727fb50a5f692e4f429652cdd6d673d7db4ea Mon Sep 17 00:00:00 2001 From: cvs2svn Date: Mon, 17 Mar 2003 18:35:42 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create branch 'release22-maint'. --- Lib/email/_parseaddr.py | 481 +++++++++++++++++++++++++++++++ Lib/email/test/data/audiotest.au | Bin 0 -> 23493 bytes Lib/email/test/data/msg_34.txt | 19 ++ Lib/email/test/data/msg_35.txt | 4 + Lib/plat-darwin/IN.py | 357 +++++++++++++++++++++++ Lib/plat-darwin/regen | 3 + 6 files changed, 864 insertions(+) create mode 100644 Lib/email/_parseaddr.py create mode 100644 Lib/email/test/data/audiotest.au create mode 100644 Lib/email/test/data/msg_34.txt create mode 100644 Lib/email/test/data/msg_35.txt create mode 100644 Lib/plat-darwin/IN.py create mode 100755 Lib/plat-darwin/regen diff --git a/Lib/email/_parseaddr.py b/Lib/email/_parseaddr.py new file mode 100644 index 000000000000..2b28b6430488 --- /dev/null +++ b/Lib/email/_parseaddr.py @@ -0,0 +1,481 @@ +# Copyright (C) 2002 Python Software Foundation + +"""Email address parsing code. + +Lifted directly from rfc822.py. This should eventually be rewritten. +""" + +import time +from types import TupleType + +try: + True, False +except NameError: + True = 1 + False = 0 + +SPACE = ' ' +EMPTYSTRING = '' +COMMASPACE = ', ' + +# Parse a date field +_monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', + 'aug', 'sep', 'oct', 'nov', 'dec', + 'january', 'february', 'march', 'april', 'may', 'june', 'july', + 'august', 'september', 'october', 'november', 'december'] + +_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] + +# The timezone table does not include the military time zones defined +# in RFC822, other than Z. According to RFC1123, the description in +# RFC822 gets the signs wrong, so we can't rely on any such time +# zones. RFC1123 recommends that numeric timezone indicators be used +# instead of timezone names. + +_timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0, + 'AST': -400, 'ADT': -300, # Atlantic (used in Canada) + 'EST': -500, 'EDT': -400, # Eastern + 'CST': -600, 'CDT': -500, # Central + 'MST': -700, 'MDT': -600, # Mountain + 'PST': -800, 'PDT': -700 # Pacific + } + + +def parsedate_tz(data): + """Convert a date string to a time tuple. + + Accounts for military timezones. + """ + data = data.split() + # The FWS after the comma after the day-of-week is optional, so search and + # adjust for this. + if data[0].endswith(',') or data[0].lower() in _daynames: + # There's a dayname here. Skip it + del data[0] + else: + i = data[0].rfind(',') + if i < 0: + return None + data[0] = data[0][i+1:] + if len(data) == 3: # RFC 850 date, deprecated + stuff = data[0].split('-') + if len(stuff) == 3: + data = stuff + data[1:] + if len(data) == 4: + s = data[3] + i = s.find('+') + if i > 0: + data[3:] = [s[:i], s[i+1:]] + else: + data.append('') # Dummy tz + if len(data) < 5: + return None + data = data[:5] + [dd, mm, yy, tm, tz] = data + mm = mm.lower() + if mm not in _monthnames: + dd, mm = mm, dd.lower() + if mm not in _monthnames: + return None + mm = _monthnames.index(mm) + 1 + if mm > 12: + mm -= 12 + if dd[-1] == ',': + dd = dd[:-1] + i = yy.find(':') + if i > 0: + yy, tm = tm, yy + if yy[-1] == ',': + yy = yy[:-1] + if not yy[0].isdigit(): + yy, tz = tz, yy + if tm[-1] == ',': + tm = tm[:-1] + tm = tm.split(':') + if len(tm) == 2: + [thh, tmm] = tm + tss = '0' + elif len(tm) == 3: + [thh, tmm, tss] = tm + else: + return None + try: + yy = int(yy) + dd = int(dd) + thh = int(thh) + tmm = int(tmm) + tss = int(tss) + except ValueError: + return None + tzoffset = None + tz = tz.upper() + if _timezones.has_key(tz): + tzoffset = _timezones[tz] + else: + try: + tzoffset = int(tz) + except ValueError: + pass + # Convert a timezone offset into seconds ; -0500 -> -18000 + if tzoffset: + if tzoffset < 0: + tzsign = -1 + tzoffset = -tzoffset + else: + tzsign = 1 + tzoffset = tzsign * ( (tzoffset/100)*3600 + (tzoffset % 100)*60) + tuple = (yy, mm, dd, thh, tmm, tss, 0, 0, 0, tzoffset) + return tuple + + +def parsedate(data): + """Convert a time string to a time tuple.""" + t = parsedate_tz(data) + if isinstance(t, TupleType): + return t[:9] + else: + return t + + +def mktime_tz(data): + """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp.""" + if data[9] is None: + # No zone info, so localtime is better assumption than GMT + return time.mktime(data[:8] + (-1,)) + else: + t = time.mktime(data[:8] + (0,)) + return t - data[9] - time.timezone + + +def quote(str): + """Add quotes around a string.""" + return str.replace('\\', '\\\\').replace('"', '\\"') + + +class AddrlistClass: + """Address parser class by Ben Escoto. + + To understand what this class does, it helps to have a copy of RFC 2822 in + front of you. + + Note: this class interface is deprecated and may be removed in the future. + Use rfc822.AddressList instead. + """ + + def __init__(self, field): + """Initialize a new instance. + + `field' is an unparsed address header field, containing + one or more addresses. + """ + self.specials = '()<>@,:;.\"[]' + self.pos = 0 + self.LWS = ' \t' + self.CR = '\r\n' + self.atomends = self.specials + self.LWS + self.CR + # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it + # is obsolete syntax. RFC 2822 requires that we recognize obsolete + # syntax, so allow dots in phrases. + self.phraseends = self.atomends.replace('.', '') + self.field = field + self.commentlist = [] + + def gotonext(self): + """Parse up to the start of the next address.""" + while self.pos < len(self.field): + if self.field[self.pos] in self.LWS + '\n\r': + self.pos += 1 + elif self.field[self.pos] == '(': + self.commentlist.append(self.getcomment()) + else: + break + + def getaddrlist(self): + """Parse all addresses. + + Returns a list containing all of the addresses. + """ + result = [] + while self.pos < len(self.field): + ad = self.getaddress() + if ad: + result += ad + else: + result.append(('', '')) + return result + + def getaddress(self): + """Parse the next address.""" + self.commentlist = [] + self.gotonext() + + oldpos = self.pos + oldcl = self.commentlist + plist = self.getphraselist() + + self.gotonext() + returnlist = [] + + if self.pos >= len(self.field): + # Bad email address technically, no domain. + if plist: + returnlist = [(SPACE.join(self.commentlist), plist[0])] + + elif self.field[self.pos] in '.@': + # email address is just an addrspec + # this isn't very efficient since we start over + self.pos = oldpos + self.commentlist = oldcl + addrspec = self.getaddrspec() + returnlist = [(SPACE.join(self.commentlist), addrspec)] + + elif self.field[self.pos] == ':': + # address is a group + returnlist = [] + + fieldlen = len(self.field) + self.pos += 1 + while self.pos < len(self.field): + self.gotonext() + if self.pos < fieldlen and self.field[self.pos] == ';': + self.pos += 1 + break + returnlist = returnlist + self.getaddress() + + elif self.field[self.pos] == '<': + # Address is a phrase then a route addr + routeaddr = self.getrouteaddr() + + if self.commentlist: + returnlist = [(SPACE.join(plist) + ' (' + + ' '.join(self.commentlist) + ')', routeaddr)] + else: + returnlist = [(SPACE.join(plist), routeaddr)] + + else: + if plist: + returnlist = [(SPACE.join(self.commentlist), plist[0])] + elif self.field[self.pos] in self.specials: + self.pos += 1 + + self.gotonext() + if self.pos < len(self.field) and self.field[self.pos] == ',': + self.pos += 1 + return returnlist + + def getrouteaddr(self): + """Parse a route address (Return-path value). + + This method just skips all the route stuff and returns the addrspec. + """ + if self.field[self.pos] != '<': + return + + expectroute = False + self.pos += 1 + self.gotonext() + adlist = '' + while self.pos < len(self.field): + if expectroute: + self.getdomain() + expectroute = False + elif self.field[self.pos] == '>': + self.pos += 1 + break + elif self.field[self.pos] == '@': + self.pos += 1 + expectroute = True + elif self.field[self.pos] == ':': + self.pos += 1 + else: + adlist = self.getaddrspec() + self.pos += 1 + break + self.gotonext() + + return adlist + + def getaddrspec(self): + """Parse an RFC 2822 addr-spec.""" + aslist = [] + + self.gotonext() + while self.pos < len(self.field): + if self.field[self.pos] == '.': + aslist.append('.') + self.pos += 1 + elif self.field[self.pos] == '"': + aslist.append('"%s"' % self.getquote()) + elif self.field[self.pos] in self.atomends: + break + else: + aslist.append(self.getatom()) + self.gotonext() + + if self.pos >= len(self.field) or self.field[self.pos] != '@': + return EMPTYSTRING.join(aslist) + + aslist.append('@') + self.pos += 1 + self.gotonext() + return EMPTYSTRING.join(aslist) + self.getdomain() + + def getdomain(self): + """Get the complete domain name from an address.""" + sdlist = [] + while self.pos < len(self.field): + if self.field[self.pos] in self.LWS: + self.pos += 1 + elif self.field[self.pos] == '(': + self.commentlist.append(self.getcomment()) + elif self.field[self.pos] == '[': + sdlist.append(self.getdomainliteral()) + elif self.field[self.pos] == '.': + self.pos += 1 + sdlist.append('.') + elif self.field[self.pos] in self.atomends: + break + else: + sdlist.append(self.getatom()) + return EMPTYSTRING.join(sdlist) + + def getdelimited(self, beginchar, endchars, allowcomments=True): + """Parse a header fragment delimited by special characters. + + `beginchar' is the start character for the fragment. + If self is not looking at an instance of `beginchar' then + getdelimited returns the empty string. + + `endchars' is a sequence of allowable end-delimiting characters. + Parsing stops when one of these is encountered. + + If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed + within the parsed fragment. + """ + if self.field[self.pos] != beginchar: + return '' + + slist = [''] + quote = False + self.pos += 1 + while self.pos < len(self.field): + if quote: + slist.append(self.field[self.pos]) + quote = False + elif self.field[self.pos] in endchars: + self.pos += 1 + break + elif allowcomments and self.field[self.pos] == '(': + slist.append(self.getcomment()) + elif self.field[self.pos] == '\\': + quote = True + else: + slist.append(self.field[self.pos]) + self.pos += 1 + + return EMPTYSTRING.join(slist) + + def getquote(self): + """Get a quote-delimited fragment from self's field.""" + return self.getdelimited('"', '"\r', False) + + def getcomment(self): + """Get a parenthesis-delimited fragment from self's field.""" + return self.getdelimited('(', ')\r', True) + + def getdomainliteral(self): + """Parse an RFC 2822 domain-literal.""" + return '[%s]' % self.getdelimited('[', ']\r', False) + + def getatom(self, atomends=None): + """Parse an RFC 2822 atom. + + Optional atomends specifies a different set of end token delimiters + (the default is to use self.atomends). This is used e.g. in + getphraselist() since phrase endings must not include the `.' (which + is legal in phrases).""" + atomlist = [''] + if atomends is None: + atomends = self.atomends + + while self.pos < len(self.field): + if self.field[self.pos] in atomends: + break + else: + atomlist.append(self.field[self.pos]) + self.pos += 1 + + return EMPTYSTRING.join(atomlist) + + def getphraselist(self): + """Parse a sequence of RFC 2822 phrases. + + A phrase is a sequence of words, which are in turn either RFC 2822 + atoms or quoted-strings. Phrases are canonicalized by squeezing all + runs of continuous whitespace into one space. + """ + plist = [] + + while self.pos < len(self.field): + if self.field[self.pos] in self.LWS: + self.pos += 1 + elif self.field[self.pos] == '"': + plist.append(self.getquote()) + elif self.field[self.pos] == '(': + self.commentlist.append(self.getcomment()) + elif self.field[self.pos] in self.phraseends: + break + else: + plist.append(self.getatom(self.phraseends)) + + return plist + +class AddressList(AddrlistClass): + """An AddressList encapsulates a list of parsed RFC 2822 addresses.""" + def __init__(self, field): + AddrlistClass.__init__(self, field) + if field: + self.addresslist = self.getaddrlist() + else: + self.addresslist = [] + + def __len__(self): + return len(self.addresslist) + + def __str__(self): + return COMMASPACE.join(map(dump_address_pair, self.addresslist)) + + def __add__(self, other): + # Set union + newaddr = AddressList(None) + newaddr.addresslist = self.addresslist[:] + for x in other.addresslist: + if not x in self.addresslist: + newaddr.addresslist.append(x) + return newaddr + + def __iadd__(self, other): + # Set union, in-place + for x in other.addresslist: + if not x in self.addresslist: + self.addresslist.append(x) + return self + + def __sub__(self, other): + # Set difference + newaddr = AddressList(None) + for x in self.addresslist: + if not x in other.addresslist: + newaddr.addresslist.append(x) + return newaddr + + def __isub__(self, other): + # Set difference, in-place + for x in other.addresslist: + if x in self.addresslist: + self.addresslist.remove(x) + return self + + def __getitem__(self, index): + # Make indexing, slices, and 'in' work + return self.addresslist[index] diff --git a/Lib/email/test/data/audiotest.au b/Lib/email/test/data/audiotest.au new file mode 100644 index 0000000000000000000000000000000000000000..1ad28ce1fdd781e4370fdf68577e8a35b4ed8a8e GIT binary patch literal 23493 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 Y?X@jQbyxQOsoJ*4ld7_7%@$zt4