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

Side by Side Diff: third_party/twisted_8_1/twisted/mail/alias.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 #
3 # Copyright (c) 2001-2007 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6
7 """
8 Support for aliases(5) configuration files
9
10 @author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
11
12 TODO::
13 Monitor files for reparsing
14 Handle non-local alias targets
15 Handle maildir alias targets
16 """
17
18 import os
19 import tempfile
20
21 from twisted.mail import smtp
22 from twisted.internet import reactor
23 from twisted.internet import protocol
24 from twisted.internet import defer
25 from twisted.python import failure
26 from twisted.python import log
27 from zope.interface import implements, Interface
28
29
30 def handle(result, line, filename, lineNo):
31 parts = [p.strip() for p in line.split(':', 1)]
32 if len(parts) != 2:
33 fmt = "Invalid format on line %d of alias file %s."
34 arg = (lineNo, filename)
35 log.err(fmt % arg)
36 else:
37 user, alias = parts
38 result.setdefault(user.strip(), []).extend(map(str.strip, alias.split(', ')))
39
40 def loadAliasFile(domains, filename=None, fp=None):
41 """Load a file containing email aliases.
42
43 Lines in the file should be formatted like so::
44
45 username: alias1,alias2,...,aliasN
46
47 Aliases beginning with a | will be treated as programs, will be run, and
48 the message will be written to their stdin.
49
50 Aliases without a host part will be assumed to be addresses on localhost.
51
52 If a username is specified multiple times, the aliases for each are joined
53 together as if they had all been on one line.
54
55 @type domains: C{dict} of implementor of C{IDomain}
56 @param domains: The domains to which these aliases will belong.
57
58 @type filename: C{str}
59 @param filename: The filename from which to load aliases.
60
61 @type fp: Any file-like object.
62 @param fp: If specified, overrides C{filename}, and aliases are read from
63 it.
64
65 @rtype: C{dict}
66 @return: A dictionary mapping usernames to C{AliasGroup} objects.
67 """
68 result = {}
69 if fp is None:
70 fp = file(filename)
71 else:
72 filename = getattr(fp, 'name', '<unknown>')
73 i = 0
74 prev = ''
75 for line in fp:
76 i += 1
77 line = line.rstrip()
78 if line.lstrip().startswith('#'):
79 continue
80 elif line.startswith(' ') or line.startswith('\t'):
81 prev = prev + line
82 else:
83 if prev:
84 handle(result, prev, filename, i)
85 prev = line
86 if prev:
87 handle(result, prev, filename, i)
88 for (u, a) in result.items():
89 addr = smtp.Address(u)
90 result[u] = AliasGroup(a, domains, u)
91 return result
92
93 class IAlias(Interface):
94 def createMessageReceiver():
95 pass
96
97 class AliasBase:
98 def __init__(self, domains, original):
99 self.domains = domains
100 self.original = smtp.Address(original)
101
102 def domain(self):
103 return self.domains[self.original.domain]
104
105 def resolve(self, aliasmap, memo=None):
106 if memo is None:
107 memo = {}
108 if str(self) in memo:
109 return None
110 memo[str(self)] = None
111 return self.createMessageReceiver()
112
113 class AddressAlias(AliasBase):
114 """The simplest alias, translating one email address into another."""
115
116 implements(IAlias)
117
118 def __init__(self, alias, *args):
119 AliasBase.__init__(self, *args)
120 self.alias = smtp.Address(alias)
121
122 def __str__(self):
123 return '<Address %s>' % (self.alias,)
124
125 def createMessageReceiver(self):
126 return self.domain().startMessage(str(self.alias))
127
128 def resolve(self, aliasmap, memo=None):
129 if memo is None:
130 memo = {}
131 if str(self) in memo:
132 return None
133 memo[str(self)] = None
134 try:
135 return self.domain().exists(smtp.User(self.alias, None, None, None), memo)()
136 except smtp.SMTPBadRcpt:
137 pass
138 if self.alias.local in aliasmap:
139 return aliasmap[self.alias.local].resolve(aliasmap, memo)
140 return None
141
142 class FileWrapper:
143 implements(smtp.IMessage)
144
145 def __init__(self, filename):
146 self.fp = tempfile.TemporaryFile()
147 self.finalname = filename
148
149 def lineReceived(self, line):
150 self.fp.write(line + '\n')
151
152 def eomReceived(self):
153 self.fp.seek(0, 0)
154 try:
155 f = file(self.finalname, 'a')
156 except:
157 return defer.fail(failure.Failure())
158
159 f.write(self.fp.read())
160 self.fp.close()
161 f.close()
162
163 return defer.succeed(self.finalname)
164
165 def connectionLost(self):
166 self.fp.close()
167 self.fp = None
168
169 def __str__(self):
170 return '<FileWrapper %s>' % (self.finalname,)
171
172
173 class FileAlias(AliasBase):
174
175 implements(IAlias)
176
177 def __init__(self, filename, *args):
178 AliasBase.__init__(self, *args)
179 self.filename = filename
180
181 def __str__(self):
182 return '<File %s>' % (self.filename,)
183
184 def createMessageReceiver(self):
185 return FileWrapper(self.filename)
186
187
188
189 class ProcessAliasTimeout(Exception):
190 """
191 A timeout occurred while processing aliases.
192 """
193
194
195
196 class MessageWrapper:
197 """
198 A message receiver which delivers content to a child process.
199
200 @type completionTimeout: C{int} or C{float}
201 @ivar completionTimeout: The number of seconds to wait for the child
202 process to exit before reporting the delivery as a failure.
203
204 @type _timeoutCallID: C{NoneType} or L{IDelayedCall}
205 @ivar _timeoutCallID: The call used to time out delivery, started when the
206 connection to the child process is closed.
207
208 @type done: C{bool}
209 @ivar done: Flag indicating whether the child process has exited or not.
210
211 @ivar reactor: An L{IReactorTime} provider which will be used to schedule
212 timeouts.
213 """
214 implements(smtp.IMessage)
215
216 done = False
217
218 completionTimeout = 60
219 _timeoutCallID = None
220
221 reactor = reactor
222
223 def __init__(self, protocol, process=None, reactor=None):
224 self.processName = process
225 self.protocol = protocol
226 self.completion = defer.Deferred()
227 self.protocol.onEnd = self.completion
228 self.completion.addBoth(self._processEnded)
229
230 if reactor is not None:
231 self.reactor = reactor
232
233
234 def _processEnded(self, result):
235 """
236 Record process termination and cancel the timeout call if it is active.
237 """
238 self.done = True
239 if self._timeoutCallID is not None:
240 # eomReceived was called, we're actually waiting for the process to
241 # exit.
242 self._timeoutCallID.cancel()
243 self._timeoutCallID = None
244 else:
245 # eomReceived was not called, this is unexpected, propagate the
246 # error.
247 return result
248
249
250 def lineReceived(self, line):
251 if self.done:
252 return
253 self.protocol.transport.write(line + '\n')
254
255
256 def eomReceived(self):
257 """
258 Disconnect from the child process, set up a timeout to wait for it to
259 exit, and return a Deferred which will be called back when the child
260 process exits.
261 """
262 if not self.done:
263 self.protocol.transport.loseConnection()
264 self._timeoutCallID = self.reactor.callLater(
265 self.completionTimeout, self._completionCancel)
266 return self.completion
267
268
269 def _completionCancel(self):
270 """
271 Handle the expiration of the timeout for the child process to exit by
272 terminating the child process forcefully and issuing a failure to the
273 completion deferred returned by L{eomReceived}.
274 """
275 self._timeoutCallID = None
276 self.protocol.transport.signalProcess('KILL')
277 exc = ProcessAliasTimeout(
278 "No answer after %s seconds" % (self.completionTimeout,))
279 self.protocol.onEnd = None
280 self.completion.errback(failure.Failure(exc))
281
282
283 def connectionLost(self):
284 # Heh heh
285 pass
286
287
288 def __str__(self):
289 return '<ProcessWrapper %s>' % (self.processName,)
290
291
292
293 class ProcessAliasProtocol(protocol.ProcessProtocol):
294 """
295 Trivial process protocol which will callback a Deferred when the associated
296 process ends.
297
298 @ivar onEnd: If not C{None}, a L{Deferred} which will be called back with
299 the failure passed to C{processEnded}, when C{processEnded} is called.
300 """
301
302 onEnd = None
303
304 def processEnded(self, reason):
305 """
306 Call back C{onEnd} if it is set.
307 """
308 if self.onEnd is not None:
309 self.onEnd.errback(reason)
310
311
312
313 class ProcessAlias(AliasBase):
314 """
315 An alias which is handled by the execution of a particular program.
316
317 @ivar reactor: An L{IReactorProcess} and L{IReactorTime} provider which
318 will be used to create and timeout the alias child process.
319 """
320 implements(IAlias)
321
322 reactor = reactor
323
324 def __init__(self, path, *args):
325 AliasBase.__init__(self, *args)
326 self.path = path.split()
327 self.program = self.path[0]
328
329
330 def __str__(self):
331 """
332 Build a string representation containing the path.
333 """
334 return '<Process %s>' % (self.path,)
335
336
337 def spawnProcess(self, proto, program, path):
338 """
339 Wrapper around C{reactor.spawnProcess}, to be customized for tests
340 purpose.
341 """
342 return self.reactor.spawnProcess(proto, program, path)
343
344
345 def createMessageReceiver(self):
346 """
347 Create a message receiver by launching a process.
348 """
349 p = ProcessAliasProtocol()
350 m = MessageWrapper(p, self.program, self.reactor)
351 fd = self.spawnProcess(p, self.program, self.path)
352 return m
353
354
355
356 class MultiWrapper:
357 """
358 Wrapper to deliver a single message to multiple recipients.
359 """
360
361 implements(smtp.IMessage)
362
363 def __init__(self, objs):
364 self.objs = objs
365
366 def lineReceived(self, line):
367 for o in self.objs:
368 o.lineReceived(line)
369
370 def eomReceived(self):
371 return defer.DeferredList([
372 o.eomReceived() for o in self.objs
373 ])
374
375 def connectionLost(self):
376 for o in self.objs:
377 o.connectionLost()
378
379 def __str__(self):
380 return '<GroupWrapper %r>' % (map(str, self.objs),)
381
382
383
384 class AliasGroup(AliasBase):
385 """
386 An alias which points to more than one recipient.
387
388 @ivar processAliasFactory: a factory for resolving process aliases.
389 @type processAliasFactory: C{class}
390 """
391
392 implements(IAlias)
393
394 processAliasFactory = ProcessAlias
395
396 def __init__(self, items, *args):
397 AliasBase.__init__(self, *args)
398 self.aliases = []
399 while items:
400 addr = items.pop().strip()
401 if addr.startswith(':'):
402 try:
403 f = file(addr[1:])
404 except:
405 log.err("Invalid filename in alias file %r" % (addr[1:],))
406 else:
407 addr = ' '.join([l.strip() for l in f])
408 items.extend(addr.split(','))
409 elif addr.startswith('|'):
410 self.aliases.append(self.processAliasFactory(addr[1:], *args))
411 elif addr.startswith('/'):
412 if os.path.isdir(addr):
413 log.err("Directory delivery not supported")
414 else:
415 self.aliases.append(FileAlias(addr, *args))
416 else:
417 self.aliases.append(AddressAlias(addr, *args))
418
419 def __len__(self):
420 return len(self.aliases)
421
422 def __str__(self):
423 return '<AliasGroup [%s]>' % (', '.join(map(str, self.aliases)))
424
425 def createMessageReceiver(self):
426 return MultiWrapper([a.createMessageReceiver() for a in self.aliases])
427
428 def resolve(self, aliasmap, memo=None):
429 if memo is None:
430 memo = {}
431 r = []
432 for a in self.aliases:
433 r.append(a.resolve(aliasmap, memo))
434 return MultiWrapper(filter(None, r))
435
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/mail/_version.py ('k') | third_party/twisted_8_1/twisted/mail/bounce.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698