Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1408)

Side by Side Diff: third_party/twisted_8_1/twisted/mail/maildir.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.mail.test.test_mail -*-
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """Maildir-style mailbox support
7 """
8
9 from __future__ import generators
10
11 import os
12 import stat
13 import socket
14 import time
15 import md5
16 import cStringIO
17
18 from zope.interface import implements
19
20 try:
21 import cStringIO as StringIO
22 except ImportError:
23 import StringIO
24
25 from twisted.mail import pop3
26 from twisted.mail import smtp
27 from twisted.protocols import basic
28 from twisted.persisted import dirdbm
29 from twisted.python import log, failure
30 from twisted.mail import mail
31 from twisted.mail import alias
32 from twisted.internet import interfaces, defer, reactor
33
34 from twisted import cred
35 import twisted.cred.portal
36 import twisted.cred.credentials
37 import twisted.cred.checkers
38 import twisted.cred.error
39
40 INTERNAL_ERROR = '''\
41 From: Twisted.mail Internals
42 Subject: An Error Occurred
43
44 An internal server error has occurred. Please contact the
45 server administrator.
46 '''
47
48 class _MaildirNameGenerator:
49 """Utility class to generate a unique maildir name
50 """
51 n = 0
52 p = os.getpid()
53 s = socket.gethostname().replace('/', r'\057').replace(':', r'\072')
54
55 def generate(self):
56 self.n = self.n + 1
57 t = time.time()
58 seconds = str(int(t))
59 microseconds = str(int((t-int(t))*10e6))
60 return '%s.M%sP%sQ%s.%s' % (seconds, microseconds,
61 self.p, self.n, self.s)
62
63 _generateMaildirName = _MaildirNameGenerator().generate
64
65 def initializeMaildir(dir):
66 if not os.path.isdir(dir):
67 os.mkdir(dir, 0700)
68 for subdir in ['new', 'cur', 'tmp', '.Trash']:
69 os.mkdir(os.path.join(dir, subdir), 0700)
70 for subdir in ['new', 'cur', 'tmp']:
71 os.mkdir(os.path.join(dir, '.Trash', subdir), 0700)
72 # touch
73 open(os.path.join(dir, '.Trash', 'maildirfolder'), 'w').close()
74
75
76 class MaildirMessage(mail.FileMessage):
77 size = None
78
79 def __init__(self, address, fp, *a, **kw):
80 header = "Delivered-To: %s\n" % address
81 fp.write(header)
82 self.size = len(header)
83 mail.FileMessage.__init__(self, fp, *a, **kw)
84
85 def lineReceived(self, line):
86 mail.FileMessage.lineReceived(self, line)
87 self.size += len(line)+1
88
89 def eomReceived(self):
90 self.finalName = self.finalName+',S=%d' % self.size
91 return mail.FileMessage.eomReceived(self)
92
93 class AbstractMaildirDomain:
94 """Abstract maildir-backed domain.
95 """
96 alias = None
97 root = None
98
99 def __init__(self, service, root):
100 """Initialize.
101 """
102 self.root = root
103
104 def userDirectory(self, user):
105 """Get the maildir directory for a given user
106
107 Override to specify where to save mails for users.
108 Return None for non-existing users.
109 """
110 return None
111
112 ##
113 ## IAliasableDomain
114 ##
115
116 def setAliasGroup(self, alias):
117 self.alias = alias
118
119 ##
120 ## IDomain
121 ##
122 def exists(self, user, memo=None):
123 """Check for existence of user in the domain
124 """
125 if self.userDirectory(user.dest.local) is not None:
126 return lambda: self.startMessage(user)
127 try:
128 a = self.alias[user.dest.local]
129 except:
130 raise smtp.SMTPBadRcpt(user)
131 else:
132 aliases = a.resolve(self.alias, memo)
133 if aliases:
134 return lambda: aliases
135 log.err("Bad alias configuration: " + str(user))
136 raise smtp.SMTPBadRcpt(user)
137
138 def startMessage(self, user):
139 """Save a message for a given user
140 """
141 if isinstance(user, str):
142 name, domain = user.split('@', 1)
143 else:
144 name, domain = user.dest.local, user.dest.domain
145 dir = self.userDirectory(name)
146 fname = _generateMaildirName()
147 filename = os.path.join(dir, 'tmp', fname)
148 fp = open(filename, 'w')
149 return MaildirMessage('%s@%s' % (name, domain), fp, filename,
150 os.path.join(dir, 'new', fname))
151
152 def willRelay(self, user, protocol):
153 return False
154
155 def addUser(self, user, password):
156 raise NotImplementedError
157
158 def getCredentialsCheckers(self):
159 raise NotImplementedError
160 ##
161 ## end of IDomain
162 ##
163
164 class _MaildirMailboxAppendMessageTask:
165 implements(interfaces.IConsumer)
166
167 osopen = staticmethod(os.open)
168 oswrite = staticmethod(os.write)
169 osclose = staticmethod(os.close)
170 osrename = staticmethod(os.rename)
171
172 def __init__(self, mbox, msg):
173 self.mbox = mbox
174 self.defer = defer.Deferred()
175 self.openCall = None
176 if not hasattr(msg, "read"):
177 msg = StringIO.StringIO(msg)
178 self.msg = msg
179 # This is needed, as this startup phase might call defer.errback and zer o out self.defer
180 # By doing it on the reactor iteration appendMessage is able to use .def er without problems.
181 reactor.callLater(0, self.startUp)
182
183 def startUp(self):
184 self.createTempFile()
185 if self.fh != -1:
186 self.filesender = basic.FileSender()
187 self.filesender.beginFileTransfer(self.msg, self)
188
189 def registerProducer(self, producer, streaming):
190 self.myproducer = producer
191 self.streaming = streaming
192 if not streaming:
193 self.prodProducer()
194
195 def prodProducer(self):
196 self.openCall = None
197 if self.myproducer is not None:
198 self.openCall = reactor.callLater(0, self.prodProducer)
199 self.myproducer.resumeProducing()
200
201 def unregisterProducer(self):
202 self.myproducer = None
203 self.streaming = None
204 self.osclose(self.fh)
205 self.moveFileToNew()
206
207 def write(self, data):
208 try:
209 self.oswrite(self.fh, data)
210 except:
211 self.fail()
212
213 def fail(self, err=None):
214 if err is None:
215 err = failure.Failure()
216 if self.openCall is not None:
217 self.openCall.cancel()
218 self.defer.errback(err)
219 self.defer = None
220
221 def moveFileToNew(self):
222 while True:
223 newname = os.path.join(self.mbox.path, "new", _generateMaildirName() )
224 try:
225 self.osrename(self.tmpname, newname)
226 break
227 except OSError, (err, estr):
228 import errno
229 # if the newname exists, retry with a new newname.
230 if err != errno.EEXIST:
231 self.fail()
232 newname = None
233 break
234 if newname is not None:
235 self.mbox.list.append(newname)
236 self.defer.callback(None)
237 self.defer = None
238
239 def createTempFile(self):
240 attr = (os.O_RDWR | os.O_CREAT | os.O_EXCL
241 | getattr(os, "O_NOINHERIT", 0)
242 | getattr(os, "O_NOFOLLOW", 0))
243 tries = 0
244 self.fh = -1
245 while True:
246 self.tmpname = os.path.join(self.mbox.path, "tmp", _generateMaildirN ame())
247 try:
248 self.fh = self.osopen(self.tmpname, attr, 0600)
249 return None
250 except OSError:
251 tries += 1
252 if tries > 500:
253 self.defer.errback(RuntimeError("Could not create tmp file f or %s" % self.mbox.path))
254 self.defer = None
255 return None
256
257 class MaildirMailbox(pop3.Mailbox):
258 """Implement the POP3 mailbox semantics for a Maildir mailbox
259 """
260 AppendFactory = _MaildirMailboxAppendMessageTask
261
262 def __init__(self, path):
263 """Initialize with name of the Maildir mailbox
264 """
265 self.path = path
266 self.list = []
267 self.deleted = {}
268 initializeMaildir(path)
269 for name in ('cur', 'new'):
270 for file in os.listdir(os.path.join(path, name)):
271 self.list.append((file, os.path.join(path, name, file)))
272 self.list.sort()
273 self.list = [e[1] for e in self.list]
274
275 def listMessages(self, i=None):
276 """Return a list of lengths of all files in new/ and cur/
277 """
278 if i is None:
279 ret = []
280 for mess in self.list:
281 if mess:
282 ret.append(os.stat(mess)[stat.ST_SIZE])
283 else:
284 ret.append(0)
285 return ret
286 return self.list[i] and os.stat(self.list[i])[stat.ST_SIZE] or 0
287
288 def getMessage(self, i):
289 """Return an open file-pointer to a message
290 """
291 return open(self.list[i])
292
293 def getUidl(self, i):
294 """Return a unique identifier for a message
295
296 This is done using the basename of the filename.
297 It is globally unique because this is how Maildirs are designed.
298 """
299 # Returning the actual filename is a mistake. Hash it.
300 base = os.path.basename(self.list[i])
301 return md5.md5(base).hexdigest()
302
303 def deleteMessage(self, i):
304 """Delete a message
305
306 This only moves a message to the .Trash/ subfolder,
307 so it can be undeleted by an administrator.
308 """
309 trashFile = os.path.join(
310 self.path, '.Trash', 'cur', os.path.basename(self.list[i])
311 )
312 os.rename(self.list[i], trashFile)
313 self.deleted[self.list[i]] = trashFile
314 self.list[i] = 0
315
316 def undeleteMessages(self):
317 """Undelete any deleted messages it is possible to undelete
318
319 This moves any messages from .Trash/ subfolder back to their
320 original position, and empties out the deleted dictionary.
321 """
322 for (real, trash) in self.deleted.items():
323 try:
324 os.rename(trash, real)
325 except OSError, (err, estr):
326 import errno
327 # If the file has been deleted from disk, oh well!
328 if err != errno.ENOENT:
329 raise
330 # This is a pass
331 else:
332 try:
333 self.list[self.list.index(0)] = real
334 except ValueError:
335 self.list.append(real)
336 self.deleted.clear()
337
338 def appendMessage(self, txt):
339 """Appends a message into the mailbox."""
340 task = self.AppendFactory(self, txt)
341 return task.defer
342
343 class StringListMailbox:
344 implements(pop3.IMailbox)
345
346 def __init__(self, msgs):
347 self.msgs = msgs
348
349 def listMessages(self, i=None):
350 if i is None:
351 return map(len, self.msgs)
352 return len(self.msgs[i])
353
354 def getMessage(self, i):
355 return StringIO.StringIO(self.msgs[i])
356
357 def getUidl(self, i):
358 return md5.new(self.msgs[i]).hexdigest()
359
360 def deleteMessage(self, i):
361 pass
362
363 def undeleteMessages(self):
364 pass
365
366 def sync(self):
367 pass
368
369 class MaildirDirdbmDomain(AbstractMaildirDomain):
370 """A Maildir Domain where membership is checked by a dirdbm file
371 """
372
373 implements(cred.portal.IRealm, mail.IAliasableDomain)
374
375 portal = None
376 _credcheckers = None
377
378 def __init__(self, service, root, postmaster=0):
379 """Initialize
380
381 The first argument is where the Domain directory is rooted.
382 The second is whether non-existing addresses are simply
383 forwarded to postmaster instead of outright bounce
384
385 The directory structure of a MailddirDirdbmDomain is:
386
387 /passwd <-- a dirdbm file
388 /USER/{cur,new,del} <-- each user has these three directories
389 """
390 AbstractMaildirDomain.__init__(self, service, root)
391 dbm = os.path.join(root, 'passwd')
392 if not os.path.exists(dbm):
393 os.makedirs(dbm)
394 self.dbm = dirdbm.open(dbm)
395 self.postmaster = postmaster
396
397 def userDirectory(self, name):
398 """Get the directory for a user
399
400 If the user exists in the dirdbm file, return the directory
401 os.path.join(root, name), creating it if necessary.
402 Otherwise, returns postmaster's mailbox instead if bounces
403 go to postmaster, otherwise return None
404 """
405 if not self.dbm.has_key(name):
406 if not self.postmaster:
407 return None
408 name = 'postmaster'
409 dir = os.path.join(self.root, name)
410 if not os.path.exists(dir):
411 initializeMaildir(dir)
412 return dir
413
414 ##
415 ## IDomain
416 ##
417 def addUser(self, user, password):
418 self.dbm[user] = password
419 # Ensure it is initialized
420 self.userDirectory(user)
421
422 def getCredentialsCheckers(self):
423 if self._credcheckers is None:
424 self._credcheckers = [DirdbmDatabase(self.dbm)]
425 return self._credcheckers
426
427 ##
428 ## IRealm
429 ##
430 def requestAvatar(self, avatarId, mind, *interfaces):
431 if pop3.IMailbox not in interfaces:
432 raise NotImplementedError("No interface")
433 if avatarId == cred.checkers.ANONYMOUS:
434 mbox = StringListMailbox([INTERNAL_ERROR])
435 else:
436 mbox = MaildirMailbox(os.path.join(self.root, avatarId))
437
438 return (
439 pop3.IMailbox,
440 mbox,
441 lambda: None
442 )
443
444 class DirdbmDatabase:
445 implements(cred.checkers.ICredentialsChecker)
446
447 credentialInterfaces = (
448 cred.credentials.IUsernamePassword,
449 cred.credentials.IUsernameHashedPassword
450 )
451
452 def __init__(self, dbm):
453 self.dirdbm = dbm
454
455 def requestAvatarId(self, c):
456 if c.username in self.dirdbm:
457 if c.checkPassword(self.dirdbm[c.username]):
458 return c.username
459 raise cred.error.UnauthorizedLogin()
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/mail/mail.py ('k') | third_party/twisted_8_1/twisted/mail/pb.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698