1edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep"""RFC 2822 message manipulation.
2edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
3edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepNote: This is only a very rough sketch of a full RFC-822 parser; in particular
4edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepthe tokenizing of addresses does not adhere to all the quoting rules.
5edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
6edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepNote: RFC 2822 is a long awaited update to RFC 822.  This module should
7edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepconform to RFC 2822, and is thus mis-named (it's not worth renaming it).  Some
8edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepeffort at RFC 2822 updates have been made, but a thorough audit has not been
9edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepperformed.  Consider any RFC 2822 non-conformance to be a bug.
10edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
11edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    RFC 2822: http://www.faqs.org/rfcs/rfc2822.html
12edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    RFC 822 : http://www.faqs.org/rfcs/rfc822.html (obsolete)
13edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
14edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepDirections for use:
15edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
16edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepTo create a Message object: first open a file, e.g.:
17edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
18edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep  fp = open(file, 'r')
19edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
20edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepYou can use any other legal way of getting an open file object, e.g. use
21edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepsys.stdin or call os.popen().  Then pass the open file object to the Message()
22edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepconstructor:
23edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
24edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep  m = Message(fp)
25edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
26edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepThis class can work with any input object that supports a readline method.  If
27edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepthe input object has seek and tell capability, the rewindbody method will
28edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepwork; also illegal lines will be pushed back onto the input stream.  If the
29edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepinput object lacks seek but has an `unread' method that can push back a line
30edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepof input, Message will use that to push back illegal lines.  Thus this class
31edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepcan be used to parse messages coming from a buffered stream.
32edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
33edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepThe optional `seekable' argument is provided as a workaround for certain stdio
34edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoeplibraries in which tell() discards buffered data before discovering that the
35edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoeplseek() system call doesn't work.  For maximum portability, you should set the
36edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepseekable argument to zero to prevent that initial \code{tell} when passing in
37edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepan unseekable object such as a file object created from a socket object.  If
38edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepit is 1 on entry -- which it is by default -- the tell() method of the open
39edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepfile object is called once; if this raises an exception, seekable is reset to
40edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep0.  For other nonzero values of seekable, this test is not made.
41edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
42edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepTo get the text of a particular header there are several methods:
43edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
44edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep  str = m.getheader(name)
45edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep  str = m.getrawheader(name)
46edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
47edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepwhere name is the name of the header, e.g. 'Subject'.  The difference is that
48edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepgetheader() strips the leading and trailing whitespace, while getrawheader()
49edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdoesn't.  Both functions retain embedded whitespace (including newlines)
50edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepexactly as they are specified in the header, and leave the case of the text
51edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepunchanged.
52edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
53edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepFor addresses and address lists there are functions
54edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
55edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep  realname, mailaddress = m.getaddr(name)
56edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep  list = m.getaddrlist(name)
57edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
58edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepwhere the latter returns a list of (realname, mailaddr) tuples.
59edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
60edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepThere is also a method
61edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
62edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep  time = m.getdate(name)
63edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
64edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepwhich parses a Date-like field and returns a time-compatible tuple,
65edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepi.e. a tuple such as returned by time.localtime() or accepted by
66edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoeptime.mktime().
67edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
68edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepSee the class definition for lower level access methods.
69edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
70edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander StoepThere are also some utility functions here.
71edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep"""
72edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# Cleanup and extensions by Eric S. Raymond <esr@thyrsus.com>
73edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
74edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepimport time
75edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
76edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepfrom warnings import warnpy3k
77edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepwarnpy3k("in 3.x, rfc822 has been removed in favor of the email package",
78edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep         stacklevel=2)
79edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
80edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep__all__ = ["Message","AddressList","parsedate","parsedate_tz","mktime_tz"]
81edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
82edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep_blanklines = ('\r\n', '\n')            # Optimization for islast()
83edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
84edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
85edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepclass Message:
86edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Represents a single RFC 2822-compliant message."""
87edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
88edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __init__(self, fp, seekable = 1):
89edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Initialize the class instance and read the headers."""
90edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if seekable == 1:
91edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # Exercise tell() to make sure it works
92edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # (and then assume seek() works, too)
93edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            try:
94edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                fp.tell()
95edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            except (AttributeError, IOError):
96edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                seekable = 0
97edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.fp = fp
98edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.seekable = seekable
99edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.startofheaders = None
100edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.startofbody = None
101edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        #
102edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if self.seekable:
103edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            try:
104edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.startofheaders = self.fp.tell()
105edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            except IOError:
106edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.seekable = 0
107edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        #
108edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.readheaders()
109edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        #
110edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if self.seekable:
111edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            try:
112edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.startofbody = self.fp.tell()
113edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            except IOError:
114edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.seekable = 0
115edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
116edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def rewindbody(self):
117edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Rewind the file to the start of the body (if seekable)."""
118edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if not self.seekable:
119edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            raise IOError, "unseekable file"
120edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.fp.seek(self.startofbody)
121edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
122edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def readheaders(self):
123edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Read header lines.
124edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
125edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Read header lines up to the entirely blank line that terminates them.
126edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        The (normally blank) line that ends the headers is skipped, but not
127edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        included in the returned list.  If a non-header line ends the headers,
128edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        (which is an error), an attempt is made to backspace over it; it is
129edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        never included in the returned list.
130edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
131edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        The variable self.status is set to the empty string if all went well,
132edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        otherwise it is an error message.  The variable self.headers is a
133edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        completely uninterpreted list of lines contained in the header (so
134edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        printing them will reproduce the header exactly as it appears in the
135edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        file).
136edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
137edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.dict = {}
138edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.unixfrom = ''
139edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.headers = lst = []
140edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.status = ''
141edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        headerseen = ""
142edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        firstline = 1
143edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        startofline = unread = tell = None
144edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if hasattr(self.fp, 'unread'):
145edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            unread = self.fp.unread
146edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        elif self.seekable:
147edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            tell = self.fp.tell
148edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while 1:
149edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if tell:
150edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                try:
151edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    startofline = tell()
152edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                except IOError:
153edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    startofline = tell = None
154edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    self.seekable = 0
155edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            line = self.fp.readline()
156edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if not line:
157edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.status = 'EOF in headers'
158edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
159edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # Skip unix From name time lines
160edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if firstline and line.startswith('From '):
161edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.unixfrom = self.unixfrom + line
162edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                continue
163edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            firstline = 0
164edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if headerseen and line[0] in ' \t':
165edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                # It's a continuation line.
166edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                lst.append(line)
167edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                x = (self.dict[headerseen] + "\n " + line.strip())
168edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.dict[headerseen] = x.strip()
169edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                continue
170edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.iscomment(line):
171edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                # It's a comment.  Ignore it.
172edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                continue
173edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.islast(line):
174edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                # Note! No pushback here!  The delimiter line gets eaten.
175edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
176edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            headerseen = self.isheader(line)
177edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if headerseen:
178edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                # It's a legal header line, save it.
179edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                lst.append(line)
180edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.dict[headerseen] = line[len(headerseen)+1:].strip()
181edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                continue
182edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else:
183edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                # It's not a header line; throw it back and stop here.
184edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if not self.dict:
185edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    self.status = 'No headers'
186edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                else:
187edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    self.status = 'Non-header line where header expected'
188edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                # Try to undo the read.
189edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if unread:
190edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    unread(line)
191edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                elif tell:
192edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    self.fp.seek(startofline)
193edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                else:
194edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    self.status = self.status + '; bad seek'
195edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
196edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
197edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def isheader(self, line):
198edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Determine whether a given line is a legal header.
199edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
200edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        This method should return the header name, suitably canonicalized.
201edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        You may override this method in order to use Message parsing on tagged
202edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        data in RFC 2822-like formats with special header formats.
203edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
204edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        i = line.find(':')
205edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if i > 0:
206edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return line[:i].lower()
207edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return None
208edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
209edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def islast(self, line):
210edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Determine whether a line is a legal end of RFC 2822 headers.
211edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
212edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        You may override this method if your application wants to bend the
213edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        rules, e.g. to strip trailing whitespace, or to recognize MH template
214edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        separators ('--------').  For convenience (e.g. for code reading from
215edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        sockets) a line consisting of \\r\\n also matches.
216edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
217edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return line in _blanklines
218edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
219edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def iscomment(self, line):
220edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Determine whether a line should be skipped entirely.
221edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
222edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        You may override this method in order to use Message parsing on tagged
223edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        data in RFC 2822-like formats that support embedded comments or
224edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        free-text data.
225edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
226edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return False
227edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
228edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getallmatchingheaders(self, name):
229edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Find all header lines matching a given header name.
230edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
231edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Look through the list of headers and find all lines matching a given
232edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        header name (and their continuation lines).  A list of the lines is
233edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        returned, without interpretation.  If the header does not occur, an
234edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        empty list is returned.  If the header occurs multiple times, all
235edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        occurrences are returned.  Case is not important in the header name.
236edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
237edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        name = name.lower() + ':'
238edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        n = len(name)
239edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        lst = []
240edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        hit = 0
241edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for line in self.headers:
242edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if line[:n].lower() == name:
243edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                hit = 1
244edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif not line[:1].isspace():
245edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                hit = 0
246edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if hit:
247edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                lst.append(line)
248edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return lst
249edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
250edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getfirstmatchingheader(self, name):
251edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get the first header line matching name.
252edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
253edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        This is similar to getallmatchingheaders, but it returns only the
254edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        first matching header (and its continuation lines).
255edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
256edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        name = name.lower() + ':'
257edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        n = len(name)
258edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        lst = []
259edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        hit = 0
260edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for line in self.headers:
261edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if hit:
262edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if not line[:1].isspace():
263edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    break
264edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif line[:n].lower() == name:
265edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                hit = 1
266edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if hit:
267edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                lst.append(line)
268edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return lst
269edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
270edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getrawheader(self, name):
271edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """A higher-level interface to getfirstmatchingheader().
272edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
273edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Return a string containing the literal text of the header but with the
274edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        keyword stripped.  All leading, trailing and embedded whitespace is
275edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        kept in the string, however.  Return None if the header does not
276edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        occur.
277edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
278edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
279edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        lst = self.getfirstmatchingheader(name)
280edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if not lst:
281edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return None
282edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        lst[0] = lst[0][len(name) + 1:]
283edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return ''.join(lst)
284edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
285edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getheader(self, name, default=None):
286edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get the header value for a name.
287edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
288edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        This is the normal interface: it returns a stripped version of the
289edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        header value for a given header name, or None if it doesn't exist.
290edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        This uses the dictionary version which finds the *last* such header.
291edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
292edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.dict.get(name.lower(), default)
293edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    get = getheader
294edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
295edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getheaders(self, name):
296edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get all values for a header.
297edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
298edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        This returns a list of values for headers given more than once; each
299edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        value in the result list is stripped in the same way as the result of
300edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        getheader().  If the header is not given, return an empty list.
301edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
302edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        result = []
303edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        current = ''
304edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        have_header = 0
305edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for s in self.getallmatchingheaders(name):
306edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if s[0].isspace():
307edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if current:
308edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    current = "%s\n %s" % (current, s.strip())
309edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                else:
310edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    current = s.strip()
311edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else:
312edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if have_header:
313edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    result.append(current)
314edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                current = s[s.find(":") + 1:].strip()
315edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                have_header = 1
316edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if have_header:
317edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            result.append(current)
318edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return result
319edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
320edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getaddr(self, name):
321edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get a single address from a header, as a tuple.
322edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
323edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        An example return value:
324edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        ('Guido van Rossum', 'guido@cwi.nl')
325edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
326edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # New, by Ben Escoto
327edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        alist = self.getaddrlist(name)
328edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if alist:
329edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return alist[0]
330edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        else:
331edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return (None, None)
332edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
333edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getaddrlist(self, name):
334edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get a list of addresses from a header.
335edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
336edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Retrieves a list of addresses from a header, where each address is a
337edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        tuple as returned by getaddr().  Scans all named headers, so it works
338edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        properly with multiple To: or Cc: headers for example.
339edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
340edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        raw = []
341edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for h in self.getallmatchingheaders(name):
342edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if h[0] in ' \t':
343edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                raw.append(h)
344edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else:
345edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if raw:
346edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    raw.append(', ')
347edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                i = h.find(':')
348edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if i > 0:
349edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    addr = h[i+1:]
350edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                raw.append(addr)
351edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        alladdrs = ''.join(raw)
352edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        a = AddressList(alladdrs)
353edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return a.addresslist
354edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
355edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getdate(self, name):
356edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Retrieve a date field from a header.
357edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
358edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Retrieves a date field from the named header, returning a tuple
359edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        compatible with time.mktime().
360edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
361edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        try:
362edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            data = self[name]
363edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        except KeyError:
364edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return None
365edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return parsedate(data)
366edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
367edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getdate_tz(self, name):
368edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Retrieve a date field from a header as a 10-tuple.
369edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
370edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        The first 9 elements make up a tuple compatible with time.mktime(),
371edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        and the 10th is the offset of the poster's time zone from GMT/UTC.
372edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
373edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        try:
374edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            data = self[name]
375edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        except KeyError:
376edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return None
377edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return parsedate_tz(data)
378edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
379edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
380edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    # Access as a dictionary (only finds *last* header of each type):
381edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
382edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __len__(self):
383edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get the number of headers in a message."""
384edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return len(self.dict)
385edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
386edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __getitem__(self, name):
387edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get a specific header, as from a dictionary."""
388edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.dict[name.lower()]
389edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
390edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __setitem__(self, name, value):
391edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Set the value of a header.
392edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
393edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Note: This is not a perfect inversion of __getitem__, because any
394edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        changed headers get stuck at the end of the raw-headers list rather
395edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        than where the altered header was.
396edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
397edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        del self[name] # Won't fail if it doesn't exist
398edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.dict[name.lower()] = value
399edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        text = name + ": " + value
400edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for line in text.split("\n"):
401edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.headers.append(line + "\n")
402edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
403edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __delitem__(self, name):
404edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Delete all occurrences of a specific header, if it is present."""
405edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        name = name.lower()
406edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if not name in self.dict:
407edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return
408edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        del self.dict[name]
409edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        name = name + ':'
410edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        n = len(name)
411edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        lst = []
412edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        hit = 0
413edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for i in range(len(self.headers)):
414edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            line = self.headers[i]
415edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if line[:n].lower() == name:
416edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                hit = 1
417edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif not line[:1].isspace():
418edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                hit = 0
419edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if hit:
420edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                lst.append(i)
421edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for i in reversed(lst):
422edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            del self.headers[i]
423edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
424edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def setdefault(self, name, default=""):
425edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        lowername = name.lower()
426edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if lowername in self.dict:
427edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return self.dict[lowername]
428edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        else:
429edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            text = name + ": " + default
430edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            for line in text.split("\n"):
431edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.headers.append(line + "\n")
432edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.dict[lowername] = default
433edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return default
434edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
435edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def has_key(self, name):
436edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Determine whether a message contains the named header."""
437edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return name.lower() in self.dict
438edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
439edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __contains__(self, name):
440edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Determine whether a message contains the named header."""
441edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return name.lower() in self.dict
442edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
443edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __iter__(self):
444edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return iter(self.dict)
445edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
446edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def keys(self):
447edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get all of a message's header field names."""
448edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.dict.keys()
449edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
450edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def values(self):
451edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get all of a message's header field values."""
452edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.dict.values()
453edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
454edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def items(self):
455edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get all of a message's headers.
456edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
457edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Returns a list of name, value tuples.
458edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
459edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.dict.items()
460edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
461edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __str__(self):
462edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return ''.join(self.headers)
463edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
464edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
465edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# Utility functions
466edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# -----------------
467edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
468edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# XXX Should fix unquote() and quote() to be really conformant.
469edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# XXX The inverses of the parse functions may also be useful.
470edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
471edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
472edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef unquote(s):
473edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Remove quotes from a string."""
474edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if len(s) > 1:
475edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if s.startswith('"') and s.endswith('"'):
476edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return s[1:-1].replace('\\\\', '\\').replace('\\"', '"')
477edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if s.startswith('<') and s.endswith('>'):
478edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return s[1:-1]
479edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    return s
480edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
481edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
482edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef quote(s):
483edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Add quotes around a string."""
484edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    return s.replace('\\', '\\\\').replace('"', '\\"')
485edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
486edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
487edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef parseaddr(address):
488edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Parse an address into a (realname, mailaddr) tuple."""
489edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    a = AddressList(address)
490edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    lst = a.addresslist
491edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if not lst:
492edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return (None, None)
493edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    return lst[0]
494edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
495edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
496edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepclass AddrlistClass:
497edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Address parser class by Ben Escoto.
498edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
499edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    To understand what this class does, it helps to have a copy of
500edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    RFC 2822 in front of you.
501edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
502edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    http://www.faqs.org/rfcs/rfc2822.html
503edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
504edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    Note: this class interface is deprecated and may be removed in the future.
505edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    Use rfc822.AddressList instead.
506edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """
507edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
508edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __init__(self, field):
509edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Initialize a new instance.
510edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
511edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        `field' is an unparsed address header field, containing one or more
512edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        addresses.
513edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
514edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.specials = '()<>@,:;.\"[]'
515edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.pos = 0
516edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.LWS = ' \t'
517edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.CR = '\r\n'
518edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.atomends = self.specials + self.LWS + self.CR
519edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
520edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # is obsolete syntax.  RFC 2822 requires that we recognize obsolete
521edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # syntax, so allow dots in phrases.
522edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.phraseends = self.atomends.replace('.', '')
523edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.field = field
524edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.commentlist = []
525edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
526edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def gotonext(self):
527edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse up to the start of the next address."""
528edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while self.pos < len(self.field):
529edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if self.field[self.pos] in self.LWS + '\n\r':
530edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos = self.pos + 1
531edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '(':
532edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.commentlist.append(self.getcomment())
533edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else: break
534edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
535edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getaddrlist(self):
536edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse all addresses.
537edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
538edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Returns a list containing all of the addresses.
539edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
540edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        result = []
541edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        ad = self.getaddress()
542edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while ad:
543edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            result += ad
544edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            ad = self.getaddress()
545edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return result
546edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
547edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getaddress(self):
548edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse the next address."""
549edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.commentlist = []
550edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.gotonext()
551edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
552edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        oldpos = self.pos
553edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        oldcl = self.commentlist
554edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        plist = self.getphraselist()
555edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
556edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.gotonext()
557edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        returnlist = []
558edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
559edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if self.pos >= len(self.field):
560edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # Bad email address technically, no domain.
561edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if plist:
562edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                returnlist = [(' '.join(self.commentlist), plist[0])]
563edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
564edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        elif self.field[self.pos] in '.@':
565edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # email address is just an addrspec
566edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # this isn't very efficient since we start over
567edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.pos = oldpos
568edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.commentlist = oldcl
569edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            addrspec = self.getaddrspec()
570edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            returnlist = [(' '.join(self.commentlist), addrspec)]
571edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
572edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        elif self.field[self.pos] == ':':
573edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # address is a group
574edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            returnlist = []
575edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
576edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            fieldlen = len(self.field)
577edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.pos += 1
578edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            while self.pos < len(self.field):
579edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.gotonext()
580edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                if self.pos < fieldlen and self.field[self.pos] == ';':
581edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    self.pos += 1
582edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                    break
583edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                returnlist = returnlist + self.getaddress()
584edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
585edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        elif self.field[self.pos] == '<':
586edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            # Address is a phrase then a route addr
587edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            routeaddr = self.getrouteaddr()
588edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
589edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if self.commentlist:
590edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                returnlist = [(' '.join(plist) + ' (' + \
591edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                         ' '.join(self.commentlist) + ')', routeaddr)]
592edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else: returnlist = [(' '.join(plist), routeaddr)]
593edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
594edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        else:
595edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if plist:
596edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                returnlist = [(' '.join(self.commentlist), plist[0])]
597edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] in self.specials:
598edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
599edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
600edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.gotonext()
601edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if self.pos < len(self.field) and self.field[self.pos] == ',':
602edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.pos += 1
603edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return returnlist
604edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
605edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getrouteaddr(self):
606edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse a route address (Return-path value).
607edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
608edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        This method just skips all the route stuff and returns the addrspec.
609edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
610edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if self.field[self.pos] != '<':
611edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return
612edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
613edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        expectroute = 0
614edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.pos += 1
615edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.gotonext()
616edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        adlist = ""
617edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while self.pos < len(self.field):
618edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if expectroute:
619edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.getdomain()
620edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                expectroute = 0
621edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '>':
622edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
623edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
624edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '@':
625edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
626edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                expectroute = 1
627edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == ':':
628edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
629edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else:
630edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                adlist = self.getaddrspec()
631edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
632edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
633edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.gotonext()
634edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
635edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return adlist
636edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
637edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getaddrspec(self):
638edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse an RFC 2822 addr-spec."""
639edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        aslist = []
640edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
641edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.gotonext()
642edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while self.pos < len(self.field):
643edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if self.field[self.pos] == '.':
644edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                aslist.append('.')
645edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
646edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '"':
647edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                aslist.append('"%s"' % self.getquote())
648edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] in self.atomends:
649edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
650edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else: aslist.append(self.getatom())
651edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.gotonext()
652edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
653edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if self.pos >= len(self.field) or self.field[self.pos] != '@':
654edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return ''.join(aslist)
655edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
656edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        aslist.append('@')
657edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.pos += 1
658edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.gotonext()
659edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return ''.join(aslist) + self.getdomain()
660edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
661edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getdomain(self):
662edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get the complete domain name from an address."""
663edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        sdlist = []
664edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while self.pos < len(self.field):
665edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if self.field[self.pos] in self.LWS:
666edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
667edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '(':
668edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.commentlist.append(self.getcomment())
669edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '[':
670edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                sdlist.append(self.getdomainliteral())
671edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '.':
672edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
673edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                sdlist.append('.')
674edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] in self.atomends:
675edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
676edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else: sdlist.append(self.getatom())
677edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return ''.join(sdlist)
678edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
679edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getdelimited(self, beginchar, endchars, allowcomments = 1):
680edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse a header fragment delimited by special characters.
681edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
682edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        `beginchar' is the start character for the fragment.  If self is not
683edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        looking at an instance of `beginchar' then getdelimited returns the
684edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        empty string.
685edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
686edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        `endchars' is a sequence of allowable end-delimiting characters.
687edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Parsing stops when one of these is encountered.
688edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
689edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
690edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        within the parsed fragment.
691edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
692edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if self.field[self.pos] != beginchar:
693edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return ''
694edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
695edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        slist = ['']
696edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        quote = 0
697edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        self.pos += 1
698edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while self.pos < len(self.field):
699edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if quote == 1:
700edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                slist.append(self.field[self.pos])
701edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                quote = 0
702edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] in endchars:
703edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
704edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
705edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif allowcomments and self.field[self.pos] == '(':
706edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                slist.append(self.getcomment())
707edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                continue        # have already advanced pos from getcomment
708edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '\\':
709edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                quote = 1
710edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else:
711edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                slist.append(self.field[self.pos])
712edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.pos += 1
713edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
714edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return ''.join(slist)
715edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
716edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getquote(self):
717edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get a quote-delimited fragment from self's field."""
718edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.getdelimited('"', '"\r', 0)
719edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
720edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getcomment(self):
721edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Get a parenthesis-delimited fragment from self's field."""
722edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.getdelimited('(', ')\r', 1)
723edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
724edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getdomainliteral(self):
725edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse an RFC 2822 domain-literal."""
726edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return '[%s]' % self.getdelimited('[', ']\r', 0)
727edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
728edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getatom(self, atomends=None):
729edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse an RFC 2822 atom.
730edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
731edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        Optional atomends specifies a different set of end token delimiters
732edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        (the default is to use self.atomends).  This is used e.g. in
733edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        getphraselist() since phrase endings must not include the `.' (which
734edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        is legal in phrases)."""
735edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        atomlist = ['']
736edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if atomends is None:
737edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            atomends = self.atomends
738edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
739edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while self.pos < len(self.field):
740edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if self.field[self.pos] in atomends:
741edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
742edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else: atomlist.append(self.field[self.pos])
743edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.pos += 1
744edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
745edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return ''.join(atomlist)
746edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
747edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def getphraselist(self):
748edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """Parse a sequence of RFC 2822 phrases.
749edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
750edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        A phrase is a sequence of words, which are in turn either RFC 2822
751edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        atoms or quoted-strings.  Phrases are canonicalized by squeezing all
752edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        runs of continuous whitespace into one space.
753edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        """
754edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        plist = []
755edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
756edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        while self.pos < len(self.field):
757edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if self.field[self.pos] in self.LWS:
758edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.pos += 1
759edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '"':
760edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                plist.append(self.getquote())
761edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] == '(':
762edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.commentlist.append(self.getcomment())
763edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            elif self.field[self.pos] in self.phraseends:
764edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                break
765edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            else:
766edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                plist.append(self.getatom(self.phraseends))
767edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
768edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return plist
769edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
770edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepclass AddressList(AddrlistClass):
771edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
772edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __init__(self, field):
773edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        AddrlistClass.__init__(self, field)
774edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if field:
775edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.addresslist = self.getaddrlist()
776edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        else:
777edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            self.addresslist = []
778edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
779edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __len__(self):
780edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return len(self.addresslist)
781edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
782edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __str__(self):
783edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return ", ".join(map(dump_address_pair, self.addresslist))
784edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
785edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __add__(self, other):
786edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # Set union
787edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        newaddr = AddressList(None)
788edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        newaddr.addresslist = self.addresslist[:]
789edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for x in other.addresslist:
790edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if not x in self.addresslist:
791edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                newaddr.addresslist.append(x)
792edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return newaddr
793edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
794edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __iadd__(self, other):
795edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # Set union, in-place
796edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for x in other.addresslist:
797edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if not x in self.addresslist:
798edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.addresslist.append(x)
799edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self
800edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
801edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __sub__(self, other):
802edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # Set difference
803edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        newaddr = AddressList(None)
804edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for x in self.addresslist:
805edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if not x in other.addresslist:
806edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                newaddr.addresslist.append(x)
807edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return newaddr
808edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
809edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __isub__(self, other):
810edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # Set difference, in-place
811edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        for x in other.addresslist:
812edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            if x in self.addresslist:
813edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                self.addresslist.remove(x)
814edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self
815edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
816edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    def __getitem__(self, index):
817edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # Make indexing, slices, and 'in' work
818edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return self.addresslist[index]
819edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
820edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef dump_address_pair(pair):
821edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Dump a (name, address) pair in a canonicalized form."""
822edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if pair[0]:
823edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return '"' + pair[0] + '" <' + pair[1] + '>'
824edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    else:
825edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return pair[1]
826edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
827edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# Parse a date field
828edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
829edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep_monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
830edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep               'aug', 'sep', 'oct', 'nov', 'dec',
831edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep               'january', 'february', 'march', 'april', 'may', 'june', 'july',
832edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep               'august', 'september', 'october', 'november', 'december']
833edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
834edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
835edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# The timezone table does not include the military time zones defined
836edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# in RFC822, other than Z.  According to RFC1123, the description in
837edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# RFC822 gets the signs wrong, so we can't rely on any such time
838edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# zones.  RFC1123 recommends that numeric timezone indicators be used
839edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# instead of timezone names.
840edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
841edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep_timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
842edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep              'AST': -400, 'ADT': -300,  # Atlantic (used in Canada)
843edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep              'EST': -500, 'EDT': -400,  # Eastern
844edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep              'CST': -600, 'CDT': -500,  # Central
845edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep              'MST': -700, 'MDT': -600,  # Mountain
846edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep              'PST': -800, 'PDT': -700   # Pacific
847edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep              }
848edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
849edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
850edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef parsedate_tz(data):
851edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Convert a date string to a time tuple.
852edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
853edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    Accounts for military timezones.
854edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """
855edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if not data:
856edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return None
857edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    data = data.split()
858edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if data[0][-1] in (',', '.') or data[0].lower() in _daynames:
859edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # There's a dayname here. Skip it
860edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        del data[0]
861edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    else:
862edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # no space after the "weekday,"?
863edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        i = data[0].rfind(',')
864edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if i >= 0:
865edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            data[0] = data[0][i+1:]
866edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if len(data) == 3: # RFC 850 date, deprecated
867edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        stuff = data[0].split('-')
868edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if len(stuff) == 3:
869edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            data = stuff + data[1:]
870edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if len(data) == 4:
871edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        s = data[3]
872edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        i = s.find('+')
873edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if i > 0:
874edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            data[3:] = [s[:i], s[i+1:]]
875edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        else:
876edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            data.append('') # Dummy tz
877edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if len(data) < 5:
878edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return None
879edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    data = data[:5]
880edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    [dd, mm, yy, tm, tz] = data
881edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    mm = mm.lower()
882edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if not mm in _monthnames:
883edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        dd, mm = mm, dd.lower()
884edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if not mm in _monthnames:
885edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            return None
886edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    mm = _monthnames.index(mm)+1
887edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if mm > 12: mm = mm - 12
888edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if dd[-1] == ',':
889edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        dd = dd[:-1]
890edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    i = yy.find(':')
891edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if i > 0:
892edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        yy, tm = tm, yy
893edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if yy[-1] == ',':
894edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        yy = yy[:-1]
895edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if not yy[0].isdigit():
896edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        yy, tz = tz, yy
897edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if tm[-1] == ',':
898edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        tm = tm[:-1]
899edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    tm = tm.split(':')
900edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if len(tm) == 2:
901edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        [thh, tmm] = tm
902edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        tss = '0'
903edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    elif len(tm) == 3:
904edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        [thh, tmm, tss] = tm
905edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    else:
906edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return None
907edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    try:
908edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        yy = int(yy)
909edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        dd = int(dd)
910edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        thh = int(thh)
911edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        tmm = int(tmm)
912edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        tss = int(tss)
913edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    except ValueError:
914edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return None
915edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    tzoffset = None
916edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    tz = tz.upper()
917edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if tz in _timezones:
918edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        tzoffset = _timezones[tz]
919edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    else:
920edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        try:
921edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            tzoffset = int(tz)
922edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        except ValueError:
923edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            pass
924edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    # Convert a timezone offset into seconds ; -0500 -> -18000
925edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if tzoffset:
926edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if tzoffset < 0:
927edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            tzsign = -1
928edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            tzoffset = -tzoffset
929edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        else:
930edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            tzsign = 1
931edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
932edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    return (yy, mm, dd, thh, tmm, tss, 0, 1, 0, tzoffset)
933edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
934edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
935edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef parsedate(data):
936edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Convert a time string to a time tuple."""
937edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    t = parsedate_tz(data)
938edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if t is None:
939edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return t
940edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    return t[:9]
941edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
942edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
943edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef mktime_tz(data):
944edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp."""
945edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if data[9] is None:
946edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        # No zone info, so localtime is better assumption than GMT
947edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return time.mktime(data[:8] + (-1,))
948edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    else:
949edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        t = time.mktime(data[:8] + (0,))
950edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        return t - data[9] - time.timezone
951edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
952edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepdef formatdate(timeval=None):
953edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """Returns time format preferred for Internet standards.
954edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
955edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
956edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
957edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    According to RFC 1123, day and month names must always be in
958edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    English.  If not for that, this code could use strftime().  It
959edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    can't because strftime() honors the locale and could generated
960edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    non-English names.
961edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    """
962edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if timeval is None:
963edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        timeval = time.time()
964edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    timeval = time.gmtime(timeval)
965edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
966edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")[timeval[6]],
967edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            timeval[2],
968edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep            ("Jan", "Feb", "Mar", "Apr", "May", "Jun",
969edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[timeval[1]-1],
970edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep                                timeval[0], timeval[3], timeval[4], timeval[5])
971edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
972edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
973edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# When used as script, run a small test program.
974edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# The first command line argument must be a filename containing one
975edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep# message in RFC-822 format.
976edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep
977edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoepif __name__ == '__main__':
978edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    import sys, os
979edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    file = os.path.join(os.environ['HOME'], 'Mail/inbox/1')
980edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if sys.argv[1:]: file = sys.argv[1]
981edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    f = open(file, 'r')
982edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    m = Message(f)
983edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'From:', m.getaddr('from')
984edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'To:', m.getaddrlist('to')
985edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'Subject:', m.getheader('subject')
986edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'Date:', m.getheader('date')
987edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    date = m.getdate_tz('date')
988edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    tz = date[-1]
989edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    date = time.localtime(mktime_tz(date))
990edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if date:
991edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        print 'ParsedDate:', time.asctime(date),
992edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        hhmmss = tz
993edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        hhmm, ss = divmod(hhmmss, 60)
994edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        hh, mm = divmod(hhmm, 60)
995edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        print "%+03d%02d" % (hh, mm),
996edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        if ss: print ".%02d" % ss,
997edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        print
998edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    else:
999edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        print 'ParsedDate:', None
1000edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    m.rewindbody()
1001edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    n = 0
1002edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    while f.readline():
1003edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep        n += 1
1004edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'Lines:', n
1005edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print '-'*70
1006edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'len =', len(m)
1007edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if 'Date' in m: print 'Date =', m['Date']
1008edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    if 'X-Nonsense' in m: pass
1009edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'keys =', m.keys()
1010edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'values =', m.values()
1011edbb763a2b63074cd468a5d33a17908b2cc0654Jeff Vander Stoep    print 'items =', m.items()
1012