blob: a5659486cedfa0a6943748374ef70681b7a9a5ff [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
Skip Montanaro17ab1232001-01-24 06:27:27 +00009__all__ = ["UnixMailbox","MmdfMailbox","MHMailbox","Maildir","BabylMailbox"]
10
Guido van Rossumc7b68821994-04-28 09:53:33 +000011class _Mailbox:
Fred Drakedbbf76b2000-07-09 16:44:26 +000012 def __init__(self, fp):
13 self.fp = fp
14 self.seekp = 0
Guido van Rossum8ca84201998-03-26 20:56:10 +000015
Fred Drakedbbf76b2000-07-09 16:44:26 +000016 def seek(self, pos, whence=0):
17 if whence==1: # Relative to current position
18 self.pos = self.pos + pos
19 if whence==2: # Relative to file's end
20 self.pos = self.stop + pos
21 else: # Default - absolute position
22 self.pos = self.start + pos
Guido van Rossumc7b68821994-04-28 09:53:33 +000023
Fred Drakedbbf76b2000-07-09 16:44:26 +000024 def next(self):
25 while 1:
26 self.fp.seek(self.seekp)
27 try:
28 self._search_start()
29 except EOFError:
30 self.seekp = self.fp.tell()
31 return None
32 start = self.fp.tell()
33 self._search_end()
34 self.seekp = stop = self.fp.tell()
Fred Drake8152d322000-12-12 23:20:45 +000035 if start != stop:
Fred Drakedbbf76b2000-07-09 16:44:26 +000036 break
37 return rfc822.Message(_Subfile(self.fp, start, stop))
Guido van Rossumc7b68821994-04-28 09:53:33 +000038
Guido van Rossumc7b68821994-04-28 09:53:33 +000039
40class _Subfile:
Fred Drakedbbf76b2000-07-09 16:44:26 +000041 def __init__(self, fp, start, stop):
42 self.fp = fp
43 self.start = start
44 self.stop = stop
45 self.pos = self.start
Guido van Rossum8ca84201998-03-26 20:56:10 +000046
Fred Drakedbbf76b2000-07-09 16:44:26 +000047 def read(self, length = None):
48 if self.pos >= self.stop:
49 return ''
50 remaining = self.stop - self.pos
51 if length is None or length < 0:
52 length = remaining
53 elif length > remaining:
54 length = remaining
55 self.fp.seek(self.pos)
56 data = self.fp.read(length)
57 self.pos = self.fp.tell()
58 return data
Guido van Rossumc7b68821994-04-28 09:53:33 +000059
Fred Drakedbbf76b2000-07-09 16:44:26 +000060 def readline(self, length = None):
61 if self.pos >= self.stop:
62 return ''
63 if length is None:
64 length = self.stop - self.pos
65 self.fp.seek(self.pos)
66 data = self.fp.readline(length)
67 self.pos = self.fp.tell()
68 return data
Guido van Rossumc7b68821994-04-28 09:53:33 +000069
Fred Drakedbbf76b2000-07-09 16:44:26 +000070 def readlines(self, sizehint = -1):
71 lines = []
72 while 1:
73 line = self.readline()
74 if not line:
75 break
76 lines.append(line)
77 if sizehint >= 0:
78 sizehint = sizehint - len(line)
79 if sizehint <= 0:
80 break
81 return lines
Guido van Rossumc7b68821994-04-28 09:53:33 +000082
Fred Drakedbbf76b2000-07-09 16:44:26 +000083 def tell(self):
84 return self.pos - self.start
Guido van Rossume256a0f1999-03-24 16:20:45 +000085
Fred Drakedbbf76b2000-07-09 16:44:26 +000086 def seek(self, pos, whence=0):
87 if whence == 0:
88 self.pos = self.start + pos
89 elif whence == 1:
90 self.pos = self.pos + pos
91 elif whence == 2:
92 self.pos = self.stop + pos
Guido van Rossumc7b68821994-04-28 09:53:33 +000093
Fred Drakedbbf76b2000-07-09 16:44:26 +000094 def close(self):
95 del self.fp
Guido van Rossumc7b68821994-04-28 09:53:33 +000096
Guido van Rossumc7b68821994-04-28 09:53:33 +000097
98class UnixMailbox(_Mailbox):
Fred Drakedbbf76b2000-07-09 16:44:26 +000099 def _search_start(self):
100 while 1:
101 pos = self.fp.tell()
102 line = self.fp.readline()
103 if not line:
104 raise EOFError
105 if line[:5] == 'From ' and self._isrealfromline(line):
106 self.fp.seek(pos)
107 return
Guido van Rossum8ca84201998-03-26 20:56:10 +0000108
Fred Drakedbbf76b2000-07-09 16:44:26 +0000109 def _search_end(self):
110 self.fp.readline() # Throw away header line
111 while 1:
112 pos = self.fp.tell()
113 line = self.fp.readline()
114 if not line:
115 return
116 if line[:5] == 'From ' and self._isrealfromline(line):
117 self.fp.seek(pos)
118 return
Guido van Rossumc7b68821994-04-28 09:53:33 +0000119
Fred Drakedbbf76b2000-07-09 16:44:26 +0000120 # An overridable mechanism to test for From-line-ness.
121 # You can either specify a different regular expression
122 # or define a whole new _isrealfromline() method.
123 # Note that this only gets called for lines starting with
124 # the 5 characters "From ".
Guido van Rossumc7b68821994-04-28 09:53:33 +0000125
Fred Drakedbbf76b2000-07-09 16:44:26 +0000126 _fromlinepattern = r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" \
127 r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*$"
128 _regexp = None
Guido van Rossumfbe63de1998-04-03 16:04:05 +0000129
Fred Drakedbbf76b2000-07-09 16:44:26 +0000130 def _isrealfromline(self, line):
131 if not self._regexp:
132 import re
133 self._regexp = re.compile(self._fromlinepattern)
134 return self._regexp.match(line)
Guido van Rossumfbe63de1998-04-03 16:04:05 +0000135
Guido van Rossumfbe63de1998-04-03 16:04:05 +0000136
Guido van Rossumc7b68821994-04-28 09:53:33 +0000137class MmdfMailbox(_Mailbox):
Fred Drakedbbf76b2000-07-09 16:44:26 +0000138 def _search_start(self):
139 while 1:
140 line = self.fp.readline()
141 if not line:
142 raise EOFError
143 if line[:5] == '\001\001\001\001\n':
144 return
Guido van Rossum8ca84201998-03-26 20:56:10 +0000145
Fred Drakedbbf76b2000-07-09 16:44:26 +0000146 def _search_end(self):
147 while 1:
148 pos = self.fp.tell()
149 line = self.fp.readline()
150 if not line:
151 return
152 if line == '\001\001\001\001\n':
153 self.fp.seek(pos)
154 return
Guido van Rossumc7b68821994-04-28 09:53:33 +0000155
Guido van Rossumc7b68821994-04-28 09:53:33 +0000156
Jack Jansen97157791995-10-23 13:59:53 +0000157class MHMailbox:
Fred Drakedbbf76b2000-07-09 16:44:26 +0000158 def __init__(self, dirname):
159 import re
Guido van Rossum0707fea2000-08-10 03:05:26 +0000160 pat = re.compile('^[1-9][0-9]*$')
Fred Drakedbbf76b2000-07-09 16:44:26 +0000161 self.dirname = dirname
Sjoerd Mullenderd2653a92000-08-11 07:48:36 +0000162 # the three following lines could be combined into:
163 # list = map(long, filter(pat.match, os.listdir(self.dirname)))
164 list = os.listdir(self.dirname)
165 list = filter(pat.match, list)
Guido van Rossum0707fea2000-08-10 03:05:26 +0000166 list = map(long, list)
167 list.sort()
168 # This only works in Python 1.6 or later;
169 # before that str() added 'L':
170 self.boxes = map(str, list)
Jack Jansen97157791995-10-23 13:59:53 +0000171
Fred Drakedbbf76b2000-07-09 16:44:26 +0000172 def next(self):
173 if not self.boxes:
174 return None
175 fn = self.boxes[0]
176 del self.boxes[0]
177 fp = open(os.path.join(self.dirname, fn))
178 return rfc822.Message(fp)
Guido van Rossum8ca84201998-03-26 20:56:10 +0000179
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000180
181class Maildir:
Fred Drakedbbf76b2000-07-09 16:44:26 +0000182 # Qmail directory mailbox
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000183
Fred Drakedbbf76b2000-07-09 16:44:26 +0000184 def __init__(self, dirname):
Fred Drakedbbf76b2000-07-09 16:44:26 +0000185 self.dirname = dirname
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000186
Fred Drakedbbf76b2000-07-09 16:44:26 +0000187 # check for new mail
188 newdir = os.path.join(self.dirname, 'new')
Fred Draked9a8dec2000-09-22 18:41:50 +0000189 boxes = [os.path.join(newdir, f)
190 for f in os.listdir(newdir) if f[0] != '.']
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000191
Fred Drakedbbf76b2000-07-09 16:44:26 +0000192 # Now check for current mail in this maildir
193 curdir = os.path.join(self.dirname, 'cur')
Fred Draked9a8dec2000-09-22 18:41:50 +0000194 boxes += [os.path.join(curdir, f)
195 for f in os.listdir(curdir) if f[0] != '.']
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000196
Fred Drakede3518e2000-10-23 13:37:01 +0000197 self.boxes = boxes
198
Fred Drakedbbf76b2000-07-09 16:44:26 +0000199 def next(self):
200 if not self.boxes:
201 return None
202 fn = self.boxes[0]
203 del self.boxes[0]
Fred Drakee108a022000-09-14 14:44:43 +0000204 fp = open(fn)
Fred Drakedbbf76b2000-07-09 16:44:26 +0000205 return rfc822.Message(fp)
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000206
Guido van Rossum9a4d6371998-12-23 22:05:42 +0000207
Guido van Rossumfdf58fe1997-05-15 14:33:09 +0000208class BabylMailbox(_Mailbox):
Fred Drakedbbf76b2000-07-09 16:44:26 +0000209 def _search_start(self):
210 while 1:
211 line = self.fp.readline()
212 if not line:
213 raise EOFError
214 if line == '*** EOOH ***\n':
215 return
Guido van Rossumfdf58fe1997-05-15 14:33:09 +0000216
Fred Drakedbbf76b2000-07-09 16:44:26 +0000217 def _search_end(self):
218 while 1:
219 pos = self.fp.tell()
220 line = self.fp.readline()
221 if not line:
222 return
223 if line == '\037\014\n':
224 self.fp.seek(pos)
225 return
Guido van Rossumfdf58fe1997-05-15 14:33:09 +0000226
227
Guido van Rossum62448671996-09-17 21:33:15 +0000228def _test():
Fred Drakedbbf76b2000-07-09 16:44:26 +0000229 import time
230 import sys
Fred Drakedbbf76b2000-07-09 16:44:26 +0000231 import os
Jack Jansen97157791995-10-23 13:59:53 +0000232
Fred Drakedbbf76b2000-07-09 16:44:26 +0000233 args = sys.argv[1:]
234 if not args:
235 for key in 'MAILDIR', 'MAIL', 'LOGNAME', 'USER':
236 if os.environ.has_key(key):
237 mbox = os.environ[key]
238 break
Fred Drake13a2c272000-02-10 17:17:14 +0000239 else:
Fred Drakedbbf76b2000-07-09 16:44:26 +0000240 print "$MAIL, $LOGNAME nor $USER set -- who are you?"
241 return
242 else:
243 mbox = args[0]
244 if mbox[:1] == '+':
245 mbox = os.environ['HOME'] + '/Mail/' + mbox[1:]
246 elif not '/' in mbox:
247 mbox = '/usr/mail/' + mbox
248 if os.path.isdir(mbox):
249 if os.path.isdir(os.path.join(mbox, 'cur')):
250 mb = Maildir(mbox)
Fred Drake13a2c272000-02-10 17:17:14 +0000251 else:
Fred Drakedbbf76b2000-07-09 16:44:26 +0000252 mb = MHMailbox(mbox)
253 else:
254 fp = open(mbox, 'r')
255 mb = UnixMailbox(fp)
256
257 msgs = []
258 while 1:
259 msg = mb.next()
260 if msg is None:
261 break
262 msgs.append(msg)
263 if len(args) <= 1:
264 msg.fp = None
265 if len(args) > 1:
Fred Drakede3518e2000-10-23 13:37:01 +0000266 num = int(args[1])
Fred Drakedbbf76b2000-07-09 16:44:26 +0000267 print 'Message %d body:'%num
268 msg = msgs[num-1]
269 msg.rewindbody()
270 sys.stdout.write(msg.fp.read())
271 else:
272 print 'Mailbox',mbox,'has',len(msgs),'messages:'
273 for msg in msgs:
274 f = msg.getheader('from') or ""
275 s = msg.getheader('subject') or ""
276 d = msg.getheader('date') or ""
Fred Drakecc4adf22000-09-30 23:59:04 +0000277 print '-%20.20s %20.20s %-30.30s'%(f, d[5:], s)
Guido van Rossum62448671996-09-17 21:33:15 +0000278
279
280if __name__ == '__main__':
Fred Drakedbbf76b2000-07-09 16:44:26 +0000281 _test()