# Copyright (C) 2002-2004 Python Software Foundation
# Contact: email-sig@python.org
"""Email address parsing code.
Lifted directly from rfc822.py. This should eventually be rewritten.
_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
"""Convert a date string to a time tuple.
Accounts for military timezones.
# The FWS after the comma after the day-of-week is optional, so search and
if data
[0].endswith(',') or data
[0].lower() in _daynames
:
# There's a dayname here. Skip it
if len(data
) == 3: # RFC 850 date, deprecated
stuff
= data
[0].split('-')
data
[3:] = [s
[:i
], s
[i
+1:]]
data
.append('') # Dummy tz
[dd
, mm
, yy
, tm
, tz
] = data
if mm
not in _monthnames
:
if mm
not in _monthnames
:
mm
= _monthnames
.index(mm
) + 1
if _timezones
.has_key(tz
):
tzoffset
= _timezones
[tz
]
# Convert a timezone offset into seconds ; -0500 -> -18000
tzoffset
= tzsign
* ( (tzoffset
//100)*3600 + (tzoffset
% 100)*60)
tuple = (yy
, mm
, dd
, thh
, tmm
, tss
, 0, 1, 0, tzoffset
)
"""Convert a time string to a time tuple."""
"""Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp."""
# No zone info, so localtime is better assumption than GMT
return time
.mktime(data
[:8] + (-1,))
t
= time
.mktime(data
[:8] + (0,))
return t
- data
[9] - time
.timezone
"""Add quotes around a string."""
return str.replace('\\', '\\\\').replace('"', '\\"')
"""Address parser class by Ben Escoto.
To understand what this class does, it helps to have a copy of RFC 2822 in
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
self
.specials
= '()<>@,:;.\"[]'
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('.', '')
"""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':
elif self
.field
[self
.pos
] == '(':
self
.commentlist
.append(self
.getcomment())
Returns a list containing all of the addresses.
while self
.pos
< len(self
.field
):
"""Parse the next address."""
plist
= self
.getphraselist()
if self
.pos
>= len(self
.field
):
# Bad email address technically, no domain.
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
addrspec
= self
.getaddrspec()
returnlist
= [(SPACE
.join(self
.commentlist
), addrspec
)]
elif self
.field
[self
.pos
] == ':':
fieldlen
= len(self
.field
)
while self
.pos
< len(self
.field
):
if self
.pos
< fieldlen
and self
.field
[self
.pos
] == ';':
returnlist
= returnlist
+ self
.getaddress()
elif self
.field
[self
.pos
] == '<':
# Address is a phrase then a route addr
routeaddr
= self
.getrouteaddr()
returnlist
= [(SPACE
.join(plist
) + ' (' +
' '.join(self
.commentlist
) + ')', routeaddr
)]
returnlist
= [(SPACE
.join(plist
), routeaddr
)]
returnlist
= [(SPACE
.join(self
.commentlist
), plist
[0])]
elif self
.field
[self
.pos
] in self
.specials
:
if self
.pos
< len(self
.field
) and self
.field
[self
.pos
] == ',':
"""Parse a route address (Return-path value).
This method just skips all the route stuff and returns the addrspec.
if self
.field
[self
.pos
] != '<':
while self
.pos
< len(self
.field
):
elif self
.field
[self
.pos
] == '>':
elif self
.field
[self
.pos
] == '@':
elif self
.field
[self
.pos
] == ':':
adlist
= self
.getaddrspec()
"""Parse an RFC 2822 addr-spec."""
while self
.pos
< len(self
.field
):
if self
.field
[self
.pos
] == '.':
elif self
.field
[self
.pos
] == '"':
aslist
.append('"%s"' % self
.getquote())
elif self
.field
[self
.pos
] in self
.atomends
:
aslist
.append(self
.getatom())
if self
.pos
>= len(self
.field
) or self
.field
[self
.pos
] != '@':
return EMPTYSTRING
.join(aslist
)
return EMPTYSTRING
.join(aslist
) + self
.getdomain()
"""Get the complete domain name from an address."""
while self
.pos
< len(self
.field
):
if self
.field
[self
.pos
] in self
.LWS
:
elif self
.field
[self
.pos
] == '(':
self
.commentlist
.append(self
.getcomment())
elif self
.field
[self
.pos
] == '[':
sdlist
.append(self
.getdomainliteral())
elif self
.field
[self
.pos
] == '.':
elif self
.field
[self
.pos
] in self
.atomends
:
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
:
while self
.pos
< len(self
.field
):
slist
.append(self
.field
[self
.pos
])
elif self
.field
[self
.pos
] in endchars
:
elif allowcomments
and self
.field
[self
.pos
] == '(':
slist
.append(self
.getcomment())
elif self
.field
[self
.pos
] == '\\':
slist
.append(self
.field
[self
.pos
])
return EMPTYSTRING
.join(slist
)
"""Get a quote-delimited fragment from self's field."""
return self
.getdelimited('"', '"\r', False)
"""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
while self
.pos
< len(self
.field
):
if self
.field
[self
.pos
] in atomends
:
atomlist
.append(self
.field
[self
.pos
])
return EMPTYSTRING
.join(atomlist
)
"""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.
while self
.pos
< len(self
.field
):
if self
.field
[self
.pos
] in self
.LWS
:
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
:
plist
.append(self
.getatom(self
.phraseends
))
class AddressList(AddrlistClass
):
"""An AddressList encapsulates a list of parsed RFC 2822 addresses."""
def __init__(self
, field
):
AddrlistClass
.__init
__(self
, field
)
self
.addresslist
= self
.getaddrlist()
return len(self
.addresslist
)
def __add__(self
, other
):
newaddr
= AddressList(None)
newaddr
.addresslist
= self
.addresslist
[:]
for x
in other
.addresslist
:
if not x
in self
.addresslist
:
newaddr
.addresslist
.append(x
)
def __iadd__(self
, other
):
for x
in other
.addresslist
:
if not x
in self
.addresslist
:
self
.addresslist
.append(x
)
def __sub__(self
, other
):
newaddr
= AddressList(None)
for x
in self
.addresslist
:
if not x
in other
.addresslist
:
newaddr
.addresslist
.append(x
)
def __isub__(self
, other
):
# Set difference, in-place
for x
in other
.addresslist
:
if x
in self
.addresslist
:
self
.addresslist
.remove(x
)
def __getitem__(self
, index
):
# Make indexing, slices, and 'in' work
return self
.addresslist
[index
]