blob: b6c16fe1d6945e9d58ee53bf081bf297d0a9ef8d [file] [log] [blame]
Guido van Rossumf06ee5f1996-11-27 19:52:01 +00001#! /usr/bin/env python
Guido van Rossum62448671996-09-17 21:33:15 +00002
3"""Classes to handle Unix style, MMDF style, and MH style mailboxes."""
4
5
Guido van Rossumc7b68821994-04-28 09:53:33 +00006import rfc822
Jack Jansen97157791995-10-23 13:59:53 +00007import os
Guido van Rossumc7b68821994-04-28 09:53:33 +00008
9class _Mailbox:
Guido van Rossum8ca84201998-03-26 20:56:10 +000010
Fred Drake13a2c272000-02-10 17:17:14 +000011 def __init__(self, fp):
12 self.fp = fp
13 self.seekp = 0
Guido van Rossumc7b68821994-04-28 09:53:33 +000014
Fred Drake13a2c272000-02-10 17:17:14 +000015 def seek(self, pos, whence=0):
16 if whence==1: # Relative to current position
17 self.pos = self.pos + pos
18 if whence==2: # Relative to file's end
19 self.pos = self.stop + pos
20 else: # Default - absolute position
21 self.pos = self.start + pos
Guido van Rossumc7b68821994-04-28 09:53:33 +000022
Fred Drake13a2c272000-02-10 17:17:14 +000023 def next(self):
24 while 1:
25 self.fp.seek(self.seekp)
26 try:
27 self._search_start()
28 except EOFError:
29 self.seekp = self.fp.tell()
30 return None
31 start = self.fp.tell()
32 self._search_end()
33 self.seekp = stop = self.fp.tell()
34 if start <> stop:
35 break
36 return rfc822.Message(_Subfile(self.fp, start, stop))
Guido van Rossumc7b68821994-04-28 09:53:33 +000037
38class _Subfile:
Guido van Rossum8ca84201998-03-26 20:56:10 +000039
Fred Drake13a2c272000-02-10 17:17:14 +000040 def __init__(self, fp, start, stop):
41 self.fp = fp
42 self.start = start
43 self.stop = stop
44 self.pos = self.start
Guido van Rossumc7b68821994-04-28 09:53:33 +000045
Fred Drake13a2c272000-02-10 17:17:14 +000046 def read(self, length = None):
47 if self.pos >= self.stop:
48 return ''
49 remaining = self.stop - self.pos
50 if length is None or length < 0:
51 length = remaining
52 elif length > remaining:
53 length = remaining
54 self.fp.seek(self.pos)
55 data = self.fp.read(length)
56 self.pos = self.fp.tell()
57 return data
Guido van Rossumc7b68821994-04-28 09:53:33 +000058
Fred Drake13a2c272000-02-10 17:17:14 +000059 def readline(self, length = None):
60 if self.pos >= self.stop:
61 return ''
62 if length is None:
63 length = self.stop - self.pos
64 self.fp.seek(self.pos)
65 data = self.fp.readline(length)
66 self.pos = self.fp.tell()
67 return data
Guido van Rossumc7b68821994-04-28 09:53:33 +000068
Guido van Rossume256a0f1999-03-24 16:20:45 +000069 def readlines(self, sizehint = -1):
Fred Drake13a2c272000-02-10 17:17:14 +000070 lines = []
71 while 1:
72 line = self.readline()
73 if not line:
74 break
75 lines.append(line)
76 if sizehint >= 0:
77 sizehint = sizehint - len(line)
78 if sizehint <= 0:
79 break
80 return lines
Guido van Rossume256a0f1999-03-24 16:20:45 +000081
Fred Drake13a2c272000-02-10 17:17:14 +000082 def tell(self):
83 return self.pos - self.start
Guido van Rossumc7b68821994-04-28 09:53:33 +000084
Fred Drake13a2c272000-02-10 17:17:14 +000085 def seek(self, pos, whence=0):
86 if whence == 0:
87 self.pos = self.start + pos
88 elif whence == 1:
89 self.pos = self.pos + pos
90 elif whence == 2:
91 self.pos = self.stop + pos
Guido van Rossumc7b68821994-04-28 09:53:33 +000092
Fred Drake13a2c272000-02-10 17:17:14 +000093 def close(self):
94 del self.fp
Guido van Rossumc7b68821994-04-28 09:53:33 +000095
96class UnixMailbox(_Mailbox):
Guido van Rossum8ca84201998-03-26 20:56:10 +000097
Fred Drake13a2c272000-02-10 17:17:14 +000098 def _search_start(self):
99 while 1:
Guido van Rossum1571a1e2000-04-04 03:31:39 +0000100 pos = self.fp.tell()
Fred Drake13a2c272000-02-10 17:17:14 +0000101 line = self.fp.readline()
102 if not line:
103 raise EOFError
104 if line[:5] == 'From ' and self._isrealfromline(line):
Guido van Rossum1571a1e2000-04-04 03:31:39 +0000105 self.fp.seek(pos)
Fred Drake13a2c272000-02-10 17:17:14 +0000106 return
Guido van Rossumc7b68821994-04-28 09:53:33 +0000107
Fred Drake13a2c272000-02-10 17:17:14 +0000108 def _search_end(self):
Guido van Rossum1571a1e2000-04-04 03:31:39 +0000109 self.fp.readline() # Throw away header line
Fred Drake13a2c272000-02-10 17:17:14 +0000110 while 1:
111 pos = self.fp.tell()
112 line = self.fp.readline()
113 if not line:
114 return
115 if line[:5] == 'From ' and self._isrealfromline(line):
116 self.fp.seek(pos)
117 return
Guido van Rossumc7b68821994-04-28 09:53:33 +0000118
Fred Drake13a2c272000-02-10 17:17:14 +0000119 # An overridable mechanism to test for From-line-ness.
120 # You can either specify a different regular expression
121 # or define a whole new _isrealfromline() method.
122 # Note that this only gets called for lines starting with
123 # the 5 characters "From ".
Guido van Rossumfbe63de1998-04-03 16:04:05 +0000124
Fred Drake13a2c272000-02-10 17:17:14 +0000125 _fromlinepattern = r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" \
126 r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*$"
127 _regexp = None
Guido van Rossumfbe63de1998-04-03 16:04:05 +0000128
Fred Drake13a2c272000-02-10 17:17:14 +0000129 def _isrealfromline(self, line):
130 if not self._regexp:
131 import re
132 self._regexp = re.compile(self._fromlinepattern)
133 return self._regexp.match(line)
Guido van Rossumfbe63de1998-04-03 16:04:05 +0000134
Guido van Rossumc7b68821994-04-28 09:53:33 +0000135class MmdfMailbox(_Mailbox):
Guido van Rossum8ca84201998-03-26 20:56:10 +0000136
Fred Drake13a2c272000-02-10 17:17:14 +0000137 def _search_start(self):
138 while 1:
139 line = self.fp.readline()
140 if not line:
141 raise EOFError
142 if line[:5] == '\001\001\001\001\n':
143 return
Guido van Rossumc7b68821994-04-28 09:53:33 +0000144
Fred Drake13a2c272000-02-10 17:17:14 +0000145 def _search_end(self):
146 while 1:
147 pos = self.fp.tell()
148 line = self.fp.readline()
149 if not line:
150 return
151 if line == '\001\001\001\001\n':
152 self.fp.seek(pos)
153 return
Guido van Rossumc7b68821994-04-28 09:53:33 +0000154
Jack Jansen97157791995-10-23 13:59:53 +0000155class MHMailbox:
Jack Jansen97157791995-10-23 13:59:53 +0000156
Fred Drake13a2c272000-02-10 17:17:14 +0000157 def __init__(self, dirname):
158 import re
159 pat = re.compile('^[0-9][0-9]*$')
160 self.dirname = dirname
161 files = os.listdir(self.dirname)
162 self.boxes = []
163 for f in files:
164 if pat.match(f):
165 self.boxes.append(f)
Guido van Rossum8ca84201998-03-26 20:56:10 +0000166
Fred Drake13a2c272000-02-10 17:17:14 +0000167 def next(self):
168 if not self.boxes:
169 return None
170 fn = self.boxes[0]
171 del self.boxes[0]
172 fp = open(os.path.join(self.dirname, fn))
173 return rfc822.Message(fp)
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000174
175class Maildir:
176
Fred Drake13a2c272000-02-10 17:17:14 +0000177 # Qmail directory mailbox
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000178
Fred Drake13a2c272000-02-10 17:17:14 +0000179 def __init__(self, dirname):
180 import string
181 self.dirname = dirname
182 self.boxes = []
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000183
Fred Drake13a2c272000-02-10 17:17:14 +0000184 # check for new mail
185 newdir = os.path.join(self.dirname, 'new')
186 for file in os.listdir(newdir):
187 if len(string.split(file, '.')) > 2:
188 self.boxes.append(os.path.join(newdir, file))
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000189
Fred Drake13a2c272000-02-10 17:17:14 +0000190 # Now check for current mail in this maildir
191 curdir = os.path.join(self.dirname, 'cur')
192 for file in os.listdir(curdir):
193 if len(string.split(file, '.')) > 2:
194 self.boxes.append(os.path.join(curdir, file))
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000195
Fred Drake13a2c272000-02-10 17:17:14 +0000196 def next(self):
197 if not self.boxes:
198 return None
199 fn = self.boxes[0]
200 del self.boxes[0]
201 fp = open(os.path.join(self.dirname, fn))
202 return rfc822.Message(fp)
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000203
Guido van Rossumfdf58fe1997-05-15 14:33:09 +0000204class BabylMailbox(_Mailbox):
Guido van Rossumfdf58fe1997-05-15 14:33:09 +0000205
Fred Drake13a2c272000-02-10 17:17:14 +0000206 def _search_start(self):
207 while 1:
208 line = self.fp.readline()
209 if not line:
210 raise EOFError
211 if line == '*** EOOH ***\n':
212 return
Guido van Rossum8ca84201998-03-26 20:56:10 +0000213
Fred Drake13a2c272000-02-10 17:17:14 +0000214 def _search_end(self):
215 while 1:
216 pos = self.fp.tell()
217 line = self.fp.readline()
218 if not line:
219 return
220 if line == '\037\014\n':
221 self.fp.seek(pos)
222 return
Guido van Rossumfdf58fe1997-05-15 14:33:09 +0000223
224
Guido van Rossum62448671996-09-17 21:33:15 +0000225def _test():
Fred Drake13a2c272000-02-10 17:17:14 +0000226 import time
227 import sys
228 import string
229 import os
Jack Jansen97157791995-10-23 13:59:53 +0000230
Fred Drake13a2c272000-02-10 17:17:14 +0000231 args = sys.argv[1:]
232 if not args:
233 for key in 'MAILDIR', 'MAIL', 'LOGNAME', 'USER':
234 if os.environ.has_key(key):
235 mbox = os.environ[key]
236 break
237 else:
238 print "$MAIL, $LOGNAME nor $USER set -- who are you?"
239 return
240 else:
241 mbox = args[0]
242 if mbox[:1] == '+':
243 mbox = os.environ['HOME'] + '/Mail/' + mbox[1:]
244 elif not '/' in mbox:
245 mbox = '/usr/mail/' + mbox
246 if os.path.isdir(mbox):
247 if os.path.isdir(os.path.join(mbox, 'cur')):
248 mb = Maildir(mbox)
249 else:
250 mb = MHMailbox(mbox)
251 else:
252 fp = open(mbox, 'r')
253 mb = UnixMailbox(fp)
254
255 msgs = []
256 while 1:
257 msg = mb.next()
258 if msg is None:
259 break
260 msgs.append(msg)
261 if len(args) <= 1:
262 msg.fp = None
263 if len(args) > 1:
264 num = string.atoi(args[1])
265 print 'Message %d body:'%num
266 msg = msgs[num-1]
267 msg.rewindbody()
268 sys.stdout.write(msg.fp.read())
269 else:
270 print 'Mailbox',mbox,'has',len(msgs),'messages:'
271 for msg in msgs:
272 f = msg.getheader('from') or ""
273 s = msg.getheader('subject') or ""
274 d = msg.getheader('date') or ""
275 print '%20.20s %18.18s %-30.30s'%(f, d[5:], s)
Guido van Rossum62448671996-09-17 21:33:15 +0000276
277
278if __name__ == '__main__':
Fred Drake13a2c272000-02-10 17:17:14 +0000279 _test()