aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail')
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/__init__.py15
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/_version.py3
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/alias.py435
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/bounce.py60
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/imap4.py6208
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/mail.py333
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/maildir.py518
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pb.py115
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3.py1071
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3client.py706
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/protocols.py233
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relay.py114
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relaymanager.py631
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/__init__.py1
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/mailmail.py366
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/smtp.py1934
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/tap.py333
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/__init__.py1
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/pop3testserver.py314
-rw-r--r--lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/rfc822.message86
-rw-r--r--lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/server.pem36
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_bounce.py32
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_imap.py4829
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mail.py2062
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mailmail.py75
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_options.py247
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3.py1071
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3client.py582
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_scripts.py18
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_smtp.py1520
-rw-r--r--lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/NEWS309
-rw-r--r--lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/README6
32 files changed, 0 insertions, 24264 deletions
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/__init__.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/__init__.py
deleted file mode 100755
index b434715f..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""
-
-Twisted Mail: a Twisted E-Mail Server.
-
-Maintainer: Jp Calderone
-
-"""
-
-from twisted.mail._version import version
-__version__ = version.short()
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/_version.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/_version.py
deleted file mode 100755
index de980e31..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/_version.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# This is an auto-generated file. Do not edit it.
-from twisted.python import versions
-version = versions.Version('twisted.mail', 12, 2, 0)
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/alias.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/alias.py
deleted file mode 100755
index 8eccea6b..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/alias.py
+++ /dev/null
@@ -1,435 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_mail -*-
-#
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""
-Support for aliases(5) configuration files
-
-@author: Jp Calderone
-
-TODO::
- Monitor files for reparsing
- Handle non-local alias targets
- Handle maildir alias targets
-"""
-
-import os
-import tempfile
-
-from twisted.mail import smtp
-from twisted.internet import reactor
-from twisted.internet import protocol
-from twisted.internet import defer
-from twisted.python import failure
-from twisted.python import log
-from zope.interface import implements, Interface
-
-
-def handle(result, line, filename, lineNo):
- parts = [p.strip() for p in line.split(':', 1)]
- if len(parts) != 2:
- fmt = "Invalid format on line %d of alias file %s."
- arg = (lineNo, filename)
- log.err(fmt % arg)
- else:
- user, alias = parts
- result.setdefault(user.strip(), []).extend(map(str.strip, alias.split(',')))
-
-def loadAliasFile(domains, filename=None, fp=None):
- """Load a file containing email aliases.
-
- Lines in the file should be formatted like so::
-
- username: alias1,alias2,...,aliasN
-
- Aliases beginning with a | will be treated as programs, will be run, and
- the message will be written to their stdin.
-
- Aliases without a host part will be assumed to be addresses on localhost.
-
- If a username is specified multiple times, the aliases for each are joined
- together as if they had all been on one line.
-
- @type domains: C{dict} of implementor of C{IDomain}
- @param domains: The domains to which these aliases will belong.
-
- @type filename: C{str}
- @param filename: The filename from which to load aliases.
-
- @type fp: Any file-like object.
- @param fp: If specified, overrides C{filename}, and aliases are read from
- it.
-
- @rtype: C{dict}
- @return: A dictionary mapping usernames to C{AliasGroup} objects.
- """
- result = {}
- if fp is None:
- fp = file(filename)
- else:
- filename = getattr(fp, 'name', '<unknown>')
- i = 0
- prev = ''
- for line in fp:
- i += 1
- line = line.rstrip()
- if line.lstrip().startswith('#'):
- continue
- elif line.startswith(' ') or line.startswith('\t'):
- prev = prev + line
- else:
- if prev:
- handle(result, prev, filename, i)
- prev = line
- if prev:
- handle(result, prev, filename, i)
- for (u, a) in result.items():
- addr = smtp.Address(u)
- result[u] = AliasGroup(a, domains, u)
- return result
-
-class IAlias(Interface):
- def createMessageReceiver():
- pass
-
-class AliasBase:
- def __init__(self, domains, original):
- self.domains = domains
- self.original = smtp.Address(original)
-
- def domain(self):
- return self.domains[self.original.domain]
-
- def resolve(self, aliasmap, memo=None):
- if memo is None:
- memo = {}
- if str(self) in memo:
- return None
- memo[str(self)] = None
- return self.createMessageReceiver()
-
-class AddressAlias(AliasBase):
- """The simplest alias, translating one email address into another."""
-
- implements(IAlias)
-
- def __init__(self, alias, *args):
- AliasBase.__init__(self, *args)
- self.alias = smtp.Address(alias)
-
- def __str__(self):
- return '<Address %s>' % (self.alias,)
-
- def createMessageReceiver(self):
- return self.domain().startMessage(str(self.alias))
-
- def resolve(self, aliasmap, memo=None):
- if memo is None:
- memo = {}
- if str(self) in memo:
- return None
- memo[str(self)] = None
- try:
- return self.domain().exists(smtp.User(self.alias, None, None, None), memo)()
- except smtp.SMTPBadRcpt:
- pass
- if self.alias.local in aliasmap:
- return aliasmap[self.alias.local].resolve(aliasmap, memo)
- return None
-
-class FileWrapper:
- implements(smtp.IMessage)
-
- def __init__(self, filename):
- self.fp = tempfile.TemporaryFile()
- self.finalname = filename
-
- def lineReceived(self, line):
- self.fp.write(line + '\n')
-
- def eomReceived(self):
- self.fp.seek(0, 0)
- try:
- f = file(self.finalname, 'a')
- except:
- return defer.fail(failure.Failure())
-
- f.write(self.fp.read())
- self.fp.close()
- f.close()
-
- return defer.succeed(self.finalname)
-
- def connectionLost(self):
- self.fp.close()
- self.fp = None
-
- def __str__(self):
- return '<FileWrapper %s>' % (self.finalname,)
-
-
-class FileAlias(AliasBase):
-
- implements(IAlias)
-
- def __init__(self, filename, *args):
- AliasBase.__init__(self, *args)
- self.filename = filename
-
- def __str__(self):
- return '<File %s>' % (self.filename,)
-
- def createMessageReceiver(self):
- return FileWrapper(self.filename)
-
-
-
-class ProcessAliasTimeout(Exception):
- """
- A timeout occurred while processing aliases.
- """
-
-
-
-class MessageWrapper:
- """
- A message receiver which delivers content to a child process.
-
- @type completionTimeout: C{int} or C{float}
- @ivar completionTimeout: The number of seconds to wait for the child
- process to exit before reporting the delivery as a failure.
-
- @type _timeoutCallID: C{NoneType} or L{IDelayedCall}
- @ivar _timeoutCallID: The call used to time out delivery, started when the
- connection to the child process is closed.
-
- @type done: C{bool}
- @ivar done: Flag indicating whether the child process has exited or not.
-
- @ivar reactor: An L{IReactorTime} provider which will be used to schedule
- timeouts.
- """
- implements(smtp.IMessage)
-
- done = False
-
- completionTimeout = 60
- _timeoutCallID = None
-
- reactor = reactor
-
- def __init__(self, protocol, process=None, reactor=None):
- self.processName = process
- self.protocol = protocol
- self.completion = defer.Deferred()
- self.protocol.onEnd = self.completion
- self.completion.addBoth(self._processEnded)
-
- if reactor is not None:
- self.reactor = reactor
-
-
- def _processEnded(self, result):
- """
- Record process termination and cancel the timeout call if it is active.
- """
- self.done = True
- if self._timeoutCallID is not None:
- # eomReceived was called, we're actually waiting for the process to
- # exit.
- self._timeoutCallID.cancel()
- self._timeoutCallID = None
- else:
- # eomReceived was not called, this is unexpected, propagate the
- # error.
- return result
-
-
- def lineReceived(self, line):
- if self.done:
- return
- self.protocol.transport.write(line + '\n')
-
-
- def eomReceived(self):
- """
- Disconnect from the child process, set up a timeout to wait for it to
- exit, and return a Deferred which will be called back when the child
- process exits.
- """
- if not self.done:
- self.protocol.transport.loseConnection()
- self._timeoutCallID = self.reactor.callLater(
- self.completionTimeout, self._completionCancel)
- return self.completion
-
-
- def _completionCancel(self):
- """
- Handle the expiration of the timeout for the child process to exit by
- terminating the child process forcefully and issuing a failure to the
- completion deferred returned by L{eomReceived}.
- """
- self._timeoutCallID = None
- self.protocol.transport.signalProcess('KILL')
- exc = ProcessAliasTimeout(
- "No answer after %s seconds" % (self.completionTimeout,))
- self.protocol.onEnd = None
- self.completion.errback(failure.Failure(exc))
-
-
- def connectionLost(self):
- # Heh heh
- pass
-
-
- def __str__(self):
- return '<ProcessWrapper %s>' % (self.processName,)
-
-
-
-class ProcessAliasProtocol(protocol.ProcessProtocol):
- """
- Trivial process protocol which will callback a Deferred when the associated
- process ends.
-
- @ivar onEnd: If not C{None}, a L{Deferred} which will be called back with
- the failure passed to C{processEnded}, when C{processEnded} is called.
- """
-
- onEnd = None
-
- def processEnded(self, reason):
- """
- Call back C{onEnd} if it is set.
- """
- if self.onEnd is not None:
- self.onEnd.errback(reason)
-
-
-
-class ProcessAlias(AliasBase):
- """
- An alias which is handled by the execution of a particular program.
-
- @ivar reactor: An L{IReactorProcess} and L{IReactorTime} provider which
- will be used to create and timeout the alias child process.
- """
- implements(IAlias)
-
- reactor = reactor
-
- def __init__(self, path, *args):
- AliasBase.__init__(self, *args)
- self.path = path.split()
- self.program = self.path[0]
-
-
- def __str__(self):
- """
- Build a string representation containing the path.
- """
- return '<Process %s>' % (self.path,)
-
-
- def spawnProcess(self, proto, program, path):
- """
- Wrapper around C{reactor.spawnProcess}, to be customized for tests
- purpose.
- """
- return self.reactor.spawnProcess(proto, program, path)
-
-
- def createMessageReceiver(self):
- """
- Create a message receiver by launching a process.
- """
- p = ProcessAliasProtocol()
- m = MessageWrapper(p, self.program, self.reactor)
- fd = self.spawnProcess(p, self.program, self.path)
- return m
-
-
-
-class MultiWrapper:
- """
- Wrapper to deliver a single message to multiple recipients.
- """
-
- implements(smtp.IMessage)
-
- def __init__(self, objs):
- self.objs = objs
-
- def lineReceived(self, line):
- for o in self.objs:
- o.lineReceived(line)
-
- def eomReceived(self):
- return defer.DeferredList([
- o.eomReceived() for o in self.objs
- ])
-
- def connectionLost(self):
- for o in self.objs:
- o.connectionLost()
-
- def __str__(self):
- return '<GroupWrapper %r>' % (map(str, self.objs),)
-
-
-
-class AliasGroup(AliasBase):
- """
- An alias which points to more than one recipient.
-
- @ivar processAliasFactory: a factory for resolving process aliases.
- @type processAliasFactory: C{class}
- """
-
- implements(IAlias)
-
- processAliasFactory = ProcessAlias
-
- def __init__(self, items, *args):
- AliasBase.__init__(self, *args)
- self.aliases = []
- while items:
- addr = items.pop().strip()
- if addr.startswith(':'):
- try:
- f = file(addr[1:])
- except:
- log.err("Invalid filename in alias file %r" % (addr[1:],))
- else:
- addr = ' '.join([l.strip() for l in f])
- items.extend(addr.split(','))
- elif addr.startswith('|'):
- self.aliases.append(self.processAliasFactory(addr[1:], *args))
- elif addr.startswith('/'):
- if os.path.isdir(addr):
- log.err("Directory delivery not supported")
- else:
- self.aliases.append(FileAlias(addr, *args))
- else:
- self.aliases.append(AddressAlias(addr, *args))
-
- def __len__(self):
- return len(self.aliases)
-
- def __str__(self):
- return '<AliasGroup [%s]>' % (', '.join(map(str, self.aliases)))
-
- def createMessageReceiver(self):
- return MultiWrapper([a.createMessageReceiver() for a in self.aliases])
-
- def resolve(self, aliasmap, memo=None):
- if memo is None:
- memo = {}
- r = []
- for a in self.aliases:
- r.append(a.resolve(aliasmap, memo))
- return MultiWrapper(filter(None, r))
-
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/bounce.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/bounce.py
deleted file mode 100755
index 5d5bde9a..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/bounce.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_bounce -*-
-#
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-import StringIO
-import rfc822
-import time
-import os
-
-
-from twisted.mail import smtp
-
-BOUNCE_FORMAT = """\
-From: postmaster@%(failedDomain)s
-To: %(failedFrom)s
-Subject: Returned Mail: see transcript for details
-Message-ID: %(messageID)s
-Content-Type: multipart/report; report-type=delivery-status;
- boundary="%(boundary)s"
-
---%(boundary)s
-
-%(transcript)s
-
---%(boundary)s
-Content-Type: message/delivery-status
-Arrival-Date: %(ctime)s
-Final-Recipient: RFC822; %(failedTo)s
-"""
-
-def generateBounce(message, failedFrom, failedTo, transcript=''):
- if not transcript:
- transcript = '''\
-I'm sorry, the following address has permanent errors: %(failedTo)s.
-I've given up, and I will not retry the message again.
-''' % vars()
-
- boundary = "%s_%s_%s" % (time.time(), os.getpid(), 'XXXXX')
- failedAddress = rfc822.AddressList(failedTo)[0][1]
- failedDomain = failedAddress.split('@', 1)[1]
- messageID = smtp.messageid(uniq='bounce')
- ctime = time.ctime(time.time())
-
- fp = StringIO.StringIO()
- fp.write(BOUNCE_FORMAT % vars())
- orig = message.tell()
- message.seek(2, 0)
- sz = message.tell()
- message.seek(0, orig)
- if sz > 10000:
- while 1:
- line = message.readline()
- if len(line)<=1:
- break
- fp.write(line)
- else:
- fp.write(message.read())
- return '', failedFrom, fp.getvalue()
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/imap4.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/imap4.py
deleted file mode 100755
index f23483bd..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/imap4.py
+++ /dev/null
@@ -1,6208 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_imap -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-An IMAP4 protocol implementation
-
-@author: Jp Calderone
-
-To do::
- Suspend idle timeout while server is processing
- Use an async message parser instead of buffering in memory
- Figure out a way to not queue multi-message client requests (Flow? A simple callback?)
- Clarify some API docs (Query, etc)
- Make APPEND recognize (again) non-existent mailboxes before accepting the literal
-"""
-
-import rfc822
-import base64
-import binascii
-import hmac
-import re
-import copy
-import tempfile
-import string
-import time
-import random
-import types
-
-import email.Utils
-
-try:
- import cStringIO as StringIO
-except:
- import StringIO
-
-from zope.interface import implements, Interface
-
-from twisted.protocols import basic
-from twisted.protocols import policies
-from twisted.internet import defer
-from twisted.internet import error
-from twisted.internet.defer import maybeDeferred
-from twisted.python import log, text
-from twisted.internet import interfaces
-
-from twisted import cred
-import twisted.cred.error
-import twisted.cred.credentials
-
-
-# locale-independent month names to use instead of strftime's
-_MONTH_NAMES = dict(zip(
- range(1, 13),
- "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split()))
-
-
-class MessageSet(object):
- """
- Essentially an infinite bitfield, with some extra features.
-
- @type getnext: Function taking C{int} returning C{int}
- @ivar getnext: A function that returns the next message number,
- used when iterating through the MessageSet. By default, a function
- returning the next integer is supplied, but as this can be rather
- inefficient for sparse UID iterations, it is recommended to supply
- one when messages are requested by UID. The argument is provided
- as a hint to the implementation and may be ignored if it makes sense
- to do so (eg, if an iterator is being used that maintains its own
- state, it is guaranteed that it will not be called out-of-order).
- """
- _empty = []
-
- def __init__(self, start=_empty, end=_empty):
- """
- Create a new MessageSet()
-
- @type start: Optional C{int}
- @param start: Start of range, or only message number
-
- @type end: Optional C{int}
- @param end: End of range.
- """
- self._last = self._empty # Last message/UID in use
- self.ranges = [] # List of ranges included
- self.getnext = lambda x: x+1 # A function which will return the next
- # message id. Handy for UID requests.
-
- if start is self._empty:
- return
-
- if isinstance(start, types.ListType):
- self.ranges = start[:]
- self.clean()
- else:
- self.add(start,end)
-
- # Ooo. A property.
- def last():
- def _setLast(self, value):
- if self._last is not self._empty:
- raise ValueError("last already set")
-
- self._last = value
- for i, (l, h) in enumerate(self.ranges):
- if l is not None:
- break # There are no more Nones after this
- l = value
- if h is None:
- h = value
- if l > h:
- l, h = h, l
- self.ranges[i] = (l, h)
-
- self.clean()
-
- def _getLast(self):
- return self._last
-
- doc = '''
- "Highest" message number, refered to by "*".
- Must be set before attempting to use the MessageSet.
- '''
- return _getLast, _setLast, None, doc
- last = property(*last())
-
- def add(self, start, end=_empty):
- """
- Add another range
-
- @type start: C{int}
- @param start: Start of range, or only message number
-
- @type end: Optional C{int}
- @param end: End of range.
- """
- if end is self._empty:
- end = start
-
- if self._last is not self._empty:
- if start is None:
- start = self.last
- if end is None:
- end = self.last
-
- if start > end:
- # Try to keep in low, high order if possible
- # (But we don't know what None means, this will keep
- # None at the start of the ranges list)
- start, end = end, start
-
- self.ranges.append((start, end))
- self.clean()
-
- def __add__(self, other):
- if isinstance(other, MessageSet):
- ranges = self.ranges + other.ranges
- return MessageSet(ranges)
- else:
- res = MessageSet(self.ranges)
- try:
- res.add(*other)
- except TypeError:
- res.add(other)
- return res
-
-
- def extend(self, other):
- if isinstance(other, MessageSet):
- self.ranges.extend(other.ranges)
- self.clean()
- else:
- try:
- self.add(*other)
- except TypeError:
- self.add(other)
-
- return self
-
-
- def clean(self):
- """
- Clean ranges list, combining adjacent ranges
- """
-
- self.ranges.sort()
-
- oldl, oldh = None, None
- for i,(l, h) in enumerate(self.ranges):
- if l is None:
- continue
- # l is >= oldl and h is >= oldh due to sort()
- if oldl is not None and l <= oldh + 1:
- l = oldl
- h = max(oldh, h)
- self.ranges[i - 1] = None
- self.ranges[i] = (l, h)
-
- oldl, oldh = l, h
-
- self.ranges = filter(None, self.ranges)
-
-
- def __contains__(self, value):
- """
- May raise TypeError if we encounter an open-ended range
- """
- for l, h in self.ranges:
- if l is None:
- raise TypeError(
- "Can't determine membership; last value not set")
- if l <= value <= h:
- return True
-
- return False
-
-
- def _iterator(self):
- for l, h in self.ranges:
- l = self.getnext(l-1)
- while l <= h:
- yield l
- l = self.getnext(l)
- if l is None:
- break
-
- def __iter__(self):
- if self.ranges and self.ranges[0][0] is None:
- raise TypeError("Can't iterate; last value not set")
-
- return self._iterator()
-
- def __len__(self):
- res = 0
- for l, h in self.ranges:
- if l is None:
- if h is None:
- res += 1
- else:
- raise TypeError("Can't size object; last value not set")
- else:
- res += (h - l) + 1
-
- return res
-
- def __str__(self):
- p = []
- for low, high in self.ranges:
- if low == high:
- if low is None:
- p.append('*')
- else:
- p.append(str(low))
- elif low is None:
- p.append('%d:*' % (high,))
- else:
- p.append('%d:%d' % (low, high))
- return ','.join(p)
-
- def __repr__(self):
- return '<MessageSet %s>' % (str(self),)
-
- def __eq__(self, other):
- if isinstance(other, MessageSet):
- return self.ranges == other.ranges
- return False
-
-
-class LiteralString:
- def __init__(self, size, defered):
- self.size = size
- self.data = []
- self.defer = defered
-
- def write(self, data):
- self.size -= len(data)
- passon = None
- if self.size > 0:
- self.data.append(data)
- else:
- if self.size:
- data, passon = data[:self.size], data[self.size:]
- else:
- passon = ''
- if data:
- self.data.append(data)
- return passon
-
- def callback(self, line):
- """
- Call defered with data and rest of line
- """
- self.defer.callback((''.join(self.data), line))
-
-class LiteralFile:
- _memoryFileLimit = 1024 * 1024 * 10
-
- def __init__(self, size, defered):
- self.size = size
- self.defer = defered
- if size > self._memoryFileLimit:
- self.data = tempfile.TemporaryFile()
- else:
- self.data = StringIO.StringIO()
-
- def write(self, data):
- self.size -= len(data)
- passon = None
- if self.size > 0:
- self.data.write(data)
- else:
- if self.size:
- data, passon = data[:self.size], data[self.size:]
- else:
- passon = ''
- if data:
- self.data.write(data)
- return passon
-
- def callback(self, line):
- """
- Call defered with data and rest of line
- """
- self.data.seek(0,0)
- self.defer.callback((self.data, line))
-
-
-class WriteBuffer:
- """Buffer up a bunch of writes before sending them all to a transport at once.
- """
- def __init__(self, transport, size=8192):
- self.bufferSize = size
- self.transport = transport
- self._length = 0
- self._writes = []
-
- def write(self, s):
- self._length += len(s)
- self._writes.append(s)
- if self._length > self.bufferSize:
- self.flush()
-
- def flush(self):
- if self._writes:
- self.transport.writeSequence(self._writes)
- self._writes = []
- self._length = 0
-
-
-class Command:
- _1_RESPONSES = ('CAPABILITY', 'FLAGS', 'LIST', 'LSUB', 'STATUS', 'SEARCH', 'NAMESPACE')
- _2_RESPONSES = ('EXISTS', 'EXPUNGE', 'FETCH', 'RECENT')
- _OK_RESPONSES = ('UIDVALIDITY', 'UNSEEN', 'READ-WRITE', 'READ-ONLY', 'UIDNEXT', 'PERMANENTFLAGS')
- defer = None
-
- def __init__(self, command, args=None, wantResponse=(),
- continuation=None, *contArgs, **contKw):
- self.command = command
- self.args = args
- self.wantResponse = wantResponse
- self.continuation = lambda x: continuation(x, *contArgs, **contKw)
- self.lines = []
-
- def format(self, tag):
- if self.args is None:
- return ' '.join((tag, self.command))
- return ' '.join((tag, self.command, self.args))
-
- def finish(self, lastLine, unusedCallback):
- send = []
- unuse = []
- for L in self.lines:
- names = parseNestedParens(L)
- N = len(names)
- if (N >= 1 and names[0] in self._1_RESPONSES or
- N >= 2 and names[1] in self._2_RESPONSES or
- N >= 2 and names[0] == 'OK' and isinstance(names[1], types.ListType) and names[1][0] in self._OK_RESPONSES):
- send.append(names)
- else:
- unuse.append(names)
- d, self.defer = self.defer, None
- d.callback((send, lastLine))
- if unuse:
- unusedCallback(unuse)
-
-class LOGINCredentials(cred.credentials.UsernamePassword):
- def __init__(self):
- self.challenges = ['Password\0', 'User Name\0']
- self.responses = ['password', 'username']
- cred.credentials.UsernamePassword.__init__(self, None, None)
-
- def getChallenge(self):
- return self.challenges.pop()
-
- def setResponse(self, response):
- setattr(self, self.responses.pop(), response)
-
- def moreChallenges(self):
- return bool(self.challenges)
-
-class PLAINCredentials(cred.credentials.UsernamePassword):
- def __init__(self):
- cred.credentials.UsernamePassword.__init__(self, None, None)
-
- def getChallenge(self):
- return ''
-
- def setResponse(self, response):
- parts = response.split('\0')
- if len(parts) != 3:
- raise IllegalClientResponse("Malformed Response - wrong number of parts")
- useless, self.username, self.password = parts
-
- def moreChallenges(self):
- return False
-
-class IMAP4Exception(Exception):
- def __init__(self, *args):
- Exception.__init__(self, *args)
-
-class IllegalClientResponse(IMAP4Exception): pass
-
-class IllegalOperation(IMAP4Exception): pass
-
-class IllegalMailboxEncoding(IMAP4Exception): pass
-
-class IMailboxListener(Interface):
- """Interface for objects interested in mailbox events"""
-
- def modeChanged(writeable):
- """Indicates that the write status of a mailbox has changed.
-
- @type writeable: C{bool}
- @param writeable: A true value if write is now allowed, false
- otherwise.
- """
-
- def flagsChanged(newFlags):
- """Indicates that the flags of one or more messages have changed.
-
- @type newFlags: C{dict}
- @param newFlags: A mapping of message identifiers to tuples of flags
- now set on that message.
- """
-
- def newMessages(exists, recent):
- """Indicates that the number of messages in a mailbox has changed.
-
- @type exists: C{int} or C{None}
- @param exists: The total number of messages now in this mailbox.
- If the total number of messages has not changed, this should be
- C{None}.
-
- @type recent: C{int}
- @param recent: The number of messages now flagged \\Recent.
- If the number of recent messages has not changed, this should be
- C{None}.
- """
-
-class IMAP4Server(basic.LineReceiver, policies.TimeoutMixin):
- """
- Protocol implementation for an IMAP4rev1 server.
-
- The server can be in any of four states:
- - Non-authenticated
- - Authenticated
- - Selected
- - Logout
- """
- implements(IMailboxListener)
-
- # Identifier for this server software
- IDENT = 'Twisted IMAP4rev1 Ready'
-
- # Number of seconds before idle timeout
- # Initially 1 minute. Raised to 30 minutes after login.
- timeOut = 60
-
- POSTAUTH_TIMEOUT = 60 * 30
-
- # Whether STARTTLS has been issued successfully yet or not.
- startedTLS = False
-
- # Whether our transport supports TLS
- canStartTLS = False
-
- # Mapping of tags to commands we have received
- tags = None
-
- # The object which will handle logins for us
- portal = None
-
- # The account object for this connection
- account = None
-
- # Logout callback
- _onLogout = None
-
- # The currently selected mailbox
- mbox = None
-
- # Command data to be processed when literal data is received
- _pendingLiteral = None
-
- # Maximum length to accept for a "short" string literal
- _literalStringLimit = 4096
-
- # IChallengeResponse factories for AUTHENTICATE command
- challengers = None
-
- # Search terms the implementation of which needs to be passed both the last
- # message identifier (UID) and the last sequence id.
- _requiresLastMessageInfo = set(["OR", "NOT", "UID"])
-
- state = 'unauth'
-
- parseState = 'command'
-
- def __init__(self, chal = None, contextFactory = None, scheduler = None):
- if chal is None:
- chal = {}
- self.challengers = chal
- self.ctx = contextFactory
- if scheduler is None:
- scheduler = iterateInReactor
- self._scheduler = scheduler
- self._queuedAsync = []
-
- def capabilities(self):
- cap = {'AUTH': self.challengers.keys()}
- if self.ctx and self.canStartTLS:
- if not self.startedTLS and interfaces.ISSLTransport(self.transport, None) is None:
- cap['LOGINDISABLED'] = None
- cap['STARTTLS'] = None
- cap['NAMESPACE'] = None
- cap['IDLE'] = None
- return cap
-
- def connectionMade(self):
- self.tags = {}
- self.canStartTLS = interfaces.ITLSTransport(self.transport, None) is not None
- self.setTimeout(self.timeOut)
- self.sendServerGreeting()
-
- def connectionLost(self, reason):
- self.setTimeout(None)
- if self._onLogout:
- self._onLogout()
- self._onLogout = None
-
- def timeoutConnection(self):
- self.sendLine('* BYE Autologout; connection idle too long')
- self.transport.loseConnection()
- if self.mbox:
- self.mbox.removeListener(self)
- cmbx = ICloseableMailbox(self.mbox, None)
- if cmbx is not None:
- maybeDeferred(cmbx.close).addErrback(log.err)
- self.mbox = None
- self.state = 'timeout'
-
- def rawDataReceived(self, data):
- self.resetTimeout()
- passon = self._pendingLiteral.write(data)
- if passon is not None:
- self.setLineMode(passon)
-
- # Avoid processing commands while buffers are being dumped to
- # our transport
- blocked = None
-
- def _unblock(self):
- commands = self.blocked
- self.blocked = None
- while commands and self.blocked is None:
- self.lineReceived(commands.pop(0))
- if self.blocked is not None:
- self.blocked.extend(commands)
-
- def lineReceived(self, line):
- if self.blocked is not None:
- self.blocked.append(line)
- return
-
- self.resetTimeout()
-
- f = getattr(self, 'parse_' + self.parseState)
- try:
- f(line)
- except Exception, e:
- self.sendUntaggedResponse('BAD Server error: ' + str(e))
- log.err()
-
- def parse_command(self, line):
- args = line.split(None, 2)
- rest = None
- if len(args) == 3:
- tag, cmd, rest = args
- elif len(args) == 2:
- tag, cmd = args
- elif len(args) == 1:
- tag = args[0]
- self.sendBadResponse(tag, 'Missing command')
- return None
- else:
- self.sendBadResponse(None, 'Null command')
- return None
-
- cmd = cmd.upper()
- try:
- return self.dispatchCommand(tag, cmd, rest)
- except IllegalClientResponse, e:
- self.sendBadResponse(tag, 'Illegal syntax: ' + str(e))
- except IllegalOperation, e:
- self.sendNegativeResponse(tag, 'Illegal operation: ' + str(e))
- except IllegalMailboxEncoding, e:
- self.sendNegativeResponse(tag, 'Illegal mailbox name: ' + str(e))
-
- def parse_pending(self, line):
- d = self._pendingLiteral
- self._pendingLiteral = None
- self.parseState = 'command'
- d.callback(line)
-
- def dispatchCommand(self, tag, cmd, rest, uid=None):
- f = self.lookupCommand(cmd)
- if f:
- fn = f[0]
- parseargs = f[1:]
- self.__doCommand(tag, fn, [self, tag], parseargs, rest, uid)
- else:
- self.sendBadResponse(tag, 'Unsupported command')
-
- def lookupCommand(self, cmd):
- return getattr(self, '_'.join((self.state, cmd.upper())), None)
-
- def __doCommand(self, tag, handler, args, parseargs, line, uid):
- for (i, arg) in enumerate(parseargs):
- if callable(arg):
- parseargs = parseargs[i+1:]
- maybeDeferred(arg, self, line).addCallback(
- self.__cbDispatch, tag, handler, args,
- parseargs, uid).addErrback(self.__ebDispatch, tag)
- return
- else:
- args.append(arg)
-
- if line:
- # Too many arguments
- raise IllegalClientResponse("Too many arguments for command: " + repr(line))
-
- if uid is not None:
- handler(uid=uid, *args)
- else:
- handler(*args)
-
- def __cbDispatch(self, (arg, rest), tag, fn, args, parseargs, uid):
- args.append(arg)
- self.__doCommand(tag, fn, args, parseargs, rest, uid)
-
- def __ebDispatch(self, failure, tag):
- if failure.check(IllegalClientResponse):
- self.sendBadResponse(tag, 'Illegal syntax: ' + str(failure.value))
- elif failure.check(IllegalOperation):
- self.sendNegativeResponse(tag, 'Illegal operation: ' +
- str(failure.value))
- elif failure.check(IllegalMailboxEncoding):
- self.sendNegativeResponse(tag, 'Illegal mailbox name: ' +
- str(failure.value))
- else:
- self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
- log.err(failure)
-
- def _stringLiteral(self, size):
- if size > self._literalStringLimit:
- raise IllegalClientResponse(
- "Literal too long! I accept at most %d octets" %
- (self._literalStringLimit,))
- d = defer.Deferred()
- self.parseState = 'pending'
- self._pendingLiteral = LiteralString(size, d)
- self.sendContinuationRequest('Ready for %d octets of text' % size)
- self.setRawMode()
- return d
-
- def _fileLiteral(self, size):
- d = defer.Deferred()
- self.parseState = 'pending'
- self._pendingLiteral = LiteralFile(size, d)
- self.sendContinuationRequest('Ready for %d octets of data' % size)
- self.setRawMode()
- return d
-
- def arg_astring(self, line):
- """
- Parse an astring from the line, return (arg, rest), possibly
- via a deferred (to handle literals)
- """
- line = line.strip()
- if not line:
- raise IllegalClientResponse("Missing argument")
- d = None
- arg, rest = None, None
- if line[0] == '"':
- try:
- spam, arg, rest = line.split('"',2)
- rest = rest[1:] # Strip space
- except ValueError:
- raise IllegalClientResponse("Unmatched quotes")
- elif line[0] == '{':
- # literal
- if line[-1] != '}':
- raise IllegalClientResponse("Malformed literal")
- try:
- size = int(line[1:-1])
- except ValueError:
- raise IllegalClientResponse("Bad literal size: " + line[1:-1])
- d = self._stringLiteral(size)
- else:
- arg = line.split(' ',1)
- if len(arg) == 1:
- arg.append('')
- arg, rest = arg
- return d or (arg, rest)
-
- # ATOM: Any CHAR except ( ) { % * " \ ] CTL SP (CHAR is 7bit)
- atomre = re.compile(r'(?P<atom>[^\](){%*"\\\x00-\x20\x80-\xff]+)( (?P<rest>.*$)|$)')
-
- def arg_atom(self, line):
- """
- Parse an atom from the line
- """
- if not line:
- raise IllegalClientResponse("Missing argument")
- m = self.atomre.match(line)
- if m:
- return m.group('atom'), m.group('rest')
- else:
- raise IllegalClientResponse("Malformed ATOM")
-
- def arg_plist(self, line):
- """
- Parse a (non-nested) parenthesised list from the line
- """
- if not line:
- raise IllegalClientResponse("Missing argument")
-
- if line[0] != "(":
- raise IllegalClientResponse("Missing parenthesis")
-
- i = line.find(")")
-
- if i == -1:
- raise IllegalClientResponse("Mismatched parenthesis")
-
- return (parseNestedParens(line[1:i],0), line[i+2:])
-
- def arg_literal(self, line):
- """
- Parse a literal from the line
- """
- if not line:
- raise IllegalClientResponse("Missing argument")
-
- if line[0] != '{':
- raise IllegalClientResponse("Missing literal")
-
- if line[-1] != '}':
- raise IllegalClientResponse("Malformed literal")
-
- try:
- size = int(line[1:-1])
- except ValueError:
- raise IllegalClientResponse("Bad literal size: " + line[1:-1])
-
- return self._fileLiteral(size)
-
- def arg_searchkeys(self, line):
- """
- searchkeys
- """
- query = parseNestedParens(line)
- # XXX Should really use list of search terms and parse into
- # a proper tree
-
- return (query, '')
-
- def arg_seqset(self, line):
- """
- sequence-set
- """
- rest = ''
- arg = line.split(' ',1)
- if len(arg) == 2:
- rest = arg[1]
- arg = arg[0]
-
- try:
- return (parseIdList(arg), rest)
- except IllegalIdentifierError, e:
- raise IllegalClientResponse("Bad message number " + str(e))
-
- def arg_fetchatt(self, line):
- """
- fetch-att
- """
- p = _FetchParser()
- p.parseString(line)
- return (p.result, '')
-
- def arg_flaglist(self, line):
- """
- Flag part of store-att-flag
- """
- flags = []
- if line[0] == '(':
- if line[-1] != ')':
- raise IllegalClientResponse("Mismatched parenthesis")
- line = line[1:-1]
-
- while line:
- m = self.atomre.search(line)
- if not m:
- raise IllegalClientResponse("Malformed flag")
- if line[0] == '\\' and m.start() == 1:
- flags.append('\\' + m.group('atom'))
- elif m.start() == 0:
- flags.append(m.group('atom'))
- else:
- raise IllegalClientResponse("Malformed flag")
- line = m.group('rest')
-
- return (flags, '')
-
- def arg_line(self, line):
- """
- Command line of UID command
- """
- return (line, '')
-
- def opt_plist(self, line):
- """
- Optional parenthesised list
- """
- if line.startswith('('):
- return self.arg_plist(line)
- else:
- return (None, line)
-
- def opt_datetime(self, line):
- """
- Optional date-time string
- """
- if line.startswith('"'):
- try:
- spam, date, rest = line.split('"',2)
- except IndexError:
- raise IllegalClientResponse("Malformed date-time")
- return (date, rest[1:])
- else:
- return (None, line)
-
- def opt_charset(self, line):
- """
- Optional charset of SEARCH command
- """
- if line[:7].upper() == 'CHARSET':
- arg = line.split(' ',2)
- if len(arg) == 1:
- raise IllegalClientResponse("Missing charset identifier")
- if len(arg) == 2:
- arg.append('')
- spam, arg, rest = arg
- return (arg, rest)
- else:
- return (None, line)
-
- def sendServerGreeting(self):
- msg = '[CAPABILITY %s] %s' % (' '.join(self.listCapabilities()), self.IDENT)
- self.sendPositiveResponse(message=msg)
-
- def sendBadResponse(self, tag = None, message = ''):
- self._respond('BAD', tag, message)
-
- def sendPositiveResponse(self, tag = None, message = ''):
- self._respond('OK', tag, message)
-
- def sendNegativeResponse(self, tag = None, message = ''):
- self._respond('NO', tag, message)
-
- def sendUntaggedResponse(self, message, async=False):
- if not async or (self.blocked is None):
- self._respond(message, None, None)
- else:
- self._queuedAsync.append(message)
-
- def sendContinuationRequest(self, msg = 'Ready for additional command text'):
- if msg:
- self.sendLine('+ ' + msg)
- else:
- self.sendLine('+')
-
- def _respond(self, state, tag, message):
- if state in ('OK', 'NO', 'BAD') and self._queuedAsync:
- lines = self._queuedAsync
- self._queuedAsync = []
- for msg in lines:
- self._respond(msg, None, None)
- if not tag:
- tag = '*'
- if message:
- self.sendLine(' '.join((tag, state, message)))
- else:
- self.sendLine(' '.join((tag, state)))
-
- def listCapabilities(self):
- caps = ['IMAP4rev1']
- for c, v in self.capabilities().iteritems():
- if v is None:
- caps.append(c)
- elif len(v):
- caps.extend([('%s=%s' % (c, cap)) for cap in v])
- return caps
-
- def do_CAPABILITY(self, tag):
- self.sendUntaggedResponse('CAPABILITY ' + ' '.join(self.listCapabilities()))
- self.sendPositiveResponse(tag, 'CAPABILITY completed')
-
- unauth_CAPABILITY = (do_CAPABILITY,)
- auth_CAPABILITY = unauth_CAPABILITY
- select_CAPABILITY = unauth_CAPABILITY
- logout_CAPABILITY = unauth_CAPABILITY
-
- def do_LOGOUT(self, tag):
- self.sendUntaggedResponse('BYE Nice talking to you')
- self.sendPositiveResponse(tag, 'LOGOUT successful')
- self.transport.loseConnection()
-
- unauth_LOGOUT = (do_LOGOUT,)
- auth_LOGOUT = unauth_LOGOUT
- select_LOGOUT = unauth_LOGOUT
- logout_LOGOUT = unauth_LOGOUT
-
- def do_NOOP(self, tag):
- self.sendPositiveResponse(tag, 'NOOP No operation performed')
-
- unauth_NOOP = (do_NOOP,)
- auth_NOOP = unauth_NOOP
- select_NOOP = unauth_NOOP
- logout_NOOP = unauth_NOOP
-
- def do_AUTHENTICATE(self, tag, args):
- args = args.upper().strip()
- if args not in self.challengers:
- self.sendNegativeResponse(tag, 'AUTHENTICATE method unsupported')
- else:
- self.authenticate(self.challengers[args](), tag)
-
- unauth_AUTHENTICATE = (do_AUTHENTICATE, arg_atom)
-
- def authenticate(self, chal, tag):
- if self.portal is None:
- self.sendNegativeResponse(tag, 'Temporary authentication failure')
- return
-
- self._setupChallenge(chal, tag)
-
- def _setupChallenge(self, chal, tag):
- try:
- challenge = chal.getChallenge()
- except Exception, e:
- self.sendBadResponse(tag, 'Server error: ' + str(e))
- else:
- coded = base64.encodestring(challenge)[:-1]
- self.parseState = 'pending'
- self._pendingLiteral = defer.Deferred()
- self.sendContinuationRequest(coded)
- self._pendingLiteral.addCallback(self.__cbAuthChunk, chal, tag)
- self._pendingLiteral.addErrback(self.__ebAuthChunk, tag)
-
- def __cbAuthChunk(self, result, chal, tag):
- try:
- uncoded = base64.decodestring(result)
- except binascii.Error:
- raise IllegalClientResponse("Malformed Response - not base64")
-
- chal.setResponse(uncoded)
- if chal.moreChallenges():
- self._setupChallenge(chal, tag)
- else:
- self.portal.login(chal, None, IAccount).addCallbacks(
- self.__cbAuthResp,
- self.__ebAuthResp,
- (tag,), None, (tag,), None
- )
-
- def __cbAuthResp(self, (iface, avatar, logout), tag):
- assert iface is IAccount, "IAccount is the only supported interface"
- self.account = avatar
- self.state = 'auth'
- self._onLogout = logout
- self.sendPositiveResponse(tag, 'Authentication successful')
- self.setTimeout(self.POSTAUTH_TIMEOUT)
-
- def __ebAuthResp(self, failure, tag):
- if failure.check(cred.error.UnauthorizedLogin):
- self.sendNegativeResponse(tag, 'Authentication failed: unauthorized')
- elif failure.check(cred.error.UnhandledCredentials):
- self.sendNegativeResponse(tag, 'Authentication failed: server misconfigured')
- else:
- self.sendBadResponse(tag, 'Server error: login failed unexpectedly')
- log.err(failure)
-
- def __ebAuthChunk(self, failure, tag):
- self.sendNegativeResponse(tag, 'Authentication failed: ' + str(failure.value))
-
- def do_STARTTLS(self, tag):
- if self.startedTLS:
- self.sendNegativeResponse(tag, 'TLS already negotiated')
- elif self.ctx and self.canStartTLS:
- self.sendPositiveResponse(tag, 'Begin TLS negotiation now')
- self.transport.startTLS(self.ctx)
- self.startedTLS = True
- self.challengers = self.challengers.copy()
- if 'LOGIN' not in self.challengers:
- self.challengers['LOGIN'] = LOGINCredentials
- if 'PLAIN' not in self.challengers:
- self.challengers['PLAIN'] = PLAINCredentials
- else:
- self.sendNegativeResponse(tag, 'TLS not available')
-
- unauth_STARTTLS = (do_STARTTLS,)
-
- def do_LOGIN(self, tag, user, passwd):
- if 'LOGINDISABLED' in self.capabilities():
- self.sendBadResponse(tag, 'LOGIN is disabled before STARTTLS')
- return
-
- maybeDeferred(self.authenticateLogin, user, passwd
- ).addCallback(self.__cbLogin, tag
- ).addErrback(self.__ebLogin, tag
- )
-
- unauth_LOGIN = (do_LOGIN, arg_astring, arg_astring)
-
- def authenticateLogin(self, user, passwd):
- """Lookup the account associated with the given parameters
-
- Override this method to define the desired authentication behavior.
-
- The default behavior is to defer authentication to C{self.portal}
- if it is not None, or to deny the login otherwise.
-
- @type user: C{str}
- @param user: The username to lookup
-
- @type passwd: C{str}
- @param passwd: The password to login with
- """
- if self.portal:
- return self.portal.login(
- cred.credentials.UsernamePassword(user, passwd),
- None, IAccount
- )
- raise cred.error.UnauthorizedLogin()
-
- def __cbLogin(self, (iface, avatar, logout), tag):
- if iface is not IAccount:
- self.sendBadResponse(tag, 'Server error: login returned unexpected value')
- log.err("__cbLogin called with %r, IAccount expected" % (iface,))
- else:
- self.account = avatar
- self._onLogout = logout
- self.sendPositiveResponse(tag, 'LOGIN succeeded')
- self.state = 'auth'
- self.setTimeout(self.POSTAUTH_TIMEOUT)
-
- def __ebLogin(self, failure, tag):
- if failure.check(cred.error.UnauthorizedLogin):
- self.sendNegativeResponse(tag, 'LOGIN failed')
- else:
- self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
- log.err(failure)
-
- def do_NAMESPACE(self, tag):
- personal = public = shared = None
- np = INamespacePresenter(self.account, None)
- if np is not None:
- personal = np.getPersonalNamespaces()
- public = np.getSharedNamespaces()
- shared = np.getSharedNamespaces()
- self.sendUntaggedResponse('NAMESPACE ' + collapseNestedLists([personal, public, shared]))
- self.sendPositiveResponse(tag, "NAMESPACE command completed")
-
- auth_NAMESPACE = (do_NAMESPACE,)
- select_NAMESPACE = auth_NAMESPACE
-
- def _parseMbox(self, name):
- if isinstance(name, unicode):
- return name
- try:
- return name.decode('imap4-utf-7')
- except:
- log.err()
- raise IllegalMailboxEncoding(name)
-
- def _selectWork(self, tag, name, rw, cmdName):
- if self.mbox:
- self.mbox.removeListener(self)
- cmbx = ICloseableMailbox(self.mbox, None)
- if cmbx is not None:
- maybeDeferred(cmbx.close).addErrback(log.err)
- self.mbox = None
- self.state = 'auth'
-
- name = self._parseMbox(name)
- maybeDeferred(self.account.select, self._parseMbox(name), rw
- ).addCallback(self._cbSelectWork, cmdName, tag
- ).addErrback(self._ebSelectWork, cmdName, tag
- )
-
- def _ebSelectWork(self, failure, cmdName, tag):
- self.sendBadResponse(tag, "%s failed: Server error" % (cmdName,))
- log.err(failure)
-
- def _cbSelectWork(self, mbox, cmdName, tag):
- if mbox is None:
- self.sendNegativeResponse(tag, 'No such mailbox')
- return
- if '\\noselect' in [s.lower() for s in mbox.getFlags()]:
- self.sendNegativeResponse(tag, 'Mailbox cannot be selected')
- return
-
- flags = mbox.getFlags()
- self.sendUntaggedResponse(str(mbox.getMessageCount()) + ' EXISTS')
- self.sendUntaggedResponse(str(mbox.getRecentCount()) + ' RECENT')
- self.sendUntaggedResponse('FLAGS (%s)' % ' '.join(flags))
- self.sendPositiveResponse(None, '[UIDVALIDITY %d]' % mbox.getUIDValidity())
-
- s = mbox.isWriteable() and 'READ-WRITE' or 'READ-ONLY'
- mbox.addListener(self)
- self.sendPositiveResponse(tag, '[%s] %s successful' % (s, cmdName))
- self.state = 'select'
- self.mbox = mbox
-
- auth_SELECT = ( _selectWork, arg_astring, 1, 'SELECT' )
- select_SELECT = auth_SELECT
-
- auth_EXAMINE = ( _selectWork, arg_astring, 0, 'EXAMINE' )
- select_EXAMINE = auth_EXAMINE
-
-
- def do_IDLE(self, tag):
- self.sendContinuationRequest(None)
- self.parseTag = tag
- self.lastState = self.parseState
- self.parseState = 'idle'
-
- def parse_idle(self, *args):
- self.parseState = self.lastState
- del self.lastState
- self.sendPositiveResponse(self.parseTag, "IDLE terminated")
- del self.parseTag
-
- select_IDLE = ( do_IDLE, )
- auth_IDLE = select_IDLE
-
-
- def do_CREATE(self, tag, name):
- name = self._parseMbox(name)
- try:
- result = self.account.create(name)
- except MailboxException, c:
- self.sendNegativeResponse(tag, str(c))
- except:
- self.sendBadResponse(tag, "Server error encountered while creating mailbox")
- log.err()
- else:
- if result:
- self.sendPositiveResponse(tag, 'Mailbox created')
- else:
- self.sendNegativeResponse(tag, 'Mailbox not created')
-
- auth_CREATE = (do_CREATE, arg_astring)
- select_CREATE = auth_CREATE
-
- def do_DELETE(self, tag, name):
- name = self._parseMbox(name)
- if name.lower() == 'inbox':
- self.sendNegativeResponse(tag, 'You cannot delete the inbox')
- return
- try:
- self.account.delete(name)
- except MailboxException, m:
- self.sendNegativeResponse(tag, str(m))
- except:
- self.sendBadResponse(tag, "Server error encountered while deleting mailbox")
- log.err()
- else:
- self.sendPositiveResponse(tag, 'Mailbox deleted')
-
- auth_DELETE = (do_DELETE, arg_astring)
- select_DELETE = auth_DELETE
-
- def do_RENAME(self, tag, oldname, newname):
- oldname, newname = [self._parseMbox(n) for n in oldname, newname]
- if oldname.lower() == 'inbox' or newname.lower() == 'inbox':
- self.sendNegativeResponse(tag, 'You cannot rename the inbox, or rename another mailbox to inbox.')
- return
- try:
- self.account.rename(oldname, newname)
- except TypeError:
- self.sendBadResponse(tag, 'Invalid command syntax')
- except MailboxException, m:
- self.sendNegativeResponse(tag, str(m))
- except:
- self.sendBadResponse(tag, "Server error encountered while renaming mailbox")
- log.err()
- else:
- self.sendPositiveResponse(tag, 'Mailbox renamed')
-
- auth_RENAME = (do_RENAME, arg_astring, arg_astring)
- select_RENAME = auth_RENAME
-
- def do_SUBSCRIBE(self, tag, name):
- name = self._parseMbox(name)
- try:
- self.account.subscribe(name)
- except MailboxException, m:
- self.sendNegativeResponse(tag, str(m))
- except:
- self.sendBadResponse(tag, "Server error encountered while subscribing to mailbox")
- log.err()
- else:
- self.sendPositiveResponse(tag, 'Subscribed')
-
- auth_SUBSCRIBE = (do_SUBSCRIBE, arg_astring)
- select_SUBSCRIBE = auth_SUBSCRIBE
-
- def do_UNSUBSCRIBE(self, tag, name):
- name = self._parseMbox(name)
- try:
- self.account.unsubscribe(name)
- except MailboxException, m:
- self.sendNegativeResponse(tag, str(m))
- except:
- self.sendBadResponse(tag, "Server error encountered while unsubscribing from mailbox")
- log.err()
- else:
- self.sendPositiveResponse(tag, 'Unsubscribed')
-
- auth_UNSUBSCRIBE = (do_UNSUBSCRIBE, arg_astring)
- select_UNSUBSCRIBE = auth_UNSUBSCRIBE
-
- def _listWork(self, tag, ref, mbox, sub, cmdName):
- mbox = self._parseMbox(mbox)
- maybeDeferred(self.account.listMailboxes, ref, mbox
- ).addCallback(self._cbListWork, tag, sub, cmdName
- ).addErrback(self._ebListWork, tag
- )
-
- def _cbListWork(self, mailboxes, tag, sub, cmdName):
- for (name, box) in mailboxes:
- if not sub or self.account.isSubscribed(name):
- flags = box.getFlags()
- delim = box.getHierarchicalDelimiter()
- resp = (DontQuoteMe(cmdName), map(DontQuoteMe, flags), delim, name.encode('imap4-utf-7'))
- self.sendUntaggedResponse(collapseNestedLists(resp))
- self.sendPositiveResponse(tag, '%s completed' % (cmdName,))
-
- def _ebListWork(self, failure, tag):
- self.sendBadResponse(tag, "Server error encountered while listing mailboxes.")
- log.err(failure)
-
- auth_LIST = (_listWork, arg_astring, arg_astring, 0, 'LIST')
- select_LIST = auth_LIST
-
- auth_LSUB = (_listWork, arg_astring, arg_astring, 1, 'LSUB')
- select_LSUB = auth_LSUB
-
- def do_STATUS(self, tag, mailbox, names):
- mailbox = self._parseMbox(mailbox)
- maybeDeferred(self.account.select, mailbox, 0
- ).addCallback(self._cbStatusGotMailbox, tag, mailbox, names
- ).addErrback(self._ebStatusGotMailbox, tag
- )
-
- def _cbStatusGotMailbox(self, mbox, tag, mailbox, names):
- if mbox:
- maybeDeferred(mbox.requestStatus, names).addCallbacks(
- self.__cbStatus, self.__ebStatus,
- (tag, mailbox), None, (tag, mailbox), None
- )
- else:
- self.sendNegativeResponse(tag, "Could not open mailbox")
-
- def _ebStatusGotMailbox(self, failure, tag):
- self.sendBadResponse(tag, "Server error encountered while opening mailbox.")
- log.err(failure)
-
- auth_STATUS = (do_STATUS, arg_astring, arg_plist)
- select_STATUS = auth_STATUS
-
- def __cbStatus(self, status, tag, box):
- line = ' '.join(['%s %s' % x for x in status.iteritems()])
- self.sendUntaggedResponse('STATUS %s (%s)' % (box, line))
- self.sendPositiveResponse(tag, 'STATUS complete')
-
- def __ebStatus(self, failure, tag, box):
- self.sendBadResponse(tag, 'STATUS %s failed: %s' % (box, str(failure.value)))
-
- def do_APPEND(self, tag, mailbox, flags, date, message):
- mailbox = self._parseMbox(mailbox)
- maybeDeferred(self.account.select, mailbox
- ).addCallback(self._cbAppendGotMailbox, tag, flags, date, message
- ).addErrback(self._ebAppendGotMailbox, tag
- )
-
- def _cbAppendGotMailbox(self, mbox, tag, flags, date, message):
- if not mbox:
- self.sendNegativeResponse(tag, '[TRYCREATE] No such mailbox')
- return
-
- d = mbox.addMessage(message, flags, date)
- d.addCallback(self.__cbAppend, tag, mbox)
- d.addErrback(self.__ebAppend, tag)
-
- def _ebAppendGotMailbox(self, failure, tag):
- self.sendBadResponse(tag, "Server error encountered while opening mailbox.")
- log.err(failure)
-
- auth_APPEND = (do_APPEND, arg_astring, opt_plist, opt_datetime,
- arg_literal)
- select_APPEND = auth_APPEND
-
- def __cbAppend(self, result, tag, mbox):
- self.sendUntaggedResponse('%d EXISTS' % mbox.getMessageCount())
- self.sendPositiveResponse(tag, 'APPEND complete')
-
- def __ebAppend(self, failure, tag):
- self.sendBadResponse(tag, 'APPEND failed: ' + str(failure.value))
-
- def do_CHECK(self, tag):
- d = self.checkpoint()
- if d is None:
- self.__cbCheck(None, tag)
- else:
- d.addCallbacks(
- self.__cbCheck,
- self.__ebCheck,
- callbackArgs=(tag,),
- errbackArgs=(tag,)
- )
- select_CHECK = (do_CHECK,)
-
- def __cbCheck(self, result, tag):
- self.sendPositiveResponse(tag, 'CHECK completed')
-
- def __ebCheck(self, failure, tag):
- self.sendBadResponse(tag, 'CHECK failed: ' + str(failure.value))
-
- def checkpoint(self):
- """Called when the client issues a CHECK command.
-
- This should perform any checkpoint operations required by the server.
- It may be a long running operation, but may not block. If it returns
- a deferred, the client will only be informed of success (or failure)
- when the deferred's callback (or errback) is invoked.
- """
- return None
-
- def do_CLOSE(self, tag):
- d = None
- if self.mbox.isWriteable():
- d = maybeDeferred(self.mbox.expunge)
- cmbx = ICloseableMailbox(self.mbox, None)
- if cmbx is not None:
- if d is not None:
- d.addCallback(lambda result: cmbx.close())
- else:
- d = maybeDeferred(cmbx.close)
- if d is not None:
- d.addCallbacks(self.__cbClose, self.__ebClose, (tag,), None, (tag,), None)
- else:
- self.__cbClose(None, tag)
-
- select_CLOSE = (do_CLOSE,)
-
- def __cbClose(self, result, tag):
- self.sendPositiveResponse(tag, 'CLOSE completed')
- self.mbox.removeListener(self)
- self.mbox = None
- self.state = 'auth'
-
- def __ebClose(self, failure, tag):
- self.sendBadResponse(tag, 'CLOSE failed: ' + str(failure.value))
-
- def do_EXPUNGE(self, tag):
- if self.mbox.isWriteable():
- maybeDeferred(self.mbox.expunge).addCallbacks(
- self.__cbExpunge, self.__ebExpunge, (tag,), None, (tag,), None
- )
- else:
- self.sendNegativeResponse(tag, 'EXPUNGE ignored on read-only mailbox')
-
- select_EXPUNGE = (do_EXPUNGE,)
-
- def __cbExpunge(self, result, tag):
- for e in result:
- self.sendUntaggedResponse('%d EXPUNGE' % e)
- self.sendPositiveResponse(tag, 'EXPUNGE completed')
-
- def __ebExpunge(self, failure, tag):
- self.sendBadResponse(tag, 'EXPUNGE failed: ' + str(failure.value))
- log.err(failure)
-
- def do_SEARCH(self, tag, charset, query, uid=0):
- sm = ISearchableMailbox(self.mbox, None)
- if sm is not None:
- maybeDeferred(sm.search, query, uid=uid
- ).addCallback(self.__cbSearch, tag, self.mbox, uid
- ).addErrback(self.__ebSearch, tag)
- else:
- # that's not the ideal way to get all messages, there should be a
- # method on mailboxes that gives you all of them
- s = parseIdList('1:*')
- maybeDeferred(self.mbox.fetch, s, uid=uid
- ).addCallback(self.__cbManualSearch,
- tag, self.mbox, query, uid
- ).addErrback(self.__ebSearch, tag)
-
-
- select_SEARCH = (do_SEARCH, opt_charset, arg_searchkeys)
-
- def __cbSearch(self, result, tag, mbox, uid):
- if uid:
- result = map(mbox.getUID, result)
- ids = ' '.join([str(i) for i in result])
- self.sendUntaggedResponse('SEARCH ' + ids)
- self.sendPositiveResponse(tag, 'SEARCH completed')
-
-
- def __cbManualSearch(self, result, tag, mbox, query, uid,
- searchResults=None):
- """
- Apply the search filter to a set of messages. Send the response to the
- client.
-
- @type result: C{list} of C{tuple} of (C{int}, provider of
- L{imap4.IMessage})
- @param result: A list two tuples of messages with their sequence ids,
- sorted by the ids in descending order.
-
- @type tag: C{str}
- @param tag: A command tag.
-
- @type mbox: Provider of L{imap4.IMailbox}
- @param mbox: The searched mailbox.
-
- @type query: C{list}
- @param query: A list representing the parsed form of the search query.
-
- @param uid: A flag indicating whether the search is over message
- sequence numbers or UIDs.
-
- @type searchResults: C{list}
- @param searchResults: The search results so far or C{None} if no
- results yet.
- """
- if searchResults is None:
- searchResults = []
- i = 0
-
- # result is a list of tuples (sequenceId, Message)
- lastSequenceId = result and result[-1][0]
- lastMessageId = result and result[-1][1].getUID()
-
- for (i, (id, msg)) in zip(range(5), result):
- # searchFilter and singleSearchStep will mutate the query. Dang.
- # Copy it here or else things will go poorly for subsequent
- # messages.
- if self._searchFilter(copy.deepcopy(query), id, msg,
- lastSequenceId, lastMessageId):
- if uid:
- searchResults.append(str(msg.getUID()))
- else:
- searchResults.append(str(id))
- if i == 4:
- from twisted.internet import reactor
- reactor.callLater(
- 0, self.__cbManualSearch, result[5:], tag, mbox, query, uid,
- searchResults)
- else:
- if searchResults:
- self.sendUntaggedResponse('SEARCH ' + ' '.join(searchResults))
- self.sendPositiveResponse(tag, 'SEARCH completed')
-
-
- def _searchFilter(self, query, id, msg, lastSequenceId, lastMessageId):
- """
- Pop search terms from the beginning of C{query} until there are none
- left and apply them to the given message.
-
- @param query: A list representing the parsed form of the search query.
-
- @param id: The sequence number of the message being checked.
-
- @param msg: The message being checked.
-
- @type lastSequenceId: C{int}
- @param lastSequenceId: The highest sequence number of any message in
- the mailbox being searched.
-
- @type lastMessageId: C{int}
- @param lastMessageId: The highest UID of any message in the mailbox
- being searched.
-
- @return: Boolean indicating whether all of the query terms match the
- message.
- """
- while query:
- if not self._singleSearchStep(query, id, msg,
- lastSequenceId, lastMessageId):
- return False
- return True
-
-
- def _singleSearchStep(self, query, id, msg, lastSequenceId, lastMessageId):
- """
- Pop one search term from the beginning of C{query} (possibly more than
- one element) and return whether it matches the given message.
-
- @param query: A list representing the parsed form of the search query.
-
- @param id: The sequence number of the message being checked.
-
- @param msg: The message being checked.
-
- @param lastSequenceId: The highest sequence number of any message in
- the mailbox being searched.
-
- @param lastMessageId: The highest UID of any message in the mailbox
- being searched.
-
- @return: Boolean indicating whether the query term matched the message.
- """
-
- q = query.pop(0)
- if isinstance(q, list):
- if not self._searchFilter(q, id, msg,
- lastSequenceId, lastMessageId):
- return False
- else:
- c = q.upper()
- if not c[:1].isalpha():
- # A search term may be a word like ALL, ANSWERED, BCC, etc (see
- # below) or it may be a message sequence set. Here we
- # recognize a message sequence set "N:M".
- messageSet = parseIdList(c, lastSequenceId)
- return id in messageSet
- else:
- f = getattr(self, 'search_' + c, None)
- if f is None:
- raise IllegalQueryError("Invalid search command %s" % c)
-
- if c in self._requiresLastMessageInfo:
- result = f(query, id, msg, (lastSequenceId,
- lastMessageId))
- else:
- result = f(query, id, msg)
-
- if not result:
- return False
- return True
-
- def search_ALL(self, query, id, msg):
- """
- Returns C{True} if the message matches the ALL search key (always).
-
- @type query: A C{list} of C{str}
- @param query: A list representing the parsed query string.
-
- @type id: C{int}
- @param id: The sequence number of the message being checked.
-
- @type msg: Provider of L{imap4.IMessage}
- """
- return True
-
- def search_ANSWERED(self, query, id, msg):
- """
- Returns C{True} if the message has been answered.
-
- @type query: A C{list} of C{str}
- @param query: A list representing the parsed query string.
-
- @type id: C{int}
- @param id: The sequence number of the message being checked.
-
- @type msg: Provider of L{imap4.IMessage}
- """
- return '\\Answered' in msg.getFlags()
-
- def search_BCC(self, query, id, msg):
- """
- Returns C{True} if the message has a BCC address matching the query.
-
- @type query: A C{list} of C{str}
- @param query: A list whose first element is a BCC C{str}
-
- @type id: C{int}
- @param id: The sequence number of the message being checked.
-
- @type msg: Provider of L{imap4.IMessage}
- """
- bcc = msg.getHeaders(False, 'bcc').get('bcc', '')
- return bcc.lower().find(query.pop(0).lower()) != -1
-
- def search_BEFORE(self, query, id, msg):
- date = parseTime(query.pop(0))
- return rfc822.parsedate(msg.getInternalDate()) < date
-
- def search_BODY(self, query, id, msg):
- body = query.pop(0).lower()
- return text.strFile(body, msg.getBodyFile(), False)
-
- def search_CC(self, query, id, msg):
- cc = msg.getHeaders(False, 'cc').get('cc', '')
- return cc.lower().find(query.pop(0).lower()) != -1
-
- def search_DELETED(self, query, id, msg):
- return '\\Deleted' in msg.getFlags()
-
- def search_DRAFT(self, query, id, msg):
- return '\\Draft' in msg.getFlags()
-
- def search_FLAGGED(self, query, id, msg):
- return '\\Flagged' in msg.getFlags()
-
- def search_FROM(self, query, id, msg):
- fm = msg.getHeaders(False, 'from').get('from', '')
- return fm.lower().find(query.pop(0).lower()) != -1
-
- def search_HEADER(self, query, id, msg):
- hdr = query.pop(0).lower()
- hdr = msg.getHeaders(False, hdr).get(hdr, '')
- return hdr.lower().find(query.pop(0).lower()) != -1
-
- def search_KEYWORD(self, query, id, msg):
- query.pop(0)
- return False
-
- def search_LARGER(self, query, id, msg):
- return int(query.pop(0)) < msg.getSize()
-
- def search_NEW(self, query, id, msg):
- return '\\Recent' in msg.getFlags() and '\\Seen' not in msg.getFlags()
-
- def search_NOT(self, query, id, msg, (lastSequenceId, lastMessageId)):
- """
- Returns C{True} if the message does not match the query.
-
- @type query: A C{list} of C{str}
- @param query: A list representing the parsed form of the search query.
-
- @type id: C{int}
- @param id: The sequence number of the message being checked.
-
- @type msg: Provider of L{imap4.IMessage}
- @param msg: The message being checked.
-
- @type lastSequenceId: C{int}
- @param lastSequenceId: The highest sequence number of a message in the
- mailbox.
-
- @type lastMessageId: C{int}
- @param lastMessageId: The highest UID of a message in the mailbox.
- """
- return not self._singleSearchStep(query, id, msg,
- lastSequenceId, lastMessageId)
-
- def search_OLD(self, query, id, msg):
- return '\\Recent' not in msg.getFlags()
-
- def search_ON(self, query, id, msg):
- date = parseTime(query.pop(0))
- return rfc822.parsedate(msg.getInternalDate()) == date
-
- def search_OR(self, query, id, msg, (lastSequenceId, lastMessageId)):
- """
- Returns C{True} if the message matches any of the first two query
- items.
-
- @type query: A C{list} of C{str}
- @param query: A list representing the parsed form of the search query.
-
- @type id: C{int}
- @param id: The sequence number of the message being checked.
-
- @type msg: Provider of L{imap4.IMessage}
- @param msg: The message being checked.
-
- @type lastSequenceId: C{int}
- @param lastSequenceId: The highest sequence number of a message in the
- mailbox.
-
- @type lastMessageId: C{int}
- @param lastMessageId: The highest UID of a message in the mailbox.
- """
- a = self._singleSearchStep(query, id, msg,
- lastSequenceId, lastMessageId)
- b = self._singleSearchStep(query, id, msg,
- lastSequenceId, lastMessageId)
- return a or b
-
- def search_RECENT(self, query, id, msg):
- return '\\Recent' in msg.getFlags()
-
- def search_SEEN(self, query, id, msg):
- return '\\Seen' in msg.getFlags()
-
- def search_SENTBEFORE(self, query, id, msg):
- """
- Returns C{True} if the message date is earlier than the query date.
-
- @type query: A C{list} of C{str}
- @param query: A list whose first element starts with a stringified date
- that is a fragment of an L{imap4.Query()}. The date must be in the
- format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
-
- @type id: C{int}
- @param id: The sequence number of the message being checked.
-
- @type msg: Provider of L{imap4.IMessage}
- """
- date = msg.getHeaders(False, 'date').get('date', '')
- date = rfc822.parsedate(date)
- return date < parseTime(query.pop(0))
-
- def search_SENTON(self, query, id, msg):
- """
- Returns C{True} if the message date is the same as the query date.
-
- @type query: A C{list} of C{str}
- @param query: A list whose first element starts with a stringified date
- that is a fragment of an L{imap4.Query()}. The date must be in the
- format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
-
- @type msg: Provider of L{imap4.IMessage}
- """
- date = msg.getHeaders(False, 'date').get('date', '')
- date = rfc822.parsedate(date)
- return date[:3] == parseTime(query.pop(0))[:3]
-
- def search_SENTSINCE(self, query, id, msg):
- """
- Returns C{True} if the message date is later than the query date.
-
- @type query: A C{list} of C{str}
- @param query: A list whose first element starts with a stringified date
- that is a fragment of an L{imap4.Query()}. The date must be in the
- format 'DD-Mon-YYYY', for example '03-March-2003' or '03-Mar-2003'.
-
- @type msg: Provider of L{imap4.IMessage}
- """
- date = msg.getHeaders(False, 'date').get('date', '')
- date = rfc822.parsedate(date)
- return date > parseTime(query.pop(0))
-
- def search_SINCE(self, query, id, msg):
- date = parseTime(query.pop(0))
- return rfc822.parsedate(msg.getInternalDate()) > date
-
- def search_SMALLER(self, query, id, msg):
- return int(query.pop(0)) > msg.getSize()
-
- def search_SUBJECT(self, query, id, msg):
- subj = msg.getHeaders(False, 'subject').get('subject', '')
- return subj.lower().find(query.pop(0).lower()) != -1
-
- def search_TEXT(self, query, id, msg):
- # XXX - This must search headers too
- body = query.pop(0).lower()
- return text.strFile(body, msg.getBodyFile(), False)
-
- def search_TO(self, query, id, msg):
- to = msg.getHeaders(False, 'to').get('to', '')
- return to.lower().find(query.pop(0).lower()) != -1
-
- def search_UID(self, query, id, msg, (lastSequenceId, lastMessageId)):
- """
- Returns C{True} if the message UID is in the range defined by the
- search query.
-
- @type query: A C{list} of C{str}
- @param query: A list representing the parsed form of the search
- query. Its first element should be a C{str} that can be interpreted
- as a sequence range, for example '2:4,5:*'.
-
- @type id: C{int}
- @param id: The sequence number of the message being checked.
-
- @type msg: Provider of L{imap4.IMessage}
- @param msg: The message being checked.
-
- @type lastSequenceId: C{int}
- @param lastSequenceId: The highest sequence number of a message in the
- mailbox.
-
- @type lastMessageId: C{int}
- @param lastMessageId: The highest UID of a message in the mailbox.
- """
- c = query.pop(0)
- m = parseIdList(c, lastMessageId)
- return msg.getUID() in m
-
- def search_UNANSWERED(self, query, id, msg):
- return '\\Answered' not in msg.getFlags()
-
- def search_UNDELETED(self, query, id, msg):
- return '\\Deleted' not in msg.getFlags()
-
- def search_UNDRAFT(self, query, id, msg):
- return '\\Draft' not in msg.getFlags()
-
- def search_UNFLAGGED(self, query, id, msg):
- return '\\Flagged' not in msg.getFlags()
-
- def search_UNKEYWORD(self, query, id, msg):
- query.pop(0)
- return False
-
- def search_UNSEEN(self, query, id, msg):
- return '\\Seen' not in msg.getFlags()
-
- def __ebSearch(self, failure, tag):
- self.sendBadResponse(tag, 'SEARCH failed: ' + str(failure.value))
- log.err(failure)
-
- def do_FETCH(self, tag, messages, query, uid=0):
- if query:
- self._oldTimeout = self.setTimeout(None)
- maybeDeferred(self.mbox.fetch, messages, uid=uid
- ).addCallback(iter
- ).addCallback(self.__cbFetch, tag, query, uid
- ).addErrback(self.__ebFetch, tag
- )
- else:
- self.sendPositiveResponse(tag, 'FETCH complete')
-
- select_FETCH = (do_FETCH, arg_seqset, arg_fetchatt)
-
- def __cbFetch(self, results, tag, query, uid):
- if self.blocked is None:
- self.blocked = []
- try:
- id, msg = results.next()
- except StopIteration:
- # The idle timeout was suspended while we delivered results,
- # restore it now.
- self.setTimeout(self._oldTimeout)
- del self._oldTimeout
-
- # All results have been processed, deliver completion notification.
-
- # It's important to run this *after* resetting the timeout to "rig
- # a race" in some test code. writing to the transport will
- # synchronously call test code, which synchronously loses the
- # connection, calling our connectionLost method, which cancels the
- # timeout. We want to make sure that timeout is cancelled *after*
- # we reset it above, so that the final state is no timed
- # calls. This avoids reactor uncleanliness errors in the test
- # suite.
- # XXX: Perhaps loopback should be fixed to not call the user code
- # synchronously in transport.write?
- self.sendPositiveResponse(tag, 'FETCH completed')
-
- # Instance state is now consistent again (ie, it is as though
- # the fetch command never ran), so allow any pending blocked
- # commands to execute.
- self._unblock()
- else:
- self.spewMessage(id, msg, query, uid
- ).addCallback(lambda _: self.__cbFetch(results, tag, query, uid)
- ).addErrback(self.__ebSpewMessage
- )
-
- def __ebSpewMessage(self, failure):
- # This indicates a programming error.
- # There's no reliable way to indicate anything to the client, since we
- # may have already written an arbitrary amount of data in response to
- # the command.
- log.err(failure)
- self.transport.loseConnection()
-
- def spew_envelope(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- _w('ENVELOPE ' + collapseNestedLists([getEnvelope(msg)]))
-
- def spew_flags(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- _w('FLAGS ' + '(%s)' % (' '.join(msg.getFlags())))
-
- def spew_internaldate(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- idate = msg.getInternalDate()
- ttup = rfc822.parsedate_tz(idate)
- if ttup is None:
- log.msg("%d:%r: unpareseable internaldate: %r" % (id, msg, idate))
- raise IMAP4Exception("Internal failure generating INTERNALDATE")
-
- # need to specify the month manually, as strftime depends on locale
- strdate = time.strftime("%d-%%s-%Y %H:%M:%S ", ttup[:9])
- odate = strdate % (_MONTH_NAMES[ttup[1]],)
- if ttup[9] is None:
- odate = odate + "+0000"
- else:
- if ttup[9] >= 0:
- sign = "+"
- else:
- sign = "-"
- odate = odate + sign + str(((abs(ttup[9]) // 3600) * 100 + (abs(ttup[9]) % 3600) // 60)).zfill(4)
- _w('INTERNALDATE ' + _quote(odate))
-
- def spew_rfc822header(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- hdrs = _formatHeaders(msg.getHeaders(True))
- _w('RFC822.HEADER ' + _literal(hdrs))
-
- def spew_rfc822text(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- _w('RFC822.TEXT ')
- _f()
- return FileProducer(msg.getBodyFile()
- ).beginProducing(self.transport
- )
-
- def spew_rfc822size(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- _w('RFC822.SIZE ' + str(msg.getSize()))
-
- def spew_rfc822(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- _w('RFC822 ')
- _f()
- mf = IMessageFile(msg, None)
- if mf is not None:
- return FileProducer(mf.open()
- ).beginProducing(self.transport
- )
- return MessageProducer(msg, None, self._scheduler
- ).beginProducing(self.transport
- )
-
- def spew_uid(self, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- _w('UID ' + str(msg.getUID()))
-
- def spew_bodystructure(self, id, msg, _w=None, _f=None):
- _w('BODYSTRUCTURE ' + collapseNestedLists([getBodyStructure(msg, True)]))
-
- def spew_body(self, part, id, msg, _w=None, _f=None):
- if _w is None:
- _w = self.transport.write
- for p in part.part:
- if msg.isMultipart():
- msg = msg.getSubPart(p)
- elif p > 0:
- # Non-multipart messages have an implicit first part but no
- # other parts - reject any request for any other part.
- raise TypeError("Requested subpart of non-multipart message")
-
- if part.header:
- hdrs = msg.getHeaders(part.header.negate, *part.header.fields)
- hdrs = _formatHeaders(hdrs)
- _w(str(part) + ' ' + _literal(hdrs))
- elif part.text:
- _w(str(part) + ' ')
- _f()
- return FileProducer(msg.getBodyFile()
- ).beginProducing(self.transport
- )
- elif part.mime:
- hdrs = _formatHeaders(msg.getHeaders(True))
- _w(str(part) + ' ' + _literal(hdrs))
- elif part.empty:
- _w(str(part) + ' ')
- _f()
- if part.part:
- return FileProducer(msg.getBodyFile()
- ).beginProducing(self.transport
- )
- else:
- mf = IMessageFile(msg, None)
- if mf is not None:
- return FileProducer(mf.open()).beginProducing(self.transport)
- return MessageProducer(msg, None, self._scheduler).beginProducing(self.transport)
-
- else:
- _w('BODY ' + collapseNestedLists([getBodyStructure(msg)]))
-
- def spewMessage(self, id, msg, query, uid):
- wbuf = WriteBuffer(self.transport)
- write = wbuf.write
- flush = wbuf.flush
- def start():
- write('* %d FETCH (' % (id,))
- def finish():
- write(')\r\n')
- def space():
- write(' ')
-
- def spew():
- seenUID = False
- start()
- for part in query:
- if part.type == 'uid':
- seenUID = True
- if part.type == 'body':
- yield self.spew_body(part, id, msg, write, flush)
- else:
- f = getattr(self, 'spew_' + part.type)
- yield f(id, msg, write, flush)
- if part is not query[-1]:
- space()
- if uid and not seenUID:
- space()
- yield self.spew_uid(id, msg, write, flush)
- finish()
- flush()
- return self._scheduler(spew())
-
- def __ebFetch(self, failure, tag):
- self.setTimeout(self._oldTimeout)
- del self._oldTimeout
- log.err(failure)
- self.sendBadResponse(tag, 'FETCH failed: ' + str(failure.value))
-
- def do_STORE(self, tag, messages, mode, flags, uid=0):
- mode = mode.upper()
- silent = mode.endswith('SILENT')
- if mode.startswith('+'):
- mode = 1
- elif mode.startswith('-'):
- mode = -1
- else:
- mode = 0
-
- maybeDeferred(self.mbox.store, messages, flags, mode, uid=uid).addCallbacks(
- self.__cbStore, self.__ebStore, (tag, self.mbox, uid, silent), None, (tag,), None
- )
-
- select_STORE = (do_STORE, arg_seqset, arg_atom, arg_flaglist)
-
- def __cbStore(self, result, tag, mbox, uid, silent):
- if result and not silent:
- for (k, v) in result.iteritems():
- if uid:
- uidstr = ' UID %d' % mbox.getUID(k)
- else:
- uidstr = ''
- self.sendUntaggedResponse('%d FETCH (FLAGS (%s)%s)' %
- (k, ' '.join(v), uidstr))
- self.sendPositiveResponse(tag, 'STORE completed')
-
- def __ebStore(self, failure, tag):
- self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
-
- def do_COPY(self, tag, messages, mailbox, uid=0):
- mailbox = self._parseMbox(mailbox)
- maybeDeferred(self.account.select, mailbox
- ).addCallback(self._cbCopySelectedMailbox, tag, messages, mailbox, uid
- ).addErrback(self._ebCopySelectedMailbox, tag
- )
- select_COPY = (do_COPY, arg_seqset, arg_astring)
-
- def _cbCopySelectedMailbox(self, mbox, tag, messages, mailbox, uid):
- if not mbox:
- self.sendNegativeResponse(tag, 'No such mailbox: ' + mailbox)
- else:
- maybeDeferred(self.mbox.fetch, messages, uid
- ).addCallback(self.__cbCopy, tag, mbox
- ).addCallback(self.__cbCopied, tag, mbox
- ).addErrback(self.__ebCopy, tag
- )
-
- def _ebCopySelectedMailbox(self, failure, tag):
- self.sendBadResponse(tag, 'Server error: ' + str(failure.value))
-
- def __cbCopy(self, messages, tag, mbox):
- # XXX - This should handle failures with a rollback or something
- addedDeferreds = []
- addedIDs = []
- failures = []
-
- fastCopyMbox = IMessageCopier(mbox, None)
- for (id, msg) in messages:
- if fastCopyMbox is not None:
- d = maybeDeferred(fastCopyMbox.copy, msg)
- addedDeferreds.append(d)
- continue
-
- # XXX - The following should be an implementation of IMessageCopier.copy
- # on an IMailbox->IMessageCopier adapter.
-
- flags = msg.getFlags()
- date = msg.getInternalDate()
-
- body = IMessageFile(msg, None)
- if body is not None:
- bodyFile = body.open()
- d = maybeDeferred(mbox.addMessage, bodyFile, flags, date)
- else:
- def rewind(f):
- f.seek(0)
- return f
- buffer = tempfile.TemporaryFile()
- d = MessageProducer(msg, buffer, self._scheduler
- ).beginProducing(None
- ).addCallback(lambda _, b=buffer, f=flags, d=date: mbox.addMessage(rewind(b), f, d)
- )
- addedDeferreds.append(d)
- return defer.DeferredList(addedDeferreds)
-
- def __cbCopied(self, deferredIds, tag, mbox):
- ids = []
- failures = []
- for (status, result) in deferredIds:
- if status:
- ids.append(result)
- else:
- failures.append(result.value)
- if failures:
- self.sendNegativeResponse(tag, '[ALERT] Some messages were not copied')
- else:
- self.sendPositiveResponse(tag, 'COPY completed')
-
- def __ebCopy(self, failure, tag):
- self.sendBadResponse(tag, 'COPY failed:' + str(failure.value))
- log.err(failure)
-
- def do_UID(self, tag, command, line):
- command = command.upper()
-
- if command not in ('COPY', 'FETCH', 'STORE', 'SEARCH'):
- raise IllegalClientResponse(command)
-
- self.dispatchCommand(tag, command, line, uid=1)
-
- select_UID = (do_UID, arg_atom, arg_line)
- #
- # IMailboxListener implementation
- #
- def modeChanged(self, writeable):
- if writeable:
- self.sendUntaggedResponse(message='[READ-WRITE]', async=True)
- else:
- self.sendUntaggedResponse(message='[READ-ONLY]', async=True)
-
- def flagsChanged(self, newFlags):
- for (mId, flags) in newFlags.iteritems():
- msg = '%d FETCH (FLAGS (%s))' % (mId, ' '.join(flags))
- self.sendUntaggedResponse(msg, async=True)
-
- def newMessages(self, exists, recent):
- if exists is not None:
- self.sendUntaggedResponse('%d EXISTS' % exists, async=True)
- if recent is not None:
- self.sendUntaggedResponse('%d RECENT' % recent, async=True)
-
-
-class UnhandledResponse(IMAP4Exception): pass
-
-class NegativeResponse(IMAP4Exception): pass
-
-class NoSupportedAuthentication(IMAP4Exception):
- def __init__(self, serverSupports, clientSupports):
- IMAP4Exception.__init__(self, 'No supported authentication schemes available')
- self.serverSupports = serverSupports
- self.clientSupports = clientSupports
-
- def __str__(self):
- return (IMAP4Exception.__str__(self)
- + ': Server supports %r, client supports %r'
- % (self.serverSupports, self.clientSupports))
-
-class IllegalServerResponse(IMAP4Exception): pass
-
-TIMEOUT_ERROR = error.TimeoutError()
-
-class IMAP4Client(basic.LineReceiver, policies.TimeoutMixin):
- """IMAP4 client protocol implementation
-
- @ivar state: A string representing the state the connection is currently
- in.
- """
- implements(IMailboxListener)
-
- tags = None
- waiting = None
- queued = None
- tagID = 1
- state = None
-
- startedTLS = False
-
- # Number of seconds to wait before timing out a connection.
- # If the number is <= 0 no timeout checking will be performed.
- timeout = 0
-
- # Capabilities are not allowed to change during the session
- # So cache the first response and use that for all later
- # lookups
- _capCache = None
-
- _memoryFileLimit = 1024 * 1024 * 10
-
- # Authentication is pluggable. This maps names to IClientAuthentication
- # objects.
- authenticators = None
-
- STATUS_CODES = ('OK', 'NO', 'BAD', 'PREAUTH', 'BYE')
-
- STATUS_TRANSFORMATIONS = {
- 'MESSAGES': int, 'RECENT': int, 'UNSEEN': int
- }
-
- context = None
-
- def __init__(self, contextFactory = None):
- self.tags = {}
- self.queued = []
- self.authenticators = {}
- self.context = contextFactory
-
- self._tag = None
- self._parts = None
- self._lastCmd = None
-
- def registerAuthenticator(self, auth):
- """Register a new form of authentication
-
- When invoking the authenticate() method of IMAP4Client, the first
- matching authentication scheme found will be used. The ordering is
- that in which the server lists support authentication schemes.
-
- @type auth: Implementor of C{IClientAuthentication}
- @param auth: The object to use to perform the client
- side of this authentication scheme.
- """
- self.authenticators[auth.getName().upper()] = auth
-
- def rawDataReceived(self, data):
- if self.timeout > 0:
- self.resetTimeout()
-
- self._pendingSize -= len(data)
- if self._pendingSize > 0:
- self._pendingBuffer.write(data)
- else:
- passon = ''
- if self._pendingSize < 0:
- data, passon = data[:self._pendingSize], data[self._pendingSize:]
- self._pendingBuffer.write(data)
- rest = self._pendingBuffer
- self._pendingBuffer = None
- self._pendingSize = None
- rest.seek(0, 0)
- self._parts.append(rest.read())
- self.setLineMode(passon.lstrip('\r\n'))
-
-# def sendLine(self, line):
-# print 'S:', repr(line)
-# return basic.LineReceiver.sendLine(self, line)
-
- def _setupForLiteral(self, rest, octets):
- self._pendingBuffer = self.messageFile(octets)
- self._pendingSize = octets
- if self._parts is None:
- self._parts = [rest, '\r\n']
- else:
- self._parts.extend([rest, '\r\n'])
- self.setRawMode()
-
- def connectionMade(self):
- if self.timeout > 0:
- self.setTimeout(self.timeout)
-
- def connectionLost(self, reason):
- """We are no longer connected"""
- if self.timeout > 0:
- self.setTimeout(None)
- if self.queued is not None:
- queued = self.queued
- self.queued = None
- for cmd in queued:
- cmd.defer.errback(reason)
- if self.tags is not None:
- tags = self.tags
- self.tags = None
- for cmd in tags.itervalues():
- if cmd is not None and cmd.defer is not None:
- cmd.defer.errback(reason)
-
-
- def lineReceived(self, line):
- """
- Attempt to parse a single line from the server.
-
- @type line: C{str}
- @param line: The line from the server, without the line delimiter.
-
- @raise IllegalServerResponse: If the line or some part of the line
- does not represent an allowed message from the server at this time.
- """
-# print 'C: ' + repr(line)
- if self.timeout > 0:
- self.resetTimeout()
-
- lastPart = line.rfind('{')
- if lastPart != -1:
- lastPart = line[lastPart + 1:]
- if lastPart.endswith('}'):
- # It's a literal a-comin' in
- try:
- octets = int(lastPart[:-1])
- except ValueError:
- raise IllegalServerResponse(line)
- if self._parts is None:
- self._tag, parts = line.split(None, 1)
- else:
- parts = line
- self._setupForLiteral(parts, octets)
- return
-
- if self._parts is None:
- # It isn't a literal at all
- self._regularDispatch(line)
- else:
- # If an expression is in progress, no tag is required here
- # Since we didn't find a literal indicator, this expression
- # is done.
- self._parts.append(line)
- tag, rest = self._tag, ''.join(self._parts)
- self._tag = self._parts = None
- self.dispatchCommand(tag, rest)
-
- def timeoutConnection(self):
- if self._lastCmd and self._lastCmd.defer is not None:
- d, self._lastCmd.defer = self._lastCmd.defer, None
- d.errback(TIMEOUT_ERROR)
-
- if self.queued:
- for cmd in self.queued:
- if cmd.defer is not None:
- d, cmd.defer = cmd.defer, d
- d.errback(TIMEOUT_ERROR)
-
- self.transport.loseConnection()
-
- def _regularDispatch(self, line):
- parts = line.split(None, 1)
- if len(parts) != 2:
- parts.append('')
- tag, rest = parts
- self.dispatchCommand(tag, rest)
-
- def messageFile(self, octets):
- """Create a file to which an incoming message may be written.
-
- @type octets: C{int}
- @param octets: The number of octets which will be written to the file
-
- @rtype: Any object which implements C{write(string)} and
- C{seek(int, int)}
- @return: A file-like object
- """
- if octets > self._memoryFileLimit:
- return tempfile.TemporaryFile()
- else:
- return StringIO.StringIO()
-
- def makeTag(self):
- tag = '%0.4X' % self.tagID
- self.tagID += 1
- return tag
-
- def dispatchCommand(self, tag, rest):
- if self.state is None:
- f = self.response_UNAUTH
- else:
- f = getattr(self, 'response_' + self.state.upper(), None)
- if f:
- try:
- f(tag, rest)
- except:
- log.err()
- self.transport.loseConnection()
- else:
- log.err("Cannot dispatch: %s, %s, %s" % (self.state, tag, rest))
- self.transport.loseConnection()
-
- def response_UNAUTH(self, tag, rest):
- if self.state is None:
- # Server greeting, this is
- status, rest = rest.split(None, 1)
- if status.upper() == 'OK':
- self.state = 'unauth'
- elif status.upper() == 'PREAUTH':
- self.state = 'auth'
- else:
- # XXX - This is rude.
- self.transport.loseConnection()
- raise IllegalServerResponse(tag + ' ' + rest)
-
- b, e = rest.find('['), rest.find(']')
- if b != -1 and e != -1:
- self.serverGreeting(
- self.__cbCapabilities(
- ([parseNestedParens(rest[b + 1:e])], None)))
- else:
- self.serverGreeting(None)
- else:
- self._defaultHandler(tag, rest)
-
- def response_AUTH(self, tag, rest):
- self._defaultHandler(tag, rest)
-
- def _defaultHandler(self, tag, rest):
- if tag == '*' or tag == '+':
- if not self.waiting:
- self._extraInfo([parseNestedParens(rest)])
- else:
- cmd = self.tags[self.waiting]
- if tag == '+':
- cmd.continuation(rest)
- else:
- cmd.lines.append(rest)
- else:
- try:
- cmd = self.tags[tag]
- except KeyError:
- # XXX - This is rude.
- self.transport.loseConnection()
- raise IllegalServerResponse(tag + ' ' + rest)
- else:
- status, line = rest.split(None, 1)
- if status == 'OK':
- # Give them this last line, too
- cmd.finish(rest, self._extraInfo)
- else:
- cmd.defer.errback(IMAP4Exception(line))
- del self.tags[tag]
- self.waiting = None
- self._flushQueue()
-
- def _flushQueue(self):
- if self.queued:
- cmd = self.queued.pop(0)
- t = self.makeTag()
- self.tags[t] = cmd
- self.sendLine(cmd.format(t))
- self.waiting = t
-
- def _extraInfo(self, lines):
- # XXX - This is terrible.
- # XXX - Also, this should collapse temporally proximate calls into single
- # invocations of IMailboxListener methods, where possible.
- flags = {}
- recent = exists = None
- for response in lines:
- elements = len(response)
- if elements == 1 and response[0] == ['READ-ONLY']:
- self.modeChanged(False)
- elif elements == 1 and response[0] == ['READ-WRITE']:
- self.modeChanged(True)
- elif elements == 2 and response[1] == 'EXISTS':
- exists = int(response[0])
- elif elements == 2 and response[1] == 'RECENT':
- recent = int(response[0])
- elif elements == 3 and response[1] == 'FETCH':
- mId = int(response[0])
- values = self._parseFetchPairs(response[2])
- flags.setdefault(mId, []).extend(values.get('FLAGS', ()))
- else:
- log.msg('Unhandled unsolicited response: %s' % (response,))
-
- if flags:
- self.flagsChanged(flags)
- if recent is not None or exists is not None:
- self.newMessages(exists, recent)
-
- def sendCommand(self, cmd):
- cmd.defer = defer.Deferred()
- if self.waiting:
- self.queued.append(cmd)
- return cmd.defer
- t = self.makeTag()
- self.tags[t] = cmd
- self.sendLine(cmd.format(t))
- self.waiting = t
- self._lastCmd = cmd
- return cmd.defer
-
- def getCapabilities(self, useCache=1):
- """Request the capabilities available on this server.
-
- This command is allowed in any state of connection.
-
- @type useCache: C{bool}
- @param useCache: Specify whether to use the capability-cache or to
- re-retrieve the capabilities from the server. Server capabilities
- should never change, so for normal use, this flag should never be
- false.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback will be invoked with a
- dictionary mapping capability types to lists of supported
- mechanisms, or to None if a support list is not applicable.
- """
- if useCache and self._capCache is not None:
- return defer.succeed(self._capCache)
- cmd = 'CAPABILITY'
- resp = ('CAPABILITY',)
- d = self.sendCommand(Command(cmd, wantResponse=resp))
- d.addCallback(self.__cbCapabilities)
- return d
-
- def __cbCapabilities(self, (lines, tagline)):
- caps = {}
- for rest in lines:
- for cap in rest[1:]:
- parts = cap.split('=', 1)
- if len(parts) == 1:
- category, value = parts[0], None
- else:
- category, value = parts
- caps.setdefault(category, []).append(value)
-
- # Preserve a non-ideal API for backwards compatibility. It would
- # probably be entirely sensible to have an object with a wider API than
- # dict here so this could be presented less insanely.
- for category in caps:
- if caps[category] == [None]:
- caps[category] = None
- self._capCache = caps
- return caps
-
- def logout(self):
- """Inform the server that we are done with the connection.
-
- This command is allowed in any state of connection.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback will be invoked with None
- when the proper server acknowledgement has been received.
- """
- d = self.sendCommand(Command('LOGOUT', wantResponse=('BYE',)))
- d.addCallback(self.__cbLogout)
- return d
-
- def __cbLogout(self, (lines, tagline)):
- self.transport.loseConnection()
- # We don't particularly care what the server said
- return None
-
-
- def noop(self):
- """Perform no operation.
-
- This command is allowed in any state of connection.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback will be invoked with a list
- of untagged status updates the server responds with.
- """
- d = self.sendCommand(Command('NOOP'))
- d.addCallback(self.__cbNoop)
- return d
-
- def __cbNoop(self, (lines, tagline)):
- # Conceivable, this is elidable.
- # It is, afterall, a no-op.
- return lines
-
- def startTLS(self, contextFactory=None):
- """
- Initiates a 'STARTTLS' request and negotiates the TLS / SSL
- Handshake.
-
- @param contextFactory: The TLS / SSL Context Factory to
- leverage. If the contextFactory is None the IMAP4Client will
- either use the current TLS / SSL Context Factory or attempt to
- create a new one.
-
- @type contextFactory: C{ssl.ClientContextFactory}
-
- @return: A Deferred which fires when the transport has been
- secured according to the given contextFactory, or which fails
- if the transport cannot be secured.
- """
- assert not self.startedTLS, "Client and Server are currently communicating via TLS"
-
- if contextFactory is None:
- contextFactory = self._getContextFactory()
-
- if contextFactory is None:
- return defer.fail(IMAP4Exception(
- "IMAP4Client requires a TLS context to "
- "initiate the STARTTLS handshake"))
-
- if 'STARTTLS' not in self._capCache:
- return defer.fail(IMAP4Exception(
- "Server does not support secure communication "
- "via TLS / SSL"))
-
- tls = interfaces.ITLSTransport(self.transport, None)
- if tls is None:
- return defer.fail(IMAP4Exception(
- "IMAP4Client transport does not implement "
- "interfaces.ITLSTransport"))
-
- d = self.sendCommand(Command('STARTTLS'))
- d.addCallback(self._startedTLS, contextFactory)
- d.addCallback(lambda _: self.getCapabilities())
- return d
-
-
- def authenticate(self, secret):
- """Attempt to enter the authenticated state with the server
-
- This command is allowed in the Non-Authenticated state.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if the authentication
- succeeds and whose errback will be invoked otherwise.
- """
- if self._capCache is None:
- d = self.getCapabilities()
- else:
- d = defer.succeed(self._capCache)
- d.addCallback(self.__cbAuthenticate, secret)
- return d
-
- def __cbAuthenticate(self, caps, secret):
- auths = caps.get('AUTH', ())
- for scheme in auths:
- if scheme.upper() in self.authenticators:
- cmd = Command('AUTHENTICATE', scheme, (),
- self.__cbContinueAuth, scheme,
- secret)
- return self.sendCommand(cmd)
-
- if self.startedTLS:
- return defer.fail(NoSupportedAuthentication(
- auths, self.authenticators.keys()))
- else:
- def ebStartTLS(err):
- err.trap(IMAP4Exception)
- # We couldn't negotiate TLS for some reason
- return defer.fail(NoSupportedAuthentication(
- auths, self.authenticators.keys()))
-
- d = self.startTLS()
- d.addErrback(ebStartTLS)
- d.addCallback(lambda _: self.getCapabilities())
- d.addCallback(self.__cbAuthTLS, secret)
- return d
-
-
- def __cbContinueAuth(self, rest, scheme, secret):
- try:
- chal = base64.decodestring(rest + '\n')
- except binascii.Error:
- self.sendLine('*')
- raise IllegalServerResponse(rest)
- self.transport.loseConnection()
- else:
- auth = self.authenticators[scheme]
- chal = auth.challengeResponse(secret, chal)
- self.sendLine(base64.encodestring(chal).strip())
-
- def __cbAuthTLS(self, caps, secret):
- auths = caps.get('AUTH', ())
- for scheme in auths:
- if scheme.upper() in self.authenticators:
- cmd = Command('AUTHENTICATE', scheme, (),
- self.__cbContinueAuth, scheme,
- secret)
- return self.sendCommand(cmd)
- raise NoSupportedAuthentication(auths, self.authenticators.keys())
-
-
- def login(self, username, password):
- """Authenticate with the server using a username and password
-
- This command is allowed in the Non-Authenticated state. If the
- server supports the STARTTLS capability and our transport supports
- TLS, TLS is negotiated before the login command is issued.
-
- A more secure way to log in is to use C{startTLS} or
- C{authenticate} or both.
-
- @type username: C{str}
- @param username: The username to log in with
-
- @type password: C{str}
- @param password: The password to log in with
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if login is successful
- and whose errback is invoked otherwise.
- """
- d = maybeDeferred(self.getCapabilities)
- d.addCallback(self.__cbLoginCaps, username, password)
- return d
-
- def serverGreeting(self, caps):
- """Called when the server has sent us a greeting.
-
- @type caps: C{dict}
- @param caps: Capabilities the server advertised in its greeting.
- """
-
- def _getContextFactory(self):
- if self.context is not None:
- return self.context
- try:
- from twisted.internet import ssl
- except ImportError:
- return None
- else:
- context = ssl.ClientContextFactory()
- context.method = ssl.SSL.TLSv1_METHOD
- return context
-
- def __cbLoginCaps(self, capabilities, username, password):
- # If the server advertises STARTTLS, we might want to try to switch to TLS
- tryTLS = 'STARTTLS' in capabilities
-
- # If our transport supports switching to TLS, we might want to try to switch to TLS.
- tlsableTransport = interfaces.ITLSTransport(self.transport, None) is not None
-
- # If our transport is not already using TLS, we might want to try to switch to TLS.
- nontlsTransport = interfaces.ISSLTransport(self.transport, None) is None
-
- if not self.startedTLS and tryTLS and tlsableTransport and nontlsTransport:
- d = self.startTLS()
-
- d.addCallbacks(
- self.__cbLoginTLS,
- self.__ebLoginTLS,
- callbackArgs=(username, password),
- )
- return d
- else:
- if nontlsTransport:
- log.msg("Server has no TLS support. logging in over cleartext!")
- args = ' '.join((_quote(username), _quote(password)))
- return self.sendCommand(Command('LOGIN', args))
-
- def _startedTLS(self, result, context):
- self.transport.startTLS(context)
- self._capCache = None
- self.startedTLS = True
- return result
-
- def __cbLoginTLS(self, result, username, password):
- args = ' '.join((_quote(username), _quote(password)))
- return self.sendCommand(Command('LOGIN', args))
-
- def __ebLoginTLS(self, failure):
- log.err(failure)
- return failure
-
- def namespace(self):
- """Retrieve information about the namespaces available to this account
-
- This command is allowed in the Authenticated and Selected states.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with namespace
- information. An example of this information is::
-
- [[['', '/']], [], []]
-
- which indicates a single personal namespace called '' with '/'
- as its hierarchical delimiter, and no shared or user namespaces.
- """
- cmd = 'NAMESPACE'
- resp = ('NAMESPACE',)
- d = self.sendCommand(Command(cmd, wantResponse=resp))
- d.addCallback(self.__cbNamespace)
- return d
-
- def __cbNamespace(self, (lines, last)):
- for parts in lines:
- if len(parts) == 4 and parts[0] == 'NAMESPACE':
- return [e or [] for e in parts[1:]]
- log.err("No NAMESPACE response to NAMESPACE command")
- return [[], [], []]
-
-
- def select(self, mailbox):
- """
- Select a mailbox
-
- This command is allowed in the Authenticated and Selected states.
-
- @type mailbox: C{str}
- @param mailbox: The name of the mailbox to select
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with mailbox
- information if the select is successful and whose errback is
- invoked otherwise. Mailbox information consists of a dictionary
- with the following keys and values::
-
- FLAGS: A list of strings containing the flags settable on
- messages in this mailbox.
-
- EXISTS: An integer indicating the number of messages in this
- mailbox.
-
- RECENT: An integer indicating the number of "recent"
- messages in this mailbox.
-
- UNSEEN: The message sequence number (an integer) of the
- first unseen message in the mailbox.
-
- PERMANENTFLAGS: A list of strings containing the flags that
- can be permanently set on messages in this mailbox.
-
- UIDVALIDITY: An integer uniquely identifying this mailbox.
- """
- cmd = 'SELECT'
- args = _prepareMailboxName(mailbox)
- resp = ('FLAGS', 'EXISTS', 'RECENT', 'UNSEEN', 'PERMANENTFLAGS', 'UIDVALIDITY')
- d = self.sendCommand(Command(cmd, args, wantResponse=resp))
- d.addCallback(self.__cbSelect, 1)
- return d
-
-
- def examine(self, mailbox):
- """Select a mailbox in read-only mode
-
- This command is allowed in the Authenticated and Selected states.
-
- @type mailbox: C{str}
- @param mailbox: The name of the mailbox to examine
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with mailbox
- information if the examine is successful and whose errback
- is invoked otherwise. Mailbox information consists of a dictionary
- with the following keys and values::
-
- 'FLAGS': A list of strings containing the flags settable on
- messages in this mailbox.
-
- 'EXISTS': An integer indicating the number of messages in this
- mailbox.
-
- 'RECENT': An integer indicating the number of \"recent\"
- messages in this mailbox.
-
- 'UNSEEN': An integer indicating the number of messages not
- flagged \\Seen in this mailbox.
-
- 'PERMANENTFLAGS': A list of strings containing the flags that
- can be permanently set on messages in this mailbox.
-
- 'UIDVALIDITY': An integer uniquely identifying this mailbox.
- """
- cmd = 'EXAMINE'
- args = _prepareMailboxName(mailbox)
- resp = ('FLAGS', 'EXISTS', 'RECENT', 'UNSEEN', 'PERMANENTFLAGS', 'UIDVALIDITY')
- d = self.sendCommand(Command(cmd, args, wantResponse=resp))
- d.addCallback(self.__cbSelect, 0)
- return d
-
-
- def _intOrRaise(self, value, phrase):
- """
- Parse C{value} as an integer and return the result or raise
- L{IllegalServerResponse} with C{phrase} as an argument if C{value}
- cannot be parsed as an integer.
- """
- try:
- return int(value)
- except ValueError:
- raise IllegalServerResponse(phrase)
-
-
- def __cbSelect(self, (lines, tagline), rw):
- """
- Handle lines received in response to a SELECT or EXAMINE command.
-
- See RFC 3501, section 6.3.1.
- """
- # In the absense of specification, we are free to assume:
- # READ-WRITE access
- datum = {'READ-WRITE': rw}
- lines.append(parseNestedParens(tagline))
- for split in lines:
- if len(split) > 0 and split[0].upper() == 'OK':
- # Handle all the kinds of OK response.
- content = split[1]
- key = content[0].upper()
- if key == 'READ-ONLY':
- datum['READ-WRITE'] = False
- elif key == 'READ-WRITE':
- datum['READ-WRITE'] = True
- elif key == 'UIDVALIDITY':
- datum['UIDVALIDITY'] = self._intOrRaise(
- content[1], split)
- elif key == 'UNSEEN':
- datum['UNSEEN'] = self._intOrRaise(content[1], split)
- elif key == 'UIDNEXT':
- datum['UIDNEXT'] = self._intOrRaise(content[1], split)
- elif key == 'PERMANENTFLAGS':
- datum['PERMANENTFLAGS'] = tuple(content[1])
- else:
- log.err('Unhandled SELECT response (2): %s' % (split,))
- elif len(split) == 2:
- # Handle FLAGS, EXISTS, and RECENT
- if split[0].upper() == 'FLAGS':
- datum['FLAGS'] = tuple(split[1])
- elif isinstance(split[1], str):
- # Must make sure things are strings before treating them as
- # strings since some other forms of response have nesting in
- # places which results in lists instead.
- if split[1].upper() == 'EXISTS':
- datum['EXISTS'] = self._intOrRaise(split[0], split)
- elif split[1].upper() == 'RECENT':
- datum['RECENT'] = self._intOrRaise(split[0], split)
- else:
- log.err('Unhandled SELECT response (0): %s' % (split,))
- else:
- log.err('Unhandled SELECT response (1): %s' % (split,))
- else:
- log.err('Unhandled SELECT response (4): %s' % (split,))
- return datum
-
-
- def create(self, name):
- """Create a new mailbox on the server
-
- This command is allowed in the Authenticated and Selected states.
-
- @type name: C{str}
- @param name: The name of the mailbox to create.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if the mailbox creation
- is successful and whose errback is invoked otherwise.
- """
- return self.sendCommand(Command('CREATE', _prepareMailboxName(name)))
-
- def delete(self, name):
- """Delete a mailbox
-
- This command is allowed in the Authenticated and Selected states.
-
- @type name: C{str}
- @param name: The name of the mailbox to delete.
-
- @rtype: C{Deferred}
- @return: A deferred whose calblack is invoked if the mailbox is
- deleted successfully and whose errback is invoked otherwise.
- """
- return self.sendCommand(Command('DELETE', _prepareMailboxName(name)))
-
- def rename(self, oldname, newname):
- """Rename a mailbox
-
- This command is allowed in the Authenticated and Selected states.
-
- @type oldname: C{str}
- @param oldname: The current name of the mailbox to rename.
-
- @type newname: C{str}
- @param newname: The new name to give the mailbox.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if the rename is
- successful and whose errback is invoked otherwise.
- """
- oldname = _prepareMailboxName(oldname)
- newname = _prepareMailboxName(newname)
- return self.sendCommand(Command('RENAME', ' '.join((oldname, newname))))
-
- def subscribe(self, name):
- """Add a mailbox to the subscription list
-
- This command is allowed in the Authenticated and Selected states.
-
- @type name: C{str}
- @param name: The mailbox to mark as 'active' or 'subscribed'
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if the subscription
- is successful and whose errback is invoked otherwise.
- """
- return self.sendCommand(Command('SUBSCRIBE', _prepareMailboxName(name)))
-
- def unsubscribe(self, name):
- """Remove a mailbox from the subscription list
-
- This command is allowed in the Authenticated and Selected states.
-
- @type name: C{str}
- @param name: The mailbox to unsubscribe
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if the unsubscription
- is successful and whose errback is invoked otherwise.
- """
- return self.sendCommand(Command('UNSUBSCRIBE', _prepareMailboxName(name)))
-
- def list(self, reference, wildcard):
- """List a subset of the available mailboxes
-
- This command is allowed in the Authenticated and Selected states.
-
- @type reference: C{str}
- @param reference: The context in which to interpret C{wildcard}
-
- @type wildcard: C{str}
- @param wildcard: The pattern of mailbox names to match, optionally
- including either or both of the '*' and '%' wildcards. '*' will
- match zero or more characters and cross hierarchical boundaries.
- '%' will also match zero or more characters, but is limited to a
- single hierarchical level.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a list of C{tuple}s,
- the first element of which is a C{tuple} of mailbox flags, the second
- element of which is the hierarchy delimiter for this mailbox, and the
- third of which is the mailbox name; if the command is unsuccessful,
- the deferred's errback is invoked instead.
- """
- cmd = 'LIST'
- args = '"%s" "%s"' % (reference, wildcard.encode('imap4-utf-7'))
- resp = ('LIST',)
- d = self.sendCommand(Command(cmd, args, wantResponse=resp))
- d.addCallback(self.__cbList, 'LIST')
- return d
-
- def lsub(self, reference, wildcard):
- """List a subset of the subscribed available mailboxes
-
- This command is allowed in the Authenticated and Selected states.
-
- The parameters and returned object are the same as for the C{list}
- method, with one slight difference: Only mailboxes which have been
- subscribed can be included in the resulting list.
- """
- cmd = 'LSUB'
- args = '"%s" "%s"' % (reference, wildcard.encode('imap4-utf-7'))
- resp = ('LSUB',)
- d = self.sendCommand(Command(cmd, args, wantResponse=resp))
- d.addCallback(self.__cbList, 'LSUB')
- return d
-
- def __cbList(self, (lines, last), command):
- results = []
- for parts in lines:
- if len(parts) == 4 and parts[0] == command:
- parts[1] = tuple(parts[1])
- results.append(tuple(parts[1:]))
- return results
-
- def status(self, mailbox, *names):
- """
- Retrieve the status of the given mailbox
-
- This command is allowed in the Authenticated and Selected states.
-
- @type mailbox: C{str}
- @param mailbox: The name of the mailbox to query
-
- @type *names: C{str}
- @param *names: The status names to query. These may be any number of:
- C{'MESSAGES'}, C{'RECENT'}, C{'UIDNEXT'}, C{'UIDVALIDITY'}, and
- C{'UNSEEN'}.
-
- @rtype: C{Deferred}
- @return: A deferred which fires with with the status information if the
- command is successful and whose errback is invoked otherwise. The
- status information is in the form of a C{dict}. Each element of
- C{names} is a key in the dictionary. The value for each key is the
- corresponding response from the server.
- """
- cmd = 'STATUS'
- args = "%s (%s)" % (_prepareMailboxName(mailbox), ' '.join(names))
- resp = ('STATUS',)
- d = self.sendCommand(Command(cmd, args, wantResponse=resp))
- d.addCallback(self.__cbStatus)
- return d
-
- def __cbStatus(self, (lines, last)):
- status = {}
- for parts in lines:
- if parts[0] == 'STATUS':
- items = parts[2]
- items = [items[i:i+2] for i in range(0, len(items), 2)]
- status.update(dict(items))
- for k in status.keys():
- t = self.STATUS_TRANSFORMATIONS.get(k)
- if t:
- try:
- status[k] = t(status[k])
- except Exception, e:
- raise IllegalServerResponse('(%s %s): %s' % (k, status[k], str(e)))
- return status
-
- def append(self, mailbox, message, flags = (), date = None):
- """Add the given message to the given mailbox.
-
- This command is allowed in the Authenticated and Selected states.
-
- @type mailbox: C{str}
- @param mailbox: The mailbox to which to add this message.
-
- @type message: Any file-like object
- @param message: The message to add, in RFC822 format. Newlines
- in this file should be \\r\\n-style.
-
- @type flags: Any iterable of C{str}
- @param flags: The flags to associated with this message.
-
- @type date: C{str}
- @param date: The date to associate with this message. This should
- be of the format DD-MM-YYYY HH:MM:SS +/-HHMM. For example, in
- Eastern Standard Time, on July 1st 2004 at half past 1 PM,
- \"01-07-2004 13:30:00 -0500\".
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked when this command
- succeeds or whose errback is invoked if it fails.
- """
- message.seek(0, 2)
- L = message.tell()
- message.seek(0, 0)
- fmt = '%s (%s)%s {%d}'
- if date:
- date = ' "%s"' % date
- else:
- date = ''
- cmd = fmt % (
- _prepareMailboxName(mailbox), ' '.join(flags),
- date, L
- )
- d = self.sendCommand(Command('APPEND', cmd, (), self.__cbContinueAppend, message))
- return d
-
- def __cbContinueAppend(self, lines, message):
- s = basic.FileSender()
- return s.beginFileTransfer(message, self.transport, None
- ).addCallback(self.__cbFinishAppend)
-
- def __cbFinishAppend(self, foo):
- self.sendLine('')
-
- def check(self):
- """Tell the server to perform a checkpoint
-
- This command is allowed in the Selected state.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked when this command
- succeeds or whose errback is invoked if it fails.
- """
- return self.sendCommand(Command('CHECK'))
-
- def close(self):
- """Return the connection to the Authenticated state.
-
- This command is allowed in the Selected state.
-
- Issuing this command will also remove all messages flagged \\Deleted
- from the selected mailbox if it is opened in read-write mode,
- otherwise it indicates success by no messages are removed.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked when the command
- completes successfully or whose errback is invoked if it fails.
- """
- return self.sendCommand(Command('CLOSE'))
-
-
- def expunge(self):
- """Return the connection to the Authenticate state.
-
- This command is allowed in the Selected state.
-
- Issuing this command will perform the same actions as issuing the
- close command, but will also generate an 'expunge' response for
- every message deleted.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a list of the
- 'expunge' responses when this command is successful or whose errback
- is invoked otherwise.
- """
- cmd = 'EXPUNGE'
- resp = ('EXPUNGE',)
- d = self.sendCommand(Command(cmd, wantResponse=resp))
- d.addCallback(self.__cbExpunge)
- return d
-
-
- def __cbExpunge(self, (lines, last)):
- ids = []
- for parts in lines:
- if len(parts) == 2 and parts[1] == 'EXPUNGE':
- ids.append(self._intOrRaise(parts[0], parts))
- return ids
-
-
- def search(self, *queries, **kwarg):
- """Search messages in the currently selected mailbox
-
- This command is allowed in the Selected state.
-
- Any non-zero number of queries are accepted by this method, as
- returned by the C{Query}, C{Or}, and C{Not} functions.
-
- One keyword argument is accepted: if uid is passed in with a non-zero
- value, the server is asked to return message UIDs instead of message
- sequence numbers.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback will be invoked with a list of all
- the message sequence numbers return by the search, or whose errback
- will be invoked if there is an error.
- """
- if kwarg.get('uid'):
- cmd = 'UID SEARCH'
- else:
- cmd = 'SEARCH'
- args = ' '.join(queries)
- d = self.sendCommand(Command(cmd, args, wantResponse=(cmd,)))
- d.addCallback(self.__cbSearch)
- return d
-
-
- def __cbSearch(self, (lines, end)):
- ids = []
- for parts in lines:
- if len(parts) > 0 and parts[0] == 'SEARCH':
- ids.extend([self._intOrRaise(p, parts) for p in parts[1:]])
- return ids
-
-
- def fetchUID(self, messages, uid=0):
- """Retrieve the unique identifier for one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message sequence numbers to unique message identifiers, or whose
- errback is invoked if there is an error.
- """
- return self._fetch(messages, useUID=uid, uid=1)
-
-
- def fetchFlags(self, messages, uid=0):
- """Retrieve the flags for one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: The messages for which to retrieve flags.
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to lists of flags, or whose errback is invoked if
- there is an error.
- """
- return self._fetch(str(messages), useUID=uid, flags=1)
-
-
- def fetchInternalDate(self, messages, uid=0):
- """Retrieve the internal date associated with one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: The messages for which to retrieve the internal date.
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to date strings, or whose errback is invoked
- if there is an error. Date strings take the format of
- \"day-month-year time timezone\".
- """
- return self._fetch(str(messages), useUID=uid, internaldate=1)
-
-
- def fetchEnvelope(self, messages, uid=0):
- """Retrieve the envelope data for one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: The messages for which to retrieve envelope data.
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to envelope data, or whose errback is invoked
- if there is an error. Envelope data consists of a sequence of the
- date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to,
- and message-id header fields. The date, subject, in-reply-to, and
- message-id fields are strings, while the from, sender, reply-to,
- to, cc, and bcc fields contain address data. Address data consists
- of a sequence of name, source route, mailbox name, and hostname.
- Fields which are not present for a particular address may be C{None}.
- """
- return self._fetch(str(messages), useUID=uid, envelope=1)
-
-
- def fetchBodyStructure(self, messages, uid=0):
- """Retrieve the structure of the body of one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: The messages for which to retrieve body structure
- data.
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to body structure data, or whose errback is invoked
- if there is an error. Body structure data describes the MIME-IMB
- format of a message and consists of a sequence of mime type, mime
- subtype, parameters, content id, description, encoding, and size.
- The fields following the size field are variable: if the mime
- type/subtype is message/rfc822, the contained message's envelope
- information, body structure data, and number of lines of text; if
- the mime type is text, the number of lines of text. Extension fields
- may also be included; if present, they are: the MD5 hash of the body,
- body disposition, body language.
- """
- return self._fetch(messages, useUID=uid, bodystructure=1)
-
-
- def fetchSimplifiedBody(self, messages, uid=0):
- """Retrieve the simplified body structure of one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to body data, or whose errback is invoked
- if there is an error. The simplified body structure is the same
- as the body structure, except that extension fields will never be
- present.
- """
- return self._fetch(messages, useUID=uid, body=1)
-
-
- def fetchMessage(self, messages, uid=0):
- """Retrieve one or more entire messages
-
- This command is allowed in the Selected state.
-
- @type messages: L{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: L{Deferred}
-
- @return: A L{Deferred} which will fire with a C{dict} mapping message
- sequence numbers to C{dict}s giving message data for the
- corresponding message. If C{uid} is true, the inner dictionaries
- have a C{'UID'} key mapped to a C{str} giving the UID for the
- message. The text of the message is a C{str} associated with the
- C{'RFC822'} key in each dictionary.
- """
- return self._fetch(messages, useUID=uid, rfc822=1)
-
-
- def fetchHeaders(self, messages, uid=0):
- """Retrieve headers of one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to dicts of message headers, or whose errback is
- invoked if there is an error.
- """
- return self._fetch(messages, useUID=uid, rfc822header=1)
-
-
- def fetchBody(self, messages, uid=0):
- """Retrieve body text of one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to file-like objects containing body text, or whose
- errback is invoked if there is an error.
- """
- return self._fetch(messages, useUID=uid, rfc822text=1)
-
-
- def fetchSize(self, messages, uid=0):
- """Retrieve the size, in octets, of one or more messages
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to sizes, or whose errback is invoked if there is
- an error.
- """
- return self._fetch(messages, useUID=uid, rfc822size=1)
-
-
- def fetchFull(self, messages, uid=0):
- """Retrieve several different fields of one or more messages
-
- This command is allowed in the Selected state. This is equivalent
- to issuing all of the C{fetchFlags}, C{fetchInternalDate},
- C{fetchSize}, C{fetchEnvelope}, and C{fetchSimplifiedBody}
- functions.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to dict of the retrieved data values, or whose
- errback is invoked if there is an error. They dictionary keys
- are "flags", "date", "size", "envelope", and "body".
- """
- return self._fetch(
- messages, useUID=uid, flags=1, internaldate=1,
- rfc822size=1, envelope=1, body=1)
-
-
- def fetchAll(self, messages, uid=0):
- """Retrieve several different fields of one or more messages
-
- This command is allowed in the Selected state. This is equivalent
- to issuing all of the C{fetchFlags}, C{fetchInternalDate},
- C{fetchSize}, and C{fetchEnvelope} functions.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to dict of the retrieved data values, or whose
- errback is invoked if there is an error. They dictionary keys
- are "flags", "date", "size", and "envelope".
- """
- return self._fetch(
- messages, useUID=uid, flags=1, internaldate=1,
- rfc822size=1, envelope=1)
-
-
- def fetchFast(self, messages, uid=0):
- """Retrieve several different fields of one or more messages
-
- This command is allowed in the Selected state. This is equivalent
- to issuing all of the C{fetchFlags}, C{fetchInternalDate}, and
- C{fetchSize} functions.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a dict mapping
- message numbers to dict of the retrieved data values, or whose
- errback is invoked if there is an error. They dictionary keys are
- "flags", "date", and "size".
- """
- return self._fetch(
- messages, useUID=uid, flags=1, internaldate=1, rfc822size=1)
-
-
- def _parseFetchPairs(self, fetchResponseList):
- """
- Given the result of parsing a single I{FETCH} response, construct a
- C{dict} mapping response keys to response values.
-
- @param fetchResponseList: The result of parsing a I{FETCH} response
- with L{parseNestedParens} and extracting just the response data
- (that is, just the part that comes after C{"FETCH"}). The form
- of this input (and therefore the output of this method) is very
- disagreable. A valuable improvement would be to enumerate the
- possible keys (representing them as structured objects of some
- sort) rather than using strings and tuples of tuples of strings
- and so forth. This would allow the keys to be documented more
- easily and would allow for a much simpler application-facing API
- (one not based on looking up somewhat hard to predict keys in a
- dict). Since C{fetchResponseList} notionally represents a
- flattened sequence of pairs (identifying keys followed by their
- associated values), collapsing such complex elements of this
- list as C{["BODY", ["HEADER.FIELDS", ["SUBJECT"]]]} into a
- single object would also greatly simplify the implementation of
- this method.
-
- @return: A C{dict} of the response data represented by C{pairs}. Keys
- in this dictionary are things like C{"RFC822.TEXT"}, C{"FLAGS"}, or
- C{("BODY", ("HEADER.FIELDS", ("SUBJECT",)))}. Values are entirely
- dependent on the key with which they are associated, but retain the
- same structured as produced by L{parseNestedParens}.
- """
- values = {}
- responseParts = iter(fetchResponseList)
- while True:
- try:
- key = responseParts.next()
- except StopIteration:
- break
-
- try:
- value = responseParts.next()
- except StopIteration:
- raise IllegalServerResponse(
- "Not enough arguments", fetchResponseList)
-
- # The parsed forms of responses like:
- #
- # BODY[] VALUE
- # BODY[TEXT] VALUE
- # BODY[HEADER.FIELDS (SUBJECT)] VALUE
- # BODY[HEADER.FIELDS (SUBJECT)]<N.M> VALUE
- #
- # are:
- #
- # ["BODY", [], VALUE]
- # ["BODY", ["TEXT"], VALUE]
- # ["BODY", ["HEADER.FIELDS", ["SUBJECT"]], VALUE]
- # ["BODY", ["HEADER.FIELDS", ["SUBJECT"]], "<N.M>", VALUE]
- #
- # Here, check for these cases and grab as many extra elements as
- # necessary to retrieve the body information.
- if key in ("BODY", "BODY.PEEK") and isinstance(value, list) and len(value) < 3:
- if len(value) < 2:
- key = (key, tuple(value))
- else:
- key = (key, (value[0], tuple(value[1])))
- try:
- value = responseParts.next()
- except StopIteration:
- raise IllegalServerResponse(
- "Not enough arguments", fetchResponseList)
-
- # Handle partial ranges
- if value.startswith('<') and value.endswith('>'):
- try:
- int(value[1:-1])
- except ValueError:
- # This isn't really a range, it's some content.
- pass
- else:
- key = key + (value,)
- try:
- value = responseParts.next()
- except StopIteration:
- raise IllegalServerResponse(
- "Not enough arguments", fetchResponseList)
-
- values[key] = value
- return values
-
-
- def _cbFetch(self, (lines, last), requestedParts, structured):
- info = {}
- for parts in lines:
- if len(parts) == 3 and parts[1] == 'FETCH':
- id = self._intOrRaise(parts[0], parts)
- if id not in info:
- info[id] = [parts[2]]
- else:
- info[id][0].extend(parts[2])
-
- results = {}
- for (messageId, values) in info.iteritems():
- mapping = self._parseFetchPairs(values[0])
- results.setdefault(messageId, {}).update(mapping)
-
- flagChanges = {}
- for messageId in results.keys():
- values = results[messageId]
- for part in values.keys():
- if part not in requestedParts and part == 'FLAGS':
- flagChanges[messageId] = values['FLAGS']
- # Find flags in the result and get rid of them.
- for i in range(len(info[messageId][0])):
- if info[messageId][0][i] == 'FLAGS':
- del info[messageId][0][i:i+2]
- break
- del values['FLAGS']
- if not values:
- del results[messageId]
-
- if flagChanges:
- self.flagsChanged(flagChanges)
-
- if structured:
- return results
- else:
- return info
-
-
- def fetchSpecific(self, messages, uid=0, headerType=None,
- headerNumber=None, headerArgs=None, peek=None,
- offset=None, length=None):
- """Retrieve a specific section of one or more messages
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @type headerType: C{str}
- @param headerType: If specified, must be one of HEADER,
- HEADER.FIELDS, HEADER.FIELDS.NOT, MIME, or TEXT, and will determine
- which part of the message is retrieved. For HEADER.FIELDS and
- HEADER.FIELDS.NOT, C{headerArgs} must be a sequence of header names.
- For MIME, C{headerNumber} must be specified.
-
- @type headerNumber: C{int} or C{int} sequence
- @param headerNumber: The nested rfc822 index specifying the
- entity to retrieve. For example, C{1} retrieves the first
- entity of the message, and C{(2, 1, 3}) retrieves the 3rd
- entity inside the first entity inside the second entity of
- the message.
-
- @type headerArgs: A sequence of C{str}
- @param headerArgs: If C{headerType} is HEADER.FIELDS, these are the
- headers to retrieve. If it is HEADER.FIELDS.NOT, these are the
- headers to exclude from retrieval.
-
- @type peek: C{bool}
- @param peek: If true, cause the server to not set the \\Seen
- flag on this message as a result of this command.
-
- @type offset: C{int}
- @param offset: The number of octets at the beginning of the result
- to skip.
-
- @type length: C{int}
- @param length: The number of octets to retrieve.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a mapping of
- message numbers to retrieved data, or whose errback is invoked
- if there is an error.
- """
- fmt = '%s BODY%s[%s%s%s]%s'
- if headerNumber is None:
- number = ''
- elif isinstance(headerNumber, int):
- number = str(headerNumber)
- else:
- number = '.'.join(map(str, headerNumber))
- if headerType is None:
- header = ''
- elif number:
- header = '.' + headerType
- else:
- header = headerType
- if header and headerType not in ('TEXT', 'MIME'):
- if headerArgs is not None:
- payload = ' (%s)' % ' '.join(headerArgs)
- else:
- payload = ' ()'
- else:
- payload = ''
- if offset is None:
- extra = ''
- else:
- extra = '<%d.%d>' % (offset, length)
- fetch = uid and 'UID FETCH' or 'FETCH'
- cmd = fmt % (messages, peek and '.PEEK' or '', number, header, payload, extra)
- d = self.sendCommand(Command(fetch, cmd, wantResponse=('FETCH',)))
- d.addCallback(self._cbFetch, (), False)
- return d
-
-
- def _fetch(self, messages, useUID=0, **terms):
- fetch = useUID and 'UID FETCH' or 'FETCH'
-
- if 'rfc822text' in terms:
- del terms['rfc822text']
- terms['rfc822.text'] = True
- if 'rfc822size' in terms:
- del terms['rfc822size']
- terms['rfc822.size'] = True
- if 'rfc822header' in terms:
- del terms['rfc822header']
- terms['rfc822.header'] = True
-
- cmd = '%s (%s)' % (messages, ' '.join([s.upper() for s in terms.keys()]))
- d = self.sendCommand(Command(fetch, cmd, wantResponse=('FETCH',)))
- d.addCallback(self._cbFetch, map(str.upper, terms.keys()), True)
- return d
-
- def setFlags(self, messages, flags, silent=1, uid=0):
- """Set the flags for one or more messages.
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type flags: Any iterable of C{str}
- @param flags: The flags to set
-
- @type silent: C{bool}
- @param silent: If true, cause the server to supress its verbose
- response.
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a list of the
- the server's responses (C{[]} if C{silent} is true) or whose
- errback is invoked if there is an error.
- """
- return self._store(str(messages), 'FLAGS', silent, flags, uid)
-
- def addFlags(self, messages, flags, silent=1, uid=0):
- """Add to the set flags for one or more messages.
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type flags: Any iterable of C{str}
- @param flags: The flags to set
-
- @type silent: C{bool}
- @param silent: If true, cause the server to supress its verbose
- response.
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a list of the
- the server's responses (C{[]} if C{silent} is true) or whose
- errback is invoked if there is an error.
- """
- return self._store(str(messages),'+FLAGS', silent, flags, uid)
-
- def removeFlags(self, messages, flags, silent=1, uid=0):
- """Remove from the set flags for one or more messages.
-
- This command is allowed in the Selected state.
-
- @type messages: C{MessageSet} or C{str}
- @param messages: A message sequence set
-
- @type flags: Any iterable of C{str}
- @param flags: The flags to set
-
- @type silent: C{bool}
- @param silent: If true, cause the server to supress its verbose
- response.
-
- @type uid: C{bool}
- @param uid: Indicates whether the message sequence set is of message
- numbers or of unique message IDs.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a list of the
- the server's responses (C{[]} if C{silent} is true) or whose
- errback is invoked if there is an error.
- """
- return self._store(str(messages), '-FLAGS', silent, flags, uid)
-
-
- def _store(self, messages, cmd, silent, flags, uid):
- if silent:
- cmd = cmd + '.SILENT'
- store = uid and 'UID STORE' or 'STORE'
- args = ' '.join((messages, cmd, '(%s)' % ' '.join(flags)))
- d = self.sendCommand(Command(store, args, wantResponse=('FETCH',)))
- expected = ()
- if not silent:
- expected = ('FLAGS',)
- d.addCallback(self._cbFetch, expected, True)
- return d
-
-
- def copy(self, messages, mailbox, uid):
- """Copy the specified messages to the specified mailbox.
-
- This command is allowed in the Selected state.
-
- @type messages: C{str}
- @param messages: A message sequence set
-
- @type mailbox: C{str}
- @param mailbox: The mailbox to which to copy the messages
-
- @type uid: C{bool}
- @param uid: If true, the C{messages} refers to message UIDs, rather
- than message sequence numbers.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with a true value
- when the copy is successful, or whose errback is invoked if there
- is an error.
- """
- if uid:
- cmd = 'UID COPY'
- else:
- cmd = 'COPY'
- args = '%s %s' % (messages, _prepareMailboxName(mailbox))
- return self.sendCommand(Command(cmd, args))
-
- #
- # IMailboxListener methods
- #
- def modeChanged(self, writeable):
- """Override me"""
-
- def flagsChanged(self, newFlags):
- """Override me"""
-
- def newMessages(self, exists, recent):
- """Override me"""
-
-
-class IllegalIdentifierError(IMAP4Exception): pass
-
-def parseIdList(s, lastMessageId=None):
- """
- Parse a message set search key into a C{MessageSet}.
-
- @type s: C{str}
- @param s: A string description of a id list, for example "1:3, 4:*"
-
- @type lastMessageId: C{int}
- @param lastMessageId: The last message sequence id or UID, depending on
- whether we are parsing the list in UID or sequence id context. The
- caller should pass in the correct value.
-
- @rtype: C{MessageSet}
- @return: A C{MessageSet} that contains the ids defined in the list
- """
- res = MessageSet()
- parts = s.split(',')
- for p in parts:
- if ':' in p:
- low, high = p.split(':', 1)
- try:
- if low == '*':
- low = None
- else:
- low = long(low)
- if high == '*':
- high = None
- else:
- high = long(high)
- if low is high is None:
- # *:* does not make sense
- raise IllegalIdentifierError(p)
- # non-positive values are illegal according to RFC 3501
- if ((low is not None and low <= 0) or
- (high is not None and high <= 0)):
- raise IllegalIdentifierError(p)
- # star means "highest value of an id in the mailbox"
- high = high or lastMessageId
- low = low or lastMessageId
-
- # RFC says that 2:4 and 4:2 are equivalent
- if low > high:
- low, high = high, low
- res.extend((low, high))
- except ValueError:
- raise IllegalIdentifierError(p)
- else:
- try:
- if p == '*':
- p = None
- else:
- p = long(p)
- if p is not None and p <= 0:
- raise IllegalIdentifierError(p)
- except ValueError:
- raise IllegalIdentifierError(p)
- else:
- res.extend(p or lastMessageId)
- return res
-
-class IllegalQueryError(IMAP4Exception): pass
-
-_SIMPLE_BOOL = (
- 'ALL', 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'NEW', 'OLD', 'RECENT',
- 'SEEN', 'UNANSWERED', 'UNDELETED', 'UNDRAFT', 'UNFLAGGED', 'UNSEEN'
-)
-
-_NO_QUOTES = (
- 'LARGER', 'SMALLER', 'UID'
-)
-
-def Query(sorted=0, **kwarg):
- """Create a query string
-
- Among the accepted keywords are::
-
- all : If set to a true value, search all messages in the
- current mailbox
-
- answered : If set to a true value, search messages flagged with
- \\Answered
-
- bcc : A substring to search the BCC header field for
-
- before : Search messages with an internal date before this
- value. The given date should be a string in the format
- of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
-
- body : A substring to search the body of the messages for
-
- cc : A substring to search the CC header field for
-
- deleted : If set to a true value, search messages flagged with
- \\Deleted
-
- draft : If set to a true value, search messages flagged with
- \\Draft
-
- flagged : If set to a true value, search messages flagged with
- \\Flagged
-
- from : A substring to search the From header field for
-
- header : A two-tuple of a header name and substring to search
- for in that header
-
- keyword : Search for messages with the given keyword set
-
- larger : Search for messages larger than this number of octets
-
- messages : Search only the given message sequence set.
-
- new : If set to a true value, search messages flagged with
- \\Recent but not \\Seen
-
- old : If set to a true value, search messages not flagged with
- \\Recent
-
- on : Search messages with an internal date which is on this
- date. The given date should be a string in the format
- of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
-
- recent : If set to a true value, search for messages flagged with
- \\Recent
-
- seen : If set to a true value, search for messages flagged with
- \\Seen
-
- sentbefore : Search for messages with an RFC822 'Date' header before
- this date. The given date should be a string in the format
- of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
-
- senton : Search for messages with an RFC822 'Date' header which is
- on this date The given date should be a string in the format
- of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
-
- sentsince : Search for messages with an RFC822 'Date' header which is
- after this date. The given date should be a string in the format
- of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
-
- since : Search for messages with an internal date that is after
- this date.. The given date should be a string in the format
- of 'DD-Mon-YYYY'. For example, '03-Mar-2003'.
-
- smaller : Search for messages smaller than this number of octets
-
- subject : A substring to search the 'subject' header for
-
- text : A substring to search the entire message for
-
- to : A substring to search the 'to' header for
-
- uid : Search only the messages in the given message set
-
- unanswered : If set to a true value, search for messages not
- flagged with \\Answered
-
- undeleted : If set to a true value, search for messages not
- flagged with \\Deleted
-
- undraft : If set to a true value, search for messages not
- flagged with \\Draft
-
- unflagged : If set to a true value, search for messages not
- flagged with \\Flagged
-
- unkeyword : Search for messages without the given keyword set
-
- unseen : If set to a true value, search for messages not
- flagged with \\Seen
-
- @type sorted: C{bool}
- @param sorted: If true, the output will be sorted, alphabetically.
- The standard does not require it, but it makes testing this function
- easier. The default is zero, and this should be acceptable for any
- application.
-
- @rtype: C{str}
- @return: The formatted query string
- """
- cmd = []
- keys = kwarg.keys()
- if sorted:
- keys.sort()
- for k in keys:
- v = kwarg[k]
- k = k.upper()
- if k in _SIMPLE_BOOL and v:
- cmd.append(k)
- elif k == 'HEADER':
- cmd.extend([k, v[0], '"%s"' % (v[1],)])
- elif k not in _NO_QUOTES:
- cmd.extend([k, '"%s"' % (v,)])
- else:
- cmd.extend([k, '%s' % (v,)])
- if len(cmd) > 1:
- return '(%s)' % ' '.join(cmd)
- else:
- return ' '.join(cmd)
-
-def Or(*args):
- """The disjunction of two or more queries"""
- if len(args) < 2:
- raise IllegalQueryError, args
- elif len(args) == 2:
- return '(OR %s %s)' % args
- else:
- return '(OR %s %s)' % (args[0], Or(*args[1:]))
-
-def Not(query):
- """The negation of a query"""
- return '(NOT %s)' % (query,)
-
-class MismatchedNesting(IMAP4Exception):
- pass
-
-class MismatchedQuoting(IMAP4Exception):
- pass
-
-def wildcardToRegexp(wildcard, delim=None):
- wildcard = wildcard.replace('*', '(?:.*?)')
- if delim is None:
- wildcard = wildcard.replace('%', '(?:.*?)')
- else:
- wildcard = wildcard.replace('%', '(?:(?:[^%s])*?)' % re.escape(delim))
- return re.compile(wildcard, re.I)
-
-def splitQuoted(s):
- """Split a string into whitespace delimited tokens
-
- Tokens that would otherwise be separated but are surrounded by \"
- remain as a single token. Any token that is not quoted and is
- equal to \"NIL\" is tokenized as C{None}.
-
- @type s: C{str}
- @param s: The string to be split
-
- @rtype: C{list} of C{str}
- @return: A list of the resulting tokens
-
- @raise MismatchedQuoting: Raised if an odd number of quotes are present
- """
- s = s.strip()
- result = []
- word = []
- inQuote = inWord = False
- for i, c in enumerate(s):
- if c == '"':
- if i and s[i-1] == '\\':
- word.pop()
- word.append('"')
- elif not inQuote:
- inQuote = True
- else:
- inQuote = False
- result.append(''.join(word))
- word = []
- elif not inWord and not inQuote and c not in ('"' + string.whitespace):
- inWord = True
- word.append(c)
- elif inWord and not inQuote and c in string.whitespace:
- w = ''.join(word)
- if w == 'NIL':
- result.append(None)
- else:
- result.append(w)
- word = []
- inWord = False
- elif inWord or inQuote:
- word.append(c)
-
- if inQuote:
- raise MismatchedQuoting(s)
- if inWord:
- w = ''.join(word)
- if w == 'NIL':
- result.append(None)
- else:
- result.append(w)
-
- return result
-
-
-
-def splitOn(sequence, predicate, transformers):
- result = []
- mode = predicate(sequence[0])
- tmp = [sequence[0]]
- for e in sequence[1:]:
- p = predicate(e)
- if p != mode:
- result.extend(transformers[mode](tmp))
- tmp = [e]
- mode = p
- else:
- tmp.append(e)
- result.extend(transformers[mode](tmp))
- return result
-
-def collapseStrings(results):
- """
- Turns a list of length-one strings and lists into a list of longer
- strings and lists. For example,
-
- ['a', 'b', ['c', 'd']] is returned as ['ab', ['cd']]
-
- @type results: C{list} of C{str} and C{list}
- @param results: The list to be collapsed
-
- @rtype: C{list} of C{str} and C{list}
- @return: A new list which is the collapsed form of C{results}
- """
- copy = []
- begun = None
- listsList = [isinstance(s, types.ListType) for s in results]
-
- pred = lambda e: isinstance(e, types.TupleType)
- tran = {
- 0: lambda e: splitQuoted(''.join(e)),
- 1: lambda e: [''.join([i[0] for i in e])]
- }
- for (i, c, isList) in zip(range(len(results)), results, listsList):
- if isList:
- if begun is not None:
- copy.extend(splitOn(results[begun:i], pred, tran))
- begun = None
- copy.append(collapseStrings(c))
- elif begun is None:
- begun = i
- if begun is not None:
- copy.extend(splitOn(results[begun:], pred, tran))
- return copy
-
-
-def parseNestedParens(s, handleLiteral = 1):
- """Parse an s-exp-like string into a more useful data structure.
-
- @type s: C{str}
- @param s: The s-exp-like string to parse
-
- @rtype: C{list} of C{str} and C{list}
- @return: A list containing the tokens present in the input.
-
- @raise MismatchedNesting: Raised if the number or placement
- of opening or closing parenthesis is invalid.
- """
- s = s.strip()
- inQuote = 0
- contentStack = [[]]
- try:
- i = 0
- L = len(s)
- while i < L:
- c = s[i]
- if inQuote:
- if c == '\\':
- contentStack[-1].append(s[i:i+2])
- i += 2
- continue
- elif c == '"':
- inQuote = not inQuote
- contentStack[-1].append(c)
- i += 1
- else:
- if c == '"':
- contentStack[-1].append(c)
- inQuote = not inQuote
- i += 1
- elif handleLiteral and c == '{':
- end = s.find('}', i)
- if end == -1:
- raise ValueError, "Malformed literal"
- literalSize = int(s[i+1:end])
- contentStack[-1].append((s[end+3:end+3+literalSize],))
- i = end + 3 + literalSize
- elif c == '(' or c == '[':
- contentStack.append([])
- i += 1
- elif c == ')' or c == ']':
- contentStack[-2].append(contentStack.pop())
- i += 1
- else:
- contentStack[-1].append(c)
- i += 1
- except IndexError:
- raise MismatchedNesting(s)
- if len(contentStack) != 1:
- raise MismatchedNesting(s)
- return collapseStrings(contentStack[0])
-
-def _quote(s):
- return '"%s"' % (s.replace('\\', '\\\\').replace('"', '\\"'),)
-
-def _literal(s):
- return '{%d}\r\n%s' % (len(s), s)
-
-class DontQuoteMe:
- def __init__(self, value):
- self.value = value
-
- def __str__(self):
- return str(self.value)
-
-_ATOM_SPECIALS = '(){ %*"'
-def _needsQuote(s):
- if s == '':
- return 1
- for c in s:
- if c < '\x20' or c > '\x7f':
- return 1
- if c in _ATOM_SPECIALS:
- return 1
- return 0
-
-def _prepareMailboxName(name):
- name = name.encode('imap4-utf-7')
- if _needsQuote(name):
- return _quote(name)
- return name
-
-def _needsLiteral(s):
- # Change this to "return 1" to wig out stupid clients
- return '\n' in s or '\r' in s or len(s) > 1000
-
-def collapseNestedLists(items):
- """Turn a nested list structure into an s-exp-like string.
-
- Strings in C{items} will be sent as literals if they contain CR or LF,
- otherwise they will be quoted. References to None in C{items} will be
- translated to the atom NIL. Objects with a 'read' attribute will have
- it called on them with no arguments and the returned string will be
- inserted into the output as a literal. Integers will be converted to
- strings and inserted into the output unquoted. Instances of
- C{DontQuoteMe} will be converted to strings and inserted into the output
- unquoted.
-
- This function used to be much nicer, and only quote things that really
- needed to be quoted (and C{DontQuoteMe} did not exist), however, many
- broken IMAP4 clients were unable to deal with this level of sophistication,
- forcing the current behavior to be adopted for practical reasons.
-
- @type items: Any iterable
-
- @rtype: C{str}
- """
- pieces = []
- for i in items:
- if i is None:
- pieces.extend([' ', 'NIL'])
- elif isinstance(i, (DontQuoteMe, int, long)):
- pieces.extend([' ', str(i)])
- elif isinstance(i, types.StringTypes):
- if _needsLiteral(i):
- pieces.extend([' ', '{', str(len(i)), '}', IMAP4Server.delimiter, i])
- else:
- pieces.extend([' ', _quote(i)])
- elif hasattr(i, 'read'):
- d = i.read()
- pieces.extend([' ', '{', str(len(d)), '}', IMAP4Server.delimiter, d])
- else:
- pieces.extend([' ', '(%s)' % (collapseNestedLists(i),)])
- return ''.join(pieces[1:])
-
-
-class IClientAuthentication(Interface):
- def getName():
- """Return an identifier associated with this authentication scheme.
-
- @rtype: C{str}
- """
-
- def challengeResponse(secret, challenge):
- """Generate a challenge response string"""
-
-
-
-class CramMD5ClientAuthenticator:
- implements(IClientAuthentication)
-
- def __init__(self, user):
- self.user = user
-
- def getName(self):
- return "CRAM-MD5"
-
- def challengeResponse(self, secret, chal):
- response = hmac.HMAC(secret, chal).hexdigest()
- return '%s %s' % (self.user, response)
-
-
-
-class LOGINAuthenticator:
- implements(IClientAuthentication)
-
- def __init__(self, user):
- self.user = user
- self.challengeResponse = self.challengeUsername
-
- def getName(self):
- return "LOGIN"
-
- def challengeUsername(self, secret, chal):
- # Respond to something like "Username:"
- self.challengeResponse = self.challengeSecret
- return self.user
-
- def challengeSecret(self, secret, chal):
- # Respond to something like "Password:"
- return secret
-
-class PLAINAuthenticator:
- implements(IClientAuthentication)
-
- def __init__(self, user):
- self.user = user
-
- def getName(self):
- return "PLAIN"
-
- def challengeResponse(self, secret, chal):
- return '\0%s\0%s' % (self.user, secret)
-
-
-class MailboxException(IMAP4Exception): pass
-
-class MailboxCollision(MailboxException):
- def __str__(self):
- return 'Mailbox named %s already exists' % self.args
-
-class NoSuchMailbox(MailboxException):
- def __str__(self):
- return 'No mailbox named %s exists' % self.args
-
-class ReadOnlyMailbox(MailboxException):
- def __str__(self):
- return 'Mailbox open in read-only state'
-
-
-class IAccount(Interface):
- """Interface for Account classes
-
- Implementors of this interface should consider implementing
- C{INamespacePresenter}.
- """
-
- def addMailbox(name, mbox = None):
- """Add a new mailbox to this account
-
- @type name: C{str}
- @param name: The name associated with this mailbox. It may not
- contain multiple hierarchical parts.
-
- @type mbox: An object implementing C{IMailbox}
- @param mbox: The mailbox to associate with this name. If C{None},
- a suitable default is created and used.
-
- @rtype: C{Deferred} or C{bool}
- @return: A true value if the creation succeeds, or a deferred whose
- callback will be invoked when the creation succeeds.
-
- @raise MailboxException: Raised if this mailbox cannot be added for
- some reason. This may also be raised asynchronously, if a C{Deferred}
- is returned.
- """
-
- def create(pathspec):
- """Create a new mailbox from the given hierarchical name.
-
- @type pathspec: C{str}
- @param pathspec: The full hierarchical name of a new mailbox to create.
- If any of the inferior hierarchical names to this one do not exist,
- they are created as well.
-
- @rtype: C{Deferred} or C{bool}
- @return: A true value if the creation succeeds, or a deferred whose
- callback will be invoked when the creation succeeds.
-
- @raise MailboxException: Raised if this mailbox cannot be added.
- This may also be raised asynchronously, if a C{Deferred} is
- returned.
- """
-
- def select(name, rw=True):
- """Acquire a mailbox, given its name.
-
- @type name: C{str}
- @param name: The mailbox to acquire
-
- @type rw: C{bool}
- @param rw: If a true value, request a read-write version of this
- mailbox. If a false value, request a read-only version.
-
- @rtype: Any object implementing C{IMailbox} or C{Deferred}
- @return: The mailbox object, or a C{Deferred} whose callback will
- be invoked with the mailbox object. None may be returned if the
- specified mailbox may not be selected for any reason.
- """
-
- def delete(name):
- """Delete the mailbox with the specified name.
-
- @type name: C{str}
- @param name: The mailbox to delete.
-
- @rtype: C{Deferred} or C{bool}
- @return: A true value if the mailbox is successfully deleted, or a
- C{Deferred} whose callback will be invoked when the deletion
- completes.
-
- @raise MailboxException: Raised if this mailbox cannot be deleted.
- This may also be raised asynchronously, if a C{Deferred} is returned.
- """
-
- def rename(oldname, newname):
- """Rename a mailbox
-
- @type oldname: C{str}
- @param oldname: The current name of the mailbox to rename.
-
- @type newname: C{str}
- @param newname: The new name to associate with the mailbox.
-
- @rtype: C{Deferred} or C{bool}
- @return: A true value if the mailbox is successfully renamed, or a
- C{Deferred} whose callback will be invoked when the rename operation
- is completed.
-
- @raise MailboxException: Raised if this mailbox cannot be
- renamed. This may also be raised asynchronously, if a C{Deferred}
- is returned.
- """
-
- def isSubscribed(name):
- """Check the subscription status of a mailbox
-
- @type name: C{str}
- @param name: The name of the mailbox to check
-
- @rtype: C{Deferred} or C{bool}
- @return: A true value if the given mailbox is currently subscribed
- to, a false value otherwise. A C{Deferred} may also be returned
- whose callback will be invoked with one of these values.
- """
-
- def subscribe(name):
- """Subscribe to a mailbox
-
- @type name: C{str}
- @param name: The name of the mailbox to subscribe to
-
- @rtype: C{Deferred} or C{bool}
- @return: A true value if the mailbox is subscribed to successfully,
- or a Deferred whose callback will be invoked with this value when
- the subscription is successful.
-
- @raise MailboxException: Raised if this mailbox cannot be
- subscribed to. This may also be raised asynchronously, if a
- C{Deferred} is returned.
- """
-
- def unsubscribe(name):
- """Unsubscribe from a mailbox
-
- @type name: C{str}
- @param name: The name of the mailbox to unsubscribe from
-
- @rtype: C{Deferred} or C{bool}
- @return: A true value if the mailbox is unsubscribed from successfully,
- or a Deferred whose callback will be invoked with this value when
- the unsubscription is successful.
-
- @raise MailboxException: Raised if this mailbox cannot be
- unsubscribed from. This may also be raised asynchronously, if a
- C{Deferred} is returned.
- """
-
- def listMailboxes(ref, wildcard):
- """List all the mailboxes that meet a certain criteria
-
- @type ref: C{str}
- @param ref: The context in which to apply the wildcard
-
- @type wildcard: C{str}
- @param wildcard: An expression against which to match mailbox names.
- '*' matches any number of characters in a mailbox name, and '%'
- matches similarly, but will not match across hierarchical boundaries.
-
- @rtype: C{list} of C{tuple}
- @return: A list of C{(mailboxName, mailboxObject)} which meet the
- given criteria. C{mailboxObject} should implement either
- C{IMailboxInfo} or C{IMailbox}. A Deferred may also be returned.
- """
-
-class INamespacePresenter(Interface):
- def getPersonalNamespaces():
- """Report the available personal namespaces.
-
- Typically there should be only one personal namespace. A common
- name for it is \"\", and its hierarchical delimiter is usually
- \"/\".
-
- @rtype: iterable of two-tuples of strings
- @return: The personal namespaces and their hierarchical delimiters.
- If no namespaces of this type exist, None should be returned.
- """
-
- def getSharedNamespaces():
- """Report the available shared namespaces.
-
- Shared namespaces do not belong to any individual user but are
- usually to one or more of them. Examples of shared namespaces
- might be \"#news\" for a usenet gateway.
-
- @rtype: iterable of two-tuples of strings
- @return: The shared namespaces and their hierarchical delimiters.
- If no namespaces of this type exist, None should be returned.
- """
-
- def getUserNamespaces():
- """Report the available user namespaces.
-
- These are namespaces that contain folders belonging to other users
- access to which this account has been granted.
-
- @rtype: iterable of two-tuples of strings
- @return: The user namespaces and their hierarchical delimiters.
- If no namespaces of this type exist, None should be returned.
- """
-
-
-class MemoryAccount(object):
- implements(IAccount, INamespacePresenter)
-
- mailboxes = None
- subscriptions = None
- top_id = 0
-
- def __init__(self, name):
- self.name = name
- self.mailboxes = {}
- self.subscriptions = []
-
- def allocateID(self):
- id = self.top_id
- self.top_id += 1
- return id
-
- ##
- ## IAccount
- ##
- def addMailbox(self, name, mbox = None):
- name = name.upper()
- if name in self.mailboxes:
- raise MailboxCollision, name
- if mbox is None:
- mbox = self._emptyMailbox(name, self.allocateID())
- self.mailboxes[name] = mbox
- return 1
-
- def create(self, pathspec):
- paths = filter(None, pathspec.split('/'))
- for accum in range(1, len(paths)):
- try:
- self.addMailbox('/'.join(paths[:accum]))
- except MailboxCollision:
- pass
- try:
- self.addMailbox('/'.join(paths))
- except MailboxCollision:
- if not pathspec.endswith('/'):
- return False
- return True
-
- def _emptyMailbox(self, name, id):
- raise NotImplementedError
-
- def select(self, name, readwrite=1):
- return self.mailboxes.get(name.upper())
-
- def delete(self, name):
- name = name.upper()
- # See if this mailbox exists at all
- mbox = self.mailboxes.get(name)
- if not mbox:
- raise MailboxException("No such mailbox")
- # See if this box is flagged \Noselect
- if r'\Noselect' in mbox.getFlags():
- # Check for hierarchically inferior mailboxes with this one
- # as part of their root.
- for others in self.mailboxes.keys():
- if others != name and others.startswith(name):
- raise MailboxException, "Hierarchically inferior mailboxes exist and \\Noselect is set"
- mbox.destroy()
-
- # iff there are no hierarchically inferior names, we will
- # delete it from our ken.
- if self._inferiorNames(name) > 1:
- del self.mailboxes[name]
-
- def rename(self, oldname, newname):
- oldname = oldname.upper()
- newname = newname.upper()
- if oldname not in self.mailboxes:
- raise NoSuchMailbox, oldname
-
- inferiors = self._inferiorNames(oldname)
- inferiors = [(o, o.replace(oldname, newname, 1)) for o in inferiors]
-
- for (old, new) in inferiors:
- if new in self.mailboxes:
- raise MailboxCollision, new
-
- for (old, new) in inferiors:
- self.mailboxes[new] = self.mailboxes[old]
- del self.mailboxes[old]
-
- def _inferiorNames(self, name):
- inferiors = []
- for infname in self.mailboxes.keys():
- if infname.startswith(name):
- inferiors.append(infname)
- return inferiors
-
- def isSubscribed(self, name):
- return name.upper() in self.subscriptions
-
- def subscribe(self, name):
- name = name.upper()
- if name not in self.subscriptions:
- self.subscriptions.append(name)
-
- def unsubscribe(self, name):
- name = name.upper()
- if name not in self.subscriptions:
- raise MailboxException, "Not currently subscribed to " + name
- self.subscriptions.remove(name)
-
- def listMailboxes(self, ref, wildcard):
- ref = self._inferiorNames(ref.upper())
- wildcard = wildcardToRegexp(wildcard, '/')
- return [(i, self.mailboxes[i]) for i in ref if wildcard.match(i)]
-
- ##
- ## INamespacePresenter
- ##
- def getPersonalNamespaces(self):
- return [["", "/"]]
-
- def getSharedNamespaces(self):
- return None
-
- def getOtherNamespaces(self):
- return None
-
-
-
-_statusRequestDict = {
- 'MESSAGES': 'getMessageCount',
- 'RECENT': 'getRecentCount',
- 'UIDNEXT': 'getUIDNext',
- 'UIDVALIDITY': 'getUIDValidity',
- 'UNSEEN': 'getUnseenCount'
-}
-def statusRequestHelper(mbox, names):
- r = {}
- for n in names:
- r[n] = getattr(mbox, _statusRequestDict[n.upper()])()
- return r
-
-def parseAddr(addr):
- if addr is None:
- return [(None, None, None),]
- addrs = email.Utils.getaddresses([addr])
- return [[fn or None, None] + addr.split('@') for fn, addr in addrs]
-
-def getEnvelope(msg):
- headers = msg.getHeaders(True)
- date = headers.get('date')
- subject = headers.get('subject')
- from_ = headers.get('from')
- sender = headers.get('sender', from_)
- reply_to = headers.get('reply-to', from_)
- to = headers.get('to')
- cc = headers.get('cc')
- bcc = headers.get('bcc')
- in_reply_to = headers.get('in-reply-to')
- mid = headers.get('message-id')
- return (date, subject, parseAddr(from_), parseAddr(sender),
- reply_to and parseAddr(reply_to), to and parseAddr(to),
- cc and parseAddr(cc), bcc and parseAddr(bcc), in_reply_to, mid)
-
-def getLineCount(msg):
- # XXX - Super expensive, CACHE THIS VALUE FOR LATER RE-USE
- # XXX - This must be the number of lines in the ENCODED version
- lines = 0
- for _ in msg.getBodyFile():
- lines += 1
- return lines
-
-def unquote(s):
- if s[0] == s[-1] == '"':
- return s[1:-1]
- return s
-
-
-def _getContentType(msg):
- """
- Return a two-tuple of the main and subtype of the given message.
- """
- attrs = None
- mm = msg.getHeaders(False, 'content-type').get('content-type', None)
- if mm:
- mm = ''.join(mm.splitlines())
- mimetype = mm.split(';')
- if mimetype:
- type = mimetype[0].split('/', 1)
- if len(type) == 1:
- major = type[0]
- minor = None
- elif len(type) == 2:
- major, minor = type
- else:
- major = minor = None
- attrs = dict(x.strip().lower().split('=', 1) for x in mimetype[1:])
- else:
- major = minor = None
- else:
- major = minor = None
- return major, minor, attrs
-
-
-
-def _getMessageStructure(message):
- """
- Construct an appropriate type of message structure object for the given
- message object.
-
- @param message: A L{IMessagePart} provider
-
- @return: A L{_MessageStructure} instance of the most specific type available
- for the given message, determined by inspecting the MIME type of the
- message.
- """
- main, subtype, attrs = _getContentType(message)
- if main is not None:
- main = main.lower()
- if subtype is not None:
- subtype = subtype.lower()
- if main == 'multipart':
- return _MultipartMessageStructure(message, subtype, attrs)
- elif (main, subtype) == ('message', 'rfc822'):
- return _RFC822MessageStructure(message, main, subtype, attrs)
- elif main == 'text':
- return _TextMessageStructure(message, main, subtype, attrs)
- else:
- return _SinglepartMessageStructure(message, main, subtype, attrs)
-
-
-
-class _MessageStructure(object):
- """
- L{_MessageStructure} is a helper base class for message structure classes
- representing the structure of particular kinds of messages, as defined by
- their MIME type.
- """
- def __init__(self, message, attrs):
- """
- @param message: An L{IMessagePart} provider which this structure object
- reports on.
-
- @param attrs: A C{dict} giving the parameters of the I{Content-Type}
- header of the message.
- """
- self.message = message
- self.attrs = attrs
-
-
- def _disposition(self, disp):
- """
- Parse a I{Content-Disposition} header into a two-sequence of the
- disposition and a flattened list of its parameters.
-
- @return: C{None} if there is no disposition header value, a C{list} with
- two elements otherwise.
- """
- if disp:
- disp = disp.split('; ')
- if len(disp) == 1:
- disp = (disp[0].lower(), None)
- elif len(disp) > 1:
- # XXX Poorly tested parser
- params = [x for param in disp[1:] for x in param.split('=', 1)]
- disp = [disp[0].lower(), params]
- return disp
- else:
- return None
-
-
- def _unquotedAttrs(self):
- """
- @return: The I{Content-Type} parameters, unquoted, as a flat list with
- each Nth element giving a parameter name and N+1th element giving
- the corresponding parameter value.
- """
- if self.attrs:
- unquoted = [(k, unquote(v)) for (k, v) in self.attrs.iteritems()]
- return [y for x in sorted(unquoted) for y in x]
- return None
-
-
-
-class _SinglepartMessageStructure(_MessageStructure):
- """
- L{_SinglepartMessageStructure} represents the message structure of a
- non-I{multipart/*} message.
- """
- _HEADERS = [
- 'content-id', 'content-description',
- 'content-transfer-encoding']
-
- def __init__(self, message, main, subtype, attrs):
- """
- @param message: An L{IMessagePart} provider which this structure object
- reports on.
-
- @param main: A C{str} giving the main MIME type of the message (for
- example, C{"text"}).
-
- @param subtype: A C{str} giving the MIME subtype of the message (for
- example, C{"plain"}).
-
- @param attrs: A C{dict} giving the parameters of the I{Content-Type}
- header of the message.
- """
- _MessageStructure.__init__(self, message, attrs)
- self.main = main
- self.subtype = subtype
- self.attrs = attrs
-
-
- def _basicFields(self):
- """
- Return a list of the basic fields for a single-part message.
- """
- headers = self.message.getHeaders(False, *self._HEADERS)
-
- # Number of octets total
- size = self.message.getSize()
-
- major, minor = self.main, self.subtype
-
- # content-type parameter list
- unquotedAttrs = self._unquotedAttrs()
-
- return [
- major, minor, unquotedAttrs,
- headers.get('content-id'),
- headers.get('content-description'),
- headers.get('content-transfer-encoding'),
- size,
- ]
-
-
- def encode(self, extended):
- """
- Construct and return a list of the basic and extended fields for a
- single-part message. The list suitable to be encoded into a BODY or
- BODYSTRUCTURE response.
- """
- result = self._basicFields()
- if extended:
- result.extend(self._extended())
- return result
-
-
- def _extended(self):
- """
- The extension data of a non-multipart body part are in the
- following order:
-
- 1. body MD5
-
- A string giving the body MD5 value as defined in [MD5].
-
- 2. body disposition
-
- A parenthesized list with the same content and function as
- the body disposition for a multipart body part.
-
- 3. body language
-
- A string or parenthesized list giving the body language
- value as defined in [LANGUAGE-TAGS].
-
- 4. body location
-
- A string list giving the body content URI as defined in
- [LOCATION].
-
- """
- result = []
- headers = self.message.getHeaders(
- False, 'content-md5', 'content-disposition',
- 'content-language', 'content-language')
-
- result.append(headers.get('content-md5'))
- result.append(self._disposition(headers.get('content-disposition')))
- result.append(headers.get('content-language'))
- result.append(headers.get('content-location'))
-
- return result
-
-
-
-class _TextMessageStructure(_SinglepartMessageStructure):
- """
- L{_TextMessageStructure} represents the message structure of a I{text/*}
- message.
- """
- def encode(self, extended):
- """
- A body type of type TEXT contains, immediately after the basic
- fields, the size of the body in text lines. Note that this
- size is the size in its content transfer encoding and not the
- resulting size after any decoding.
- """
- result = _SinglepartMessageStructure._basicFields(self)
- result.append(getLineCount(self.message))
- if extended:
- result.extend(self._extended())
- return result
-
-
-
-class _RFC822MessageStructure(_SinglepartMessageStructure):
- """
- L{_RFC822MessageStructure} represents the message structure of a
- I{message/rfc822} message.
- """
- def encode(self, extended):
- """
- A body type of type MESSAGE and subtype RFC822 contains,
- immediately after the basic fields, the envelope structure,
- body structure, and size in text lines of the encapsulated
- message.
- """
- result = _SinglepartMessageStructure.encode(self, extended)
- contained = self.message.getSubPart(0)
- result.append(getEnvelope(contained))
- result.append(getBodyStructure(contained, False))
- result.append(getLineCount(contained))
- return result
-
-
-
-class _MultipartMessageStructure(_MessageStructure):
- """
- L{_MultipartMessageStructure} represents the message structure of a
- I{multipart/*} message.
- """
- def __init__(self, message, subtype, attrs):
- """
- @param message: An L{IMessagePart} provider which this structure object
- reports on.
-
- @param subtype: A C{str} giving the MIME subtype of the message (for
- example, C{"plain"}).
-
- @param attrs: A C{dict} giving the parameters of the I{Content-Type}
- header of the message.
- """
- _MessageStructure.__init__(self, message, attrs)
- self.subtype = subtype
-
-
- def _getParts(self):
- """
- Return an iterator over all of the sub-messages of this message.
- """
- i = 0
- while True:
- try:
- part = self.message.getSubPart(i)
- except IndexError:
- break
- else:
- yield part
- i += 1
-
-
- def encode(self, extended):
- """
- Encode each sub-message and added the additional I{multipart} fields.
- """
- result = [_getMessageStructure(p).encode(extended) for p in self._getParts()]
- result.append(self.subtype)
- if extended:
- result.extend(self._extended())
- return result
-
-
- def _extended(self):
- """
- The extension data of a multipart body part are in the following order:
-
- 1. body parameter parenthesized list
- A parenthesized list of attribute/value pairs [e.g., ("foo"
- "bar" "baz" "rag") where "bar" is the value of "foo", and
- "rag" is the value of "baz"] as defined in [MIME-IMB].
-
- 2. body disposition
- A parenthesized list, consisting of a disposition type
- string, followed by a parenthesized list of disposition
- attribute/value pairs as defined in [DISPOSITION].
-
- 3. body language
- A string or parenthesized list giving the body language
- value as defined in [LANGUAGE-TAGS].
-
- 4. body location
- A string list giving the body content URI as defined in
- [LOCATION].
- """
- result = []
- headers = self.message.getHeaders(
- False, 'content-language', 'content-location',
- 'content-disposition')
-
- result.append(self._unquotedAttrs())
- result.append(self._disposition(headers.get('content-disposition')))
- result.append(headers.get('content-language', None))
- result.append(headers.get('content-location', None))
-
- return result
-
-
-
-def getBodyStructure(msg, extended=False):
- """
- RFC 3501, 7.4.2, BODYSTRUCTURE::
-
- A parenthesized list that describes the [MIME-IMB] body structure of a
- message. This is computed by the server by parsing the [MIME-IMB] header
- fields, defaulting various fields as necessary.
-
- For example, a simple text message of 48 lines and 2279 octets can have
- a body structure of: ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL
- "7BIT" 2279 48)
-
- This is represented as::
-
- ["TEXT", "PLAIN", ["CHARSET", "US-ASCII"], None, None, "7BIT", 2279, 48]
-
- These basic fields are documented in the RFC as:
-
- 1. body type
-
- A string giving the content media type name as defined in
- [MIME-IMB].
-
- 2. body subtype
-
- A string giving the content subtype name as defined in
- [MIME-IMB].
-
- 3. body parameter parenthesized list
-
- A parenthesized list of attribute/value pairs [e.g., ("foo"
- "bar" "baz" "rag") where "bar" is the value of "foo" and
- "rag" is the value of "baz"] as defined in [MIME-IMB].
-
- 4. body id
-
- A string giving the content id as defined in [MIME-IMB].
-
- 5. body description
-
- A string giving the content description as defined in
- [MIME-IMB].
-
- 6. body encoding
-
- A string giving the content transfer encoding as defined in
- [MIME-IMB].
-
- 7. body size
-
- A number giving the size of the body in octets. Note that this size is
- the size in its transfer encoding and not the resulting size after any
- decoding.
-
- Put another way, the body structure is a list of seven elements. The
- semantics of the elements of this list are:
-
- 1. Byte string giving the major MIME type
- 2. Byte string giving the minor MIME type
- 3. A list giving the Content-Type parameters of the message
- 4. A byte string giving the content identifier for the message part, or
- None if it has no content identifier.
- 5. A byte string giving the content description for the message part, or
- None if it has no content description.
- 6. A byte string giving the Content-Encoding of the message body
- 7. An integer giving the number of octets in the message body
-
- The RFC goes on::
-
- Multiple parts are indicated by parenthesis nesting. Instead of a body
- type as the first element of the parenthesized list, there is a sequence
- of one or more nested body structures. The second element of the
- parenthesized list is the multipart subtype (mixed, digest, parallel,
- alternative, etc.).
-
- For example, a two part message consisting of a text and a
- BASE64-encoded text attachment can have a body structure of: (("TEXT"
- "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN"
- ("CHARSET" "US-ASCII" "NAME" "cc.diff")
- "<960723163407.20117h@cac.washington.edu>" "Compiler diff" "BASE64" 4554
- 73) "MIXED")
-
- This is represented as::
-
- [["TEXT", "PLAIN", ["CHARSET", "US-ASCII"], None, None, "7BIT", 1152,
- 23],
- ["TEXT", "PLAIN", ["CHARSET", "US-ASCII", "NAME", "cc.diff"],
- "<960723163407.20117h@cac.washington.edu>", "Compiler diff",
- "BASE64", 4554, 73],
- "MIXED"]
-
- In other words, a list of N + 1 elements, where N is the number of parts in
- the message. The first N elements are structures as defined by the previous
- section. The last element is the minor MIME subtype of the multipart
- message.
-
- Additionally, the RFC describes extension data::
-
- Extension data follows the multipart subtype. Extension data is never
- returned with the BODY fetch, but can be returned with a BODYSTRUCTURE
- fetch. Extension data, if present, MUST be in the defined order.
-
- The C{extended} flag controls whether extension data might be returned with
- the normal data.
- """
- return _getMessageStructure(msg).encode(extended)
-
-
-
-class IMessagePart(Interface):
- def getHeaders(negate, *names):
- """Retrieve a group of message headers.
-
- @type names: C{tuple} of C{str}
- @param names: The names of the headers to retrieve or omit.
-
- @type negate: C{bool}
- @param negate: If True, indicates that the headers listed in C{names}
- should be omitted from the return value, rather than included.
-
- @rtype: C{dict}
- @return: A mapping of header field names to header field values
- """
-
- def getBodyFile():
- """Retrieve a file object containing only the body of this message.
- """
-
- def getSize():
- """Retrieve the total size, in octets, of this message.
-
- @rtype: C{int}
- """
-
- def isMultipart():
- """Indicate whether this message has subparts.
-
- @rtype: C{bool}
- """
-
- def getSubPart(part):
- """Retrieve a MIME sub-message
-
- @type part: C{int}
- @param part: The number of the part to retrieve, indexed from 0.
-
- @raise IndexError: Raised if the specified part does not exist.
- @raise TypeError: Raised if this message is not multipart.
-
- @rtype: Any object implementing C{IMessagePart}.
- @return: The specified sub-part.
- """
-
-class IMessage(IMessagePart):
- def getUID():
- """Retrieve the unique identifier associated with this message.
- """
-
- def getFlags():
- """Retrieve the flags associated with this message.
-
- @rtype: C{iterable}
- @return: The flags, represented as strings.
- """
-
- def getInternalDate():
- """Retrieve the date internally associated with this message.
-
- @rtype: C{str}
- @return: An RFC822-formatted date string.
- """
-
-class IMessageFile(Interface):
- """Optional message interface for representing messages as files.
-
- If provided by message objects, this interface will be used instead
- the more complex MIME-based interface.
- """
- def open():
- """Return an file-like object opened for reading.
-
- Reading from the returned file will return all the bytes
- of which this message consists.
- """
-
-class ISearchableMailbox(Interface):
- def search(query, uid):
- """Search for messages that meet the given query criteria.
-
- If this interface is not implemented by the mailbox, L{IMailbox.fetch}
- and various methods of L{IMessage} will be used instead.
-
- Implementations which wish to offer better performance than the
- default implementation should implement this interface.
-
- @type query: C{list}
- @param query: The search criteria
-
- @type uid: C{bool}
- @param uid: If true, the IDs specified in the query are UIDs;
- otherwise they are message sequence IDs.
-
- @rtype: C{list} or C{Deferred}
- @return: A list of message sequence numbers or message UIDs which
- match the search criteria or a C{Deferred} whose callback will be
- invoked with such a list.
-
- @raise IllegalQueryError: Raised when query is not valid.
- """
-
-class IMessageCopier(Interface):
- def copy(messageObject):
- """Copy the given message object into this mailbox.
-
- The message object will be one which was previously returned by
- L{IMailbox.fetch}.
-
- Implementations which wish to offer better performance than the
- default implementation should implement this interface.
-
- If this interface is not implemented by the mailbox, IMailbox.addMessage
- will be used instead.
-
- @rtype: C{Deferred} or C{int}
- @return: Either the UID of the message or a Deferred which fires
- with the UID when the copy finishes.
- """
-
-class IMailboxInfo(Interface):
- """Interface specifying only the methods required for C{listMailboxes}.
-
- Implementations can return objects implementing only these methods for
- return to C{listMailboxes} if it can allow them to operate more
- efficiently.
- """
-
- def getFlags():
- """Return the flags defined in this mailbox
-
- Flags with the \\ prefix are reserved for use as system flags.
-
- @rtype: C{list} of C{str}
- @return: A list of the flags that can be set on messages in this mailbox.
- """
-
- def getHierarchicalDelimiter():
- """Get the character which delimits namespaces for in this mailbox.
-
- @rtype: C{str}
- """
-
-class IMailbox(IMailboxInfo):
- def getUIDValidity():
- """Return the unique validity identifier for this mailbox.
-
- @rtype: C{int}
- """
-
- def getUIDNext():
- """Return the likely UID for the next message added to this mailbox.
-
- @rtype: C{int}
- """
-
- def getUID(message):
- """Return the UID of a message in the mailbox
-
- @type message: C{int}
- @param message: The message sequence number
-
- @rtype: C{int}
- @return: The UID of the message.
- """
-
- def getMessageCount():
- """Return the number of messages in this mailbox.
-
- @rtype: C{int}
- """
-
- def getRecentCount():
- """Return the number of messages with the 'Recent' flag.
-
- @rtype: C{int}
- """
-
- def getUnseenCount():
- """Return the number of messages with the 'Unseen' flag.
-
- @rtype: C{int}
- """
-
- def isWriteable():
- """Get the read/write status of the mailbox.
-
- @rtype: C{int}
- @return: A true value if write permission is allowed, a false value otherwise.
- """
-
- def destroy():
- """Called before this mailbox is deleted, permanently.
-
- If necessary, all resources held by this mailbox should be cleaned
- up here. This function _must_ set the \\Noselect flag on this
- mailbox.
- """
-
- def requestStatus(names):
- """Return status information about this mailbox.
-
- Mailboxes which do not intend to do any special processing to
- generate the return value, C{statusRequestHelper} can be used
- to build the dictionary by calling the other interface methods
- which return the data for each name.
-
- @type names: Any iterable
- @param names: The status names to return information regarding.
- The possible values for each name are: MESSAGES, RECENT, UIDNEXT,
- UIDVALIDITY, UNSEEN.
-
- @rtype: C{dict} or C{Deferred}
- @return: A dictionary containing status information about the
- requested names is returned. If the process of looking this
- information up would be costly, a deferred whose callback will
- eventually be passed this dictionary is returned instead.
- """
-
- def addListener(listener):
- """Add a mailbox change listener
-
- @type listener: Any object which implements C{IMailboxListener}
- @param listener: An object to add to the set of those which will
- be notified when the contents of this mailbox change.
- """
-
- def removeListener(listener):
- """Remove a mailbox change listener
-
- @type listener: Any object previously added to and not removed from
- this mailbox as a listener.
- @param listener: The object to remove from the set of listeners.
-
- @raise ValueError: Raised when the given object is not a listener for
- this mailbox.
- """
-
- def addMessage(message, flags = (), date = None):
- """Add the given message to this mailbox.
-
- @type message: A file-like object
- @param message: The RFC822 formatted message
-
- @type flags: Any iterable of C{str}
- @param flags: The flags to associate with this message
-
- @type date: C{str}
- @param date: If specified, the date to associate with this
- message.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked with the message
- id if the message is added successfully and whose errback is
- invoked otherwise.
-
- @raise ReadOnlyMailbox: Raised if this Mailbox is not open for
- read-write.
- """
-
- def expunge():
- """Remove all messages flagged \\Deleted.
-
- @rtype: C{list} or C{Deferred}
- @return: The list of message sequence numbers which were deleted,
- or a C{Deferred} whose callback will be invoked with such a list.
-
- @raise ReadOnlyMailbox: Raised if this Mailbox is not open for
- read-write.
- """
-
- def fetch(messages, uid):
- """Retrieve one or more messages.
-
- @type messages: C{MessageSet}
- @param messages: The identifiers of messages to retrieve information
- about
-
- @type uid: C{bool}
- @param uid: If true, the IDs specified in the query are UIDs;
- otherwise they are message sequence IDs.
-
- @rtype: Any iterable of two-tuples of message sequence numbers and
- implementors of C{IMessage}.
- """
-
- def store(messages, flags, mode, uid):
- """Set the flags of one or more messages.
-
- @type messages: A MessageSet object with the list of messages requested
- @param messages: The identifiers of the messages to set the flags of.
-
- @type flags: sequence of C{str}
- @param flags: The flags to set, unset, or add.
-
- @type mode: -1, 0, or 1
- @param mode: If mode is -1, these flags should be removed from the
- specified messages. If mode is 1, these flags should be added to
- the specified messages. If mode is 0, all existing flags should be
- cleared and these flags should be added.
-
- @type uid: C{bool}
- @param uid: If true, the IDs specified in the query are UIDs;
- otherwise they are message sequence IDs.
-
- @rtype: C{dict} or C{Deferred}
- @return: A C{dict} mapping message sequence numbers to sequences of C{str}
- representing the flags set on the message after this operation has
- been performed, or a C{Deferred} whose callback will be invoked with
- such a C{dict}.
-
- @raise ReadOnlyMailbox: Raised if this mailbox is not open for
- read-write.
- """
-
-class ICloseableMailbox(Interface):
- """A supplementary interface for mailboxes which require cleanup on close.
-
- Implementing this interface is optional. If it is implemented, the protocol
- code will call the close method defined whenever a mailbox is closed.
- """
- def close():
- """Close this mailbox.
-
- @return: A C{Deferred} which fires when this mailbox
- has been closed, or None if the mailbox can be closed
- immediately.
- """
-
-def _formatHeaders(headers):
- hdrs = [': '.join((k.title(), '\r\n'.join(v.splitlines()))) for (k, v)
- in headers.iteritems()]
- hdrs = '\r\n'.join(hdrs) + '\r\n'
- return hdrs
-
-def subparts(m):
- i = 0
- try:
- while True:
- yield m.getSubPart(i)
- i += 1
- except IndexError:
- pass
-
-def iterateInReactor(i):
- """Consume an interator at most a single iteration per reactor iteration.
-
- If the iterator produces a Deferred, the next iteration will not occur
- until the Deferred fires, otherwise the next iteration will be taken
- in the next reactor iteration.
-
- @rtype: C{Deferred}
- @return: A deferred which fires (with None) when the iterator is
- exhausted or whose errback is called if there is an exception.
- """
- from twisted.internet import reactor
- d = defer.Deferred()
- def go(last):
- try:
- r = i.next()
- except StopIteration:
- d.callback(last)
- except:
- d.errback()
- else:
- if isinstance(r, defer.Deferred):
- r.addCallback(go)
- else:
- reactor.callLater(0, go, r)
- go(None)
- return d
-
-class MessageProducer:
- CHUNK_SIZE = 2 ** 2 ** 2 ** 2
-
- def __init__(self, msg, buffer = None, scheduler = None):
- """Produce this message.
-
- @param msg: The message I am to produce.
- @type msg: L{IMessage}
-
- @param buffer: A buffer to hold the message in. If None, I will
- use a L{tempfile.TemporaryFile}.
- @type buffer: file-like
- """
- self.msg = msg
- if buffer is None:
- buffer = tempfile.TemporaryFile()
- self.buffer = buffer
- if scheduler is None:
- scheduler = iterateInReactor
- self.scheduler = scheduler
- self.write = self.buffer.write
-
- def beginProducing(self, consumer):
- self.consumer = consumer
- return self.scheduler(self._produce())
-
- def _produce(self):
- headers = self.msg.getHeaders(True)
- boundary = None
- if self.msg.isMultipart():
- content = headers.get('content-type')
- parts = [x.split('=', 1) for x in content.split(';')[1:]]
- parts = dict([(k.lower().strip(), v) for (k, v) in parts])
- boundary = parts.get('boundary')
- if boundary is None:
- # Bastards
- boundary = '----=_%f_boundary_%f' % (time.time(), random.random())
- headers['content-type'] += '; boundary="%s"' % (boundary,)
- else:
- if boundary.startswith('"') and boundary.endswith('"'):
- boundary = boundary[1:-1]
-
- self.write(_formatHeaders(headers))
- self.write('\r\n')
- if self.msg.isMultipart():
- for p in subparts(self.msg):
- self.write('\r\n--%s\r\n' % (boundary,))
- yield MessageProducer(p, self.buffer, self.scheduler
- ).beginProducing(None
- )
- self.write('\r\n--%s--\r\n' % (boundary,))
- else:
- f = self.msg.getBodyFile()
- while True:
- b = f.read(self.CHUNK_SIZE)
- if b:
- self.buffer.write(b)
- yield None
- else:
- break
- if self.consumer:
- self.buffer.seek(0, 0)
- yield FileProducer(self.buffer
- ).beginProducing(self.consumer
- ).addCallback(lambda _: self
- )
-
-class _FetchParser:
- class Envelope:
- # Response should be a list of fields from the message:
- # date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to,
- # and message-id.
- #
- # from, sender, reply-to, to, cc, and bcc are themselves lists of
- # address information:
- # personal name, source route, mailbox name, host name
- #
- # reply-to and sender must not be None. If not present in a message
- # they should be defaulted to the value of the from field.
- type = 'envelope'
- __str__ = lambda self: 'envelope'
-
- class Flags:
- type = 'flags'
- __str__ = lambda self: 'flags'
-
- class InternalDate:
- type = 'internaldate'
- __str__ = lambda self: 'internaldate'
-
- class RFC822Header:
- type = 'rfc822header'
- __str__ = lambda self: 'rfc822.header'
-
- class RFC822Text:
- type = 'rfc822text'
- __str__ = lambda self: 'rfc822.text'
-
- class RFC822Size:
- type = 'rfc822size'
- __str__ = lambda self: 'rfc822.size'
-
- class RFC822:
- type = 'rfc822'
- __str__ = lambda self: 'rfc822'
-
- class UID:
- type = 'uid'
- __str__ = lambda self: 'uid'
-
- class Body:
- type = 'body'
- peek = False
- header = None
- mime = None
- text = None
- part = ()
- empty = False
- partialBegin = None
- partialLength = None
- def __str__(self):
- base = 'BODY'
- part = ''
- separator = ''
- if self.part:
- part = '.'.join([str(x + 1) for x in self.part])
- separator = '.'
-# if self.peek:
-# base += '.PEEK'
- if self.header:
- base += '[%s%s%s]' % (part, separator, self.header,)
- elif self.text:
- base += '[%s%sTEXT]' % (part, separator)
- elif self.mime:
- base += '[%s%sMIME]' % (part, separator)
- elif self.empty:
- base += '[%s]' % (part,)
- if self.partialBegin is not None:
- base += '<%d.%d>' % (self.partialBegin, self.partialLength)
- return base
-
- class BodyStructure:
- type = 'bodystructure'
- __str__ = lambda self: 'bodystructure'
-
- # These three aren't top-level, they don't need type indicators
- class Header:
- negate = False
- fields = None
- part = None
- def __str__(self):
- base = 'HEADER'
- if self.fields:
- base += '.FIELDS'
- if self.negate:
- base += '.NOT'
- fields = []
- for f in self.fields:
- f = f.title()
- if _needsQuote(f):
- f = _quote(f)
- fields.append(f)
- base += ' (%s)' % ' '.join(fields)
- if self.part:
- base = '.'.join([str(x + 1) for x in self.part]) + '.' + base
- return base
-
- class Text:
- pass
-
- class MIME:
- pass
-
- parts = None
-
- _simple_fetch_att = [
- ('envelope', Envelope),
- ('flags', Flags),
- ('internaldate', InternalDate),
- ('rfc822.header', RFC822Header),
- ('rfc822.text', RFC822Text),
- ('rfc822.size', RFC822Size),
- ('rfc822', RFC822),
- ('uid', UID),
- ('bodystructure', BodyStructure),
- ]
-
- def __init__(self):
- self.state = ['initial']
- self.result = []
- self.remaining = ''
-
- def parseString(self, s):
- s = self.remaining + s
- try:
- while s or self.state:
- # print 'Entering state_' + self.state[-1] + ' with', repr(s)
- state = self.state.pop()
- try:
- used = getattr(self, 'state_' + state)(s)
- except:
- self.state.append(state)
- raise
- else:
- # print state, 'consumed', repr(s[:used])
- s = s[used:]
- finally:
- self.remaining = s
-
- def state_initial(self, s):
- # In the initial state, the literals "ALL", "FULL", and "FAST"
- # are accepted, as is a ( indicating the beginning of a fetch_att
- # token, as is the beginning of a fetch_att token.
- if s == '':
- return 0
-
- l = s.lower()
- if l.startswith('all'):
- self.result.extend((
- self.Flags(), self.InternalDate(),
- self.RFC822Size(), self.Envelope()
- ))
- return 3
- if l.startswith('full'):
- self.result.extend((
- self.Flags(), self.InternalDate(),
- self.RFC822Size(), self.Envelope(),
- self.Body()
- ))
- return 4
- if l.startswith('fast'):
- self.result.extend((
- self.Flags(), self.InternalDate(), self.RFC822Size(),
- ))
- return 4
-
- if l.startswith('('):
- self.state.extend(('close_paren', 'maybe_fetch_att', 'fetch_att'))
- return 1
-
- self.state.append('fetch_att')
- return 0
-
- def state_close_paren(self, s):
- if s.startswith(')'):
- return 1
- raise Exception("Missing )")
-
- def state_whitespace(self, s):
- # Eat up all the leading whitespace
- if not s or not s[0].isspace():
- raise Exception("Whitespace expected, none found")
- i = 0
- for i in range(len(s)):
- if not s[i].isspace():
- break
- return i
-
- def state_maybe_fetch_att(self, s):
- if not s.startswith(')'):
- self.state.extend(('maybe_fetch_att', 'fetch_att', 'whitespace'))
- return 0
-
- def state_fetch_att(self, s):
- # Allowed fetch_att tokens are "ENVELOPE", "FLAGS", "INTERNALDATE",
- # "RFC822", "RFC822.HEADER", "RFC822.SIZE", "RFC822.TEXT", "BODY",
- # "BODYSTRUCTURE", "UID",
- # "BODY [".PEEK"] [<section>] ["<" <number> "." <nz_number> ">"]
-
- l = s.lower()
- for (name, cls) in self._simple_fetch_att:
- if l.startswith(name):
- self.result.append(cls())
- return len(name)
-
- b = self.Body()
- if l.startswith('body.peek'):
- b.peek = True
- used = 9
- elif l.startswith('body'):
- used = 4
- else:
- raise Exception("Nothing recognized in fetch_att: %s" % (l,))
-
- self.pending_body = b
- self.state.extend(('got_body', 'maybe_partial', 'maybe_section'))
- return used
-
- def state_got_body(self, s):
- self.result.append(self.pending_body)
- del self.pending_body
- return 0
-
- def state_maybe_section(self, s):
- if not s.startswith("["):
- return 0
-
- self.state.extend(('section', 'part_number'))
- return 1
-
- _partExpr = re.compile(r'(\d+(?:\.\d+)*)\.?')
- def state_part_number(self, s):
- m = self._partExpr.match(s)
- if m is not None:
- self.parts = [int(p) - 1 for p in m.groups()[0].split('.')]
- return m.end()
- else:
- self.parts = []
- return 0
-
- def state_section(self, s):
- # Grab "HEADER]" or "HEADER.FIELDS (Header list)]" or
- # "HEADER.FIELDS.NOT (Header list)]" or "TEXT]" or "MIME]" or
- # just "]".
-
- l = s.lower()
- used = 0
- if l.startswith(']'):
- self.pending_body.empty = True
- used += 1
- elif l.startswith('header]'):
- h = self.pending_body.header = self.Header()
- h.negate = True
- h.fields = ()
- used += 7
- elif l.startswith('text]'):
- self.pending_body.text = self.Text()
- used += 5
- elif l.startswith('mime]'):
- self.pending_body.mime = self.MIME()
- used += 5
- else:
- h = self.Header()
- if l.startswith('header.fields.not'):
- h.negate = True
- used += 17
- elif l.startswith('header.fields'):
- used += 13
- else:
- raise Exception("Unhandled section contents: %r" % (l,))
-
- self.pending_body.header = h
- self.state.extend(('finish_section', 'header_list', 'whitespace'))
- self.pending_body.part = tuple(self.parts)
- self.parts = None
- return used
-
- def state_finish_section(self, s):
- if not s.startswith(']'):
- raise Exception("section must end with ]")
- return 1
-
- def state_header_list(self, s):
- if not s.startswith('('):
- raise Exception("Header list must begin with (")
- end = s.find(')')
- if end == -1:
- raise Exception("Header list must end with )")
-
- headers = s[1:end].split()
- self.pending_body.header.fields = map(str.upper, headers)
- return end + 1
-
- def state_maybe_partial(self, s):
- # Grab <number.number> or nothing at all
- if not s.startswith('<'):
- return 0
- end = s.find('>')
- if end == -1:
- raise Exception("Found < but not >")
-
- partial = s[1:end]
- parts = partial.split('.', 1)
- if len(parts) != 2:
- raise Exception("Partial specification did not include two .-delimited integers")
- begin, length = map(int, parts)
- self.pending_body.partialBegin = begin
- self.pending_body.partialLength = length
-
- return end + 1
-
-class FileProducer:
- CHUNK_SIZE = 2 ** 2 ** 2 ** 2
-
- firstWrite = True
-
- def __init__(self, f):
- self.f = f
-
- def beginProducing(self, consumer):
- self.consumer = consumer
- self.produce = consumer.write
- d = self._onDone = defer.Deferred()
- self.consumer.registerProducer(self, False)
- return d
-
- def resumeProducing(self):
- b = ''
- if self.firstWrite:
- b = '{%d}\r\n' % self._size()
- self.firstWrite = False
- if not self.f:
- return
- b = b + self.f.read(self.CHUNK_SIZE)
- if not b:
- self.consumer.unregisterProducer()
- self._onDone.callback(self)
- self._onDone = self.f = self.consumer = None
- else:
- self.produce(b)
-
- def pauseProducing(self):
- pass
-
- def stopProducing(self):
- pass
-
- def _size(self):
- b = self.f.tell()
- self.f.seek(0, 2)
- e = self.f.tell()
- self.f.seek(b, 0)
- return e - b
-
-def parseTime(s):
- # XXX - This may require localization :(
- months = [
- 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct',
- 'nov', 'dec', 'january', 'february', 'march', 'april', 'may', 'june',
- 'july', 'august', 'september', 'october', 'november', 'december'
- ]
- expr = {
- 'day': r"(?P<day>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
- 'mon': r"(?P<mon>\w+)",
- 'year': r"(?P<year>\d\d\d\d)"
- }
- m = re.match('%(day)s-%(mon)s-%(year)s' % expr, s)
- if not m:
- raise ValueError, "Cannot parse time string %r" % (s,)
- d = m.groupdict()
- try:
- d['mon'] = 1 + (months.index(d['mon'].lower()) % 12)
- d['year'] = int(d['year'])
- d['day'] = int(d['day'])
- except ValueError:
- raise ValueError, "Cannot parse time string %r" % (s,)
- else:
- return time.struct_time(
- (d['year'], d['mon'], d['day'], 0, 0, 0, -1, -1, -1)
- )
-
-import codecs
-def modified_base64(s):
- s_utf7 = s.encode('utf-7')
- return s_utf7[1:-1].replace('/', ',')
-
-def modified_unbase64(s):
- s_utf7 = '+' + s.replace(',', '/') + '-'
- return s_utf7.decode('utf-7')
-
-def encoder(s, errors=None):
- """
- Encode the given C{unicode} string using the IMAP4 specific variation of
- UTF-7.
-
- @type s: C{unicode}
- @param s: The text to encode.
-
- @param errors: Policy for handling encoding errors. Currently ignored.
-
- @return: C{tuple} of a C{str} giving the encoded bytes and an C{int}
- giving the number of code units consumed from the input.
- """
- r = []
- _in = []
- for c in s:
- if ord(c) in (range(0x20, 0x26) + range(0x27, 0x7f)):
- if _in:
- r.extend(['&', modified_base64(''.join(_in)), '-'])
- del _in[:]
- r.append(str(c))
- elif c == '&':
- if _in:
- r.extend(['&', modified_base64(''.join(_in)), '-'])
- del _in[:]
- r.append('&-')
- else:
- _in.append(c)
- if _in:
- r.extend(['&', modified_base64(''.join(_in)), '-'])
- return (''.join(r), len(s))
-
-def decoder(s, errors=None):
- """
- Decode the given C{str} using the IMAP4 specific variation of UTF-7.
-
- @type s: C{str}
- @param s: The bytes to decode.
-
- @param errors: Policy for handling decoding errors. Currently ignored.
-
- @return: a C{tuple} of a C{unicode} string giving the text which was
- decoded and an C{int} giving the number of bytes consumed from the
- input.
- """
- r = []
- decode = []
- for c in s:
- if c == '&' and not decode:
- decode.append('&')
- elif c == '-' and decode:
- if len(decode) == 1:
- r.append('&')
- else:
- r.append(modified_unbase64(''.join(decode[1:])))
- decode = []
- elif decode:
- decode.append(c)
- else:
- r.append(c)
- if decode:
- r.append(modified_unbase64(''.join(decode[1:])))
- return (''.join(r), len(s))
-
-class StreamReader(codecs.StreamReader):
- def decode(self, s, errors='strict'):
- return decoder(s)
-
-class StreamWriter(codecs.StreamWriter):
- def encode(self, s, errors='strict'):
- return encoder(s)
-
-_codecInfo = (encoder, decoder, StreamReader, StreamWriter)
-try:
- _codecInfoClass = codecs.CodecInfo
-except AttributeError:
- pass
-else:
- _codecInfo = _codecInfoClass(*_codecInfo)
-
-def imap4_utf_7(name):
- if name == 'imap4-utf-7':
- return _codecInfo
-codecs.register(imap4_utf_7)
-
-__all__ = [
- # Protocol classes
- 'IMAP4Server', 'IMAP4Client',
-
- # Interfaces
- 'IMailboxListener', 'IClientAuthentication', 'IAccount', 'IMailbox',
- 'INamespacePresenter', 'ICloseableMailbox', 'IMailboxInfo',
- 'IMessage', 'IMessageCopier', 'IMessageFile', 'ISearchableMailbox',
-
- # Exceptions
- 'IMAP4Exception', 'IllegalClientResponse', 'IllegalOperation',
- 'IllegalMailboxEncoding', 'UnhandledResponse', 'NegativeResponse',
- 'NoSupportedAuthentication', 'IllegalServerResponse',
- 'IllegalIdentifierError', 'IllegalQueryError', 'MismatchedNesting',
- 'MismatchedQuoting', 'MailboxException', 'MailboxCollision',
- 'NoSuchMailbox', 'ReadOnlyMailbox',
-
- # Auth objects
- 'CramMD5ClientAuthenticator', 'PLAINAuthenticator', 'LOGINAuthenticator',
- 'PLAINCredentials', 'LOGINCredentials',
-
- # Simple query interface
- 'Query', 'Not', 'Or',
-
- # Miscellaneous
- 'MemoryAccount',
- 'statusRequestHelper',
-]
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/mail.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/mail.py
deleted file mode 100755
index 07789aa8..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/mail.py
+++ /dev/null
@@ -1,333 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_mail -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""Mail support for twisted python.
-"""
-
-# Twisted imports
-from twisted.internet import defer
-from twisted.application import service, internet
-from twisted.python import util
-from twisted.python import log
-
-from twisted import cred
-import twisted.cred.portal
-
-# Sibling imports
-from twisted.mail import protocols, smtp
-
-# System imports
-import os
-from zope.interface import implements, Interface
-
-
-class DomainWithDefaultDict:
- '''Simulate a dictionary with a default value for non-existing keys.
- '''
- def __init__(self, domains, default):
- self.domains = domains
- self.default = default
-
- def setDefaultDomain(self, domain):
- self.default = domain
-
- def has_key(self, name):
- return 1
-
- def fromkeys(klass, keys, value=None):
- d = klass()
- for k in keys:
- d[k] = value
- return d
- fromkeys = classmethod(fromkeys)
-
- def __contains__(self, name):
- return 1
-
- def __getitem__(self, name):
- return self.domains.get(name, self.default)
-
- def __setitem__(self, name, value):
- self.domains[name] = value
-
- def __delitem__(self, name):
- del self.domains[name]
-
- def __iter__(self):
- return iter(self.domains)
-
- def __len__(self):
- return len(self.domains)
-
-
- def __str__(self):
- """
- Return a string describing the underlying domain mapping of this
- object.
- """
- return '<DomainWithDefaultDict %s>' % (self.domains,)
-
-
- def __repr__(self):
- """
- Return a pseudo-executable string describing the underlying domain
- mapping of this object.
- """
- return 'DomainWithDefaultDict(%s)' % (self.domains,)
-
-
- def get(self, key, default=None):
- return self.domains.get(key, default)
-
- def copy(self):
- return DomainWithDefaultDict(self.domains.copy(), self.default)
-
- def iteritems(self):
- return self.domains.iteritems()
-
- def iterkeys(self):
- return self.domains.iterkeys()
-
- def itervalues(self):
- return self.domains.itervalues()
-
- def keys(self):
- return self.domains.keys()
-
- def values(self):
- return self.domains.values()
-
- def items(self):
- return self.domains.items()
-
- def popitem(self):
- return self.domains.popitem()
-
- def update(self, other):
- return self.domains.update(other)
-
- def clear(self):
- return self.domains.clear()
-
- def setdefault(self, key, default):
- return self.domains.setdefault(key, default)
-
-class IDomain(Interface):
- """An email domain."""
-
- def exists(user):
- """
- Check whether or not the specified user exists in this domain.
-
- @type user: C{twisted.protocols.smtp.User}
- @param user: The user to check
-
- @rtype: No-argument callable
- @return: A C{Deferred} which becomes, or a callable which
- takes no arguments and returns an object implementing C{IMessage}.
- This will be called and the returned object used to deliver the
- message when it arrives.
-
- @raise twisted.protocols.smtp.SMTPBadRcpt: Raised if the given
- user does not exist in this domain.
- """
-
- def addUser(user, password):
- """Add a username/password to this domain."""
-
- def startMessage(user):
- """Create and return a new message to be delivered to the given user.
-
- DEPRECATED. Implement validateTo() correctly instead.
- """
-
- def getCredentialsCheckers():
- """Return a list of ICredentialsChecker implementors for this domain.
- """
-
-class IAliasableDomain(IDomain):
- def setAliasGroup(aliases):
- """Set the group of defined aliases for this domain
-
- @type aliases: C{dict}
- @param aliases: Mapping of domain names to objects implementing
- C{IAlias}
- """
-
- def exists(user, memo=None):
- """
- Check whether or not the specified user exists in this domain.
-
- @type user: C{twisted.protocols.smtp.User}
- @param user: The user to check
-
- @type memo: C{dict}
- @param memo: A record of the addresses already considered while
- resolving aliases. The default value should be used by all
- external code.
-
- @rtype: No-argument callable
- @return: A C{Deferred} which becomes, or a callable which
- takes no arguments and returns an object implementing C{IMessage}.
- This will be called and the returned object used to deliver the
- message when it arrives.
-
- @raise twisted.protocols.smtp.SMTPBadRcpt: Raised if the given
- user does not exist in this domain.
- """
-
-class BounceDomain:
- """A domain in which no user exists.
-
- This can be used to block off certain domains.
- """
-
- implements(IDomain)
-
- def exists(self, user):
- raise smtp.SMTPBadRcpt(user)
-
- def willRelay(self, user, protocol):
- return False
-
- def addUser(self, user, password):
- pass
-
- def startMessage(self, user):
- """
- No code should ever call this function.
- """
- raise NotImplementedError(
- "No code should ever call this method for any reason")
-
- def getCredentialsCheckers(self):
- return []
-
-
-class FileMessage:
- """A file we can write an email too."""
-
- implements(smtp.IMessage)
-
- def __init__(self, fp, name, finalName):
- self.fp = fp
- self.name = name
- self.finalName = finalName
-
- def lineReceived(self, line):
- self.fp.write(line+'\n')
-
- def eomReceived(self):
- self.fp.close()
- os.rename(self.name, self.finalName)
- return defer.succeed(self.finalName)
-
- def connectionLost(self):
- self.fp.close()
- os.remove(self.name)
-
-
-class MailService(service.MultiService):
- """An email service."""
-
- queue = None
- domains = None
- portals = None
- aliases = None
- smtpPortal = None
-
- def __init__(self):
- service.MultiService.__init__(self)
- # Domains and portals for "client" protocols - POP3, IMAP4, etc
- self.domains = DomainWithDefaultDict({}, BounceDomain())
- self.portals = {}
-
- self.monitor = FileMonitoringService()
- self.monitor.setServiceParent(self)
- self.smtpPortal = cred.portal.Portal(self)
-
- def getPOP3Factory(self):
- return protocols.POP3Factory(self)
-
- def getSMTPFactory(self):
- return protocols.SMTPFactory(self, self.smtpPortal)
-
- def getESMTPFactory(self):
- return protocols.ESMTPFactory(self, self.smtpPortal)
-
- def addDomain(self, name, domain):
- portal = cred.portal.Portal(domain)
- map(portal.registerChecker, domain.getCredentialsCheckers())
- self.domains[name] = domain
- self.portals[name] = portal
- if self.aliases and IAliasableDomain.providedBy(domain):
- domain.setAliasGroup(self.aliases)
-
- def setQueue(self, queue):
- """Set the queue for outgoing emails."""
- self.queue = queue
-
- def requestAvatar(self, avatarId, mind, *interfaces):
- if smtp.IMessageDelivery in interfaces:
- a = protocols.ESMTPDomainDelivery(self, avatarId)
- return smtp.IMessageDelivery, a, lambda: None
- raise NotImplementedError()
-
- def lookupPortal(self, name):
- return self.portals[name]
-
- def defaultPortal(self):
- return self.portals['']
-
-
-class FileMonitoringService(internet.TimerService):
-
- def __init__(self):
- self.files = []
- self.intervals = iter(util.IntervalDifferential([], 60))
-
- def startService(self):
- service.Service.startService(self)
- self._setupMonitor()
-
- def _setupMonitor(self):
- from twisted.internet import reactor
- t, self.index = self.intervals.next()
- self._call = reactor.callLater(t, self._monitor)
-
- def stopService(self):
- service.Service.stopService(self)
- if self._call:
- self._call.cancel()
- self._call = None
-
- def monitorFile(self, name, callback, interval=10):
- try:
- mtime = os.path.getmtime(name)
- except:
- mtime = 0
- self.files.append([interval, name, callback, mtime])
- self.intervals.addInterval(interval)
-
- def unmonitorFile(self, name):
- for i in range(len(self.files)):
- if name == self.files[i][1]:
- self.intervals.removeInterval(self.files[i][0])
- del self.files[i]
- break
-
- def _monitor(self):
- self._call = None
- if self.index is not None:
- name, callback, mtime = self.files[self.index][1:]
- try:
- now = os.path.getmtime(name)
- except:
- now = 0
- if now > mtime:
- log.msg("%s changed, notifying listener" % (name,))
- self.files[self.index][3] = now
- callback(name)
- self._setupMonitor()
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/maildir.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/maildir.py
deleted file mode 100755
index 7927b325..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/maildir.py
+++ /dev/null
@@ -1,518 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_mail -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""
-Maildir-style mailbox support
-"""
-
-import os
-import stat
-import socket
-
-from zope.interface import implements
-
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
-
-from twisted.python.compat import set
-from twisted.mail import pop3
-from twisted.mail import smtp
-from twisted.protocols import basic
-from twisted.persisted import dirdbm
-from twisted.python import log, failure
-from twisted.python.hashlib import md5
-from twisted.mail import mail
-from twisted.internet import interfaces, defer, reactor
-from twisted.cred import portal, credentials, checkers
-from twisted.cred.error import UnauthorizedLogin
-
-INTERNAL_ERROR = '''\
-From: Twisted.mail Internals
-Subject: An Error Occurred
-
- An internal server error has occurred. Please contact the
- server administrator.
-'''
-
-class _MaildirNameGenerator:
- """
- Utility class to generate a unique maildir name
-
- @ivar _clock: An L{IReactorTime} provider which will be used to learn
- the current time to include in names returned by L{generate} so that
- they sort properly.
- """
- n = 0
- p = os.getpid()
- s = socket.gethostname().replace('/', r'\057').replace(':', r'\072')
-
- def __init__(self, clock):
- self._clock = clock
-
- def generate(self):
- """
- Return a string which is intended to unique across all calls to this
- function (across all processes, reboots, etc).
-
- Strings returned by earlier calls to this method will compare less
- than strings returned by later calls as long as the clock provided
- doesn't go backwards.
- """
- self.n = self.n + 1
- t = self._clock.seconds()
- seconds = str(int(t))
- microseconds = '%07d' % (int((t - int(t)) * 10e6),)
- return '%s.M%sP%sQ%s.%s' % (seconds, microseconds,
- self.p, self.n, self.s)
-
-_generateMaildirName = _MaildirNameGenerator(reactor).generate
-
-def initializeMaildir(dir):
- if not os.path.isdir(dir):
- os.mkdir(dir, 0700)
- for subdir in ['new', 'cur', 'tmp', '.Trash']:
- os.mkdir(os.path.join(dir, subdir), 0700)
- for subdir in ['new', 'cur', 'tmp']:
- os.mkdir(os.path.join(dir, '.Trash', subdir), 0700)
- # touch
- open(os.path.join(dir, '.Trash', 'maildirfolder'), 'w').close()
-
-
-class MaildirMessage(mail.FileMessage):
- size = None
-
- def __init__(self, address, fp, *a, **kw):
- header = "Delivered-To: %s\n" % address
- fp.write(header)
- self.size = len(header)
- mail.FileMessage.__init__(self, fp, *a, **kw)
-
- def lineReceived(self, line):
- mail.FileMessage.lineReceived(self, line)
- self.size += len(line)+1
-
- def eomReceived(self):
- self.finalName = self.finalName+',S=%d' % self.size
- return mail.FileMessage.eomReceived(self)
-
-class AbstractMaildirDomain:
- """Abstract maildir-backed domain.
- """
- alias = None
- root = None
-
- def __init__(self, service, root):
- """Initialize.
- """
- self.root = root
-
- def userDirectory(self, user):
- """Get the maildir directory for a given user
-
- Override to specify where to save mails for users.
- Return None for non-existing users.
- """
- return None
-
- ##
- ## IAliasableDomain
- ##
-
- def setAliasGroup(self, alias):
- self.alias = alias
-
- ##
- ## IDomain
- ##
- def exists(self, user, memo=None):
- """Check for existence of user in the domain
- """
- if self.userDirectory(user.dest.local) is not None:
- return lambda: self.startMessage(user)
- try:
- a = self.alias[user.dest.local]
- except:
- raise smtp.SMTPBadRcpt(user)
- else:
- aliases = a.resolve(self.alias, memo)
- if aliases:
- return lambda: aliases
- log.err("Bad alias configuration: " + str(user))
- raise smtp.SMTPBadRcpt(user)
-
- def startMessage(self, user):
- """Save a message for a given user
- """
- if isinstance(user, str):
- name, domain = user.split('@', 1)
- else:
- name, domain = user.dest.local, user.dest.domain
- dir = self.userDirectory(name)
- fname = _generateMaildirName()
- filename = os.path.join(dir, 'tmp', fname)
- fp = open(filename, 'w')
- return MaildirMessage('%s@%s' % (name, domain), fp, filename,
- os.path.join(dir, 'new', fname))
-
- def willRelay(self, user, protocol):
- return False
-
- def addUser(self, user, password):
- raise NotImplementedError
-
- def getCredentialsCheckers(self):
- raise NotImplementedError
- ##
- ## end of IDomain
- ##
-
-class _MaildirMailboxAppendMessageTask:
- implements(interfaces.IConsumer)
-
- osopen = staticmethod(os.open)
- oswrite = staticmethod(os.write)
- osclose = staticmethod(os.close)
- osrename = staticmethod(os.rename)
-
- def __init__(self, mbox, msg):
- self.mbox = mbox
- self.defer = defer.Deferred()
- self.openCall = None
- if not hasattr(msg, "read"):
- msg = StringIO.StringIO(msg)
- self.msg = msg
-
- def startUp(self):
- self.createTempFile()
- if self.fh != -1:
- self.filesender = basic.FileSender()
- self.filesender.beginFileTransfer(self.msg, self)
-
- def registerProducer(self, producer, streaming):
- self.myproducer = producer
- self.streaming = streaming
- if not streaming:
- self.prodProducer()
-
- def prodProducer(self):
- self.openCall = None
- if self.myproducer is not None:
- self.openCall = reactor.callLater(0, self.prodProducer)
- self.myproducer.resumeProducing()
-
- def unregisterProducer(self):
- self.myproducer = None
- self.streaming = None
- self.osclose(self.fh)
- self.moveFileToNew()
-
- def write(self, data):
- try:
- self.oswrite(self.fh, data)
- except:
- self.fail()
-
- def fail(self, err=None):
- if err is None:
- err = failure.Failure()
- if self.openCall is not None:
- self.openCall.cancel()
- self.defer.errback(err)
- self.defer = None
-
- def moveFileToNew(self):
- while True:
- newname = os.path.join(self.mbox.path, "new", _generateMaildirName())
- try:
- self.osrename(self.tmpname, newname)
- break
- except OSError, (err, estr):
- import errno
- # if the newname exists, retry with a new newname.
- if err != errno.EEXIST:
- self.fail()
- newname = None
- break
- if newname is not None:
- self.mbox.list.append(newname)
- self.defer.callback(None)
- self.defer = None
-
- def createTempFile(self):
- attr = (os.O_RDWR | os.O_CREAT | os.O_EXCL
- | getattr(os, "O_NOINHERIT", 0)
- | getattr(os, "O_NOFOLLOW", 0))
- tries = 0
- self.fh = -1
- while True:
- self.tmpname = os.path.join(self.mbox.path, "tmp", _generateMaildirName())
- try:
- self.fh = self.osopen(self.tmpname, attr, 0600)
- return None
- except OSError:
- tries += 1
- if tries > 500:
- self.defer.errback(RuntimeError("Could not create tmp file for %s" % self.mbox.path))
- self.defer = None
- return None
-
-class MaildirMailbox(pop3.Mailbox):
- """Implement the POP3 mailbox semantics for a Maildir mailbox
- """
- AppendFactory = _MaildirMailboxAppendMessageTask
-
- def __init__(self, path):
- """Initialize with name of the Maildir mailbox
- """
- self.path = path
- self.list = []
- self.deleted = {}
- initializeMaildir(path)
- for name in ('cur', 'new'):
- for file in os.listdir(os.path.join(path, name)):
- self.list.append((file, os.path.join(path, name, file)))
- self.list.sort()
- self.list = [e[1] for e in self.list]
-
- def listMessages(self, i=None):
- """Return a list of lengths of all files in new/ and cur/
- """
- if i is None:
- ret = []
- for mess in self.list:
- if mess:
- ret.append(os.stat(mess)[stat.ST_SIZE])
- else:
- ret.append(0)
- return ret
- return self.list[i] and os.stat(self.list[i])[stat.ST_SIZE] or 0
-
- def getMessage(self, i):
- """Return an open file-pointer to a message
- """
- return open(self.list[i])
-
- def getUidl(self, i):
- """Return a unique identifier for a message
-
- This is done using the basename of the filename.
- It is globally unique because this is how Maildirs are designed.
- """
- # Returning the actual filename is a mistake. Hash it.
- base = os.path.basename(self.list[i])
- return md5(base).hexdigest()
-
- def deleteMessage(self, i):
- """Delete a message
-
- This only moves a message to the .Trash/ subfolder,
- so it can be undeleted by an administrator.
- """
- trashFile = os.path.join(
- self.path, '.Trash', 'cur', os.path.basename(self.list[i])
- )
- os.rename(self.list[i], trashFile)
- self.deleted[self.list[i]] = trashFile
- self.list[i] = 0
-
- def undeleteMessages(self):
- """Undelete any deleted messages it is possible to undelete
-
- This moves any messages from .Trash/ subfolder back to their
- original position, and empties out the deleted dictionary.
- """
- for (real, trash) in self.deleted.items():
- try:
- os.rename(trash, real)
- except OSError, (err, estr):
- import errno
- # If the file has been deleted from disk, oh well!
- if err != errno.ENOENT:
- raise
- # This is a pass
- else:
- try:
- self.list[self.list.index(0)] = real
- except ValueError:
- self.list.append(real)
- self.deleted.clear()
-
- def appendMessage(self, txt):
- """
- Appends a message into the mailbox.
-
- @param txt: A C{str} or file-like object giving the message to append.
-
- @return: A L{Deferred} which fires when the message has been appended to
- the mailbox.
- """
- task = self.AppendFactory(self, txt)
- result = task.defer
- task.startUp()
- return result
-
-class StringListMailbox:
- """
- L{StringListMailbox} is an in-memory mailbox.
-
- @ivar msgs: A C{list} of C{str} giving the contents of each message in the
- mailbox.
-
- @ivar _delete: A C{set} of the indexes of messages which have been deleted
- since the last C{sync} call.
- """
- implements(pop3.IMailbox)
-
- def __init__(self, msgs):
- self.msgs = msgs
- self._delete = set()
-
-
- def listMessages(self, i=None):
- """
- Return the length of the message at the given offset, or a list of all
- message lengths.
- """
- if i is None:
- return [self.listMessages(i) for i in range(len(self.msgs))]
- if i in self._delete:
- return 0
- return len(self.msgs[i])
-
-
- def getMessage(self, i):
- """
- Return an in-memory file-like object for the message content at the
- given offset.
- """
- return StringIO.StringIO(self.msgs[i])
-
-
- def getUidl(self, i):
- """
- Return a hash of the contents of the message at the given offset.
- """
- return md5(self.msgs[i]).hexdigest()
-
-
- def deleteMessage(self, i):
- """
- Mark the given message for deletion.
- """
- self._delete.add(i)
-
-
- def undeleteMessages(self):
- """
- Reset deletion tracking, undeleting any messages which have been
- deleted since the last call to C{sync}.
- """
- self._delete = set()
-
-
- def sync(self):
- """
- Discard the contents of any message marked for deletion and reset
- deletion tracking.
- """
- for index in self._delete:
- self.msgs[index] = ""
- self._delete = set()
-
-
-
-class MaildirDirdbmDomain(AbstractMaildirDomain):
- """A Maildir Domain where membership is checked by a dirdbm file
- """
-
- implements(portal.IRealm, mail.IAliasableDomain)
-
- portal = None
- _credcheckers = None
-
- def __init__(self, service, root, postmaster=0):
- """Initialize
-
- The first argument is where the Domain directory is rooted.
- The second is whether non-existing addresses are simply
- forwarded to postmaster instead of outright bounce
-
- The directory structure of a MailddirDirdbmDomain is:
-
- /passwd <-- a dirdbm file
- /USER/{cur,new,del} <-- each user has these three directories
- """
- AbstractMaildirDomain.__init__(self, service, root)
- dbm = os.path.join(root, 'passwd')
- if not os.path.exists(dbm):
- os.makedirs(dbm)
- self.dbm = dirdbm.open(dbm)
- self.postmaster = postmaster
-
- def userDirectory(self, name):
- """Get the directory for a user
-
- If the user exists in the dirdbm file, return the directory
- os.path.join(root, name), creating it if necessary.
- Otherwise, returns postmaster's mailbox instead if bounces
- go to postmaster, otherwise return None
- """
- if not self.dbm.has_key(name):
- if not self.postmaster:
- return None
- name = 'postmaster'
- dir = os.path.join(self.root, name)
- if not os.path.exists(dir):
- initializeMaildir(dir)
- return dir
-
- ##
- ## IDomain
- ##
- def addUser(self, user, password):
- self.dbm[user] = password
- # Ensure it is initialized
- self.userDirectory(user)
-
- def getCredentialsCheckers(self):
- if self._credcheckers is None:
- self._credcheckers = [DirdbmDatabase(self.dbm)]
- return self._credcheckers
-
- ##
- ## IRealm
- ##
- def requestAvatar(self, avatarId, mind, *interfaces):
- if pop3.IMailbox not in interfaces:
- raise NotImplementedError("No interface")
- if avatarId == checkers.ANONYMOUS:
- mbox = StringListMailbox([INTERNAL_ERROR])
- else:
- mbox = MaildirMailbox(os.path.join(self.root, avatarId))
-
- return (
- pop3.IMailbox,
- mbox,
- lambda: None
- )
-
-class DirdbmDatabase:
- implements(checkers.ICredentialsChecker)
-
- credentialInterfaces = (
- credentials.IUsernamePassword,
- credentials.IUsernameHashedPassword
- )
-
- def __init__(self, dbm):
- self.dirdbm = dbm
-
- def requestAvatarId(self, c):
- if c.username in self.dirdbm:
- if c.checkPassword(self.dirdbm[c.username]):
- return c.username
- raise UnauthorizedLogin()
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pb.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pb.py
deleted file mode 100755
index 8a9417fa..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pb.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-from twisted.spread import pb
-from twisted.spread import banana
-
-import os
-import types
-
-class Maildir(pb.Referenceable):
-
- def __init__(self, directory, rootDirectory):
- self.virtualDirectory = directory
- self.rootDirectory = rootDirectory
- self.directory = os.path.join(rootDirectory, directory)
-
- def getFolderMessage(self, folder, name):
- if '/' in name:
- raise IOError("can only open files in '%s' directory'" % folder)
- fp = open(os.path.join(self.directory, 'new', name))
- try:
- return fp.read()
- finally:
- fp.close()
-
- def deleteFolderMessage(self, folder, name):
- if '/' in name:
- raise IOError("can only delete files in '%s' directory'" % folder)
- os.rename(os.path.join(self.directory, folder, name),
- os.path.join(self.rootDirectory, '.Trash', folder, name))
-
- def deleteNewMessage(self, name):
- return self.deleteFolderMessage('new', name)
- remote_deleteNewMessage = deleteNewMessage
-
- def deleteCurMessage(self, name):
- return self.deleteFolderMessage('cur', name)
- remote_deleteCurMessage = deleteCurMessage
-
- def getNewMessages(self):
- return os.listdir(os.path.join(self.directory, 'new'))
- remote_getNewMessages = getNewMessages
-
- def getCurMessages(self):
- return os.listdir(os.path.join(self.directory, 'cur'))
- remote_getCurMessages = getCurMessages
-
- def getNewMessage(self, name):
- return self.getFolderMessage('new', name)
- remote_getNewMessage = getNewMessage
-
- def getCurMessage(self, name):
- return self.getFolderMessage('cur', name)
- remote_getCurMessage = getCurMessage
-
- def getSubFolder(self, name):
- if name[0] == '.':
- raise IOError("subfolder name cannot begin with a '.'")
- name = name.replace('/', ':')
- if self.virtualDirectoy == '.':
- name = '.'+name
- else:
- name = self.virtualDirectory+':'+name
- if not self._isSubFolder(name):
- raise IOError("not a subfolder")
- return Maildir(name, self.rootDirectory)
- remote_getSubFolder = getSubFolder
-
- def _isSubFolder(self, name):
- return (not os.path.isdir(os.path.join(self.rootDirectory, name)) or
- not os.path.isfile(os.path.join(self.rootDirectory, name,
- 'maildirfolder')))
-
-
-class MaildirCollection(pb.Referenceable):
-
- def __init__(self, root):
- self.root = root
-
- def getSubFolders(self):
- return os.listdir(self.getRoot())
- remote_getSubFolders = getSubFolders
-
- def getSubFolder(self, name):
- if '/' in name or name[0] == '.':
- raise IOError("invalid name")
- return Maildir('.', os.path.join(self.getRoot(), name))
- remote_getSubFolder = getSubFolder
-
-
-class MaildirBroker(pb.Broker):
-
- def proto_getCollection(self, requestID, name, domain, password):
- collection = self._getCollection()
- if collection is None:
- self.sendError(requestID, "permission denied")
- else:
- self.sendAnswer(requestID, collection)
-
- def getCollection(self, name, domain, password):
- if not self.domains.has_key(domain):
- return
- domain = self.domains[domain]
- if (domain.dbm.has_key(name) and
- domain.dbm[name] == password):
- return MaildirCollection(domain.userDirectory(name))
-
-
-class MaildirClient(pb.Broker):
-
- def getCollection(self, name, domain, password, callback, errback):
- requestID = self.newRequestID()
- self.waitingForAnswers[requestID] = callback, errback
- self.sendCall("getCollection", requestID, name, domain, password)
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3.py
deleted file mode 100755
index 3b65242d..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3.py
+++ /dev/null
@@ -1,1071 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_pop3 -*-
-#
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""
-Post-office Protocol version 3
-
-@author: Glyph Lefkowitz
-@author: Jp Calderone
-"""
-
-import base64
-import binascii
-import warnings
-
-from zope.interface import implements, Interface
-
-from twisted.mail import smtp
-from twisted.protocols import basic
-from twisted.protocols import policies
-from twisted.internet import task
-from twisted.internet import defer
-from twisted.internet import interfaces
-from twisted.python import log
-from twisted.python.hashlib import md5
-
-from twisted import cred
-import twisted.cred.error
-import twisted.cred.credentials
-
-##
-## Authentication
-##
-class APOPCredentials:
- implements(cred.credentials.IUsernamePassword)
-
- def __init__(self, magic, username, digest):
- self.magic = magic
- self.username = username
- self.digest = digest
-
- def checkPassword(self, password):
- seed = self.magic + password
- myDigest = md5(seed).hexdigest()
- return myDigest == self.digest
-
-
-class _HeadersPlusNLines:
- def __init__(self, f, n):
- self.f = f
- self.n = n
- self.linecount = 0
- self.headers = 1
- self.done = 0
- self.buf = ''
-
- def read(self, bytes):
- if self.done:
- return ''
- data = self.f.read(bytes)
- if not data:
- return data
- if self.headers:
- df, sz = data.find('\r\n\r\n'), 4
- if df == -1:
- df, sz = data.find('\n\n'), 2
- if df != -1:
- df += sz
- val = data[:df]
- data = data[df:]
- self.linecount = 1
- self.headers = 0
- else:
- val = ''
- if self.linecount > 0:
- dsplit = (self.buf+data).split('\n')
- self.buf = dsplit[-1]
- for ln in dsplit[:-1]:
- if self.linecount > self.n:
- self.done = 1
- return val
- val += (ln + '\n')
- self.linecount += 1
- return val
- else:
- return data
-
-
-
-class _POP3MessageDeleted(Exception):
- """
- Internal control-flow exception. Indicates the file of a deleted message
- was requested.
- """
-
-
-class POP3Error(Exception):
- pass
-
-
-
-class _IteratorBuffer(object):
- bufSize = 0
-
- def __init__(self, write, iterable, memoryBufferSize=None):
- """
- Create a _IteratorBuffer.
-
- @param write: A one-argument callable which will be invoked with a list
- of strings which have been buffered.
-
- @param iterable: The source of input strings as any iterable.
-
- @param memoryBufferSize: The upper limit on buffered string length,
- beyond which the buffer will be flushed to the writer.
- """
- self.lines = []
- self.write = write
- self.iterator = iter(iterable)
- if memoryBufferSize is None:
- memoryBufferSize = 2 ** 16
- self.memoryBufferSize = memoryBufferSize
-
-
- def __iter__(self):
- return self
-
-
- def next(self):
- try:
- v = self.iterator.next()
- except StopIteration:
- if self.lines:
- self.write(self.lines)
- # Drop some references, in case they're edges in a cycle.
- del self.iterator, self.lines, self.write
- raise
- else:
- if v is not None:
- self.lines.append(v)
- self.bufSize += len(v)
- if self.bufSize > self.memoryBufferSize:
- self.write(self.lines)
- self.lines = []
- self.bufSize = 0
-
-
-
-def iterateLineGenerator(proto, gen):
- """
- Hook the given protocol instance up to the given iterator with an
- _IteratorBuffer and schedule the result to be exhausted via the protocol.
-
- @type proto: L{POP3}
- @type gen: iterator
- @rtype: L{twisted.internet.defer.Deferred}
- """
- coll = _IteratorBuffer(proto.transport.writeSequence, gen)
- return proto.schedule(coll)
-
-
-
-def successResponse(response):
- """
- Format the given object as a positive response.
- """
- response = str(response)
- return '+OK %s\r\n' % (response,)
-
-
-
-def formatStatResponse(msgs):
- """
- Format the list of message sizes appropriately for a STAT response.
-
- Yields None until it finishes computing a result, then yields a str
- instance that is suitable for use as a response to the STAT command.
- Intended to be used with a L{twisted.internet.task.Cooperator}.
- """
- i = 0
- bytes = 0
- for size in msgs:
- i += 1
- bytes += size
- yield None
- yield successResponse('%d %d' % (i, bytes))
-
-
-
-def formatListLines(msgs):
- """
- Format a list of message sizes appropriately for the lines of a LIST
- response.
-
- Yields str instances formatted appropriately for use as lines in the
- response to the LIST command. Does not include the trailing '.'.
- """
- i = 0
- for size in msgs:
- i += 1
- yield '%d %d\r\n' % (i, size)
-
-
-
-def formatListResponse(msgs):
- """
- Format a list of message sizes appropriately for a complete LIST response.
-
- Yields str instances formatted appropriately for use as a LIST command
- response.
- """
- yield successResponse(len(msgs))
- for ele in formatListLines(msgs):
- yield ele
- yield '.\r\n'
-
-
-
-def formatUIDListLines(msgs, getUidl):
- """
- Format the list of message sizes appropriately for the lines of a UIDL
- response.
-
- Yields str instances formatted appropriately for use as lines in the
- response to the UIDL command. Does not include the trailing '.'.
- """
- for i, m in enumerate(msgs):
- if m is not None:
- uid = getUidl(i)
- yield '%d %s\r\n' % (i + 1, uid)
-
-
-
-def formatUIDListResponse(msgs, getUidl):
- """
- Format a list of message sizes appropriately for a complete UIDL response.
-
- Yields str instances formatted appropriately for use as a UIDL command
- response.
- """
- yield successResponse('')
- for ele in formatUIDListLines(msgs, getUidl):
- yield ele
- yield '.\r\n'
-
-
-
-class POP3(basic.LineOnlyReceiver, policies.TimeoutMixin):
- """
- POP3 server protocol implementation.
-
- @ivar portal: A reference to the L{twisted.cred.portal.Portal} instance we
- will authenticate through.
-
- @ivar factory: A L{twisted.mail.pop3.IServerFactory} which will be used to
- determine some extended behavior of the server.
-
- @ivar timeOut: An integer which defines the minimum amount of time which
- may elapse without receiving any traffic after which the client will be
- disconnected.
-
- @ivar schedule: A one-argument callable which should behave like
- L{twisted.internet.task.coiterate}.
- """
- implements(interfaces.IProducer)
-
- magic = None
- _userIs = None
- _onLogout = None
-
- AUTH_CMDS = ['CAPA', 'USER', 'PASS', 'APOP', 'AUTH', 'RPOP', 'QUIT']
-
- portal = None
- factory = None
-
- # The mailbox we're serving
- mbox = None
-
- # Set this pretty low -- POP3 clients are expected to log in, download
- # everything, and log out.
- timeOut = 300
-
- # Current protocol state
- state = "COMMAND"
-
- # PIPELINE
- blocked = None
-
- # Cooperate and suchlike.
- schedule = staticmethod(task.coiterate)
-
- # Message index of the highest retrieved message.
- _highest = 0
-
- def connectionMade(self):
- if self.magic is None:
- self.magic = self.generateMagic()
- self.successResponse(self.magic)
- self.setTimeout(self.timeOut)
- if getattr(self.factory, 'noisy', True):
- log.msg("New connection from " + str(self.transport.getPeer()))
-
-
- def connectionLost(self, reason):
- if self._onLogout is not None:
- self._onLogout()
- self._onLogout = None
- self.setTimeout(None)
-
-
- def generateMagic(self):
- return smtp.messageid()
-
-
- def successResponse(self, message=''):
- self.transport.write(successResponse(message))
-
- def failResponse(self, message=''):
- self.sendLine('-ERR ' + str(message))
-
-# def sendLine(self, line):
-# print 'S:', repr(line)
-# basic.LineOnlyReceiver.sendLine(self, line)
-
- def lineReceived(self, line):
-# print 'C:', repr(line)
- self.resetTimeout()
- getattr(self, 'state_' + self.state)(line)
-
- def _unblock(self, _):
- commands = self.blocked
- self.blocked = None
- while commands and self.blocked is None:
- cmd, args = commands.pop(0)
- self.processCommand(cmd, *args)
- if self.blocked is not None:
- self.blocked.extend(commands)
-
- def state_COMMAND(self, line):
- try:
- return self.processCommand(*line.split(' '))
- except (ValueError, AttributeError, POP3Error, TypeError), e:
- log.err()
- self.failResponse('bad protocol or server: %s: %s' % (e.__class__.__name__, e))
-
- def processCommand(self, command, *args):
- if self.blocked is not None:
- self.blocked.append((command, args))
- return
-
- command = command.upper()
- authCmd = command in self.AUTH_CMDS
- if not self.mbox and not authCmd:
- raise POP3Error("not authenticated yet: cannot do " + command)
- f = getattr(self, 'do_' + command, None)
- if f:
- return f(*args)
- raise POP3Error("Unknown protocol command: " + command)
-
-
- def listCapabilities(self):
- baseCaps = [
- "TOP",
- "USER",
- "UIDL",
- "PIPELINE",
- "CELERITY",
- "AUSPEX",
- "POTENCE",
- ]
-
- if IServerFactory.providedBy(self.factory):
- # Oh my god. We can't just loop over a list of these because
- # each has spectacularly different return value semantics!
- try:
- v = self.factory.cap_IMPLEMENTATION()
- except NotImplementedError:
- pass
- except:
- log.err()
- else:
- baseCaps.append("IMPLEMENTATION " + str(v))
-
- try:
- v = self.factory.cap_EXPIRE()
- except NotImplementedError:
- pass
- except:
- log.err()
- else:
- if v is None:
- v = "NEVER"
- if self.factory.perUserExpiration():
- if self.mbox:
- v = str(self.mbox.messageExpiration)
- else:
- v = str(v) + " USER"
- v = str(v)
- baseCaps.append("EXPIRE " + v)
-
- try:
- v = self.factory.cap_LOGIN_DELAY()
- except NotImplementedError:
- pass
- except:
- log.err()
- else:
- if self.factory.perUserLoginDelay():
- if self.mbox:
- v = str(self.mbox.loginDelay)
- else:
- v = str(v) + " USER"
- v = str(v)
- baseCaps.append("LOGIN-DELAY " + v)
-
- try:
- v = self.factory.challengers
- except AttributeError:
- pass
- except:
- log.err()
- else:
- baseCaps.append("SASL " + ' '.join(v.keys()))
- return baseCaps
-
- def do_CAPA(self):
- self.successResponse("I can do the following:")
- for cap in self.listCapabilities():
- self.sendLine(cap)
- self.sendLine(".")
-
- def do_AUTH(self, args=None):
- if not getattr(self.factory, 'challengers', None):
- self.failResponse("AUTH extension unsupported")
- return
-
- if args is None:
- self.successResponse("Supported authentication methods:")
- for a in self.factory.challengers:
- self.sendLine(a.upper())
- self.sendLine(".")
- return
-
- auth = self.factory.challengers.get(args.strip().upper())
- if not self.portal or not auth:
- self.failResponse("Unsupported SASL selected")
- return
-
- self._auth = auth()
- chal = self._auth.getChallenge()
-
- self.sendLine('+ ' + base64.encodestring(chal).rstrip('\n'))
- self.state = 'AUTH'
-
- def state_AUTH(self, line):
- self.state = "COMMAND"
- try:
- parts = base64.decodestring(line).split(None, 1)
- except binascii.Error:
- self.failResponse("Invalid BASE64 encoding")
- else:
- if len(parts) != 2:
- self.failResponse("Invalid AUTH response")
- return
- self._auth.username = parts[0]
- self._auth.response = parts[1]
- d = self.portal.login(self._auth, None, IMailbox)
- d.addCallback(self._cbMailbox, parts[0])
- d.addErrback(self._ebMailbox)
- d.addErrback(self._ebUnexpected)
-
- def do_APOP(self, user, digest):
- d = defer.maybeDeferred(self.authenticateUserAPOP, user, digest)
- d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,)
- ).addErrback(self._ebUnexpected)
-
- def _cbMailbox(self, (interface, avatar, logout), user):
- if interface is not IMailbox:
- self.failResponse('Authentication failed')
- log.err("_cbMailbox() called with an interface other than IMailbox")
- return
-
- self.mbox = avatar
- self._onLogout = logout
- self.successResponse('Authentication succeeded')
- if getattr(self.factory, 'noisy', True):
- log.msg("Authenticated login for " + user)
-
- def _ebMailbox(self, failure):
- failure = failure.trap(cred.error.LoginDenied, cred.error.LoginFailed)
- if issubclass(failure, cred.error.LoginDenied):
- self.failResponse("Access denied: " + str(failure))
- elif issubclass(failure, cred.error.LoginFailed):
- self.failResponse('Authentication failed')
- if getattr(self.factory, 'noisy', True):
- log.msg("Denied login attempt from " + str(self.transport.getPeer()))
-
- def _ebUnexpected(self, failure):
- self.failResponse('Server error: ' + failure.getErrorMessage())
- log.err(failure)
-
- def do_USER(self, user):
- self._userIs = user
- self.successResponse('USER accepted, send PASS')
-
- def do_PASS(self, password):
- if self._userIs is None:
- self.failResponse("USER required before PASS")
- return
- user = self._userIs
- self._userIs = None
- d = defer.maybeDeferred(self.authenticateUserPASS, user, password)
- d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,)
- ).addErrback(self._ebUnexpected)
-
-
- def _longOperation(self, d):
- # Turn off timeouts and block further processing until the Deferred
- # fires, then reverse those changes.
- timeOut = self.timeOut
- self.setTimeout(None)
- self.blocked = []
- d.addCallback(self._unblock)
- d.addCallback(lambda ign: self.setTimeout(timeOut))
- return d
-
-
- def _coiterate(self, gen):
- return self.schedule(_IteratorBuffer(self.transport.writeSequence, gen))
-
-
- def do_STAT(self):
- d = defer.maybeDeferred(self.mbox.listMessages)
- def cbMessages(msgs):
- return self._coiterate(formatStatResponse(msgs))
- def ebMessages(err):
- self.failResponse(err.getErrorMessage())
- log.msg("Unexpected do_STAT failure:")
- log.err(err)
- return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
-
-
- def do_LIST(self, i=None):
- if i is None:
- d = defer.maybeDeferred(self.mbox.listMessages)
- def cbMessages(msgs):
- return self._coiterate(formatListResponse(msgs))
- def ebMessages(err):
- self.failResponse(err.getErrorMessage())
- log.msg("Unexpected do_LIST failure:")
- log.err(err)
- return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
- else:
- try:
- i = int(i)
- if i < 1:
- raise ValueError()
- except ValueError:
- self.failResponse("Invalid message-number: %r" % (i,))
- else:
- d = defer.maybeDeferred(self.mbox.listMessages, i - 1)
- def cbMessage(msg):
- self.successResponse('%d %d' % (i, msg))
- def ebMessage(err):
- errcls = err.check(ValueError, IndexError)
- if errcls is not None:
- if errcls is IndexError:
- # IndexError was supported for a while, but really
- # shouldn't be. One error condition, one exception
- # type.
- warnings.warn(
- "twisted.mail.pop3.IMailbox.listMessages may not "
- "raise IndexError for out-of-bounds message numbers: "
- "raise ValueError instead.",
- PendingDeprecationWarning)
- self.failResponse("Invalid message-number: %r" % (i,))
- else:
- self.failResponse(err.getErrorMessage())
- log.msg("Unexpected do_LIST failure:")
- log.err(err)
- return self._longOperation(d.addCallbacks(cbMessage, ebMessage))
-
-
- def do_UIDL(self, i=None):
- if i is None:
- d = defer.maybeDeferred(self.mbox.listMessages)
- def cbMessages(msgs):
- return self._coiterate(formatUIDListResponse(msgs, self.mbox.getUidl))
- def ebMessages(err):
- self.failResponse(err.getErrorMessage())
- log.msg("Unexpected do_UIDL failure:")
- log.err(err)
- return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
- else:
- try:
- i = int(i)
- if i < 1:
- raise ValueError()
- except ValueError:
- self.failResponse("Bad message number argument")
- else:
- try:
- msg = self.mbox.getUidl(i - 1)
- except IndexError:
- # XXX TODO See above comment regarding IndexError.
- warnings.warn(
- "twisted.mail.pop3.IMailbox.getUidl may not "
- "raise IndexError for out-of-bounds message numbers: "
- "raise ValueError instead.",
- PendingDeprecationWarning)
- self.failResponse("Bad message number argument")
- except ValueError:
- self.failResponse("Bad message number argument")
- else:
- self.successResponse(str(msg))
-
-
- def _getMessageFile(self, i):
- """
- Retrieve the size and contents of a given message, as a two-tuple.
-
- @param i: The number of the message to operate on. This is a base-ten
- string representation starting at 1.
-
- @return: A Deferred which fires with a two-tuple of an integer and a
- file-like object.
- """
- try:
- msg = int(i) - 1
- if msg < 0:
- raise ValueError()
- except ValueError:
- self.failResponse("Bad message number argument")
- return defer.succeed(None)
-
- sizeDeferred = defer.maybeDeferred(self.mbox.listMessages, msg)
- def cbMessageSize(size):
- if not size:
- return defer.fail(_POP3MessageDeleted())
- fileDeferred = defer.maybeDeferred(self.mbox.getMessage, msg)
- fileDeferred.addCallback(lambda fObj: (size, fObj))
- return fileDeferred
-
- def ebMessageSomething(err):
- errcls = err.check(_POP3MessageDeleted, ValueError, IndexError)
- if errcls is _POP3MessageDeleted:
- self.failResponse("message deleted")
- elif errcls in (ValueError, IndexError):
- if errcls is IndexError:
- # XXX TODO See above comment regarding IndexError.
- warnings.warn(
- "twisted.mail.pop3.IMailbox.listMessages may not "
- "raise IndexError for out-of-bounds message numbers: "
- "raise ValueError instead.",
- PendingDeprecationWarning)
- self.failResponse("Bad message number argument")
- else:
- log.msg("Unexpected _getMessageFile failure:")
- log.err(err)
- return None
-
- sizeDeferred.addCallback(cbMessageSize)
- sizeDeferred.addErrback(ebMessageSomething)
- return sizeDeferred
-
-
- def _sendMessageContent(self, i, fpWrapper, successResponse):
- d = self._getMessageFile(i)
- def cbMessageFile(info):
- if info is None:
- # Some error occurred - a failure response has been sent
- # already, just give up.
- return
-
- self._highest = max(self._highest, int(i))
- resp, fp = info
- fp = fpWrapper(fp)
- self.successResponse(successResponse(resp))
- s = basic.FileSender()
- d = s.beginFileTransfer(fp, self.transport, self.transformChunk)
-
- def cbFileTransfer(lastsent):
- if lastsent != '\n':
- line = '\r\n.'
- else:
- line = '.'
- self.sendLine(line)
-
- def ebFileTransfer(err):
- self.transport.loseConnection()
- log.msg("Unexpected error in _sendMessageContent:")
- log.err(err)
-
- d.addCallback(cbFileTransfer)
- d.addErrback(ebFileTransfer)
- return d
- return self._longOperation(d.addCallback(cbMessageFile))
-
-
- def do_TOP(self, i, size):
- try:
- size = int(size)
- if size < 0:
- raise ValueError
- except ValueError:
- self.failResponse("Bad line count argument")
- else:
- return self._sendMessageContent(
- i,
- lambda fp: _HeadersPlusNLines(fp, size),
- lambda size: "Top of message follows")
-
-
- def do_RETR(self, i):
- return self._sendMessageContent(
- i,
- lambda fp: fp,
- lambda size: "%d" % (size,))
-
-
- def transformChunk(self, chunk):
- return chunk.replace('\n', '\r\n').replace('\r\n.', '\r\n..')
-
-
- def finishedFileTransfer(self, lastsent):
- if lastsent != '\n':
- line = '\r\n.'
- else:
- line = '.'
- self.sendLine(line)
-
-
- def do_DELE(self, i):
- i = int(i)-1
- self.mbox.deleteMessage(i)
- self.successResponse()
-
-
- def do_NOOP(self):
- """Perform no operation. Return a success code"""
- self.successResponse()
-
-
- def do_RSET(self):
- """Unset all deleted message flags"""
- try:
- self.mbox.undeleteMessages()
- except:
- log.err()
- self.failResponse()
- else:
- self._highest = 0
- self.successResponse()
-
-
- def do_LAST(self):
- """
- Return the index of the highest message yet downloaded.
- """
- self.successResponse(self._highest)
-
-
- def do_RPOP(self, user):
- self.failResponse('permission denied, sucker')
-
-
- def do_QUIT(self):
- if self.mbox:
- self.mbox.sync()
- self.successResponse()
- self.transport.loseConnection()
-
-
- def authenticateUserAPOP(self, user, digest):
- """Perform authentication of an APOP login.
-
- @type user: C{str}
- @param user: The name of the user attempting to log in.
-
- @type digest: C{str}
- @param digest: The response string with which the user replied.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if the login is
- successful, and whose errback will be invoked otherwise. The
- callback will be passed a 3-tuple consisting of IMailbox,
- an object implementing IMailbox, and a zero-argument callable
- to be invoked when this session is terminated.
- """
- if self.portal is not None:
- return self.portal.login(
- APOPCredentials(self.magic, user, digest),
- None,
- IMailbox
- )
- raise cred.error.UnauthorizedLogin()
-
- def authenticateUserPASS(self, user, password):
- """Perform authentication of a username/password login.
-
- @type user: C{str}
- @param user: The name of the user attempting to log in.
-
- @type password: C{str}
- @param password: The password to attempt to authenticate with.
-
- @rtype: C{Deferred}
- @return: A deferred whose callback is invoked if the login is
- successful, and whose errback will be invoked otherwise. The
- callback will be passed a 3-tuple consisting of IMailbox,
- an object implementing IMailbox, and a zero-argument callable
- to be invoked when this session is terminated.
- """
- if self.portal is not None:
- return self.portal.login(
- cred.credentials.UsernamePassword(user, password),
- None,
- IMailbox
- )
- raise cred.error.UnauthorizedLogin()
-
-
-class IServerFactory(Interface):
- """Interface for querying additional parameters of this POP3 server.
-
- Any cap_* method may raise NotImplementedError if the particular
- capability is not supported. If cap_EXPIRE() does not raise
- NotImplementedError, perUserExpiration() must be implemented, otherwise
- they are optional. If cap_LOGIN_DELAY() is implemented,
- perUserLoginDelay() must be implemented, otherwise they are optional.
-
- @ivar challengers: A dictionary mapping challenger names to classes
- implementing C{IUsernameHashedPassword}.
- """
-
- def cap_IMPLEMENTATION():
- """Return a string describing this POP3 server implementation."""
-
- def cap_EXPIRE():
- """Return the minimum number of days messages are retained."""
-
- def perUserExpiration():
- """Indicate whether message expiration is per-user.
-
- @return: True if it is, false otherwise.
- """
-
- def cap_LOGIN_DELAY():
- """Return the minimum number of seconds between client logins."""
-
- def perUserLoginDelay():
- """Indicate whether the login delay period is per-user.
-
- @return: True if it is, false otherwise.
- """
-
-class IMailbox(Interface):
- """
- @type loginDelay: C{int}
- @ivar loginDelay: The number of seconds between allowed logins for the
- user associated with this mailbox. None
-
- @type messageExpiration: C{int}
- @ivar messageExpiration: The number of days messages in this mailbox will
- remain on the server before being deleted.
- """
-
- def listMessages(index=None):
- """Retrieve the size of one or more messages.
-
- @type index: C{int} or C{None}
- @param index: The number of the message for which to retrieve the
- size (starting at 0), or None to retrieve the size of all messages.
-
- @rtype: C{int} or any iterable of C{int} or a L{Deferred} which fires
- with one of these.
-
- @return: The number of octets in the specified message, or an iterable
- of integers representing the number of octets in all the messages. Any
- value which would have referred to a deleted message should be set to 0.
-
- @raise ValueError: if C{index} is greater than the index of any message
- in the mailbox.
- """
-
- def getMessage(index):
- """Retrieve a file-like object for a particular message.
-
- @type index: C{int}
- @param index: The number of the message to retrieve
-
- @rtype: A file-like object
- @return: A file containing the message data with lines delimited by
- C{\\n}.
- """
-
- def getUidl(index):
- """Get a unique identifier for a particular message.
-
- @type index: C{int}
- @param index: The number of the message for which to retrieve a UIDL
-
- @rtype: C{str}
- @return: A string of printable characters uniquely identifying for all
- time the specified message.
-
- @raise ValueError: if C{index} is greater than the index of any message
- in the mailbox.
- """
-
- def deleteMessage(index):
- """Delete a particular message.
-
- This must not change the number of messages in this mailbox. Further
- requests for the size of deleted messages should return 0. Further
- requests for the message itself may raise an exception.
-
- @type index: C{int}
- @param index: The number of the message to delete.
- """
-
- def undeleteMessages():
- """
- Undelete any messages which have been marked for deletion since the
- most recent L{sync} call.
-
- Any message which can be undeleted should be returned to its
- original position in the message sequence and retain its original
- UID.
- """
-
- def sync():
- """Perform checkpointing.
-
- This method will be called to indicate the mailbox should attempt to
- clean up any remaining deleted messages.
- """
-
-
-
-class Mailbox:
- implements(IMailbox)
-
- def listMessages(self, i=None):
- return []
- def getMessage(self, i):
- raise ValueError
- def getUidl(self, i):
- raise ValueError
- def deleteMessage(self, i):
- raise ValueError
- def undeleteMessages(self):
- pass
- def sync(self):
- pass
-
-
-NONE, SHORT, FIRST_LONG, LONG = range(4)
-
-NEXT = {}
-NEXT[NONE] = NONE
-NEXT[SHORT] = NONE
-NEXT[FIRST_LONG] = LONG
-NEXT[LONG] = NONE
-
-class POP3Client(basic.LineOnlyReceiver):
-
- mode = SHORT
- command = 'WELCOME'
- import re
- welcomeRe = re.compile('<(.*)>')
-
- def __init__(self):
- import warnings
- warnings.warn("twisted.mail.pop3.POP3Client is deprecated, "
- "please use twisted.mail.pop3.AdvancedPOP3Client "
- "instead.", DeprecationWarning,
- stacklevel=3)
-
- def sendShort(self, command, params=None):
- if params is not None:
- self.sendLine('%s %s' % (command, params))
- else:
- self.sendLine(command)
- self.command = command
- self.mode = SHORT
-
- def sendLong(self, command, params):
- if params:
- self.sendLine('%s %s' % (command, params))
- else:
- self.sendLine(command)
- self.command = command
- self.mode = FIRST_LONG
-
- def handle_default(self, line):
- if line[:-4] == '-ERR':
- self.mode = NONE
-
- def handle_WELCOME(self, line):
- code, data = line.split(' ', 1)
- if code != '+OK':
- self.transport.loseConnection()
- else:
- m = self.welcomeRe.match(line)
- if m:
- self.welcomeCode = m.group(1)
-
- def _dispatch(self, command, default, *args):
- try:
- method = getattr(self, 'handle_'+command, default)
- if method is not None:
- method(*args)
- except:
- log.err()
-
- def lineReceived(self, line):
- if self.mode == SHORT or self.mode == FIRST_LONG:
- self.mode = NEXT[self.mode]
- self._dispatch(self.command, self.handle_default, line)
- elif self.mode == LONG:
- if line == '.':
- self.mode = NEXT[self.mode]
- self._dispatch(self.command+'_end', None)
- return
- if line[:1] == '.':
- line = line[1:]
- self._dispatch(self.command+"_continue", None, line)
-
- def apopAuthenticate(self, user, password, magic):
- digest = md5(magic + password).hexdigest()
- self.apop(user, digest)
-
- def apop(self, user, digest):
- self.sendLong('APOP', ' '.join((user, digest)))
- def retr(self, i):
- self.sendLong('RETR', i)
- def dele(self, i):
- self.sendShort('DELE', i)
- def list(self, i=''):
- self.sendLong('LIST', i)
- def uidl(self, i=''):
- self.sendLong('UIDL', i)
- def user(self, name):
- self.sendShort('USER', name)
- def pass_(self, pass_):
- self.sendShort('PASS', pass_)
- def quit(self):
- self.sendShort('QUIT')
-
-from twisted.mail.pop3client import POP3Client as AdvancedPOP3Client
-from twisted.mail.pop3client import POP3ClientError
-from twisted.mail.pop3client import InsecureAuthenticationDisallowed
-from twisted.mail.pop3client import ServerErrorResponse
-from twisted.mail.pop3client import LineTooLong
-
-__all__ = [
- # Interfaces
- 'IMailbox', 'IServerFactory',
-
- # Exceptions
- 'POP3Error', 'POP3ClientError', 'InsecureAuthenticationDisallowed',
- 'ServerErrorResponse', 'LineTooLong',
-
- # Protocol classes
- 'POP3', 'POP3Client', 'AdvancedPOP3Client',
-
- # Misc
- 'APOPCredentials', 'Mailbox']
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3client.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3client.py
deleted file mode 100755
index fe8f497e..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/pop3client.py
+++ /dev/null
@@ -1,706 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_pop3client -*-
-# Copyright (c) 2001-2004 Divmod Inc.
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-POP3 client protocol implementation
-
-Don't use this module directly. Use twisted.mail.pop3 instead.
-
-@author: Jp Calderone
-"""
-
-import re
-
-from twisted.python import log
-from twisted.python.hashlib import md5
-from twisted.internet import defer
-from twisted.protocols import basic
-from twisted.protocols import policies
-from twisted.internet import error
-from twisted.internet import interfaces
-
-OK = '+OK'
-ERR = '-ERR'
-
-class POP3ClientError(Exception):
- """Base class for all exceptions raised by POP3Client.
- """
-
-class InsecureAuthenticationDisallowed(POP3ClientError):
- """Secure authentication was required but no mechanism could be found.
- """
-
-class TLSError(POP3ClientError):
- """
- Secure authentication was required but either the transport does
- not support TLS or no TLS context factory was supplied.
- """
-
-class TLSNotSupportedError(POP3ClientError):
- """
- Secure authentication was required but the server does not support
- TLS.
- """
-
-class ServerErrorResponse(POP3ClientError):
- """The server returned an error response to a request.
- """
- def __init__(self, reason, consumer=None):
- POP3ClientError.__init__(self, reason)
- self.consumer = consumer
-
-class LineTooLong(POP3ClientError):
- """The server sent an extremely long line.
- """
-
-class _ListSetter:
- # Internal helper. POP3 responses sometimes occur in the
- # form of a list of lines containing two pieces of data,
- # a message index and a value of some sort. When a message
- # is deleted, it is omitted from these responses. The
- # setitem method of this class is meant to be called with
- # these two values. In the cases where indexes are skipped,
- # it takes care of padding out the missing values with None.
- def __init__(self, L):
- self.L = L
- def setitem(self, (item, value)):
- diff = item - len(self.L) + 1
- if diff > 0:
- self.L.extend([None] * diff)
- self.L[item] = value
-
-
-def _statXform(line):
- # Parse a STAT response
- numMsgs, totalSize = line.split(None, 1)
- return int(numMsgs), int(totalSize)
-
-
-def _listXform(line):
- # Parse a LIST response
- index, size = line.split(None, 1)
- return int(index) - 1, int(size)
-
-
-def _uidXform(line):
- # Parse a UIDL response
- index, uid = line.split(None, 1)
- return int(index) - 1, uid
-
-def _codeStatusSplit(line):
- # Parse an +OK or -ERR response
- parts = line.split(' ', 1)
- if len(parts) == 1:
- return parts[0], ''
- return parts
-
-def _dotUnquoter(line):
- """
- C{'.'} characters which begin a line of a message are doubled to avoid
- confusing with the terminating C{'.\\r\\n'} sequence. This function
- unquotes them.
- """
- if line.startswith('..'):
- return line[1:]
- return line
-
-class POP3Client(basic.LineOnlyReceiver, policies.TimeoutMixin):
- """POP3 client protocol implementation class
-
- Instances of this class provide a convenient, efficient API for
- retrieving and deleting messages from a POP3 server.
-
- @type startedTLS: C{bool}
- @ivar startedTLS: Whether TLS has been negotiated successfully.
-
-
- @type allowInsecureLogin: C{bool}
- @ivar allowInsecureLogin: Indicate whether login() should be
- allowed if the server offers no authentication challenge and if
- our transport does not offer any protection via encryption.
-
- @type serverChallenge: C{str} or C{None}
- @ivar serverChallenge: Challenge received from the server
-
- @type timeout: C{int}
- @ivar timeout: Number of seconds to wait before timing out a
- connection. If the number is <= 0, no timeout checking will be
- performed.
- """
-
- startedTLS = False
- allowInsecureLogin = False
- timeout = 0
- serverChallenge = None
-
- # Capabilities are not allowed to change during the session
- # (except when TLS is negotiated), so cache the first response and
- # use that for all later lookups
- _capCache = None
-
- # Regular expression to search for in the challenge string in the server
- # greeting line.
- _challengeMagicRe = re.compile('(<[^>]+>)')
-
- # List of pending calls.
- # We are a pipelining API but don't actually
- # support pipelining on the network yet.
- _blockedQueue = None
-
- # The Deferred to which the very next result will go.
- _waiting = None
-
- # Whether we dropped the connection because of a timeout
- _timedOut = False
-
- # If the server sends an initial -ERR, this is the message it sent
- # with it.
- _greetingError = None
-
- def _blocked(self, f, *a):
- # Internal helper. If commands are being blocked, append
- # the given command and arguments to a list and return a Deferred
- # that will be chained with the return value of the function
- # when it eventually runs. Otherwise, set up for commands to be
-
- # blocked and return None.
- if self._blockedQueue is not None:
- d = defer.Deferred()
- self._blockedQueue.append((d, f, a))
- return d
- self._blockedQueue = []
- return None
-
- def _unblock(self):
- # Internal helper. Indicate that a function has completed.
- # If there are blocked commands, run the next one. If there
- # are not, set up for the next command to not be blocked.
- if self._blockedQueue == []:
- self._blockedQueue = None
- elif self._blockedQueue is not None:
- _blockedQueue = self._blockedQueue
- self._blockedQueue = None
-
- d, f, a = _blockedQueue.pop(0)
- d2 = f(*a)
- d2.chainDeferred(d)
- # f is a function which uses _blocked (otherwise it wouldn't
- # have gotten into the blocked queue), which means it will have
- # re-set _blockedQueue to an empty list, so we can put the rest
- # of the blocked queue back into it now.
- self._blockedQueue.extend(_blockedQueue)
-
-
- def sendShort(self, cmd, args):
- # Internal helper. Send a command to which a short response
- # is expected. Return a Deferred that fires when the response
- # is received. Block all further commands from being sent until
- # the response is received. Transition the state to SHORT.
- d = self._blocked(self.sendShort, cmd, args)
- if d is not None:
- return d
-
- if args:
- self.sendLine(cmd + ' ' + args)
- else:
- self.sendLine(cmd)
- self.state = 'SHORT'
- self._waiting = defer.Deferred()
- return self._waiting
-
- def sendLong(self, cmd, args, consumer, xform):
- # Internal helper. Send a command to which a multiline
- # response is expected. Return a Deferred that fires when
- # the entire response is received. Block all further commands
- # from being sent until the entire response is received.
- # Transition the state to LONG_INITIAL.
- d = self._blocked(self.sendLong, cmd, args, consumer, xform)
- if d is not None:
- return d
-
- if args:
- self.sendLine(cmd + ' ' + args)
- else:
- self.sendLine(cmd)
- self.state = 'LONG_INITIAL'
- self._xform = xform
- self._consumer = consumer
- self._waiting = defer.Deferred()
- return self._waiting
-
- # Twisted protocol callback
- def connectionMade(self):
- if self.timeout > 0:
- self.setTimeout(self.timeout)
-
- self.state = 'WELCOME'
- self._blockedQueue = []
-
- def timeoutConnection(self):
- self._timedOut = True
- self.transport.loseConnection()
-
- def connectionLost(self, reason):
- if self.timeout > 0:
- self.setTimeout(None)
-
- if self._timedOut:
- reason = error.TimeoutError()
- elif self._greetingError:
- reason = ServerErrorResponse(self._greetingError)
-
- d = []
- if self._waiting is not None:
- d.append(self._waiting)
- self._waiting = None
- if self._blockedQueue is not None:
- d.extend([deferred for (deferred, f, a) in self._blockedQueue])
- self._blockedQueue = None
- for w in d:
- w.errback(reason)
-
- def lineReceived(self, line):
- if self.timeout > 0:
- self.resetTimeout()
-
- state = self.state
- self.state = None
- state = getattr(self, 'state_' + state)(line) or state
- if self.state is None:
- self.state = state
-
- def lineLengthExceeded(self, buffer):
- # XXX - We need to be smarter about this
- if self._waiting is not None:
- waiting, self._waiting = self._waiting, None
- waiting.errback(LineTooLong())
- self.transport.loseConnection()
-
- # POP3 Client state logic - don't touch this.
- def state_WELCOME(self, line):
- # WELCOME is the first state. The server sends one line of text
- # greeting us, possibly with an APOP challenge. Transition the
- # state to WAITING.
- code, status = _codeStatusSplit(line)
- if code != OK:
- self._greetingError = status
- self.transport.loseConnection()
- else:
- m = self._challengeMagicRe.search(status)
-
- if m is not None:
- self.serverChallenge = m.group(1)
-
- self.serverGreeting(status)
-
- self._unblock()
- return 'WAITING'
-
- def state_WAITING(self, line):
- # The server isn't supposed to send us anything in this state.
- log.msg("Illegal line from server: " + repr(line))
-
- def state_SHORT(self, line):
- # This is the state we are in when waiting for a single
- # line response. Parse it and fire the appropriate callback
- # or errback. Transition the state back to WAITING.
- deferred, self._waiting = self._waiting, None
- self._unblock()
- code, status = _codeStatusSplit(line)
- if code == OK:
- deferred.callback(status)
- else:
- deferred.errback(ServerErrorResponse(status))
- return 'WAITING'
-
- def state_LONG_INITIAL(self, line):
- # This is the state we are in when waiting for the first
- # line of a long response. Parse it and transition the
- # state to LONG if it is an okay response; if it is an
- # error response, fire an errback, clean up the things
- # waiting for a long response, and transition the state
- # to WAITING.
- code, status = _codeStatusSplit(line)
- if code == OK:
- return 'LONG'
- consumer = self._consumer
- deferred = self._waiting
- self._consumer = self._waiting = self._xform = None
- self._unblock()
- deferred.errback(ServerErrorResponse(status, consumer))
- return 'WAITING'
-
- def state_LONG(self, line):
- # This is the state for each line of a long response.
- # If it is the last line, finish things, fire the
- # Deferred, and transition the state to WAITING.
- # Otherwise, pass the line to the consumer.
- if line == '.':
- consumer = self._consumer
- deferred = self._waiting
- self._consumer = self._waiting = self._xform = None
- self._unblock()
- deferred.callback(consumer)
- return 'WAITING'
- else:
- if self._xform is not None:
- self._consumer(self._xform(line))
- else:
- self._consumer(line)
- return 'LONG'
-
-
- # Callbacks - override these
- def serverGreeting(self, greeting):
- """Called when the server has sent us a greeting.
-
- @type greeting: C{str} or C{None}
- @param greeting: The status message sent with the server
- greeting. For servers implementing APOP authentication, this
- will be a challenge string. .
- """
-
-
- # External API - call these (most of 'em anyway)
- def startTLS(self, contextFactory=None):
- """
- Initiates a 'STLS' request and negotiates the TLS / SSL
- Handshake.
-
- @type contextFactory: C{ssl.ClientContextFactory} @param
- contextFactory: The context factory with which to negotiate
- TLS. If C{None}, try to create a new one.
-
- @return: A Deferred which fires when the transport has been
- secured according to the given contextFactory, or which fails
- if the transport cannot be secured.
- """
- tls = interfaces.ITLSTransport(self.transport, None)
- if tls is None:
- return defer.fail(TLSError(
- "POP3Client transport does not implement "
- "interfaces.ITLSTransport"))
-
- if contextFactory is None:
- contextFactory = self._getContextFactory()
-
- if contextFactory is None:
- return defer.fail(TLSError(
- "POP3Client requires a TLS context to "
- "initiate the STLS handshake"))
-
- d = self.capabilities()
- d.addCallback(self._startTLS, contextFactory, tls)
- return d
-
-
- def _startTLS(self, caps, contextFactory, tls):
- assert not self.startedTLS, "Client and Server are currently communicating via TLS"
-
- if 'STLS' not in caps:
- return defer.fail(TLSNotSupportedError(
- "Server does not support secure communication "
- "via TLS / SSL"))
-
- d = self.sendShort('STLS', None)
- d.addCallback(self._startedTLS, contextFactory, tls)
- d.addCallback(lambda _: self.capabilities())
- return d
-
-
- def _startedTLS(self, result, context, tls):
- self.transport = tls
- self.transport.startTLS(context)
- self._capCache = None
- self.startedTLS = True
- return result
-
-
- def _getContextFactory(self):
- try:
- from twisted.internet import ssl
- except ImportError:
- return None
- else:
- context = ssl.ClientContextFactory()
- context.method = ssl.SSL.TLSv1_METHOD
- return context
-
-
- def login(self, username, password):
- """Log into the server.
-
- If APOP is available it will be used. Otherwise, if TLS is
- available an 'STLS' session will be started and plaintext
- login will proceed. Otherwise, if the instance attribute
- allowInsecureLogin is set to True, insecure plaintext login
- will proceed. Otherwise, InsecureAuthenticationDisallowed
- will be raised (asynchronously).
-
- @param username: The username with which to log in.
- @param password: The password with which to log in.
-
- @rtype: C{Deferred}
- @return: A deferred which fires when login has
- completed.
- """
- d = self.capabilities()
- d.addCallback(self._login, username, password)
- return d
-
-
- def _login(self, caps, username, password):
- if self.serverChallenge is not None:
- return self._apop(username, password, self.serverChallenge)
-
- tryTLS = 'STLS' in caps
-
- #If our transport supports switching to TLS, we might want to try to switch to TLS.
- tlsableTransport = interfaces.ITLSTransport(self.transport, None) is not None
-
- # If our transport is not already using TLS, we might want to try to switch to TLS.
- nontlsTransport = interfaces.ISSLTransport(self.transport, None) is None
-
- if not self.startedTLS and tryTLS and tlsableTransport and nontlsTransport:
- d = self.startTLS()
-
- d.addCallback(self._loginTLS, username, password)
- return d
-
- elif self.startedTLS or not nontlsTransport or self.allowInsecureLogin:
- return self._plaintext(username, password)
- else:
- return defer.fail(InsecureAuthenticationDisallowed())
-
-
- def _loginTLS(self, res, username, password):
- return self._plaintext(username, password)
-
- def _plaintext(self, username, password):
- # Internal helper. Send a username/password pair, returning a Deferred
- # that fires when both have succeeded or fails when the server rejects
- # either.
- return self.user(username).addCallback(lambda r: self.password(password))
-
- def _apop(self, username, password, challenge):
- # Internal helper. Computes and sends an APOP response. Returns
- # a Deferred that fires when the server responds to the response.
- digest = md5(challenge + password).hexdigest()
- return self.apop(username, digest)
-
- def apop(self, username, digest):
- """Perform APOP login.
-
- This should be used in special circumstances only, when it is
- known that the server supports APOP authentication, and APOP
- authentication is absolutely required. For the common case,
- use L{login} instead.
-
- @param username: The username with which to log in.
- @param digest: The challenge response to authenticate with.
- """
- return self.sendShort('APOP', username + ' ' + digest)
-
- def user(self, username):
- """Send the user command.
-
- This performs the first half of plaintext login. Unless this
- is absolutely required, use the L{login} method instead.
-
- @param username: The username with which to log in.
- """
- return self.sendShort('USER', username)
-
- def password(self, password):
- """Send the password command.
-
- This performs the second half of plaintext login. Unless this
- is absolutely required, use the L{login} method instead.
-
- @param password: The plaintext password with which to authenticate.
- """
- return self.sendShort('PASS', password)
-
- def delete(self, index):
- """Delete a message from the server.
-
- @type index: C{int}
- @param index: The index of the message to delete.
- This is 0-based.
-
- @rtype: C{Deferred}
- @return: A deferred which fires when the delete command
- is successful, or fails if the server returns an error.
- """
- return self.sendShort('DELE', str(index + 1))
-
- def _consumeOrSetItem(self, cmd, args, consumer, xform):
- # Internal helper. Send a long command. If no consumer is
- # provided, create a consumer that puts results into a list
- # and return a Deferred that fires with that list when it
- # is complete.
- if consumer is None:
- L = []
- consumer = _ListSetter(L).setitem
- return self.sendLong(cmd, args, consumer, xform).addCallback(lambda r: L)
- return self.sendLong(cmd, args, consumer, xform)
-
- def _consumeOrAppend(self, cmd, args, consumer, xform):
- # Internal helper. Send a long command. If no consumer is
- # provided, create a consumer that appends results to a list
- # and return a Deferred that fires with that list when it is
- # complete.
- if consumer is None:
- L = []
- consumer = L.append
- return self.sendLong(cmd, args, consumer, xform).addCallback(lambda r: L)
- return self.sendLong(cmd, args, consumer, xform)
-
- def capabilities(self, useCache=True):
- """Retrieve the capabilities supported by this server.
-
- Not all servers support this command. If the server does not
- support this, it is treated as though it returned a successful
- response listing no capabilities. At some future time, this may be
- changed to instead seek out information about a server's
- capabilities in some other fashion (only if it proves useful to do
- so, and only if there are servers still in use which do not support
- CAPA but which do support POP3 extensions that are useful).
-
- @type useCache: C{bool}
- @param useCache: If set, and if capabilities have been
- retrieved previously, just return the previously retrieved
- results.
-
- @return: A Deferred which fires with a C{dict} mapping C{str}
- to C{None} or C{list}s of C{str}. For example::
-
- C: CAPA
- S: +OK Capability list follows
- S: TOP
- S: USER
- S: SASL CRAM-MD5 KERBEROS_V4
- S: RESP-CODES
- S: LOGIN-DELAY 900
- S: PIPELINING
- S: EXPIRE 60
- S: UIDL
- S: IMPLEMENTATION Shlemazle-Plotz-v302
- S: .
-
- will be lead to a result of::
-
- | {'TOP': None,
- | 'USER': None,
- | 'SASL': ['CRAM-MD5', 'KERBEROS_V4'],
- | 'RESP-CODES': None,
- | 'LOGIN-DELAY': ['900'],
- | 'PIPELINING': None,
- | 'EXPIRE': ['60'],
- | 'UIDL': None,
- | 'IMPLEMENTATION': ['Shlemazle-Plotz-v302']}
- """
- if useCache and self._capCache is not None:
- return defer.succeed(self._capCache)
-
- cache = {}
- def consume(line):
- tmp = line.split()
- if len(tmp) == 1:
- cache[tmp[0]] = None
- elif len(tmp) > 1:
- cache[tmp[0]] = tmp[1:]
-
- def capaNotSupported(err):
- err.trap(ServerErrorResponse)
- return None
-
- def gotCapabilities(result):
- self._capCache = cache
- return cache
-
- d = self._consumeOrAppend('CAPA', None, consume, None)
- d.addErrback(capaNotSupported).addCallback(gotCapabilities)
- return d
-
-
- def noop(self):
- """Do nothing, with the help of the server.
-
- No operation is performed. The returned Deferred fires when
- the server responds.
- """
- return self.sendShort("NOOP", None)
-
-
- def reset(self):
- """Remove the deleted flag from any messages which have it.
-
- The returned Deferred fires when the server responds.
- """
- return self.sendShort("RSET", None)
-
-
- def retrieve(self, index, consumer=None, lines=None):
- """Retrieve a message from the server.
-
- If L{consumer} is not None, it will be called with
- each line of the message as it is received. Otherwise,
- the returned Deferred will be fired with a list of all
- the lines when the message has been completely received.
- """
- idx = str(index + 1)
- if lines is None:
- return self._consumeOrAppend('RETR', idx, consumer, _dotUnquoter)
-
- return self._consumeOrAppend('TOP', '%s %d' % (idx, lines), consumer, _dotUnquoter)
-
-
- def stat(self):
- """Get information about the size of this mailbox.
-
- The returned Deferred will be fired with a tuple containing
- the number or messages in the mailbox and the size (in bytes)
- of the mailbox.
- """
- return self.sendShort('STAT', None).addCallback(_statXform)
-
-
- def listSize(self, consumer=None):
- """Retrieve a list of the size of all messages on the server.
-
- If L{consumer} is not None, it will be called with two-tuples
- of message index number and message size as they are received.
- Otherwise, a Deferred which will fire with a list of B{only}
- message sizes will be returned. For messages which have been
- deleted, None will be used in place of the message size.
- """
- return self._consumeOrSetItem('LIST', None, consumer, _listXform)
-
-
- def listUID(self, consumer=None):
- """Retrieve a list of the UIDs of all messages on the server.
-
- If L{consumer} is not None, it will be called with two-tuples
- of message index number and message UID as they are received.
- Otherwise, a Deferred which will fire with of list of B{only}
- message UIDs will be returned. For messages which have been
- deleted, None will be used in place of the message UID.
- """
- return self._consumeOrSetItem('UIDL', None, consumer, _uidXform)
-
-
- def quit(self):
- """Disconnect from the server.
- """
- return self.sendShort('QUIT', None)
-
-__all__ = [
- # Exceptions
- 'InsecureAuthenticationDisallowed', 'LineTooLong', 'POP3ClientError',
- 'ServerErrorResponse', 'TLSError', 'TLSNotSupportedError',
-
- # Protocol classes
- 'POP3Client']
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/protocols.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/protocols.py
deleted file mode 100755
index 446592be..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/protocols.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_mail -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""Protocol support for twisted.mail."""
-
-# twisted imports
-from twisted.mail import pop3
-from twisted.mail import smtp
-from twisted.internet import protocol
-from twisted.internet import defer
-from twisted.copyright import longversion
-from twisted.python import log
-from twisted.python.deprecate import deprecatedModuleAttribute
-from twisted.python.versions import Version
-
-from twisted import cred
-import twisted.cred.error
-import twisted.cred.credentials
-
-from twisted.mail import relay
-
-from zope.interface import implements
-
-
-class DomainDeliveryBase:
- """A server that uses twisted.mail service's domains."""
-
- implements(smtp.IMessageDelivery)
-
- service = None
- protocolName = None
-
- def __init__(self, service, user, host=smtp.DNSNAME):
- self.service = service
- self.user = user
- self.host = host
-
- def receivedHeader(self, helo, origin, recipients):
- authStr = heloStr = ""
- if self.user:
- authStr = " auth=%s" % (self.user.encode('xtext'),)
- if helo[0]:
- heloStr = " helo=%s" % (helo[0],)
- from_ = "from %s ([%s]%s%s)" % (helo[0], helo[1], heloStr, authStr)
- by = "by %s with %s (%s)" % (
- self.host, self.protocolName, longversion
- )
- for_ = "for <%s>; %s" % (' '.join(map(str, recipients)), smtp.rfc822date())
- return "Received: %s\n\t%s\n\t%s" % (from_, by, for_)
-
- def validateTo(self, user):
- # XXX - Yick. This needs cleaning up.
- if self.user and self.service.queue:
- d = self.service.domains.get(user.dest.domain, None)
- if d is None:
- d = relay.DomainQueuer(self.service, True)
- else:
- d = self.service.domains[user.dest.domain]
- return defer.maybeDeferred(d.exists, user)
-
- def validateFrom(self, helo, origin):
- if not helo:
- raise smtp.SMTPBadSender(origin, 503, "Who are you? Say HELO first.")
- if origin.local != '' and origin.domain == '':
- raise smtp.SMTPBadSender(origin, 501, "Sender address must contain domain.")
- return origin
-
- def startMessage(self, users):
- ret = []
- for user in users:
- ret.append(self.service.domains[user.dest.domain].startMessage(user))
- return ret
-
-
-class SMTPDomainDelivery(DomainDeliveryBase):
- protocolName = 'smtp'
-
-class ESMTPDomainDelivery(DomainDeliveryBase):
- protocolName = 'esmtp'
-
-class DomainSMTP(SMTPDomainDelivery, smtp.SMTP):
- service = user = None
-
- def __init__(self, *args, **kw):
- import warnings
- warnings.warn(
- "DomainSMTP is deprecated. Use IMessageDelivery objects instead.",
- DeprecationWarning, stacklevel=2,
- )
- smtp.SMTP.__init__(self, *args, **kw)
- if self.delivery is None:
- self.delivery = self
-
-class DomainESMTP(ESMTPDomainDelivery, smtp.ESMTP):
- service = user = None
-
- def __init__(self, *args, **kw):
- import warnings
- warnings.warn(
- "DomainESMTP is deprecated. Use IMessageDelivery objects instead.",
- DeprecationWarning, stacklevel=2,
- )
- smtp.ESMTP.__init__(self, *args, **kw)
- if self.delivery is None:
- self.delivery = self
-
-class SMTPFactory(smtp.SMTPFactory):
- """A protocol factory for SMTP."""
-
- protocol = smtp.SMTP
- portal = None
-
- def __init__(self, service, portal = None):
- smtp.SMTPFactory.__init__(self)
- self.service = service
- self.portal = portal
-
- def buildProtocol(self, addr):
- log.msg('Connection from %s' % (addr,))
- p = smtp.SMTPFactory.buildProtocol(self, addr)
- p.service = self.service
- p.portal = self.portal
- return p
-
-class ESMTPFactory(SMTPFactory):
- protocol = smtp.ESMTP
- context = None
-
- def __init__(self, *args):
- SMTPFactory.__init__(self, *args)
- self.challengers = {
- 'CRAM-MD5': cred.credentials.CramMD5Credentials
- }
-
- def buildProtocol(self, addr):
- p = SMTPFactory.buildProtocol(self, addr)
- p.challengers = self.challengers
- p.ctx = self.context
- return p
-
-class VirtualPOP3(pop3.POP3):
- """Virtual hosting POP3."""
-
- service = None
-
- domainSpecifier = '@' # Gaagh! I hate POP3. No standardized way
- # to indicate user@host. '@' doesn't work
- # with NS, e.g.
-
- def authenticateUserAPOP(self, user, digest):
- # Override the default lookup scheme to allow virtual domains
- user, domain = self.lookupDomain(user)
- try:
- portal = self.service.lookupPortal(domain)
- except KeyError:
- return defer.fail(cred.error.UnauthorizedLogin())
- else:
- return portal.login(
- pop3.APOPCredentials(self.magic, user, digest),
- None,
- pop3.IMailbox
- )
-
- def authenticateUserPASS(self, user, password):
- user, domain = self.lookupDomain(user)
- try:
- portal = self.service.lookupPortal(domain)
- except KeyError:
- return defer.fail(cred.error.UnauthorizedLogin())
- else:
- return portal.login(
- cred.credentials.UsernamePassword(user, password),
- None,
- pop3.IMailbox
- )
-
- def lookupDomain(self, user):
- try:
- user, domain = user.split(self.domainSpecifier, 1)
- except ValueError:
- domain = ''
- if domain not in self.service.domains:
- raise pop3.POP3Error("no such domain %s" % domain)
- return user, domain
-
-
-class POP3Factory(protocol.ServerFactory):
- """POP3 protocol factory."""
-
- protocol = VirtualPOP3
- service = None
-
- def __init__(self, service):
- self.service = service
-
- def buildProtocol(self, addr):
- p = protocol.ServerFactory.buildProtocol(self, addr)
- p.service = self.service
- return p
-
-#
-# It is useful to know, perhaps, that the required file for this to work can
-# be created thusly:
-#
-# openssl req -x509 -newkey rsa:2048 -keyout file.key -out file.crt \
-# -days 365 -nodes
-#
-# And then cat file.key and file.crt together. The number of days and bits
-# can be changed, of course.
-#
-class SSLContextFactory:
- """
- An SSL Context Factory.
-
- This loads a certificate and private key from a specified file.
- """
- deprecatedModuleAttribute(
- Version("Twisted", 12, 2, 0),
- "Use twisted.internet.ssl.DefaultOpenSSLContextFactory instead.",
- "twisted.mail.protocols", "SSLContextFactory")
-
- def __init__(self, filename):
- self.filename = filename
-
- def getContext(self):
- """Create an SSL context."""
- from OpenSSL import SSL
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- ctx.use_certificate_file(self.filename)
- ctx.use_privatekey_file(self.filename)
- return ctx
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relay.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relay.py
deleted file mode 100755
index ac68095a..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relay.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_mail -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""Support for relaying mail for twisted.mail"""
-
-from twisted.mail import smtp
-from twisted.python import log
-from twisted.internet.address import UNIXAddress
-
-import os
-
-try:
- import cPickle as pickle
-except ImportError:
- import pickle
-
-class DomainQueuer:
- """An SMTP domain which add messages to a queue intended for relaying."""
-
- def __init__(self, service, authenticated=False):
- self.service = service
- self.authed = authenticated
-
- def exists(self, user):
- """Check whether we will relay
-
- Call overridable willRelay method
- """
- if self.willRelay(user.dest, user.protocol):
- # The most cursor form of verification of the addresses
- orig = filter(None, str(user.orig).split('@', 1))
- dest = filter(None, str(user.dest).split('@', 1))
- if len(orig) == 2 and len(dest) == 2:
- return lambda: self.startMessage(user)
- raise smtp.SMTPBadRcpt(user)
-
- def willRelay(self, address, protocol):
- """Check whether we agree to relay
-
- The default is to relay for all connections over UNIX
- sockets and all connections from localhost.
- """
- peer = protocol.transport.getPeer()
- return self.authed or isinstance(peer, UNIXAddress) or peer.host == '127.0.0.1'
-
- def startMessage(self, user):
- """Add envelope to queue and returns ISMTPMessage."""
- queue = self.service.queue
- envelopeFile, smtpMessage = queue.createNewMessage()
- try:
- log.msg('Queueing mail %r -> %r' % (str(user.orig), str(user.dest)))
- pickle.dump([str(user.orig), str(user.dest)], envelopeFile)
- finally:
- envelopeFile.close()
- return smtpMessage
-
-class RelayerMixin:
-
- # XXX - This is -totally- bogus
- # It opens about a -hundred- -billion- files
- # and -leaves- them open!
-
- def loadMessages(self, messagePaths):
- self.messages = []
- self.names = []
- for message in messagePaths:
- fp = open(message+'-H')
- try:
- messageContents = pickle.load(fp)
- finally:
- fp.close()
- fp = open(message+'-D')
- messageContents.append(fp)
- self.messages.append(messageContents)
- self.names.append(message)
-
- def getMailFrom(self):
- if not self.messages:
- return None
- return self.messages[0][0]
-
- def getMailTo(self):
- if not self.messages:
- return None
- return [self.messages[0][1]]
-
- def getMailData(self):
- if not self.messages:
- return None
- return self.messages[0][2]
-
- def sentMail(self, code, resp, numOk, addresses, log):
- """Since we only use one recipient per envelope, this
- will be called with 0 or 1 addresses. We probably want
- to do something with the error message if we failed.
- """
- if code in smtp.SUCCESS:
- # At least one, i.e. all, recipients successfully delivered
- os.remove(self.names[0]+'-D')
- os.remove(self.names[0]+'-H')
- del self.messages[0]
- del self.names[0]
-
-class SMTPRelayer(RelayerMixin, smtp.SMTPClient):
- def __init__(self, messagePaths, *args, **kw):
- smtp.SMTPClient.__init__(self, *args, **kw)
- self.loadMessages(messagePaths)
-
-class ESMTPRelayer(RelayerMixin, smtp.ESMTPClient):
- def __init__(self, messagePaths, *args, **kw):
- smtp.ESMTPClient.__init__(self, *args, **kw)
- self.loadMessages(messagePaths)
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relaymanager.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relaymanager.py
deleted file mode 100755
index 66c777ad..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/relaymanager.py
+++ /dev/null
@@ -1,631 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_mail -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Infrastructure for relaying mail through smart host
-
-Today, internet e-mail has stopped being Peer-to-peer for many problems,
-spam (unsolicited bulk mail) among them. Instead, most nodes on the
-internet send all e-mail to a single computer, usually the ISP's though
-sometimes other schemes, such as SMTP-after-POP, are used. This computer
-is supposedly permanently up and traceable, and will do the work of
-figuring out MXs and connecting to them. This kind of configuration
-is usually termed "smart host", since the host we are connecting to
-is "smart" (and will find MXs and connect to them) rather then just
-accepting mail for a small set of domains.
-
-The classes here are meant to facilitate support for such a configuration
-for the twisted.mail SMTP server
-"""
-
-import rfc822
-import os
-import time
-
-try:
- import cPickle as pickle
-except ImportError:
- import pickle
-
-from twisted.python import log
-from twisted.python.failure import Failure
-from twisted.python.compat import set
-from twisted.mail import relay
-from twisted.mail import bounce
-from twisted.internet import protocol
-from twisted.internet.defer import Deferred, DeferredList
-from twisted.internet.error import DNSLookupError
-from twisted.mail import smtp
-from twisted.application import internet
-
-class ManagedRelayerMixin:
- """SMTP Relayer which notifies a manager
-
- Notify the manager about successful mail, failed mail
- and broken connections
- """
-
- def __init__(self, manager):
- self.manager = manager
-
- def sentMail(self, code, resp, numOk, addresses, log):
- """called when e-mail has been sent
-
- we will always get 0 or 1 addresses.
- """
- message = self.names[0]
- if code in smtp.SUCCESS:
- self.manager.notifySuccess(self.factory, message)
- else:
- self.manager.notifyFailure(self.factory, message)
- del self.messages[0]
- del self.names[0]
-
- def connectionLost(self, reason):
- """called when connection is broken
-
- notify manager we will try to send no more e-mail
- """
- self.manager.notifyDone(self.factory)
-
-class SMTPManagedRelayer(ManagedRelayerMixin, relay.SMTPRelayer):
- def __init__(self, messages, manager, *args, **kw):
- """
- @type messages: C{list} of C{str}
- @param messages: Filenames of messages to relay
-
- manager should support .notifySuccess, .notifyFailure
- and .notifyDone
- """
- ManagedRelayerMixin.__init__(self, manager)
- relay.SMTPRelayer.__init__(self, messages, *args, **kw)
-
-class ESMTPManagedRelayer(ManagedRelayerMixin, relay.ESMTPRelayer):
- def __init__(self, messages, manager, *args, **kw):
- """
- @type messages: C{list} of C{str}
- @param messages: Filenames of messages to relay
-
- manager should support .notifySuccess, .notifyFailure
- and .notifyDone
- """
- ManagedRelayerMixin.__init__(self, manager)
- relay.ESMTPRelayer.__init__(self, messages, *args, **kw)
-
-class SMTPManagedRelayerFactory(protocol.ClientFactory):
- protocol = SMTPManagedRelayer
-
- def __init__(self, messages, manager, *args, **kw):
- self.messages = messages
- self.manager = manager
- self.pArgs = args
- self.pKwArgs = kw
-
- def buildProtocol(self, addr):
- protocol = self.protocol(self.messages, self.manager, *self.pArgs,
- **self.pKwArgs)
- protocol.factory = self
- return protocol
-
- def clientConnectionFailed(self, connector, reason):
- """called when connection could not be made
-
- our manager should be notified that this happened,
- it might prefer some other host in that case"""
- self.manager.notifyNoConnection(self)
- self.manager.notifyDone(self)
-
-class ESMTPManagedRelayerFactory(SMTPManagedRelayerFactory):
- protocol = ESMTPManagedRelayer
-
- def __init__(self, messages, manager, secret, contextFactory, *args, **kw):
- self.secret = secret
- self.contextFactory = contextFactory
- SMTPManagedRelayerFactory.__init__(self, messages, manager, *args, **kw)
-
- def buildProtocol(self, addr):
- s = self.secret and self.secret(addr)
- protocol = self.protocol(self.messages, self.manager, s,
- self.contextFactory, *self.pArgs, **self.pKwArgs)
- protocol.factory = self
- return protocol
-
-class Queue:
- """A queue of ougoing emails."""
-
- noisy = True
-
- def __init__(self, directory):
- self.directory = directory
- self._init()
-
- def _init(self):
- self.n = 0
- self.waiting = {}
- self.relayed = {}
- self.readDirectory()
-
- def __getstate__(self):
- """(internal) delete volatile state"""
- return {'directory' : self.directory}
-
- def __setstate__(self, state):
- """(internal) restore volatile state"""
- self.__dict__.update(state)
- self._init()
-
- def readDirectory(self):
- """Read the messages directory.
-
- look for new messages.
- """
- for message in os.listdir(self.directory):
- # Skip non data files
- if message[-2:]!='-D':
- continue
- self.addMessage(message[:-2])
-
- def getWaiting(self):
- return self.waiting.keys()
-
- def hasWaiting(self):
- return len(self.waiting) > 0
-
- def getRelayed(self):
- return self.relayed.keys()
-
- def setRelaying(self, message):
- del self.waiting[message]
- self.relayed[message] = 1
-
- def setWaiting(self, message):
- del self.relayed[message]
- self.waiting[message] = 1
-
- def addMessage(self, message):
- if message not in self.relayed:
- self.waiting[message] = 1
- if self.noisy:
- log.msg('Set ' + message + ' waiting')
-
- def done(self, message):
- """Remove message to from queue."""
- message = os.path.basename(message)
- os.remove(self.getPath(message) + '-D')
- os.remove(self.getPath(message) + '-H')
- del self.relayed[message]
-
- def getPath(self, message):
- """Get the path in the filesystem of a message."""
- return os.path.join(self.directory, message)
-
- def getEnvelope(self, message):
- return pickle.load(self.getEnvelopeFile(message))
-
- def getEnvelopeFile(self, message):
- return open(os.path.join(self.directory, message+'-H'), 'rb')
-
- def createNewMessage(self):
- """Create a new message in the queue.
-
- Return a tuple - file-like object for headers, and ISMTPMessage.
- """
- fname = "%s_%s_%s_%s" % (os.getpid(), time.time(), self.n, id(self))
- self.n = self.n + 1
- headerFile = open(os.path.join(self.directory, fname+'-H'), 'wb')
- tempFilename = os.path.join(self.directory, fname+'-C')
- finalFilename = os.path.join(self.directory, fname+'-D')
- messageFile = open(tempFilename, 'wb')
-
- from twisted.mail.mail import FileMessage
- return headerFile,FileMessage(messageFile, tempFilename, finalFilename)
-
-
-class _AttemptManager(object):
- """
- Manage the state of a single attempt to flush the relay queue.
- """
- def __init__(self, manager):
- self.manager = manager
- self._completionDeferreds = []
-
-
- def getCompletionDeferred(self):
- self._completionDeferreds.append(Deferred())
- return self._completionDeferreds[-1]
-
-
- def _finish(self, relay, message):
- self.manager.managed[relay].remove(os.path.basename(message))
- self.manager.queue.done(message)
-
-
- def notifySuccess(self, relay, message):
- """a relay sent a message successfully
-
- Mark it as sent in our lists
- """
- if self.manager.queue.noisy:
- log.msg("success sending %s, removing from queue" % message)
- self._finish(relay, message)
-
-
- def notifyFailure(self, relay, message):
- """Relaying the message has failed."""
- if self.manager.queue.noisy:
- log.msg("could not relay "+message)
- # Moshe - Bounce E-mail here
- # Be careful: if it's a bounced bounce, silently
- # discard it
- message = os.path.basename(message)
- fp = self.manager.queue.getEnvelopeFile(message)
- from_, to = pickle.load(fp)
- fp.close()
- from_, to, bounceMessage = bounce.generateBounce(open(self.manager.queue.getPath(message)+'-D'), from_, to)
- fp, outgoingMessage = self.manager.queue.createNewMessage()
- pickle.dump([from_, to], fp)
- fp.close()
- for line in bounceMessage.splitlines():
- outgoingMessage.lineReceived(line)
- outgoingMessage.eomReceived()
- self._finish(relay, self.manager.queue.getPath(message))
-
-
- def notifyDone(self, relay):
- """A relaying SMTP client is disconnected.
-
- unmark all pending messages under this relay's responsibility
- as being relayed, and remove the relay.
- """
- for message in self.manager.managed.get(relay, ()):
- if self.manager.queue.noisy:
- log.msg("Setting " + message + " waiting")
- self.manager.queue.setWaiting(message)
- try:
- del self.manager.managed[relay]
- except KeyError:
- pass
- notifications = self._completionDeferreds
- self._completionDeferreds = None
- for d in notifications:
- d.callback(None)
-
-
- def notifyNoConnection(self, relay):
- """Relaying SMTP client couldn't connect.
-
- Useful because it tells us our upstream server is unavailable.
- """
- # Back off a bit
- try:
- msgs = self.manager.managed[relay]
- except KeyError:
- log.msg("notifyNoConnection passed unknown relay!")
- return
-
- if self.manager.queue.noisy:
- log.msg("Backing off on delivery of " + str(msgs))
- def setWaiting(queue, messages):
- map(queue.setWaiting, messages)
- from twisted.internet import reactor
- reactor.callLater(30, setWaiting, self.manager.queue, msgs)
- del self.manager.managed[relay]
-
-
-
-class SmartHostSMTPRelayingManager:
- """Manage SMTP Relayers
-
- Manage SMTP relayers, keeping track of the existing connections,
- each connection's responsibility in term of messages. Create
- more relayers if the need arises.
-
- Someone should press .checkState periodically
-
- @ivar fArgs: Additional positional arguments used to instantiate
- C{factory}.
-
- @ivar fKwArgs: Additional keyword arguments used to instantiate
- C{factory}.
-
- @ivar factory: A callable which returns a ClientFactory suitable for
- making SMTP connections.
- """
-
- factory = SMTPManagedRelayerFactory
-
- PORT = 25
-
- mxcalc = None
-
- def __init__(self, queue, maxConnections=2, maxMessagesPerConnection=10):
- """
- @type queue: Any implementor of C{IQueue}
- @param queue: The object used to queue messages on their way to
- delivery.
-
- @type maxConnections: C{int}
- @param maxConnections: The maximum number of SMTP connections to
- allow to be opened at any given time.
-
- @type maxMessagesPerConnection: C{int}
- @param maxMessagesPerConnection: The maximum number of messages a
- relayer will be given responsibility for.
-
- Default values are meant for a small box with 1-5 users.
- """
- self.maxConnections = maxConnections
- self.maxMessagesPerConnection = maxMessagesPerConnection
- self.managed = {} # SMTP clients we're managing
- self.queue = queue
- self.fArgs = ()
- self.fKwArgs = {}
-
- def __getstate__(self):
- """(internal) delete volatile state"""
- dct = self.__dict__.copy()
- del dct['managed']
- return dct
-
- def __setstate__(self, state):
- """(internal) restore volatile state"""
- self.__dict__.update(state)
- self.managed = {}
-
- def checkState(self):
- """
- Synchronize with the state of the world, and maybe launch a new
- relay.
-
- Call me periodically to check I am still up to date.
-
- @return: None or a Deferred which fires when all of the SMTP clients
- started by this call have disconnected.
- """
- self.queue.readDirectory()
- if (len(self.managed) >= self.maxConnections):
- return
- if not self.queue.hasWaiting():
- return
-
- return self._checkStateMX()
-
- def _checkStateMX(self):
- nextMessages = self.queue.getWaiting()
- nextMessages.reverse()
-
- exchanges = {}
- for msg in nextMessages:
- from_, to = self.queue.getEnvelope(msg)
- name, addr = rfc822.parseaddr(to)
- parts = addr.split('@', 1)
- if len(parts) != 2:
- log.err("Illegal message destination: " + to)
- continue
- domain = parts[1]
-
- self.queue.setRelaying(msg)
- exchanges.setdefault(domain, []).append(self.queue.getPath(msg))
- if len(exchanges) >= (self.maxConnections - len(self.managed)):
- break
-
- if self.mxcalc is None:
- self.mxcalc = MXCalculator()
-
- relays = []
- for (domain, msgs) in exchanges.iteritems():
- manager = _AttemptManager(self)
- factory = self.factory(msgs, manager, *self.fArgs, **self.fKwArgs)
- self.managed[factory] = map(os.path.basename, msgs)
- relayAttemptDeferred = manager.getCompletionDeferred()
- connectSetupDeferred = self.mxcalc.getMX(domain)
- connectSetupDeferred.addCallback(lambda mx: str(mx.name))
- connectSetupDeferred.addCallback(self._cbExchange, self.PORT, factory)
- connectSetupDeferred.addErrback(lambda err: (relayAttemptDeferred.errback(err), err)[1])
- connectSetupDeferred.addErrback(self._ebExchange, factory, domain)
- relays.append(relayAttemptDeferred)
- return DeferredList(relays)
-
-
- def _cbExchange(self, address, port, factory):
- from twisted.internet import reactor
- reactor.connectTCP(address, port, factory)
-
- def _ebExchange(self, failure, factory, domain):
- log.err('Error setting up managed relay factory for ' + domain)
- log.err(failure)
- def setWaiting(queue, messages):
- map(queue.setWaiting, messages)
- from twisted.internet import reactor
- reactor.callLater(30, setWaiting, self.queue, self.managed[factory])
- del self.managed[factory]
-
-class SmartHostESMTPRelayingManager(SmartHostSMTPRelayingManager):
- factory = ESMTPManagedRelayerFactory
-
-def _checkState(manager):
- manager.checkState()
-
-def RelayStateHelper(manager, delay):
- return internet.TimerService(delay, _checkState, manager)
-
-
-
-class CanonicalNameLoop(Exception):
- """
- When trying to look up the MX record for a host, a set of CNAME records was
- found which form a cycle and resolution was abandoned.
- """
-
-
-class CanonicalNameChainTooLong(Exception):
- """
- When trying to look up the MX record for a host, too many CNAME records
- which point to other CNAME records were encountered and resolution was
- abandoned.
- """
-
-
-class MXCalculator:
- """
- A utility for looking up mail exchange hosts and tracking whether they are
- working or not.
-
- @ivar clock: L{IReactorTime} provider which will be used to decide when to
- retry mail exchanges which have not been working.
- """
- timeOutBadMX = 60 * 60 # One hour
- fallbackToDomain = True
-
- def __init__(self, resolver=None, clock=None):
- self.badMXs = {}
- if resolver is None:
- from twisted.names.client import createResolver
- resolver = createResolver()
- self.resolver = resolver
- if clock is None:
- from twisted.internet import reactor as clock
- self.clock = clock
-
-
- def markBad(self, mx):
- """Indicate a given mx host is not currently functioning.
-
- @type mx: C{str}
- @param mx: The hostname of the host which is down.
- """
- self.badMXs[str(mx)] = self.clock.seconds() + self.timeOutBadMX
-
- def markGood(self, mx):
- """Indicate a given mx host is back online.
-
- @type mx: C{str}
- @param mx: The hostname of the host which is up.
- """
- try:
- del self.badMXs[mx]
- except KeyError:
- pass
-
- def getMX(self, domain, maximumCanonicalChainLength=3):
- """
- Find an MX record for the given domain.
-
- @type domain: C{str}
- @param domain: The domain name for which to look up an MX record.
-
- @type maximumCanonicalChainLength: C{int}
- @param maximumCanonicalChainLength: The maximum number of unique CNAME
- records to follow while looking up the MX record.
-
- @return: A L{Deferred} which is called back with a string giving the
- name in the found MX record or which is errbacked if no MX record
- can be found.
- """
- mailExchangeDeferred = self.resolver.lookupMailExchange(domain)
- mailExchangeDeferred.addCallback(self._filterRecords)
- mailExchangeDeferred.addCallback(
- self._cbMX, domain, maximumCanonicalChainLength)
- mailExchangeDeferred.addErrback(self._ebMX, domain)
- return mailExchangeDeferred
-
-
- def _filterRecords(self, records):
- """
- Convert a DNS response (a three-tuple of lists of RRHeaders) into a
- mapping from record names to lists of corresponding record payloads.
- """
- recordBag = {}
- for answer in records[0]:
- recordBag.setdefault(str(answer.name), []).append(answer.payload)
- return recordBag
-
-
- def _cbMX(self, answers, domain, cnamesLeft):
- """
- Try to find the MX host from the given DNS information.
-
- This will attempt to resolve CNAME results. It can recognize loops
- and will give up on non-cyclic chains after a specified number of
- lookups.
- """
- # Do this import here so that relaymanager.py doesn't depend on
- # twisted.names, only MXCalculator will.
- from twisted.names import dns, error
-
- seenAliases = set()
- exchanges = []
- # Examine the answers for the domain we asked about
- pertinentRecords = answers.get(domain, [])
- while pertinentRecords:
- record = pertinentRecords.pop()
-
- # If it's a CNAME, we'll need to do some more processing
- if record.TYPE == dns.CNAME:
-
- # Remember that this name was an alias.
- seenAliases.add(domain)
-
- canonicalName = str(record.name)
- # See if we have some local records which might be relevant.
- if canonicalName in answers:
-
- # Make sure it isn't a loop contained entirely within the
- # results we have here.
- if canonicalName in seenAliases:
- return Failure(CanonicalNameLoop(record))
-
- pertinentRecords = answers[canonicalName]
- exchanges = []
- else:
- if cnamesLeft:
- # Request more information from the server.
- return self.getMX(canonicalName, cnamesLeft - 1)
- else:
- # Give up.
- return Failure(CanonicalNameChainTooLong(record))
-
- # If it's an MX, collect it.
- if record.TYPE == dns.MX:
- exchanges.append((record.preference, record))
-
- if exchanges:
- exchanges.sort()
- for (preference, record) in exchanges:
- host = str(record.name)
- if host not in self.badMXs:
- return record
- t = self.clock.seconds() - self.badMXs[host]
- if t >= 0:
- del self.badMXs[host]
- return record
- return exchanges[0][1]
- else:
- # Treat no answers the same as an error - jump to the errback to try
- # to look up an A record. This provides behavior described as a
- # special case in RFC 974 in the section headed I{Interpreting the
- # List of MX RRs}.
- return Failure(
- error.DNSNameError("No MX records for %r" % (domain,)))
-
-
- def _ebMX(self, failure, domain):
- from twisted.names import error, dns
-
- if self.fallbackToDomain:
- failure.trap(error.DNSNameError)
- log.msg("MX lookup failed; attempting to use hostname (%s) directly" % (domain,))
-
- # Alright, I admit, this is a bit icky.
- d = self.resolver.getHostByName(domain)
- def cbResolved(addr):
- return dns.Record_MX(name=addr)
- def ebResolved(err):
- err.trap(error.DNSNameError)
- raise DNSLookupError()
- d.addCallbacks(cbResolved, ebResolved)
- return d
- elif failure.check(error.DNSNameError):
- raise IOError("No MX found for %r" % (domain,))
- return failure
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/__init__.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/__init__.py
deleted file mode 100755
index f653cc71..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"mail scripts"
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/mailmail.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/mailmail.py
deleted file mode 100755
index a045e822..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/scripts/mailmail.py
+++ /dev/null
@@ -1,366 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_mailmail -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Implementation module for the I{mailmail} command.
-"""
-
-import os
-import sys
-import rfc822
-import getpass
-from ConfigParser import ConfigParser
-
-try:
- import cStringIO as StringIO
-except:
- import StringIO
-
-from twisted.copyright import version
-from twisted.internet import reactor
-from twisted.mail import smtp
-
-GLOBAL_CFG = "/etc/mailmail"
-LOCAL_CFG = os.path.expanduser("~/.twisted/mailmail")
-SMARTHOST = '127.0.0.1'
-
-ERROR_FMT = """\
-Subject: Failed Message Delivery
-
- Message delivery failed. The following occurred:
-
- %s
---
-The Twisted sendmail application.
-"""
-
-def log(message, *args):
- sys.stderr.write(str(message) % args + '\n')
-
-class Options:
- """
- @type to: C{list} of C{str}
- @ivar to: The addresses to which to deliver this message.
-
- @type sender: C{str}
- @ivar sender: The address from which this message is being sent.
-
- @type body: C{file}
- @ivar body: The object from which the message is to be read.
- """
-
-def getlogin():
- try:
- return os.getlogin()
- except:
- return getpass.getuser()
-
-
-_unsupportedOption = SystemExit("Unsupported option.")
-
-def parseOptions(argv):
- o = Options()
- o.to = [e for e in argv if not e.startswith('-')]
- o.sender = getlogin()
-
- # Just be very stupid
-
- # Skip -bm -- it is the default
-
- # Add a non-standard option for querying the version of this tool.
- if '--version' in argv:
- print 'mailmail version:', version
- raise SystemExit()
-
- # -bp lists queue information. Screw that.
- if '-bp' in argv:
- raise _unsupportedOption
-
- # -bs makes sendmail use stdin/stdout as its transport. Screw that.
- if '-bs' in argv:
- raise _unsupportedOption
-
- # -F sets who the mail is from, but is overridable by the From header
- if '-F' in argv:
- o.sender = argv[argv.index('-F') + 1]
- o.to.remove(o.sender)
-
- # -i and -oi makes us ignore lone "."
- if ('-i' in argv) or ('-oi' in argv):
- raise _unsupportedOption
-
- # -odb is background delivery
- if '-odb' in argv:
- o.background = True
- else:
- o.background = False
-
- # -odf is foreground delivery
- if '-odf' in argv:
- o.background = False
- else:
- o.background = True
-
- # -oem and -em cause errors to be mailed back to the sender.
- # It is also the default.
-
- # -oep and -ep cause errors to be printed to stderr
- if ('-oep' in argv) or ('-ep' in argv):
- o.printErrors = True
- else:
- o.printErrors = False
-
- # -om causes a copy of the message to be sent to the sender if the sender
- # appears in an alias expansion. We do not support aliases.
- if '-om' in argv:
- raise _unsupportedOption
-
- # -t causes us to pick the recipients of the message from the To, Cc, and Bcc
- # headers, and to remove the Bcc header if present.
- if '-t' in argv:
- o.recipientsFromHeaders = True
- o.excludeAddresses = o.to
- o.to = []
- else:
- o.recipientsFromHeaders = False
- o.exludeAddresses = []
-
- requiredHeaders = {
- 'from': [],
- 'to': [],
- 'cc': [],
- 'bcc': [],
- 'date': [],
- }
-
- headers = []
- buffer = StringIO.StringIO()
- while 1:
- write = 1
- line = sys.stdin.readline()
- if not line.strip():
- break
-
- hdrs = line.split(': ', 1)
-
- hdr = hdrs[0].lower()
- if o.recipientsFromHeaders and hdr in ('to', 'cc', 'bcc'):
- o.to.extend([
- a[1] for a in rfc822.AddressList(hdrs[1]).addresslist
- ])
- if hdr == 'bcc':
- write = 0
- elif hdr == 'from':
- o.sender = rfc822.parseaddr(hdrs[1])[1]
-
- if hdr in requiredHeaders:
- requiredHeaders[hdr].append(hdrs[1])
-
- if write:
- buffer.write(line)
-
- if not requiredHeaders['from']:
- buffer.write('From: %s\r\n' % (o.sender,))
- if not requiredHeaders['to']:
- if not o.to:
- raise SystemExit("No recipients specified.")
- buffer.write('To: %s\r\n' % (', '.join(o.to),))
- if not requiredHeaders['date']:
- buffer.write('Date: %s\r\n' % (smtp.rfc822date(),))
-
- buffer.write(line)
-
- if o.recipientsFromHeaders:
- for a in o.excludeAddresses:
- try:
- o.to.remove(a)
- except:
- pass
-
- buffer.seek(0, 0)
- o.body = StringIO.StringIO(buffer.getvalue() + sys.stdin.read())
- return o
-
-class Configuration:
- """
- @ivar allowUIDs: A list of UIDs which are allowed to send mail.
- @ivar allowGIDs: A list of GIDs which are allowed to send mail.
- @ivar denyUIDs: A list of UIDs which are not allowed to send mail.
- @ivar denyGIDs: A list of GIDs which are not allowed to send mail.
-
- @type defaultAccess: C{bool}
- @ivar defaultAccess: C{True} if access will be allowed when no other access
- control rule matches or C{False} if it will be denied in that case.
-
- @ivar useraccess: Either C{'allow'} to check C{allowUID} first
- or C{'deny'} to check C{denyUID} first.
-
- @ivar groupaccess: Either C{'allow'} to check C{allowGID} first or
- C{'deny'} to check C{denyGID} first.
-
- @ivar identities: A C{dict} mapping hostnames to credentials to use when
- sending mail to that host.
-
- @ivar smarthost: C{None} or a hostname through which all outgoing mail will
- be sent.
-
- @ivar domain: C{None} or the hostname with which to identify ourselves when
- connecting to an MTA.
- """
- def __init__(self):
- self.allowUIDs = []
- self.denyUIDs = []
- self.allowGIDs = []
- self.denyGIDs = []
- self.useraccess = 'deny'
- self.groupaccess= 'deny'
-
- self.identities = {}
- self.smarthost = None
- self.domain = None
-
- self.defaultAccess = True
-
-
-def loadConfig(path):
- # [useraccess]
- # allow=uid1,uid2,...
- # deny=uid1,uid2,...
- # order=allow,deny
- # [groupaccess]
- # allow=gid1,gid2,...
- # deny=gid1,gid2,...
- # order=deny,allow
- # [identity]
- # host1=username:password
- # host2=username:password
- # [addresses]
- # smarthost=a.b.c.d
- # default_domain=x.y.z
-
- c = Configuration()
-
- if not os.access(path, os.R_OK):
- return c
-
- p = ConfigParser()
- p.read(path)
-
- au = c.allowUIDs
- du = c.denyUIDs
- ag = c.allowGIDs
- dg = c.denyGIDs
- for (section, a, d) in (('useraccess', au, du), ('groupaccess', ag, dg)):
- if p.has_section(section):
- for (mode, L) in (('allow', a), ('deny', d)):
- if p.has_option(section, mode) and p.get(section, mode):
- for id in p.get(section, mode).split(','):
- try:
- id = int(id)
- except ValueError:
- log("Illegal %sID in [%s] section: %s", section[0].upper(), section, id)
- else:
- L.append(id)
- order = p.get(section, 'order')
- order = map(str.split, map(str.lower, order.split(',')))
- if order[0] == 'allow':
- setattr(c, section, 'allow')
- else:
- setattr(c, section, 'deny')
-
- if p.has_section('identity'):
- for (host, up) in p.items('identity'):
- parts = up.split(':', 1)
- if len(parts) != 2:
- log("Illegal entry in [identity] section: %s", up)
- continue
- p.identities[host] = parts
-
- if p.has_section('addresses'):
- if p.has_option('addresses', 'smarthost'):
- c.smarthost = p.get('addresses', 'smarthost')
- if p.has_option('addresses', 'default_domain'):
- c.domain = p.get('addresses', 'default_domain')
-
- return c
-
-def success(result):
- reactor.stop()
-
-failed = None
-def failure(f):
- global failed
- reactor.stop()
- failed = f
-
-def sendmail(host, options, ident):
- d = smtp.sendmail(host, options.sender, options.to, options.body)
- d.addCallbacks(success, failure)
- reactor.run()
-
-def senderror(failure, options):
- recipient = [options.sender]
- sender = '"Internally Generated Message (%s)"<postmaster@%s>' % (sys.argv[0], smtp.DNSNAME)
- error = StringIO.StringIO()
- failure.printTraceback(file=error)
- body = StringIO.StringIO(ERROR_FMT % error.getvalue())
-
- d = smtp.sendmail('localhost', sender, recipient, body)
- d.addBoth(lambda _: reactor.stop())
-
-def deny(conf):
- uid = os.getuid()
- gid = os.getgid()
-
- if conf.useraccess == 'deny':
- if uid in conf.denyUIDs:
- return True
- if uid in conf.allowUIDs:
- return False
- else:
- if uid in conf.allowUIDs:
- return False
- if uid in conf.denyUIDs:
- return True
-
- if conf.groupaccess == 'deny':
- if gid in conf.denyGIDs:
- return True
- if gid in conf.allowGIDs:
- return False
- else:
- if gid in conf.allowGIDs:
- return False
- if gid in conf.denyGIDs:
- return True
-
- return not conf.defaultAccess
-
-def run():
- o = parseOptions(sys.argv[1:])
- gConf = loadConfig(GLOBAL_CFG)
- lConf = loadConfig(LOCAL_CFG)
-
- if deny(gConf) or deny(lConf):
- log("Permission denied")
- return
-
- host = lConf.smarthost or gConf.smarthost or SMARTHOST
-
- ident = gConf.identities.copy()
- ident.update(lConf.identities)
-
- if lConf.domain:
- smtp.DNSNAME = lConf.domain
- elif gConf.domain:
- smtp.DNSNAME = gConf.domain
-
- sendmail(host, o, ident)
-
- if failed:
- if o.printErrors:
- failed.printTraceback(file=sys.stderr)
- raise SystemExit(1)
- else:
- senderror(failed, o)
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/smtp.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/smtp.py
deleted file mode 100755
index 3b8bd0a4..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/smtp.py
+++ /dev/null
@@ -1,1934 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_smtp -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Simple Mail Transfer Protocol implementation.
-"""
-
-import time, re, base64, types, socket, os, random, rfc822
-import binascii
-from email.base64MIME import encode as encode_base64
-
-from zope.interface import implements, Interface
-
-from twisted.copyright import longversion
-from twisted.protocols import basic
-from twisted.protocols import policies
-from twisted.internet import protocol
-from twisted.internet import defer
-from twisted.internet import error
-from twisted.internet import reactor
-from twisted.internet.interfaces import ITLSTransport
-from twisted.python import log
-from twisted.python import util
-
-from twisted import cred
-from twisted.python.runtime import platform
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-# Cache the hostname (XXX Yes - this is broken)
-if platform.isMacOSX():
- # On OS X, getfqdn() is ridiculously slow - use the
- # probably-identical-but-sometimes-not gethostname() there.
- DNSNAME = socket.gethostname()
-else:
- DNSNAME = socket.getfqdn()
-
-# Used for fast success code lookup
-SUCCESS = dict.fromkeys(xrange(200,300))
-
-class IMessageDelivery(Interface):
- def receivedHeader(helo, origin, recipients):
- """
- Generate the Received header for a message
-
- @type helo: C{(str, str)}
- @param helo: The argument to the HELO command and the client's IP
- address.
-
- @type origin: C{Address}
- @param origin: The address the message is from
-
- @type recipients: C{list} of L{User}
- @param recipients: A list of the addresses for which this message
- is bound.
-
- @rtype: C{str}
- @return: The full \"Received\" header string.
- """
-
- def validateTo(user):
- """
- Validate the address for which the message is destined.
-
- @type user: C{User}
- @param user: The address to validate.
-
- @rtype: no-argument callable
- @return: A C{Deferred} which becomes, or a callable which
- takes no arguments and returns an object implementing C{IMessage}.
- This will be called and the returned object used to deliver the
- message when it arrives.
-
- @raise SMTPBadRcpt: Raised if messages to the address are
- not to be accepted.
- """
-
- def validateFrom(helo, origin):
- """
- Validate the address from which the message originates.
-
- @type helo: C{(str, str)}
- @param helo: The argument to the HELO command and the client's IP
- address.
-
- @type origin: C{Address}
- @param origin: The address the message is from
-
- @rtype: C{Deferred} or C{Address}
- @return: C{origin} or a C{Deferred} whose callback will be
- passed C{origin}.
-
- @raise SMTPBadSender: Raised of messages from this address are
- not to be accepted.
- """
-
-class IMessageDeliveryFactory(Interface):
- """An alternate interface to implement for handling message delivery.
-
- It is useful to implement this interface instead of L{IMessageDelivery}
- directly because it allows the implementor to distinguish between
- different messages delivery over the same connection. This can be
- used to optimize delivery of a single message to multiple recipients,
- something which cannot be done by L{IMessageDelivery} implementors
- due to their lack of information.
- """
- def getMessageDelivery():
- """Return an L{IMessageDelivery} object.
-
- This will be called once per message.
- """
-
-class SMTPError(Exception):
- pass
-
-
-
-class SMTPClientError(SMTPError):
- """Base class for SMTP client errors.
- """
- def __init__(self, code, resp, log=None, addresses=None, isFatal=False, retry=False):
- """
- @param code: The SMTP response code associated with this error.
- @param resp: The string response associated with this error.
-
- @param log: A string log of the exchange leading up to and including
- the error.
- @type log: L{str}
-
- @param isFatal: A boolean indicating whether this connection can
- proceed or not. If True, the connection will be dropped.
-
- @param retry: A boolean indicating whether the delivery should be
- retried. If True and the factory indicates further retries are
- desirable, they will be attempted, otherwise the delivery will
- be failed.
- """
- self.code = code
- self.resp = resp
- self.log = log
- self.addresses = addresses
- self.isFatal = isFatal
- self.retry = retry
-
-
- def __str__(self):
- if self.code > 0:
- res = ["%.3d %s" % (self.code, self.resp)]
- else:
- res = [self.resp]
- if self.log:
- res.append(self.log)
- res.append('')
- return '\n'.join(res)
-
-
-class ESMTPClientError(SMTPClientError):
- """Base class for ESMTP client errors.
- """
-
-class EHLORequiredError(ESMTPClientError):
- """The server does not support EHLO.
-
- This is considered a non-fatal error (the connection will not be
- dropped).
- """
-
-class AUTHRequiredError(ESMTPClientError):
- """Authentication was required but the server does not support it.
-
- This is considered a non-fatal error (the connection will not be
- dropped).
- """
-
-class TLSRequiredError(ESMTPClientError):
- """Transport security was required but the server does not support it.
-
- This is considered a non-fatal error (the connection will not be
- dropped).
- """
-
-class AUTHDeclinedError(ESMTPClientError):
- """The server rejected our credentials.
-
- Either the username, password, or challenge response
- given to the server was rejected.
-
- This is considered a non-fatal error (the connection will not be
- dropped).
- """
-
-class AuthenticationError(ESMTPClientError):
- """An error ocurred while authenticating.
-
- Either the server rejected our request for authentication or the
- challenge received was malformed.
-
- This is considered a non-fatal error (the connection will not be
- dropped).
- """
-
-class TLSError(ESMTPClientError):
- """An error occurred while negiotiating for transport security.
-
- This is considered a non-fatal error (the connection will not be
- dropped).
- """
-
-class SMTPConnectError(SMTPClientError):
- """Failed to connect to the mail exchange host.
-
- This is considered a fatal error. A retry will be made.
- """
- def __init__(self, code, resp, log=None, addresses=None, isFatal=True, retry=True):
- SMTPClientError.__init__(self, code, resp, log, addresses, isFatal, retry)
-
-class SMTPTimeoutError(SMTPClientError):
- """Failed to receive a response from the server in the expected time period.
-
- This is considered a fatal error. A retry will be made.
- """
- def __init__(self, code, resp, log=None, addresses=None, isFatal=True, retry=True):
- SMTPClientError.__init__(self, code, resp, log, addresses, isFatal, retry)
-
-class SMTPProtocolError(SMTPClientError):
- """The server sent a mangled response.
-
- This is considered a fatal error. A retry will not be made.
- """
- def __init__(self, code, resp, log=None, addresses=None, isFatal=True, retry=False):
- SMTPClientError.__init__(self, code, resp, log, addresses, isFatal, retry)
-
-class SMTPDeliveryError(SMTPClientError):
- """Indicates that a delivery attempt has had an error.
- """
-
-class SMTPServerError(SMTPError):
- def __init__(self, code, resp):
- self.code = code
- self.resp = resp
-
- def __str__(self):
- return "%.3d %s" % (self.code, self.resp)
-
-class SMTPAddressError(SMTPServerError):
- def __init__(self, addr, code, resp):
- SMTPServerError.__init__(self, code, resp)
- self.addr = Address(addr)
-
- def __str__(self):
- return "%.3d <%s>... %s" % (self.code, self.addr, self.resp)
-
-class SMTPBadRcpt(SMTPAddressError):
- def __init__(self, addr, code=550,
- resp='Cannot receive for specified address'):
- SMTPAddressError.__init__(self, addr, code, resp)
-
-class SMTPBadSender(SMTPAddressError):
- def __init__(self, addr, code=550, resp='Sender not acceptable'):
- SMTPAddressError.__init__(self, addr, code, resp)
-
-def rfc822date(timeinfo=None,local=1):
- """
- Format an RFC-2822 compliant date string.
-
- @param timeinfo: (optional) A sequence as returned by C{time.localtime()}
- or C{time.gmtime()}. Default is now.
- @param local: (optional) Indicates if the supplied time is local or
- universal time, or if no time is given, whether now should be local or
- universal time. Default is local, as suggested (SHOULD) by rfc-2822.
-
- @returns: A string representing the time and date in RFC-2822 format.
- """
- if not timeinfo:
- if local:
- timeinfo = time.localtime()
- else:
- timeinfo = time.gmtime()
- if local:
- if timeinfo[8]:
- # DST
- tz = -time.altzone
- else:
- tz = -time.timezone
-
- (tzhr, tzmin) = divmod(abs(tz), 3600)
- if tz:
- tzhr *= int(abs(tz)//tz)
- (tzmin, tzsec) = divmod(tzmin, 60)
- else:
- (tzhr, tzmin) = (0,0)
-
- return "%s, %02d %s %04d %02d:%02d:%02d %+03d%02d" % (
- ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][timeinfo[6]],
- timeinfo[2],
- ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
- 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][timeinfo[1] - 1],
- timeinfo[0], timeinfo[3], timeinfo[4], timeinfo[5],
- tzhr, tzmin)
-
-def idGenerator():
- i = 0
- while True:
- yield i
- i += 1
-
-def messageid(uniq=None, N=idGenerator().next):
- """Return a globally unique random string in RFC 2822 Message-ID format
-
- <datetime.pid.random@host.dom.ain>
-
- Optional uniq string will be added to strenghten uniqueness if given.
- """
- datetime = time.strftime('%Y%m%d%H%M%S', time.gmtime())
- pid = os.getpid()
- rand = random.randrange(2**31L-1)
- if uniq is None:
- uniq = ''
- else:
- uniq = '.' + uniq
-
- return '<%s.%s.%s%s.%s@%s>' % (datetime, pid, rand, uniq, N(), DNSNAME)
-
-def quoteaddr(addr):
- """Turn an email address, possibly with realname part etc, into
- a form suitable for and SMTP envelope.
- """
-
- if isinstance(addr, Address):
- return '<%s>' % str(addr)
-
- res = rfc822.parseaddr(addr)
-
- if res == (None, None):
- # It didn't parse, use it as-is
- return '<%s>' % str(addr)
- else:
- return '<%s>' % str(res[1])
-
-COMMAND, DATA, AUTH = 'COMMAND', 'DATA', 'AUTH'
-
-class AddressError(SMTPError):
- "Parse error in address"
-
-# Character classes for parsing addresses
-atom = r"[-A-Za-z0-9!\#$%&'*+/=?^_`{|}~]"
-
-class Address:
- """Parse and hold an RFC 2821 address.
-
- Source routes are stipped and ignored, UUCP-style bang-paths
- and %-style routing are not parsed.
-
- @type domain: C{str}
- @ivar domain: The domain within which this address resides.
-
- @type local: C{str}
- @ivar local: The local (\"user\") portion of this address.
- """
-
- tstring = re.compile(r'''( # A string of
- (?:"[^"]*" # quoted string
- |\\. # backslash-escaped characted
- |''' + atom + r''' # atom character
- )+|.) # or any single character''',re.X)
- atomre = re.compile(atom) # match any one atom character
-
- def __init__(self, addr, defaultDomain=None):
- if isinstance(addr, User):
- addr = addr.dest
- if isinstance(addr, Address):
- self.__dict__ = addr.__dict__.copy()
- return
- elif not isinstance(addr, types.StringTypes):
- addr = str(addr)
- self.addrstr = addr
-
- # Tokenize
- atl = filter(None,self.tstring.split(addr))
-
- local = []
- domain = []
-
- while atl:
- if atl[0] == '<':
- if atl[-1] != '>':
- raise AddressError, "Unbalanced <>"
- atl = atl[1:-1]
- elif atl[0] == '@':
- atl = atl[1:]
- if not local:
- # Source route
- while atl and atl[0] != ':':
- # remove it
- atl = atl[1:]
- if not atl:
- raise AddressError, "Malformed source route"
- atl = atl[1:] # remove :
- elif domain:
- raise AddressError, "Too many @"
- else:
- # Now in domain
- domain = ['']
- elif len(atl[0]) == 1 and not self.atomre.match(atl[0]) and atl[0] != '.':
- raise AddressError, "Parse error at %r of %r" % (atl[0], (addr, atl))
- else:
- if not domain:
- local.append(atl[0])
- else:
- domain.append(atl[0])
- atl = atl[1:]
-
- self.local = ''.join(local)
- self.domain = ''.join(domain)
- if self.local != '' and self.domain == '':
- if defaultDomain is None:
- defaultDomain = DNSNAME
- self.domain = defaultDomain
-
- dequotebs = re.compile(r'\\(.)')
-
- def dequote(self,addr):
- """Remove RFC-2821 quotes from address."""
- res = []
-
- atl = filter(None,self.tstring.split(str(addr)))
-
- for t in atl:
- if t[0] == '"' and t[-1] == '"':
- res.append(t[1:-1])
- elif '\\' in t:
- res.append(self.dequotebs.sub(r'\1',t))
- else:
- res.append(t)
-
- return ''.join(res)
-
- def __str__(self):
- if self.local or self.domain:
- return '@'.join((self.local, self.domain))
- else:
- return ''
-
- def __repr__(self):
- return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
- repr(str(self)))
-
-class User:
- """Hold information about and SMTP message recipient,
- including information on where the message came from
- """
-
- def __init__(self, destination, helo, protocol, orig):
- host = getattr(protocol, 'host', None)
- self.dest = Address(destination, host)
- self.helo = helo
- self.protocol = protocol
- if isinstance(orig, Address):
- self.orig = orig
- else:
- self.orig = Address(orig, host)
-
- def __getstate__(self):
- """Helper for pickle.
-
- protocol isn't picklabe, but we want User to be, so skip it in
- the pickle.
- """
- return { 'dest' : self.dest,
- 'helo' : self.helo,
- 'protocol' : None,
- 'orig' : self.orig }
-
- def __str__(self):
- return str(self.dest)
-
-class IMessage(Interface):
- """Interface definition for messages that can be sent via SMTP."""
-
- def lineReceived(line):
- """handle another line"""
-
- def eomReceived():
- """handle end of message
-
- return a deferred. The deferred should be called with either:
- callback(string) or errback(error)
- """
-
- def connectionLost():
- """handle message truncated
-
- semantics should be to discard the message
- """
-
-class SMTP(basic.LineOnlyReceiver, policies.TimeoutMixin):
- """SMTP server-side protocol."""
-
- timeout = 600
- host = DNSNAME
- portal = None
-
- # Control whether we log SMTP events
- noisy = True
-
- # A factory for IMessageDelivery objects. If an
- # avatar implementing IMessageDeliveryFactory can
- # be acquired from the portal, it will be used to
- # create a new IMessageDelivery object for each
- # message which is received.
- deliveryFactory = None
-
- # An IMessageDelivery object. A new instance is
- # used for each message received if we can get an
- # IMessageDeliveryFactory from the portal. Otherwise,
- # a single instance is used throughout the lifetime
- # of the connection.
- delivery = None
-
- # Cred cleanup function.
- _onLogout = None
-
- def __init__(self, delivery=None, deliveryFactory=None):
- self.mode = COMMAND
- self._from = None
- self._helo = None
- self._to = []
- self.delivery = delivery
- self.deliveryFactory = deliveryFactory
-
- def timeoutConnection(self):
- msg = '%s Timeout. Try talking faster next time!' % (self.host,)
- self.sendCode(421, msg)
- self.transport.loseConnection()
-
- def greeting(self):
- return '%s NO UCE NO UBE NO RELAY PROBES' % (self.host,)
-
- def connectionMade(self):
- # Ensure user-code always gets something sane for _helo
- peer = self.transport.getPeer()
- try:
- host = peer.host
- except AttributeError: # not an IPv4Address
- host = str(peer)
- self._helo = (None, host)
- self.sendCode(220, self.greeting())
- self.setTimeout(self.timeout)
-
- def sendCode(self, code, message=''):
- "Send an SMTP code with a message."
- lines = message.splitlines()
- lastline = lines[-1:]
- for line in lines[:-1]:
- self.sendLine('%3.3d-%s' % (code, line))
- self.sendLine('%3.3d %s' % (code,
- lastline and lastline[0] or ''))
-
- def lineReceived(self, line):
- self.resetTimeout()
- return getattr(self, 'state_' + self.mode)(line)
-
- def state_COMMAND(self, line):
- # Ignore leading and trailing whitespace, as well as an arbitrary
- # amount of whitespace between the command and its argument, though
- # it is not required by the protocol, for it is a nice thing to do.
- line = line.strip()
-
- parts = line.split(None, 1)
- if parts:
- method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
- if len(parts) == 2:
- method(parts[1])
- else:
- method('')
- else:
- self.sendSyntaxError()
-
- def sendSyntaxError(self):
- self.sendCode(500, 'Error: bad syntax')
-
- def lookupMethod(self, command):
- return getattr(self, 'do_' + command.upper(), None)
-
- def lineLengthExceeded(self, line):
- if self.mode is DATA:
- for message in self.__messages:
- message.connectionLost()
- self.mode = COMMAND
- del self.__messages
- self.sendCode(500, 'Line too long')
-
- def do_UNKNOWN(self, rest):
- self.sendCode(500, 'Command not implemented')
-
- def do_HELO(self, rest):
- peer = self.transport.getPeer()
- try:
- host = peer.host
- except AttributeError:
- host = str(peer)
- self._helo = (rest, host)
- self._from = None
- self._to = []
- self.sendCode(250, '%s Hello %s, nice to meet you' % (self.host, host))
-
- def do_QUIT(self, rest):
- self.sendCode(221, 'See you later')
- self.transport.loseConnection()
-
- # A string of quoted strings, backslash-escaped character or
- # atom characters + '@.,:'
- qstring = r'("[^"]*"|\\.|' + atom + r'|[@.,:])+'
-
- mail_re = re.compile(r'''\s*FROM:\s*(?P<path><> # Empty <>
- |<''' + qstring + r'''> # <addr>
- |''' + qstring + r''' # addr
- )\s*(\s(?P<opts>.*))? # Optional WS + ESMTP options
- $''',re.I|re.X)
- rcpt_re = re.compile(r'\s*TO:\s*(?P<path><' + qstring + r'''> # <addr>
- |''' + qstring + r''' # addr
- )\s*(\s(?P<opts>.*))? # Optional WS + ESMTP options
- $''',re.I|re.X)
-
- def do_MAIL(self, rest):
- if self._from:
- self.sendCode(503,"Only one sender per message, please")
- return
- # Clear old recipient list
- self._to = []
- m = self.mail_re.match(rest)
- if not m:
- self.sendCode(501, "Syntax error")
- return
-
- try:
- addr = Address(m.group('path'), self.host)
- except AddressError, e:
- self.sendCode(553, str(e))
- return
-
- validated = defer.maybeDeferred(self.validateFrom, self._helo, addr)
- validated.addCallbacks(self._cbFromValidate, self._ebFromValidate)
-
-
- def _cbFromValidate(self, from_, code=250, msg='Sender address accepted'):
- self._from = from_
- self.sendCode(code, msg)
-
-
- def _ebFromValidate(self, failure):
- if failure.check(SMTPBadSender):
- self.sendCode(failure.value.code,
- 'Cannot receive from specified address %s: %s'
- % (quoteaddr(failure.value.addr), failure.value.resp))
- elif failure.check(SMTPServerError):
- self.sendCode(failure.value.code, failure.value.resp)
- else:
- log.err(failure, "SMTP sender validation failure")
- self.sendCode(
- 451,
- 'Requested action aborted: local error in processing')
-
-
- def do_RCPT(self, rest):
- if not self._from:
- self.sendCode(503, "Must have sender before recipient")
- return
- m = self.rcpt_re.match(rest)
- if not m:
- self.sendCode(501, "Syntax error")
- return
-
- try:
- user = User(m.group('path'), self._helo, self, self._from)
- except AddressError, e:
- self.sendCode(553, str(e))
- return
-
- d = defer.maybeDeferred(self.validateTo, user)
- d.addCallbacks(
- self._cbToValidate,
- self._ebToValidate,
- callbackArgs=(user,)
- )
-
- def _cbToValidate(self, to, user=None, code=250, msg='Recipient address accepted'):
- if user is None:
- user = to
- self._to.append((user, to))
- self.sendCode(code, msg)
-
- def _ebToValidate(self, failure):
- if failure.check(SMTPBadRcpt, SMTPServerError):
- self.sendCode(failure.value.code, failure.value.resp)
- else:
- log.err(failure)
- self.sendCode(
- 451,
- 'Requested action aborted: local error in processing'
- )
-
- def _disconnect(self, msgs):
- for msg in msgs:
- try:
- msg.connectionLost()
- except:
- log.msg("msg raised exception from connectionLost")
- log.err()
-
- def do_DATA(self, rest):
- if self._from is None or (not self._to):
- self.sendCode(503, 'Must have valid receiver and originator')
- return
- self.mode = DATA
- helo, origin = self._helo, self._from
- recipients = self._to
-
- self._from = None
- self._to = []
- self.datafailed = None
-
- msgs = []
- for (user, msgFunc) in recipients:
- try:
- msg = msgFunc()
- rcvdhdr = self.receivedHeader(helo, origin, [user])
- if rcvdhdr:
- msg.lineReceived(rcvdhdr)
- msgs.append(msg)
- except SMTPServerError, e:
- self.sendCode(e.code, e.resp)
- self.mode = COMMAND
- self._disconnect(msgs)
- return
- except:
- log.err()
- self.sendCode(550, "Internal server error")
- self.mode = COMMAND
- self._disconnect(msgs)
- return
- self.__messages = msgs
-
- self.__inheader = self.__inbody = 0
- self.sendCode(354, 'Continue')
-
- if self.noisy:
- fmt = 'Receiving message for delivery: from=%s to=%s'
- log.msg(fmt % (origin, [str(u) for (u, f) in recipients]))
-
- def connectionLost(self, reason):
- # self.sendCode(421, 'Dropping connection.') # This does nothing...
- # Ideally, if we (rather than the other side) lose the connection,
- # we should be able to tell the other side that we are going away.
- # RFC-2821 requires that we try.
- if self.mode is DATA:
- try:
- for message in self.__messages:
- try:
- message.connectionLost()
- except:
- log.err()
- del self.__messages
- except AttributeError:
- pass
- if self._onLogout:
- self._onLogout()
- self._onLogout = None
- self.setTimeout(None)
-
- def do_RSET(self, rest):
- self._from = None
- self._to = []
- self.sendCode(250, 'I remember nothing.')
-
- def dataLineReceived(self, line):
- if line[:1] == '.':
- if line == '.':
- self.mode = COMMAND
- if self.datafailed:
- self.sendCode(self.datafailed.code,
- self.datafailed.resp)
- return
- if not self.__messages:
- self._messageHandled("thrown away")
- return
- defer.DeferredList([
- m.eomReceived() for m in self.__messages
- ], consumeErrors=True).addCallback(self._messageHandled
- )
- del self.__messages
- return
- line = line[1:]
-
- if self.datafailed:
- return
-
- try:
- # Add a blank line between the generated Received:-header
- # and the message body if the message comes in without any
- # headers
- if not self.__inheader and not self.__inbody:
- if ':' in line:
- self.__inheader = 1
- elif line:
- for message in self.__messages:
- message.lineReceived('')
- self.__inbody = 1
-
- if not line:
- self.__inbody = 1
-
- for message in self.__messages:
- message.lineReceived(line)
- except SMTPServerError, e:
- self.datafailed = e
- for message in self.__messages:
- message.connectionLost()
- state_DATA = dataLineReceived
-
- def _messageHandled(self, resultList):
- failures = 0
- for (success, result) in resultList:
- if not success:
- failures += 1
- log.err(result)
- if failures:
- msg = 'Could not send e-mail'
- L = len(resultList)
- if L > 1:
- msg += ' (%d failures out of %d recipients)' % (failures, L)
- self.sendCode(550, msg)
- else:
- self.sendCode(250, 'Delivery in progress')
-
-
- def _cbAnonymousAuthentication(self, (iface, avatar, logout)):
- """
- Save the state resulting from a successful anonymous cred login.
- """
- if issubclass(iface, IMessageDeliveryFactory):
- self.deliveryFactory = avatar
- self.delivery = None
- elif issubclass(iface, IMessageDelivery):
- self.deliveryFactory = None
- self.delivery = avatar
- else:
- raise RuntimeError("%s is not a supported interface" % (iface.__name__,))
- self._onLogout = logout
- self.challenger = None
-
-
- # overridable methods:
- def validateFrom(self, helo, origin):
- """
- Validate the address from which the message originates.
-
- @type helo: C{(str, str)}
- @param helo: The argument to the HELO command and the client's IP
- address.
-
- @type origin: C{Address}
- @param origin: The address the message is from
-
- @rtype: C{Deferred} or C{Address}
- @return: C{origin} or a C{Deferred} whose callback will be
- passed C{origin}.
-
- @raise SMTPBadSender: Raised of messages from this address are
- not to be accepted.
- """
- if self.deliveryFactory is not None:
- self.delivery = self.deliveryFactory.getMessageDelivery()
-
- if self.delivery is not None:
- return defer.maybeDeferred(self.delivery.validateFrom,
- helo, origin)
-
- # No login has been performed, no default delivery object has been
- # provided: try to perform an anonymous login and then invoke this
- # method again.
- if self.portal:
-
- result = self.portal.login(
- cred.credentials.Anonymous(),
- None,
- IMessageDeliveryFactory, IMessageDelivery)
-
- def ebAuthentication(err):
- """
- Translate cred exceptions into SMTP exceptions so that the
- protocol code which invokes C{validateFrom} can properly report
- the failure.
- """
- if err.check(cred.error.UnauthorizedLogin):
- exc = SMTPBadSender(origin)
- elif err.check(cred.error.UnhandledCredentials):
- exc = SMTPBadSender(
- origin, resp="Unauthenticated senders not allowed")
- else:
- return err
- return defer.fail(exc)
-
- result.addCallbacks(
- self._cbAnonymousAuthentication, ebAuthentication)
-
- def continueValidation(ignored):
- """
- Re-attempt from address validation.
- """
- return self.validateFrom(helo, origin)
-
- result.addCallback(continueValidation)
- return result
-
- raise SMTPBadSender(origin)
-
-
- def validateTo(self, user):
- """
- Validate the address for which the message is destined.
-
- @type user: C{User}
- @param user: The address to validate.
-
- @rtype: no-argument callable
- @return: A C{Deferred} which becomes, or a callable which
- takes no arguments and returns an object implementing C{IMessage}.
- This will be called and the returned object used to deliver the
- message when it arrives.
-
- @raise SMTPBadRcpt: Raised if messages to the address are
- not to be accepted.
- """
- if self.delivery is not None:
- return self.delivery.validateTo(user)
- raise SMTPBadRcpt(user)
-
- def receivedHeader(self, helo, origin, recipients):
- if self.delivery is not None:
- return self.delivery.receivedHeader(helo, origin, recipients)
-
- heloStr = ""
- if helo[0]:
- heloStr = " helo=%s" % (helo[0],)
- domain = self.transport.getHost().host
- from_ = "from %s ([%s]%s)" % (helo[0], helo[1], heloStr)
- by = "by %s with %s (%s)" % (domain,
- self.__class__.__name__,
- longversion)
- for_ = "for %s; %s" % (' '.join(map(str, recipients)),
- rfc822date())
- return "Received: %s\n\t%s\n\t%s" % (from_, by, for_)
-
- def startMessage(self, recipients):
- if self.delivery:
- return self.delivery.startMessage(recipients)
- return []
-
-
-class SMTPFactory(protocol.ServerFactory):
- """Factory for SMTP."""
-
- # override in instances or subclasses
- domain = DNSNAME
- timeout = 600
- protocol = SMTP
-
- portal = None
-
- def __init__(self, portal = None):
- self.portal = portal
-
- def buildProtocol(self, addr):
- p = protocol.ServerFactory.buildProtocol(self, addr)
- p.portal = self.portal
- p.host = self.domain
- return p
-
-class SMTPClient(basic.LineReceiver, policies.TimeoutMixin):
- """
- SMTP client for sending emails.
-
- After the client has connected to the SMTP server, it repeatedly calls
- L{SMTPClient.getMailFrom}, L{SMTPClient.getMailTo} and
- L{SMTPClient.getMailData} and uses this information to send an email.
- It then calls L{SMTPClient.getMailFrom} again; if it returns C{None}, the
- client will disconnect, otherwise it will continue as normal i.e. call
- L{SMTPClient.getMailTo} and L{SMTPClient.getMailData} and send a new email.
- """
-
- # If enabled then log SMTP client server communication
- debug = True
-
- # Number of seconds to wait before timing out a connection. If
- # None, perform no timeout checking.
- timeout = None
-
- def __init__(self, identity, logsize=10):
- self.identity = identity or ''
- self.toAddressesResult = []
- self.successAddresses = []
- self._from = None
- self.resp = []
- self.code = -1
- self.log = util.LineLog(logsize)
-
- def sendLine(self, line):
- # Log sendLine only if you are in debug mode for performance
- if self.debug:
- self.log.append('>>> ' + line)
-
- basic.LineReceiver.sendLine(self,line)
-
- def connectionMade(self):
- self.setTimeout(self.timeout)
-
- self._expected = [ 220 ]
- self._okresponse = self.smtpState_helo
- self._failresponse = self.smtpConnectionFailed
-
- def connectionLost(self, reason=protocol.connectionDone):
- """We are no longer connected"""
- self.setTimeout(None)
- self.mailFile = None
-
- def timeoutConnection(self):
- self.sendError(
- SMTPTimeoutError(
- -1, "Timeout waiting for SMTP server response",
- self.log.str()))
-
- def lineReceived(self, line):
- self.resetTimeout()
-
- # Log lineReceived only if you are in debug mode for performance
- if self.debug:
- self.log.append('<<< ' + line)
-
- why = None
-
- try:
- self.code = int(line[:3])
- except ValueError:
- # This is a fatal error and will disconnect the transport lineReceived will not be called again
- self.sendError(SMTPProtocolError(-1, "Invalid response from SMTP server: %s" % line, self.log.str()))
- return
-
- if line[0] == '0':
- # Verbose informational message, ignore it
- return
-
- self.resp.append(line[4:])
-
- if line[3:4] == '-':
- # continuation
- return
-
- if self.code in self._expected:
- why = self._okresponse(self.code,'\n'.join(self.resp))
- else:
- why = self._failresponse(self.code,'\n'.join(self.resp))
-
- self.code = -1
- self.resp = []
- return why
-
- def smtpConnectionFailed(self, code, resp):
- self.sendError(SMTPConnectError(code, resp, self.log.str()))
-
- def smtpTransferFailed(self, code, resp):
- if code < 0:
- self.sendError(SMTPProtocolError(code, resp, self.log.str()))
- else:
- self.smtpState_msgSent(code, resp)
-
- def smtpState_helo(self, code, resp):
- self.sendLine('HELO ' + self.identity)
- self._expected = SUCCESS
- self._okresponse = self.smtpState_from
-
- def smtpState_from(self, code, resp):
- self._from = self.getMailFrom()
- self._failresponse = self.smtpTransferFailed
- if self._from is not None:
- self.sendLine('MAIL FROM:%s' % quoteaddr(self._from))
- self._expected = [250]
- self._okresponse = self.smtpState_to
- else:
- # All messages have been sent, disconnect
- self._disconnectFromServer()
-
- def smtpState_disconnect(self, code, resp):
- self.transport.loseConnection()
-
- def smtpState_to(self, code, resp):
- self.toAddresses = iter(self.getMailTo())
- self.toAddressesResult = []
- self.successAddresses = []
- self._okresponse = self.smtpState_toOrData
- self._expected = xrange(0,1000)
- self.lastAddress = None
- return self.smtpState_toOrData(0, '')
-
- def smtpState_toOrData(self, code, resp):
- if self.lastAddress is not None:
- self.toAddressesResult.append((self.lastAddress, code, resp))
- if code in SUCCESS:
- self.successAddresses.append(self.lastAddress)
- try:
- self.lastAddress = self.toAddresses.next()
- except StopIteration:
- if self.successAddresses:
- self.sendLine('DATA')
- self._expected = [ 354 ]
- self._okresponse = self.smtpState_data
- else:
- return self.smtpState_msgSent(code,'No recipients accepted')
- else:
- self.sendLine('RCPT TO:%s' % quoteaddr(self.lastAddress))
-
- def smtpState_data(self, code, resp):
- s = basic.FileSender()
- d = s.beginFileTransfer(
- self.getMailData(), self.transport, self.transformChunk)
- def ebTransfer(err):
- self.sendError(err.value)
- d.addCallbacks(self.finishedFileTransfer, ebTransfer)
- self._expected = SUCCESS
- self._okresponse = self.smtpState_msgSent
-
-
- def smtpState_msgSent(self, code, resp):
- if self._from is not None:
- self.sentMail(code, resp, len(self.successAddresses),
- self.toAddressesResult, self.log)
-
- self.toAddressesResult = []
- self._from = None
- self.sendLine('RSET')
- self._expected = SUCCESS
- self._okresponse = self.smtpState_from
-
- ##
- ## Helpers for FileSender
- ##
- def transformChunk(self, chunk):
- """
- Perform the necessary local to network newline conversion and escape
- leading periods.
-
- This method also resets the idle timeout so that as long as process is
- being made sending the message body, the client will not time out.
- """
- self.resetTimeout()
- return chunk.replace('\n', '\r\n').replace('\r\n.', '\r\n..')
-
- def finishedFileTransfer(self, lastsent):
- if lastsent != '\n':
- line = '\r\n.'
- else:
- line = '.'
- self.sendLine(line)
-
- ##
- # these methods should be overriden in subclasses
- def getMailFrom(self):
- """Return the email address the mail is from."""
- raise NotImplementedError
-
- def getMailTo(self):
- """Return a list of emails to send to."""
- raise NotImplementedError
-
- def getMailData(self):
- """Return file-like object containing data of message to be sent.
-
- Lines in the file should be delimited by '\\n'.
- """
- raise NotImplementedError
-
- def sendError(self, exc):
- """
- If an error occurs before a mail message is sent sendError will be
- called. This base class method sends a QUIT if the error is
- non-fatal and disconnects the connection.
-
- @param exc: The SMTPClientError (or child class) raised
- @type exc: C{SMTPClientError}
- """
- if isinstance(exc, SMTPClientError) and not exc.isFatal:
- self._disconnectFromServer()
- else:
- # If the error was fatal then the communication channel with the
- # SMTP Server is broken so just close the transport connection
- self.smtpState_disconnect(-1, None)
-
-
- def sentMail(self, code, resp, numOk, addresses, log):
- """Called when an attempt to send an email is completed.
-
- If some addresses were accepted, code and resp are the response
- to the DATA command. If no addresses were accepted, code is -1
- and resp is an informative message.
-
- @param code: the code returned by the SMTP Server
- @param resp: The string response returned from the SMTP Server
- @param numOK: the number of addresses accepted by the remote host.
- @param addresses: is a list of tuples (address, code, resp) listing
- the response to each RCPT command.
- @param log: is the SMTP session log
- """
- raise NotImplementedError
-
- def _disconnectFromServer(self):
- self._expected = xrange(0, 1000)
- self._okresponse = self.smtpState_disconnect
- self.sendLine('QUIT')
-
-
-
-class ESMTPClient(SMTPClient):
- # Fall back to HELO if the server does not support EHLO
- heloFallback = True
-
- # Refuse to proceed if authentication cannot be performed
- requireAuthentication = False
-
- # Refuse to proceed if TLS is not available
- requireTransportSecurity = False
-
- # Indicate whether or not our transport can be considered secure.
- tlsMode = False
-
- # ClientContextFactory to use for STARTTLS
- context = None
-
- def __init__(self, secret, contextFactory=None, *args, **kw):
- SMTPClient.__init__(self, *args, **kw)
- self.authenticators = []
- self.secret = secret
- self.context = contextFactory
- self.tlsMode = False
-
-
- def esmtpEHLORequired(self, code=-1, resp=None):
- self.sendError(EHLORequiredError(502, "Server does not support ESMTP Authentication", self.log.str()))
-
-
- def esmtpAUTHRequired(self, code=-1, resp=None):
- tmp = []
-
- for a in self.authenticators:
- tmp.append(a.getName().upper())
-
- auth = "[%s]" % ', '.join(tmp)
-
- self.sendError(AUTHRequiredError(502, "Server does not support Client Authentication schemes %s" % auth,
- self.log.str()))
-
-
- def esmtpTLSRequired(self, code=-1, resp=None):
- self.sendError(TLSRequiredError(502, "Server does not support secure communication via TLS / SSL",
- self.log.str()))
-
- def esmtpTLSFailed(self, code=-1, resp=None):
- self.sendError(TLSError(code, "Could not complete the SSL/TLS handshake", self.log.str()))
-
- def esmtpAUTHDeclined(self, code=-1, resp=None):
- self.sendError(AUTHDeclinedError(code, resp, self.log.str()))
-
- def esmtpAUTHMalformedChallenge(self, code=-1, resp=None):
- str = "Login failed because the SMTP Server returned a malformed Authentication Challenge"
- self.sendError(AuthenticationError(501, str, self.log.str()))
-
- def esmtpAUTHServerError(self, code=-1, resp=None):
- self.sendError(AuthenticationError(code, resp, self.log.str()))
-
- def registerAuthenticator(self, auth):
- """Registers an Authenticator with the ESMTPClient. The ESMTPClient
- will attempt to login to the SMTP Server in the order the
- Authenticators are registered. The most secure Authentication
- mechanism should be registered first.
-
- @param auth: The Authentication mechanism to register
- @type auth: class implementing C{IClientAuthentication}
- """
-
- self.authenticators.append(auth)
-
- def connectionMade(self):
- SMTPClient.connectionMade(self)
- self._okresponse = self.esmtpState_ehlo
-
- def esmtpState_ehlo(self, code, resp):
- self._expected = SUCCESS
-
- self._okresponse = self.esmtpState_serverConfig
- self._failresponse = self.esmtpEHLORequired
-
- if self.heloFallback:
- self._failresponse = self.smtpState_helo
-
- self.sendLine('EHLO ' + self.identity)
-
- def esmtpState_serverConfig(self, code, resp):
- items = {}
- for line in resp.splitlines():
- e = line.split(None, 1)
- if len(e) > 1:
- items[e[0]] = e[1]
- else:
- items[e[0]] = None
-
- if self.tlsMode:
- self.authenticate(code, resp, items)
- else:
- self.tryTLS(code, resp, items)
-
- def tryTLS(self, code, resp, items):
- if self.context and 'STARTTLS' in items:
- self._expected = [220]
- self._okresponse = self.esmtpState_starttls
- self._failresponse = self.esmtpTLSFailed
- self.sendLine('STARTTLS')
- elif self.requireTransportSecurity:
- self.tlsMode = False
- self.esmtpTLSRequired()
- else:
- self.tlsMode = False
- self.authenticate(code, resp, items)
-
- def esmtpState_starttls(self, code, resp):
- try:
- self.transport.startTLS(self.context)
- self.tlsMode = True
- except:
- log.err()
- self.esmtpTLSFailed(451)
-
- # Send another EHLO once TLS has been started to
- # get the TLS / AUTH schemes. Some servers only allow AUTH in TLS mode.
- self.esmtpState_ehlo(code, resp)
-
- def authenticate(self, code, resp, items):
- if self.secret and items.get('AUTH'):
- schemes = items['AUTH'].split()
- tmpSchemes = {}
-
- #XXX: May want to come up with a more efficient way to do this
- for s in schemes:
- tmpSchemes[s.upper()] = 1
-
- for a in self.authenticators:
- auth = a.getName().upper()
-
- if auth in tmpSchemes:
- self._authinfo = a
-
- # Special condition handled
- if auth == "PLAIN":
- self._okresponse = self.smtpState_from
- self._failresponse = self._esmtpState_plainAuth
- self._expected = [235]
- challenge = encode_base64(self._authinfo.challengeResponse(self.secret, 1), eol="")
- self.sendLine('AUTH ' + auth + ' ' + challenge)
- else:
- self._expected = [334]
- self._okresponse = self.esmtpState_challenge
- # If some error occurs here, the server declined the AUTH
- # before the user / password phase. This would be
- # a very rare case
- self._failresponse = self.esmtpAUTHServerError
- self.sendLine('AUTH ' + auth)
- return
-
- if self.requireAuthentication:
- self.esmtpAUTHRequired()
- else:
- self.smtpState_from(code, resp)
-
- def _esmtpState_plainAuth(self, code, resp):
- self._okresponse = self.smtpState_from
- self._failresponse = self.esmtpAUTHDeclined
- self._expected = [235]
- challenge = encode_base64(self._authinfo.challengeResponse(self.secret, 2), eol="")
- self.sendLine('AUTH PLAIN ' + challenge)
-
- def esmtpState_challenge(self, code, resp):
- self._authResponse(self._authinfo, resp)
-
- def _authResponse(self, auth, challenge):
- self._failresponse = self.esmtpAUTHDeclined
- try:
- challenge = base64.decodestring(challenge)
- except binascii.Error:
- # Illegal challenge, give up, then quit
- self.sendLine('*')
- self._okresponse = self.esmtpAUTHMalformedChallenge
- self._failresponse = self.esmtpAUTHMalformedChallenge
- else:
- resp = auth.challengeResponse(self.secret, challenge)
- self._expected = [235, 334]
- self._okresponse = self.smtpState_maybeAuthenticated
- self.sendLine(encode_base64(resp, eol=""))
-
-
- def smtpState_maybeAuthenticated(self, code, resp):
- """
- Called to handle the next message from the server after sending a
- response to a SASL challenge. The server response might be another
- challenge or it might indicate authentication has succeeded.
- """
- if code == 235:
- # Yes, authenticated!
- del self._authinfo
- self.smtpState_from(code, resp)
- else:
- # No, not authenticated yet. Keep trying.
- self._authResponse(self._authinfo, resp)
-
-
-
-class ESMTP(SMTP):
-
- ctx = None
- canStartTLS = False
- startedTLS = False
-
- authenticated = False
-
- def __init__(self, chal = None, contextFactory = None):
- SMTP.__init__(self)
- if chal is None:
- chal = {}
- self.challengers = chal
- self.authenticated = False
- self.ctx = contextFactory
-
- def connectionMade(self):
- SMTP.connectionMade(self)
- self.canStartTLS = ITLSTransport.providedBy(self.transport)
- self.canStartTLS = self.canStartTLS and (self.ctx is not None)
-
-
- def greeting(self):
- return SMTP.greeting(self) + ' ESMTP'
-
-
- def extensions(self):
- ext = {'AUTH': self.challengers.keys()}
- if self.canStartTLS and not self.startedTLS:
- ext['STARTTLS'] = None
- return ext
-
- def lookupMethod(self, command):
- m = SMTP.lookupMethod(self, command)
- if m is None:
- m = getattr(self, 'ext_' + command.upper(), None)
- return m
-
- def listExtensions(self):
- r = []
- for (c, v) in self.extensions().iteritems():
- if v is not None:
- if v:
- # Intentionally omit extensions with empty argument lists
- r.append('%s %s' % (c, ' '.join(v)))
- else:
- r.append(c)
- return '\n'.join(r)
-
- def do_EHLO(self, rest):
- peer = self.transport.getPeer().host
- self._helo = (rest, peer)
- self._from = None
- self._to = []
- self.sendCode(
- 250,
- '%s Hello %s, nice to meet you\n%s' % (
- self.host, peer,
- self.listExtensions(),
- )
- )
-
- def ext_STARTTLS(self, rest):
- if self.startedTLS:
- self.sendCode(503, 'TLS already negotiated')
- elif self.ctx and self.canStartTLS:
- self.sendCode(220, 'Begin TLS negotiation now')
- self.transport.startTLS(self.ctx)
- self.startedTLS = True
- else:
- self.sendCode(454, 'TLS not available')
-
- def ext_AUTH(self, rest):
- if self.authenticated:
- self.sendCode(503, 'Already authenticated')
- return
- parts = rest.split(None, 1)
- chal = self.challengers.get(parts[0].upper(), lambda: None)()
- if not chal:
- self.sendCode(504, 'Unrecognized authentication type')
- return
-
- self.mode = AUTH
- self.challenger = chal
-
- if len(parts) > 1:
- chal.getChallenge() # Discard it, apparently the client does not
- # care about it.
- rest = parts[1]
- else:
- rest = None
- self.state_AUTH(rest)
-
-
- def _cbAuthenticated(self, loginInfo):
- """
- Save the state resulting from a successful cred login and mark this
- connection as authenticated.
- """
- result = SMTP._cbAnonymousAuthentication(self, loginInfo)
- self.authenticated = True
- return result
-
-
- def _ebAuthenticated(self, reason):
- """
- Handle cred login errors by translating them to the SMTP authenticate
- failed. Translate all other errors into a generic SMTP error code and
- log the failure for inspection. Stop all errors from propagating.
- """
- self.challenge = None
- if reason.check(cred.error.UnauthorizedLogin):
- self.sendCode(535, 'Authentication failed')
- else:
- log.err(reason, "SMTP authentication failure")
- self.sendCode(
- 451,
- 'Requested action aborted: local error in processing')
-
-
- def state_AUTH(self, response):
- """
- Handle one step of challenge/response authentication.
-
- @param response: The text of a response. If None, this
- function has been called as a result of an AUTH command with
- no initial response. A response of '*' aborts authentication,
- as per RFC 2554.
- """
- if self.portal is None:
- self.sendCode(454, 'Temporary authentication failure')
- self.mode = COMMAND
- return
-
- if response is None:
- challenge = self.challenger.getChallenge()
- encoded = challenge.encode('base64')
- self.sendCode(334, encoded)
- return
-
- if response == '*':
- self.sendCode(501, 'Authentication aborted')
- self.challenger = None
- self.mode = COMMAND
- return
-
- try:
- uncoded = response.decode('base64')
- except binascii.Error:
- self.sendCode(501, 'Syntax error in parameters or arguments')
- self.challenger = None
- self.mode = COMMAND
- return
-
- self.challenger.setResponse(uncoded)
- if self.challenger.moreChallenges():
- challenge = self.challenger.getChallenge()
- coded = challenge.encode('base64')[:-1]
- self.sendCode(334, coded)
- return
-
- self.mode = COMMAND
- result = self.portal.login(
- self.challenger, None,
- IMessageDeliveryFactory, IMessageDelivery)
- result.addCallback(self._cbAuthenticated)
- result.addCallback(lambda ign: self.sendCode(235, 'Authentication successful.'))
- result.addErrback(self._ebAuthenticated)
-
-
-
-class SenderMixin:
- """Utility class for sending emails easily.
-
- Use with SMTPSenderFactory or ESMTPSenderFactory.
- """
- done = 0
-
- def getMailFrom(self):
- if not self.done:
- self.done = 1
- return str(self.factory.fromEmail)
- else:
- return None
-
- def getMailTo(self):
- return self.factory.toEmail
-
- def getMailData(self):
- return self.factory.file
-
- def sendError(self, exc):
- # Call the base class to close the connection with the SMTP server
- SMTPClient.sendError(self, exc)
-
- # Do not retry to connect to SMTP Server if:
- # 1. No more retries left (This allows the correct error to be returned to the errorback)
- # 2. retry is false
- # 3. The error code is not in the 4xx range (Communication Errors)
-
- if (self.factory.retries >= 0 or
- (not exc.retry and not (exc.code >= 400 and exc.code < 500))):
- self.factory.sendFinished = 1
- self.factory.result.errback(exc)
-
- def sentMail(self, code, resp, numOk, addresses, log):
- # Do not retry, the SMTP server acknowledged the request
- self.factory.sendFinished = 1
- if code not in SUCCESS:
- errlog = []
- for addr, acode, aresp in addresses:
- if acode not in SUCCESS:
- errlog.append("%s: %03d %s" % (addr, acode, aresp))
-
- errlog.append(log.str())
-
- exc = SMTPDeliveryError(code, resp, '\n'.join(errlog), addresses)
- self.factory.result.errback(exc)
- else:
- self.factory.result.callback((numOk, addresses))
-
-
-class SMTPSender(SenderMixin, SMTPClient):
- """
- SMTP protocol that sends a single email based on information it
- gets from its factory, a L{SMTPSenderFactory}.
- """
-
-
-class SMTPSenderFactory(protocol.ClientFactory):
- """
- Utility factory for sending emails easily.
- """
-
- domain = DNSNAME
- protocol = SMTPSender
-
- def __init__(self, fromEmail, toEmail, file, deferred, retries=5,
- timeout=None):
- """
- @param fromEmail: The RFC 2821 address from which to send this
- message.
-
- @param toEmail: A sequence of RFC 2821 addresses to which to
- send this message.
-
- @param file: A file-like object containing the message to send.
-
- @param deferred: A Deferred to callback or errback when sending
- of this message completes.
-
- @param retries: The number of times to retry delivery of this
- message.
-
- @param timeout: Period, in seconds, for which to wait for
- server responses, or None to wait forever.
- """
- assert isinstance(retries, (int, long))
-
- if isinstance(toEmail, types.StringTypes):
- toEmail = [toEmail]
- self.fromEmail = Address(fromEmail)
- self.nEmails = len(toEmail)
- self.toEmail = toEmail
- self.file = file
- self.result = deferred
- self.result.addBoth(self._removeDeferred)
- self.sendFinished = 0
-
- self.retries = -retries
- self.timeout = timeout
-
- def _removeDeferred(self, argh):
- del self.result
- return argh
-
- def clientConnectionFailed(self, connector, err):
- self._processConnectionError(connector, err)
-
- def clientConnectionLost(self, connector, err):
- self._processConnectionError(connector, err)
-
- def _processConnectionError(self, connector, err):
- if self.retries < self.sendFinished <= 0:
- log.msg("SMTP Client retrying server. Retry: %s" % -self.retries)
-
- # Rewind the file in case part of it was read while attempting to
- # send the message.
- self.file.seek(0, 0)
- connector.connect()
- self.retries += 1
- elif self.sendFinished <= 0:
- # If we were unable to communicate with the SMTP server a ConnectionDone will be
- # returned. We want a more clear error message for debugging
- if err.check(error.ConnectionDone):
- err.value = SMTPConnectError(-1, "Unable to connect to server.")
- self.result.errback(err.value)
-
- def buildProtocol(self, addr):
- p = self.protocol(self.domain, self.nEmails*2+2)
- p.factory = self
- p.timeout = self.timeout
- return p
-
-
-
-from twisted.mail.imap4 import IClientAuthentication
-from twisted.mail.imap4 import CramMD5ClientAuthenticator, LOGINAuthenticator
-from twisted.mail.imap4 import LOGINCredentials as _lcredentials
-
-class LOGINCredentials(_lcredentials):
- """
- L{LOGINCredentials} generates challenges for I{LOGIN} authentication.
-
- For interoperability with Outlook, the challenge generated does not exactly
- match the one defined in the
- U{draft specification<http://sepp.oetiker.ch/sasl-2.1.19-ds/draft-murchison-sasl-login-00.txt>}.
- """
-
- def __init__(self):
- _lcredentials.__init__(self)
- self.challenges = ['Password:', 'Username:']
-
-
-
-class PLAINAuthenticator:
- implements(IClientAuthentication)
-
- def __init__(self, user):
- self.user = user
-
- def getName(self):
- return "PLAIN"
-
- def challengeResponse(self, secret, chal=1):
- if chal == 1:
- return "%s\0%s\0%s" % (self.user, self.user, secret)
- else:
- return "%s\0%s" % (self.user, secret)
-
-
-
-class ESMTPSender(SenderMixin, ESMTPClient):
-
- requireAuthentication = True
- requireTransportSecurity = True
-
- def __init__(self, username, secret, contextFactory=None, *args, **kw):
- self.heloFallback = 0
- self.username = username
-
- if contextFactory is None:
- contextFactory = self._getContextFactory()
-
- ESMTPClient.__init__(self, secret, contextFactory, *args, **kw)
-
- self._registerAuthenticators()
-
- def _registerAuthenticators(self):
- # Register Authenticator in order from most secure to least secure
- self.registerAuthenticator(CramMD5ClientAuthenticator(self.username))
- self.registerAuthenticator(LOGINAuthenticator(self.username))
- self.registerAuthenticator(PLAINAuthenticator(self.username))
-
- def _getContextFactory(self):
- if self.context is not None:
- return self.context
- try:
- from twisted.internet import ssl
- except ImportError:
- return None
- else:
- try:
- context = ssl.ClientContextFactory()
- context.method = ssl.SSL.TLSv1_METHOD
- return context
- except AttributeError:
- return None
-
-
-class ESMTPSenderFactory(SMTPSenderFactory):
- """
- Utility factory for sending emails easily.
- """
-
- protocol = ESMTPSender
-
- def __init__(self, username, password, fromEmail, toEmail, file,
- deferred, retries=5, timeout=None,
- contextFactory=None, heloFallback=False,
- requireAuthentication=True,
- requireTransportSecurity=True):
-
- SMTPSenderFactory.__init__(self, fromEmail, toEmail, file, deferred, retries, timeout)
- self.username = username
- self.password = password
- self._contextFactory = contextFactory
- self._heloFallback = heloFallback
- self._requireAuthentication = requireAuthentication
- self._requireTransportSecurity = requireTransportSecurity
-
- def buildProtocol(self, addr):
- p = self.protocol(self.username, self.password, self._contextFactory, self.domain, self.nEmails*2+2)
- p.heloFallback = self._heloFallback
- p.requireAuthentication = self._requireAuthentication
- p.requireTransportSecurity = self._requireTransportSecurity
- p.factory = self
- p.timeout = self.timeout
- return p
-
-def sendmail(smtphost, from_addr, to_addrs, msg, senderDomainName=None, port=25):
- """Send an email
-
- This interface is intended to be a direct replacement for
- smtplib.SMTP.sendmail() (with the obvious change that
- you specify the smtphost as well). Also, ESMTP options
- are not accepted, as we don't do ESMTP yet. I reserve the
- right to implement the ESMTP options differently.
-
- @param smtphost: The host the message should be sent to
- @param from_addr: The (envelope) address sending this mail.
- @param to_addrs: A list of addresses to send this mail to. A string will
- be treated as a list of one address
- @param msg: The message, including headers, either as a file or a string.
- File-like objects need to support read() and close(). Lines must be
- delimited by '\\n'. If you pass something that doesn't look like a
- file, we try to convert it to a string (so you should be able to
- pass an email.Message directly, but doing the conversion with
- email.Generator manually will give you more control over the
- process).
-
- @param senderDomainName: Name by which to identify. If None, try
- to pick something sane (but this depends on external configuration
- and may not succeed).
-
- @param port: Remote port to which to connect.
-
- @rtype: L{Deferred}
- @returns: A L{Deferred}, its callback will be called if a message is sent
- to ANY address, the errback if no message is sent.
-
- The callback will be called with a tuple (numOk, addresses) where numOk
- is the number of successful recipient addresses and addresses is a list
- of tuples (address, code, resp) giving the response to the RCPT command
- for each address.
- """
- if not hasattr(msg,'read'):
- # It's not a file
- msg = StringIO(str(msg))
-
- d = defer.Deferred()
- factory = SMTPSenderFactory(from_addr, to_addrs, msg, d)
-
- if senderDomainName is not None:
- factory.domain = senderDomainName
-
- reactor.connectTCP(smtphost, port, factory)
-
- return d
-
-
-
-##
-## Yerg. Codecs!
-##
-import codecs
-def xtext_encode(s, errors=None):
- r = []
- for ch in s:
- o = ord(ch)
- if ch == '+' or ch == '=' or o < 33 or o > 126:
- r.append('+%02X' % o)
- else:
- r.append(chr(o))
- return (''.join(r), len(s))
-
-
-def xtext_decode(s, errors=None):
- """
- Decode the xtext-encoded string C{s}.
- """
- r = []
- i = 0
- while i < len(s):
- if s[i] == '+':
- try:
- r.append(chr(int(s[i + 1:i + 3], 16)))
- except ValueError:
- r.append(s[i:i + 3])
- i += 3
- else:
- r.append(s[i])
- i += 1
- return (''.join(r), len(s))
-
-class xtextStreamReader(codecs.StreamReader):
- def decode(self, s, errors='strict'):
- return xtext_decode(s)
-
-class xtextStreamWriter(codecs.StreamWriter):
- def decode(self, s, errors='strict'):
- return xtext_encode(s)
-
-def xtext_codec(name):
- if name == 'xtext':
- return (xtext_encode, xtext_decode, xtextStreamReader, xtextStreamWriter)
-codecs.register(xtext_codec)
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/tap.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/tap.py
deleted file mode 100755
index fe525f30..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/tap.py
+++ /dev/null
@@ -1,333 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_options -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""
-I am the support module for creating mail servers with twistd
-"""
-
-import os
-import warnings
-
-from twisted.mail import mail
-from twisted.mail import maildir
-from twisted.mail import relay
-from twisted.mail import relaymanager
-from twisted.mail import alias
-
-from twisted.internet import endpoints
-
-from twisted.python import usage
-
-from twisted.cred import checkers
-from twisted.cred import strcred
-
-from twisted.application import internet
-
-
-class Options(usage.Options, strcred.AuthOptionMixin):
- synopsis = "[options]"
-
- optParameters = [
- ["pop3s", "S", 0,
- "Port to start the POP3-over-SSL server on (0 to disable). "
- "DEPRECATED: use "
- "'--pop3 ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
-
- ["certificate", "c", None,
- "Certificate file to use for SSL connections. "
- "DEPRECATED: use "
- "'--pop3 ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
-
- ["relay", "R", None,
- "Relay messages according to their envelope 'To', using "
- "the given path as a queue directory."],
-
- ["hostname", "H", None,
- "The hostname by which to identify this server."],
- ]
-
- optFlags = [
- ["esmtp", "E", "Use RFC 1425/1869 SMTP extensions"],
- ["disable-anonymous", None,
- "Disallow non-authenticated SMTP connections"],
- ["no-pop3", None, "Disable the default POP3 server."],
- ["no-smtp", None, "Disable the default SMTP server."],
- ]
-
- _protoDefaults = {
- "pop3": 8110,
- "smtp": 8025,
- }
-
- compData = usage.Completions(
- optActions={"hostname" : usage.CompleteHostnames(),
- "certificate" : usage.CompleteFiles("*.pem")}
- )
-
- longdesc = "This creates a mail.tap file that can be used by twistd."
-
- def __init__(self):
- usage.Options.__init__(self)
- self.service = mail.MailService()
- self.last_domain = None
- for service in self._protoDefaults:
- self[service] = []
-
-
- def addEndpoint(self, service, description, certificate=None):
- """
- Given a 'service' (pop3 or smtp), add an endpoint.
- """
- self[service].append(
- _toEndpoint(description, certificate=certificate))
-
-
- def opt_pop3(self, description):
- """
- Add a pop3 port listener on the specified endpoint. You can listen on
- multiple ports by specifying multiple --pop3 options. For backwards
- compatibility, a bare TCP port number can be specified, but this is
- deprecated. [SSL Example: ssl:8995:privateKey=mycert.pem] [default:
- tcp:8110]
- """
- self.addEndpoint('pop3', description)
- opt_p = opt_pop3
-
-
- def opt_smtp(self, description):
- """
- Add an smtp port listener on the specified endpoint. You can listen on
- multiple ports by specifying multiple --smtp options For backwards
- compatibility, a bare TCP port number can be specified, but this is
- deprecated. [SSL Example: ssl:8465:privateKey=mycert.pem] [default:
- tcp:8025]
- """
- self.addEndpoint('smtp', description)
- opt_s = opt_smtp
-
-
- def opt_default(self):
- """Make the most recently specified domain the default domain."""
- if self.last_domain:
- self.service.addDomain('', self.last_domain)
- else:
- raise usage.UsageError("Specify a domain before specifying using --default")
- opt_D = opt_default
-
-
- def opt_maildirdbmdomain(self, domain):
- """generate an SMTP/POP3 virtual domain which saves to \"path\"
- """
- try:
- name, path = domain.split('=')
- except ValueError:
- raise usage.UsageError("Argument to --maildirdbmdomain must be of the form 'name=path'")
-
- self.last_domain = maildir.MaildirDirdbmDomain(self.service, os.path.abspath(path))
- self.service.addDomain(name, self.last_domain)
- opt_d = opt_maildirdbmdomain
-
- def opt_user(self, user_pass):
- """add a user/password to the last specified domains
- """
- try:
- user, password = user_pass.split('=', 1)
- except ValueError:
- raise usage.UsageError("Argument to --user must be of the form 'user=password'")
- if self.last_domain:
- self.last_domain.addUser(user, password)
- else:
- raise usage.UsageError("Specify a domain before specifying users")
- opt_u = opt_user
-
- def opt_bounce_to_postmaster(self):
- """undelivered mails are sent to the postmaster
- """
- self.last_domain.postmaster = 1
- opt_b = opt_bounce_to_postmaster
-
- def opt_aliases(self, filename):
- """Specify an aliases(5) file to use for this domain"""
- if self.last_domain is not None:
- if mail.IAliasableDomain.providedBy(self.last_domain):
- aliases = alias.loadAliasFile(self.service.domains, filename)
- self.last_domain.setAliasGroup(aliases)
- self.service.monitor.monitorFile(
- filename,
- AliasUpdater(self.service.domains, self.last_domain)
- )
- else:
- raise usage.UsageError(
- "%s does not support alias files" % (
- self.last_domain.__class__.__name__,
- )
- )
- else:
- raise usage.UsageError("Specify a domain before specifying aliases")
- opt_A = opt_aliases
-
- def _getEndpoints(self, reactor, service):
- """
- Return a list of endpoints for the specified service, constructing
- defaults if necessary.
-
- @param reactor: If any endpoints are created, this is the reactor with
- which they are created.
-
- @param service: A key into self indicating the type of service to
- retrieve endpoints for. This is either C{"pop3"} or C{"smtp"}.
-
- @return: A C{list} of C{IServerStreamEndpoint} providers corresponding
- to the command line parameters that were specified for C{service}.
- If none were and the protocol was not explicitly disabled with a
- I{--no-*} option, a default endpoint for the service is created
- using C{self._protoDefaults}.
- """
- if service == 'pop3' and self['pop3s'] and len(self[service]) == 1:
- # The single endpoint here is the POP3S service we added in
- # postOptions. Include the default endpoint alongside it.
- return self[service] + [
- endpoints.TCP4ServerEndpoint(
- reactor, self._protoDefaults[service])]
- elif self[service]:
- # For any non-POP3S case, if there are any services set up, just
- # return those.
- return self[service]
- elif self['no-' + service]:
- # If there are no services, but the service was explicitly disabled,
- # return nothing.
- return []
- else:
- # Otherwise, return the old default service.
- return [
- endpoints.TCP4ServerEndpoint(
- reactor, self._protoDefaults[service])]
-
-
- def postOptions(self):
- from twisted.internet import reactor
-
- if self['pop3s']:
- if not self['certificate']:
- raise usage.UsageError("Cannot specify --pop3s without "
- "--certificate")
- elif not os.path.exists(self['certificate']):
- raise usage.UsageError("Certificate file %r does not exist."
- % self['certificate'])
- else:
- self.addEndpoint(
- 'pop3', self['pop3s'], certificate=self['certificate'])
-
- if self['esmtp'] and self['hostname'] is None:
- raise usage.UsageError("--esmtp requires --hostname")
-
- # If the --auth option was passed, this will be present -- otherwise,
- # it won't be, which is also a perfectly valid state.
- if 'credCheckers' in self:
- for ch in self['credCheckers']:
- self.service.smtpPortal.registerChecker(ch)
-
- if not self['disable-anonymous']:
- self.service.smtpPortal.registerChecker(checkers.AllowAnonymousAccess())
-
- anything = False
- for service in self._protoDefaults:
- self[service] = self._getEndpoints(reactor, service)
- if self[service]:
- anything = True
-
- if not anything:
- raise usage.UsageError("You cannot disable all protocols")
-
-
-
-class AliasUpdater:
- def __init__(self, domains, domain):
- self.domains = domains
- self.domain = domain
- def __call__(self, new):
- self.domain.setAliasGroup(alias.loadAliasFile(self.domains, new))
-
-
-def _toEndpoint(description, certificate=None):
- """
- Tries to guess whether a description is a bare TCP port or a endpoint. If a
- bare port is specified and a certificate file is present, returns an
- SSL4ServerEndpoint and otherwise returns a TCP4ServerEndpoint.
- """
- from twisted.internet import reactor
- try:
- port = int(description)
- except ValueError:
- return endpoints.serverFromString(reactor, description)
-
- warnings.warn(
- "Specifying plain ports and/or a certificate is deprecated since "
- "Twisted 11.0; use endpoint descriptions instead.",
- category=DeprecationWarning, stacklevel=3)
-
- if certificate:
- from twisted.internet.ssl import DefaultOpenSSLContextFactory
- ctx = DefaultOpenSSLContextFactory(certificate, certificate)
- return endpoints.SSL4ServerEndpoint(reactor, port, ctx)
- return endpoints.TCP4ServerEndpoint(reactor, port)
-
-
-def makeService(config):
- """
- Construct a service for operating a mail server.
-
- The returned service may include POP3 servers or SMTP servers (or both),
- depending on the configuration passed in. If there are multiple servers,
- they will share all of their non-network state (eg, the same user accounts
- are available on all of them).
-
- @param config: An L{Options} instance specifying what servers to include in
- the returned service and where they should keep mail data.
-
- @return: An L{IService} provider which contains the requested mail servers.
- """
- if config['esmtp']:
- rmType = relaymanager.SmartHostESMTPRelayingManager
- smtpFactory = config.service.getESMTPFactory
- else:
- rmType = relaymanager.SmartHostSMTPRelayingManager
- smtpFactory = config.service.getSMTPFactory
-
- if config['relay']:
- dir = config['relay']
- if not os.path.isdir(dir):
- os.mkdir(dir)
-
- config.service.setQueue(relaymanager.Queue(dir))
- default = relay.DomainQueuer(config.service)
-
- manager = rmType(config.service.queue)
- if config['esmtp']:
- manager.fArgs += (None, None)
- manager.fArgs += (config['hostname'],)
-
- helper = relaymanager.RelayStateHelper(manager, 1)
- helper.setServiceParent(config.service)
- config.service.domains.setDefaultDomain(default)
-
- if config['pop3']:
- f = config.service.getPOP3Factory()
- for endpoint in config['pop3']:
- svc = internet.StreamServerEndpointService(endpoint, f)
- svc.setServiceParent(config.service)
-
- if config['smtp']:
- f = smtpFactory()
- if config['hostname']:
- f.domain = config['hostname']
- f.fArgs = (f.domain,)
- if config['esmtp']:
- f.fArgs = (None, None) + f.fArgs
- for endpoint in config['smtp']:
- svc = internet.StreamServerEndpointService(endpoint, f)
- svc.setServiceParent(config.service)
-
- return config.service
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/__init__.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/__init__.py
deleted file mode 100755
index f8ec7056..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"Tests for twistd.mail"
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/pop3testserver.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/pop3testserver.py
deleted file mode 100755
index c87892c0..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/pop3testserver.py
+++ /dev/null
@@ -1,314 +0,0 @@
-#!/usr/bin/env python
-# -*- test-case-name: twisted.mail.test.test_pop3client -*-
-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-from twisted.internet.protocol import Factory
-from twisted.protocols import basic
-from twisted.internet import reactor
-import sys, time
-
-USER = "test"
-PASS = "twisted"
-
-PORT = 1100
-
-SSL_SUPPORT = True
-UIDL_SUPPORT = True
-INVALID_SERVER_RESPONSE = False
-INVALID_CAPABILITY_RESPONSE = False
-INVALID_LOGIN_RESPONSE = False
-DENY_CONNECTION = False
-DROP_CONNECTION = False
-BAD_TLS_RESPONSE = False
-TIMEOUT_RESPONSE = False
-TIMEOUT_DEFERRED = False
-SLOW_GREETING = False
-
-"""Commands"""
-CONNECTION_MADE = "+OK POP3 localhost v2003.83 server ready"
-
-CAPABILITIES = [
-"TOP",
-"LOGIN-DELAY 180",
-"USER",
-"SASL LOGIN"
-]
-
-CAPABILITIES_SSL = "STLS"
-CAPABILITIES_UIDL = "UIDL"
-
-
-INVALID_RESPONSE = "-ERR Unknown request"
-VALID_RESPONSE = "+OK Command Completed"
-AUTH_DECLINED = "-ERR LOGIN failed"
-AUTH_ACCEPTED = "+OK Mailbox open, 0 messages"
-TLS_ERROR = "-ERR server side error start TLS handshake"
-LOGOUT_COMPLETE = "+OK quit completed"
-NOT_LOGGED_IN = "-ERR Unknown AUHORIZATION state command"
-STAT = "+OK 0 0"
-UIDL = "+OK Unique-ID listing follows\r\n."
-LIST = "+OK Mailbox scan listing follows\r\n."
-CAP_START = "+OK Capability list follows:"
-
-
-class POP3TestServer(basic.LineReceiver):
- def __init__(self, contextFactory = None):
- self.loggedIn = False
- self.caps = None
- self.tmpUser = None
- self.ctx = contextFactory
-
- def sendSTATResp(self, req):
- self.sendLine(STAT)
-
- def sendUIDLResp(self, req):
- self.sendLine(UIDL)
-
- def sendLISTResp(self, req):
- self.sendLine(LIST)
-
- def sendCapabilities(self):
- if self.caps is None:
- self.caps = [CAP_START]
-
- if UIDL_SUPPORT:
- self.caps.append(CAPABILITIES_UIDL)
-
- if SSL_SUPPORT:
- self.caps.append(CAPABILITIES_SSL)
-
- for cap in CAPABILITIES:
- self.caps.append(cap)
- resp = '\r\n'.join(self.caps)
- resp += "\r\n."
-
- self.sendLine(resp)
-
-
- def connectionMade(self):
- if DENY_CONNECTION:
- self.disconnect()
- return
-
- if SLOW_GREETING:
- reactor.callLater(20, self.sendGreeting)
-
- else:
- self.sendGreeting()
-
- def sendGreeting(self):
- self.sendLine(CONNECTION_MADE)
-
- def lineReceived(self, line):
- """Error Conditions"""
-
- uline = line.upper()
- find = lambda s: uline.find(s) != -1
-
- if TIMEOUT_RESPONSE:
- # Do not respond to clients request
- return
-
- if DROP_CONNECTION:
- self.disconnect()
- return
-
- elif find("CAPA"):
- if INVALID_CAPABILITY_RESPONSE:
- self.sendLine(INVALID_RESPONSE)
- else:
- self.sendCapabilities()
-
- elif find("STLS") and SSL_SUPPORT:
- self.startTLS()
-
- elif find("USER"):
- if INVALID_LOGIN_RESPONSE:
- self.sendLine(INVALID_RESPONSE)
- return
-
- resp = None
- try:
- self.tmpUser = line.split(" ")[1]
- resp = VALID_RESPONSE
- except:
- resp = AUTH_DECLINED
-
- self.sendLine(resp)
-
- elif find("PASS"):
- resp = None
- try:
- pwd = line.split(" ")[1]
-
- if self.tmpUser is None or pwd is None:
- resp = AUTH_DECLINED
- elif self.tmpUser == USER and pwd == PASS:
- resp = AUTH_ACCEPTED
- self.loggedIn = True
- else:
- resp = AUTH_DECLINED
- except:
- resp = AUTH_DECLINED
-
- self.sendLine(resp)
-
- elif find("QUIT"):
- self.loggedIn = False
- self.sendLine(LOGOUT_COMPLETE)
- self.disconnect()
-
- elif INVALID_SERVER_RESPONSE:
- self.sendLine(INVALID_RESPONSE)
-
- elif not self.loggedIn:
- self.sendLine(NOT_LOGGED_IN)
-
- elif find("NOOP"):
- self.sendLine(VALID_RESPONSE)
-
- elif find("STAT"):
- if TIMEOUT_DEFERRED:
- return
- self.sendLine(STAT)
-
- elif find("LIST"):
- if TIMEOUT_DEFERRED:
- return
- self.sendLine(LIST)
-
- elif find("UIDL"):
- if TIMEOUT_DEFERRED:
- return
- elif not UIDL_SUPPORT:
- self.sendLine(INVALID_RESPONSE)
- return
-
- self.sendLine(UIDL)
-
- def startTLS(self):
- if self.ctx is None:
- self.getContext()
-
- if SSL_SUPPORT and self.ctx is not None:
- self.sendLine('+OK Begin TLS negotiation now')
- self.transport.startTLS(self.ctx)
- else:
- self.sendLine('-ERR TLS not available')
-
- def disconnect(self):
- self.transport.loseConnection()
-
- def getContext(self):
- try:
- from twisted.internet import ssl
- except ImportError:
- self.ctx = None
- else:
- self.ctx = ssl.ClientContextFactory()
- self.ctx.method = ssl.SSL.TLSv1_METHOD
-
-
-usage = """popServer.py [arg] (default is Standard POP Server with no messages)
-no_ssl - Start with no SSL support
-no_uidl - Start with no UIDL support
-bad_resp - Send a non-RFC compliant response to the Client
-bad_cap_resp - send a non-RFC compliant response when the Client sends a 'CAPABILITY' request
-bad_login_resp - send a non-RFC compliant response when the Client sends a 'LOGIN' request
-deny - Deny the connection
-drop - Drop the connection after sending the greeting
-bad_tls - Send a bad response to a STARTTLS
-timeout - Do not return a response to a Client request
-to_deferred - Do not return a response on a 'Select' request. This
- will test Deferred callback handling
-slow - Wait 20 seconds after the connection is made to return a Server Greeting
-"""
-
-def printMessage(msg):
- print "Server Starting in %s mode" % msg
-
-def processArg(arg):
-
- if arg.lower() == 'no_ssl':
- global SSL_SUPPORT
- SSL_SUPPORT = False
- printMessage("NON-SSL")
-
- elif arg.lower() == 'no_uidl':
- global UIDL_SUPPORT
- UIDL_SUPPORT = False
- printMessage("NON-UIDL")
-
- elif arg.lower() == 'bad_resp':
- global INVALID_SERVER_RESPONSE
- INVALID_SERVER_RESPONSE = True
- printMessage("Invalid Server Response")
-
- elif arg.lower() == 'bad_cap_resp':
- global INVALID_CAPABILITY_RESPONSE
- INVALID_CAPABILITY_RESPONSE = True
- printMessage("Invalid Capability Response")
-
- elif arg.lower() == 'bad_login_resp':
- global INVALID_LOGIN_RESPONSE
- INVALID_LOGIN_RESPONSE = True
- printMessage("Invalid Capability Response")
-
- elif arg.lower() == 'deny':
- global DENY_CONNECTION
- DENY_CONNECTION = True
- printMessage("Deny Connection")
-
- elif arg.lower() == 'drop':
- global DROP_CONNECTION
- DROP_CONNECTION = True
- printMessage("Drop Connection")
-
-
- elif arg.lower() == 'bad_tls':
- global BAD_TLS_RESPONSE
- BAD_TLS_RESPONSE = True
- printMessage("Bad TLS Response")
-
- elif arg.lower() == 'timeout':
- global TIMEOUT_RESPONSE
- TIMEOUT_RESPONSE = True
- printMessage("Timeout Response")
-
- elif arg.lower() == 'to_deferred':
- global TIMEOUT_DEFERRED
- TIMEOUT_DEFERRED = True
- printMessage("Timeout Deferred Response")
-
- elif arg.lower() == 'slow':
- global SLOW_GREETING
- SLOW_GREETING = True
- printMessage("Slow Greeting")
-
- elif arg.lower() == '--help':
- print usage
- sys.exit()
-
- else:
- print usage
- sys.exit()
-
-def main():
-
- if len(sys.argv) < 2:
- printMessage("POP3 with no messages")
- else:
- args = sys.argv[1:]
-
- for arg in args:
- processArg(arg)
-
- f = Factory()
- f.protocol = POP3TestServer
- reactor.listenTCP(PORT, f)
- reactor.run()
-
-if __name__ == '__main__':
- main()
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/rfc822.message b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/rfc822.message
deleted file mode 100644
index ee97ab92..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/rfc822.message
+++ /dev/null
@@ -1,86 +0,0 @@
-Return-Path: <twisted-commits-admin@twistedmatrix.com>
-Delivered-To: exarkun@meson.dyndns.org
-Received: from localhost [127.0.0.1]
- by localhost with POP3 (fetchmail-6.2.1)
- for exarkun@localhost (single-drop); Thu, 20 Mar 2003 14:50:20 -0500 (EST)
-Received: from pyramid.twistedmatrix.com (adsl-64-123-27-105.dsl.austtx.swbell.net [64.123.27.105])
- by intarweb.us (Postfix) with ESMTP id 4A4A513EA4
- for <exarkun@meson.dyndns.org>; Thu, 20 Mar 2003 14:49:27 -0500 (EST)
-Received: from localhost ([127.0.0.1] helo=pyramid.twistedmatrix.com)
- by pyramid.twistedmatrix.com with esmtp (Exim 3.35 #1 (Debian))
- id 18w648-0007Vl-00; Thu, 20 Mar 2003 13:51:04 -0600
-Received: from acapnotic by pyramid.twistedmatrix.com with local (Exim 3.35 #1 (Debian))
- id 18w63j-0007VK-00
- for <twisted-commits@twistedmatrix.com>; Thu, 20 Mar 2003 13:50:39 -0600
-To: twisted-commits@twistedmatrix.com
-From: etrepum CVS <etrepum@twistedmatrix.com>
-Reply-To: twisted-python@twistedmatrix.com
-X-Mailer: CVSToys
-Message-Id: <E18w63j-0007VK-00@pyramid.twistedmatrix.com>
-Subject: [Twisted-commits] rebuild now works on python versions from 2.2.0 and up.
-Sender: twisted-commits-admin@twistedmatrix.com
-Errors-To: twisted-commits-admin@twistedmatrix.com
-X-BeenThere: twisted-commits@twistedmatrix.com
-X-Mailman-Version: 2.0.11
-Precedence: bulk
-List-Help: <mailto:twisted-commits-request@twistedmatrix.com?subject=help>
-List-Post: <mailto:twisted-commits@twistedmatrix.com>
-List-Subscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
- <mailto:twisted-commits-request@twistedmatrix.com?subject=subscribe>
-List-Id: <twisted-commits.twistedmatrix.com>
-List-Unsubscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
- <mailto:twisted-commits-request@twistedmatrix.com?subject=unsubscribe>
-List-Archive: <http://twistedmatrix.com/pipermail/twisted-commits/>
-Date: Thu, 20 Mar 2003 13:50:39 -0600
-
-Modified files:
-Twisted/twisted/python/rebuild.py 1.19 1.20
-
-Log message:
-rebuild now works on python versions from 2.2.0 and up.
-
-
-ViewCVS links:
-http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/python/rebuild.py.diff?r1=text&tr1=1.19&r2=text&tr2=1.20&cvsroot=Twisted
-
-Index: Twisted/twisted/python/rebuild.py
-diff -u Twisted/twisted/python/rebuild.py:1.19 Twisted/twisted/python/rebuild.py:1.20
---- Twisted/twisted/python/rebuild.py:1.19 Fri Jan 17 13:50:49 2003
-+++ Twisted/twisted/python/rebuild.py Thu Mar 20 11:50:08 2003
-@@ -206,15 +206,27 @@
- clazz.__dict__.clear()
- clazz.__getattr__ = __getattr__
- clazz.__module__ = module.__name__
-+ if newclasses:
-+ import gc
-+ if (2, 2, 0) <= sys.version_info[:3] < (2, 2, 2):
-+ hasBrokenRebuild = 1
-+ gc_objects = gc.get_objects()
-+ else:
-+ hasBrokenRebuild = 0
- for nclass in newclasses:
- ga = getattr(module, nclass.__name__)
- if ga is nclass:
- log.msg("WARNING: new-class %s not replaced by reload!" % reflect.qual(nclass))
- else:
-- import gc
-- for r in gc.get_referrers(nclass):
-- if isinstance(r, nclass):
-+ if hasBrokenRebuild:
-+ for r in gc_objects:
-+ if not getattr(r, '__class__', None) is nclass:
-+ continue
- r.__class__ = ga
-+ else:
-+ for r in gc.get_referrers(nclass):
-+ if getattr(r, '__class__', None) is nclass:
-+ r.__class__ = ga
- if doLog:
- log.msg('')
- log.msg(' (fixing %s): ' % str(module.__name__))
-
-
-_______________________________________________
-Twisted-commits mailing list
-Twisted-commits@twistedmatrix.com
-http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/server.pem b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/server.pem
deleted file mode 100644
index 80ef9dcf..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/server.pem
+++ /dev/null
@@ -1,36 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
-MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
-ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
-cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
-MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
-c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
-BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
-5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
-MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
-MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
-hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
-CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
-dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
-LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
-BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
-7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
-WUQ9Ho4EzbYCOQ==
------END CERTIFICATE-----
------BEGIN RSA PRIVATE KEY-----
-MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
-5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
-OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
-ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
-nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
-HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
-oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
------END RSA PRIVATE KEY-----
------BEGIN CERTIFICATE REQUEST-----
-MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
-BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
-XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
-NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
-DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
-Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
------END CERTIFICATE REQUEST-----
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_bounce.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_bounce.py
deleted file mode 100755
index 963d21dd..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_bounce.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""Test cases for bounce message generation
-"""
-
-from twisted.trial import unittest
-from twisted.mail import bounce
-import rfc822, cStringIO
-
-class BounceTestCase(unittest.TestCase):
- """
- testcases for bounce message generation
- """
-
- def testBounceFormat(self):
- from_, to, s = bounce.generateBounce(cStringIO.StringIO('''\
-From: Moshe Zadka <moshez@example.com>
-To: nonexistant@example.org
-Subject: test
-
-'''), 'moshez@example.com', 'nonexistant@example.org')
- self.assertEqual(from_, '')
- self.assertEqual(to, 'moshez@example.com')
- mess = rfc822.Message(cStringIO.StringIO(s))
- self.assertEqual(mess['To'], 'moshez@example.com')
- self.assertEqual(mess['From'], 'postmaster@example.org')
- self.assertEqual(mess['subject'], 'Returned Mail: see transcript for details')
-
- def testBounceMIME(self):
- pass
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_imap.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_imap.py
deleted file mode 100755
index b2461ce1..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_imap.py
+++ /dev/null
@@ -1,4829 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_imap -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-
-"""
-Test case for twisted.mail.imap4
-"""
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-import codecs
-import locale
-import os
-import types
-
-from zope.interface import implements
-
-from twisted.mail.imap4 import MessageSet
-from twisted.mail import imap4
-from twisted.protocols import loopback
-from twisted.internet import defer
-from twisted.internet import error
-from twisted.internet import reactor
-from twisted.internet import interfaces
-from twisted.internet.task import Clock
-from twisted.trial import unittest
-from twisted.python import util
-from twisted.python import failure
-
-from twisted import cred
-import twisted.cred.error
-import twisted.cred.checkers
-import twisted.cred.credentials
-import twisted.cred.portal
-
-from twisted.test.proto_helpers import StringTransport, StringTransportWithDisconnection
-
-try:
- from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
-except ImportError:
- ClientTLSContext = ServerTLSContext = None
-
-def strip(f):
- return lambda result, f=f: f()
-
-def sortNest(l):
- l = l[:]
- l.sort()
- for i in range(len(l)):
- if isinstance(l[i], types.ListType):
- l[i] = sortNest(l[i])
- elif isinstance(l[i], types.TupleType):
- l[i] = tuple(sortNest(list(l[i])))
- return l
-
-class IMAP4UTF7TestCase(unittest.TestCase):
- tests = [
- [u'Hello world', 'Hello world'],
- [u'Hello & world', 'Hello &- world'],
- [u'Hello\xffworld', 'Hello&AP8-world'],
- [u'\xff\xfe\xfd\xfc', '&AP8A,gD9APw-'],
- [u'~peter/mail/\u65e5\u672c\u8a9e/\u53f0\u5317',
- '~peter/mail/&ZeVnLIqe-/&U,BTFw-'], # example from RFC 2060
- ]
-
- def test_encodeWithErrors(self):
- """
- Specifying an error policy to C{unicode.encode} with the
- I{imap4-utf-7} codec should produce the same result as not
- specifying the error policy.
- """
- text = u'Hello world'
- self.assertEqual(
- text.encode('imap4-utf-7', 'strict'),
- text.encode('imap4-utf-7'))
-
-
- def test_decodeWithErrors(self):
- """
- Similar to L{test_encodeWithErrors}, but for C{str.decode}.
- """
- bytes = 'Hello world'
- self.assertEqual(
- bytes.decode('imap4-utf-7', 'strict'),
- bytes.decode('imap4-utf-7'))
-
-
- def test_getreader(self):
- """
- C{codecs.getreader('imap4-utf-7')} returns the I{imap4-utf-7} stream
- reader class.
- """
- reader = codecs.getreader('imap4-utf-7')(StringIO('Hello&AP8-world'))
- self.assertEqual(reader.read(), u'Hello\xffworld')
-
-
- def test_getwriter(self):
- """
- C{codecs.getwriter('imap4-utf-7')} returns the I{imap4-utf-7} stream
- writer class.
- """
- output = StringIO()
- writer = codecs.getwriter('imap4-utf-7')(output)
- writer.write(u'Hello\xffworld')
- self.assertEqual(output.getvalue(), 'Hello&AP8-world')
-
-
- def test_encode(self):
- """
- The I{imap4-utf-7} can be used to encode a unicode string into a byte
- string according to the IMAP4 modified UTF-7 encoding rules.
- """
- for (input, output) in self.tests:
- self.assertEqual(input.encode('imap4-utf-7'), output)
-
-
- def test_decode(self):
- """
- The I{imap4-utf-7} can be used to decode a byte string into a unicode
- string according to the IMAP4 modified UTF-7 encoding rules.
- """
- for (input, output) in self.tests:
- self.assertEqual(input, output.decode('imap4-utf-7'))
-
-
- def test_printableSingletons(self):
- """
- The IMAP4 modified UTF-7 implementation encodes all printable
- characters which are in ASCII using the corresponding ASCII byte.
- """
- # All printables represent themselves
- for o in range(0x20, 0x26) + range(0x27, 0x7f):
- self.assertEqual(chr(o), chr(o).encode('imap4-utf-7'))
- self.assertEqual(chr(o), chr(o).decode('imap4-utf-7'))
- self.assertEqual('&'.encode('imap4-utf-7'), '&-')
- self.assertEqual('&-'.decode('imap4-utf-7'), '&')
-
-
-
-class BufferingConsumer:
- def __init__(self):
- self.buffer = []
-
- def write(self, bytes):
- self.buffer.append(bytes)
- if self.consumer:
- self.consumer.resumeProducing()
-
- def registerProducer(self, consumer, streaming):
- self.consumer = consumer
- self.consumer.resumeProducing()
-
- def unregisterProducer(self):
- self.consumer = None
-
-class MessageProducerTestCase(unittest.TestCase):
- def testSinglePart(self):
- body = 'This is body text. Rar.'
- headers = util.OrderedDict()
- headers['from'] = 'sender@host'
- headers['to'] = 'recipient@domain'
- headers['subject'] = 'booga booga boo'
- headers['content-type'] = 'text/plain'
-
- msg = FakeyMessage(headers, (), None, body, 123, None )
-
- c = BufferingConsumer()
- p = imap4.MessageProducer(msg)
- d = p.beginProducing(c)
-
- def cbProduced(result):
- self.assertIdentical(result, p)
- self.assertEqual(
- ''.join(c.buffer),
-
- '{119}\r\n'
- 'From: sender@host\r\n'
- 'To: recipient@domain\r\n'
- 'Subject: booga booga boo\r\n'
- 'Content-Type: text/plain\r\n'
- '\r\n'
- + body)
- return d.addCallback(cbProduced)
-
-
- def testSingleMultiPart(self):
- outerBody = ''
- innerBody = 'Contained body message text. Squarge.'
- headers = util.OrderedDict()
- headers['from'] = 'sender@host'
- headers['to'] = 'recipient@domain'
- headers['subject'] = 'booga booga boo'
- headers['content-type'] = 'multipart/alternative; boundary="xyz"'
-
- innerHeaders = util.OrderedDict()
- innerHeaders['subject'] = 'this is subject text'
- innerHeaders['content-type'] = 'text/plain'
- msg = FakeyMessage(headers, (), None, outerBody, 123,
- [FakeyMessage(innerHeaders, (), None, innerBody,
- None, None)],
- )
-
- c = BufferingConsumer()
- p = imap4.MessageProducer(msg)
- d = p.beginProducing(c)
-
- def cbProduced(result):
- self.failUnlessIdentical(result, p)
-
- self.assertEqual(
- ''.join(c.buffer),
-
- '{239}\r\n'
- 'From: sender@host\r\n'
- 'To: recipient@domain\r\n'
- 'Subject: booga booga boo\r\n'
- 'Content-Type: multipart/alternative; boundary="xyz"\r\n'
- '\r\n'
- '\r\n'
- '--xyz\r\n'
- 'Subject: this is subject text\r\n'
- 'Content-Type: text/plain\r\n'
- '\r\n'
- + innerBody
- + '\r\n--xyz--\r\n')
-
- return d.addCallback(cbProduced)
-
-
- def testMultipleMultiPart(self):
- outerBody = ''
- innerBody1 = 'Contained body message text. Squarge.'
- innerBody2 = 'Secondary <i>message</i> text of squarge body.'
- headers = util.OrderedDict()
- headers['from'] = 'sender@host'
- headers['to'] = 'recipient@domain'
- headers['subject'] = 'booga booga boo'
- headers['content-type'] = 'multipart/alternative; boundary="xyz"'
- innerHeaders = util.OrderedDict()
- innerHeaders['subject'] = 'this is subject text'
- innerHeaders['content-type'] = 'text/plain'
- innerHeaders2 = util.OrderedDict()
- innerHeaders2['subject'] = '<b>this is subject</b>'
- innerHeaders2['content-type'] = 'text/html'
- msg = FakeyMessage(headers, (), None, outerBody, 123, [
- FakeyMessage(innerHeaders, (), None, innerBody1, None, None),
- FakeyMessage(innerHeaders2, (), None, innerBody2, None, None)
- ],
- )
-
- c = BufferingConsumer()
- p = imap4.MessageProducer(msg)
- d = p.beginProducing(c)
-
- def cbProduced(result):
- self.failUnlessIdentical(result, p)
-
- self.assertEqual(
- ''.join(c.buffer),
-
- '{354}\r\n'
- 'From: sender@host\r\n'
- 'To: recipient@domain\r\n'
- 'Subject: booga booga boo\r\n'
- 'Content-Type: multipart/alternative; boundary="xyz"\r\n'
- '\r\n'
- '\r\n'
- '--xyz\r\n'
- 'Subject: this is subject text\r\n'
- 'Content-Type: text/plain\r\n'
- '\r\n'
- + innerBody1
- + '\r\n--xyz\r\n'
- 'Subject: <b>this is subject</b>\r\n'
- 'Content-Type: text/html\r\n'
- '\r\n'
- + innerBody2
- + '\r\n--xyz--\r\n')
- return d.addCallback(cbProduced)
-
-
-
-class IMAP4HelperTestCase(unittest.TestCase):
- """
- Tests for various helper utilities in the IMAP4 module.
- """
-
- def test_fileProducer(self):
- b = (('x' * 1) + ('y' * 1) + ('z' * 1)) * 10
- c = BufferingConsumer()
- f = StringIO(b)
- p = imap4.FileProducer(f)
- d = p.beginProducing(c)
-
- def cbProduced(result):
- self.failUnlessIdentical(result, p)
- self.assertEqual(
- ('{%d}\r\n' % len(b))+ b,
- ''.join(c.buffer))
- return d.addCallback(cbProduced)
-
-
- def test_wildcard(self):
- cases = [
- ['foo/%gum/bar',
- ['foo/bar', 'oo/lalagum/bar', 'foo/gumx/bar', 'foo/gum/baz'],
- ['foo/xgum/bar', 'foo/gum/bar'],
- ], ['foo/x%x/bar',
- ['foo', 'bar', 'fuz fuz fuz', 'foo/*/bar', 'foo/xyz/bar', 'foo/xx/baz'],
- ['foo/xyx/bar', 'foo/xx/bar', 'foo/xxxxxxxxxxxxxx/bar'],
- ], ['foo/xyz*abc/bar',
- ['foo/xyz/bar', 'foo/abc/bar', 'foo/xyzab/cbar', 'foo/xyza/bcbar'],
- ['foo/xyzabc/bar', 'foo/xyz/abc/bar', 'foo/xyz/123/abc/bar'],
- ]
- ]
-
- for (wildcard, fail, succeed) in cases:
- wildcard = imap4.wildcardToRegexp(wildcard, '/')
- for x in fail:
- self.failIf(wildcard.match(x))
- for x in succeed:
- self.failUnless(wildcard.match(x))
-
-
- def test_wildcardNoDelim(self):
- cases = [
- ['foo/%gum/bar',
- ['foo/bar', 'oo/lalagum/bar', 'foo/gumx/bar', 'foo/gum/baz'],
- ['foo/xgum/bar', 'foo/gum/bar', 'foo/x/gum/bar'],
- ], ['foo/x%x/bar',
- ['foo', 'bar', 'fuz fuz fuz', 'foo/*/bar', 'foo/xyz/bar', 'foo/xx/baz'],
- ['foo/xyx/bar', 'foo/xx/bar', 'foo/xxxxxxxxxxxxxx/bar', 'foo/x/x/bar'],
- ], ['foo/xyz*abc/bar',
- ['foo/xyz/bar', 'foo/abc/bar', 'foo/xyzab/cbar', 'foo/xyza/bcbar'],
- ['foo/xyzabc/bar', 'foo/xyz/abc/bar', 'foo/xyz/123/abc/bar'],
- ]
- ]
-
- for (wildcard, fail, succeed) in cases:
- wildcard = imap4.wildcardToRegexp(wildcard, None)
- for x in fail:
- self.failIf(wildcard.match(x), x)
- for x in succeed:
- self.failUnless(wildcard.match(x), x)
-
-
- def test_headerFormatter(self):
- """
- L{imap4._formatHeaders} accepts a C{dict} of header name/value pairs and
- returns a string representing those headers in the standard multiline,
- C{":"}-separated format.
- """
- cases = [
- ({'Header1': 'Value1', 'Header2': 'Value2'}, 'Header2: Value2\r\nHeader1: Value1\r\n'),
- ]
-
- for (input, expected) in cases:
- output = imap4._formatHeaders(input)
- self.assertEqual(sorted(output.splitlines(True)),
- sorted(expected.splitlines(True)))
-
-
- def test_messageSet(self):
- m1 = MessageSet()
- m2 = MessageSet()
-
- self.assertEqual(m1, m2)
-
- m1 = m1 + (1, 3)
- self.assertEqual(len(m1), 3)
- self.assertEqual(list(m1), [1, 2, 3])
-
- m2 = m2 + (1, 3)
- self.assertEqual(m1, m2)
- self.assertEqual(list(m1 + m2), [1, 2, 3])
-
-
- def test_messageSetStringRepresentationWithWildcards(self):
- """
- In a L{MessageSet}, in the presence of wildcards, if the highest message
- id is known, the wildcard should get replaced by that high value.
- """
- inputs = [
- MessageSet(imap4.parseIdList('*')),
- MessageSet(imap4.parseIdList('3:*', 6)),
- MessageSet(imap4.parseIdList('*:2', 6)),
- ]
-
- outputs = [
- "*",
- "3:6",
- "2:6",
- ]
-
- for i, o in zip(inputs, outputs):
- self.assertEqual(str(i), o)
-
-
- def test_messageSetStringRepresentationWithInversion(self):
- """
- In a L{MessageSet}, inverting the high and low numbers in a range
- doesn't affect the meaning of the range. For example, 3:2 displays just
- like 2:3, because according to the RFC they have the same meaning.
- """
- inputs = [
- MessageSet(imap4.parseIdList('2:3')),
- MessageSet(imap4.parseIdList('3:2')),
- ]
-
- outputs = [
- "2:3",
- "2:3",
- ]
-
- for i, o in zip(inputs, outputs):
- self.assertEqual(str(i), o)
-
-
- def test_quotedSplitter(self):
- cases = [
- '''Hello World''',
- '''Hello "World!"''',
- '''World "Hello" "How are you?"''',
- '''"Hello world" How "are you?"''',
- '''foo bar "baz buz" NIL''',
- '''foo bar "baz buz" "NIL"''',
- '''foo NIL "baz buz" bar''',
- '''foo "NIL" "baz buz" bar''',
- '''"NIL" bar "baz buz" foo''',
- 'oo \\"oo\\" oo',
- '"oo \\"oo\\" oo"',
- 'oo \t oo',
- '"oo \t oo"',
- 'oo \\t oo',
- '"oo \\t oo"',
- 'oo \o oo',
- '"oo \o oo"',
- 'oo \\o oo',
- '"oo \\o oo"',
- ]
-
- answers = [
- ['Hello', 'World'],
- ['Hello', 'World!'],
- ['World', 'Hello', 'How are you?'],
- ['Hello world', 'How', 'are you?'],
- ['foo', 'bar', 'baz buz', None],
- ['foo', 'bar', 'baz buz', 'NIL'],
- ['foo', None, 'baz buz', 'bar'],
- ['foo', 'NIL', 'baz buz', 'bar'],
- ['NIL', 'bar', 'baz buz', 'foo'],
- ['oo', '"oo"', 'oo'],
- ['oo "oo" oo'],
- ['oo', 'oo'],
- ['oo \t oo'],
- ['oo', '\\t', 'oo'],
- ['oo \\t oo'],
- ['oo', '\o', 'oo'],
- ['oo \o oo'],
- ['oo', '\\o', 'oo'],
- ['oo \\o oo'],
-
- ]
-
- errors = [
- '"mismatched quote',
- 'mismatched quote"',
- 'mismatched"quote',
- '"oops here is" another"',
- ]
-
- for s in errors:
- self.assertRaises(imap4.MismatchedQuoting, imap4.splitQuoted, s)
-
- for (case, expected) in zip(cases, answers):
- self.assertEqual(imap4.splitQuoted(case), expected)
-
-
- def test_stringCollapser(self):
- cases = [
- ['a', 'b', 'c', 'd', 'e'],
- ['a', ' ', '"', 'b', 'c', ' ', '"', ' ', 'd', 'e'],
- [['a', 'b', 'c'], 'd', 'e'],
- ['a', ['b', 'c', 'd'], 'e'],
- ['a', 'b', ['c', 'd', 'e']],
- ['"', 'a', ' ', '"', ['b', 'c', 'd'], '"', ' ', 'e', '"'],
- ['a', ['"', ' ', 'b', 'c', ' ', ' ', '"'], 'd', 'e'],
- ]
-
- answers = [
- ['abcde'],
- ['a', 'bc ', 'de'],
- [['abc'], 'de'],
- ['a', ['bcd'], 'e'],
- ['ab', ['cde']],
- ['a ', ['bcd'], ' e'],
- ['a', [' bc '], 'de'],
- ]
-
- for (case, expected) in zip(cases, answers):
- self.assertEqual(imap4.collapseStrings(case), expected)
-
-
- def test_parenParser(self):
- s = '\r\n'.join(['xx'] * 4)
- cases = [
- '(BODY.PEEK[HEADER.FIELDS.NOT (subject bcc cc)] {%d}\r\n%s)' % (len(s), s,),
-
-# '(FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" '
-# 'RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" '
-# '"IMAP4rev1 WG mtg summary and minutes" '
-# '(("Terry Gray" NIL "gray" "cac.washington.edu")) '
-# '(("Terry Gray" NIL "gray" "cac.washington.edu")) '
-# '(("Terry Gray" NIL "gray" "cac.washington.edu")) '
-# '((NIL NIL "imap" "cac.washington.edu")) '
-# '((NIL NIL "minutes" "CNRI.Reston.VA.US") '
-# '("John Klensin" NIL "KLENSIN" "INFOODS.MIT.EDU")) NIL NIL '
-# '"<B27397-0100000@cac.washington.edu>") '
-# 'BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92))',
-
- '(FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" '
- 'RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" '
- '"IMAP4rev1 WG mtg summary and minutes" '
- '(("Terry Gray" NIL gray cac.washington.edu)) '
- '(("Terry Gray" NIL gray cac.washington.edu)) '
- '(("Terry Gray" NIL gray cac.washington.edu)) '
- '((NIL NIL imap cac.washington.edu)) '
- '((NIL NIL minutes CNRI.Reston.VA.US) '
- '("John Klensin" NIL KLENSIN INFOODS.MIT.EDU)) NIL NIL '
- '<B27397-0100000@cac.washington.edu>) '
- 'BODY (TEXT PLAIN (CHARSET US-ASCII) NIL NIL 7BIT 3028 92))',
- '("oo \\"oo\\" oo")',
- '("oo \\\\ oo")',
- '("oo \\ oo")',
- '("oo \\o")',
- '("oo \o")',
- '(oo \o)',
- '(oo \\o)',
-
- ]
-
- answers = [
- ['BODY.PEEK', ['HEADER.FIELDS.NOT', ['subject', 'bcc', 'cc']], s],
-
- ['FLAGS', [r'\Seen'], 'INTERNALDATE',
- '17-Jul-1996 02:44:25 -0700', 'RFC822.SIZE', '4286', 'ENVELOPE',
- ['Wed, 17 Jul 1996 02:23:25 -0700 (PDT)',
- 'IMAP4rev1 WG mtg summary and minutes', [["Terry Gray", None,
- "gray", "cac.washington.edu"]], [["Terry Gray", None,
- "gray", "cac.washington.edu"]], [["Terry Gray", None,
- "gray", "cac.washington.edu"]], [[None, None, "imap",
- "cac.washington.edu"]], [[None, None, "minutes",
- "CNRI.Reston.VA.US"], ["John Klensin", None, "KLENSIN",
- "INFOODS.MIT.EDU"]], None, None,
- "<B27397-0100000@cac.washington.edu>"], "BODY", ["TEXT", "PLAIN",
- ["CHARSET", "US-ASCII"], None, None, "7BIT", "3028", "92"]],
- ['oo "oo" oo'],
- ['oo \\\\ oo'],
- ['oo \\ oo'],
- ['oo \\o'],
- ['oo \o'],
- ['oo', '\o'],
- ['oo', '\\o'],
- ]
-
- for (case, expected) in zip(cases, answers):
- self.assertEqual(imap4.parseNestedParens(case), [expected])
-
- # XXX This code used to work, but changes occurred within the
- # imap4.py module which made it no longer necessary for *all* of it
- # to work. In particular, only the part that makes
- # 'BODY.PEEK[HEADER.FIELDS.NOT (Subject Bcc Cc)]' come out correctly
- # no longer needs to work. So, I am loathe to delete the entire
- # section of the test. --exarkun
- #
-
-# for (case, expected) in zip(answers, cases):
-# self.assertEqual('(' + imap4.collapseNestedLists(case) + ')', expected)
-
-
- def test_fetchParserSimple(self):
- cases = [
- ['ENVELOPE', 'Envelope'],
- ['FLAGS', 'Flags'],
- ['INTERNALDATE', 'InternalDate'],
- ['RFC822.HEADER', 'RFC822Header'],
- ['RFC822.SIZE', 'RFC822Size'],
- ['RFC822.TEXT', 'RFC822Text'],
- ['RFC822', 'RFC822'],
- ['UID', 'UID'],
- ['BODYSTRUCTURE', 'BodyStructure'],
- ]
-
- for (inp, outp) in cases:
- p = imap4._FetchParser()
- p.parseString(inp)
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], getattr(p, outp)))
-
-
- def test_fetchParserMacros(self):
- cases = [
- ['ALL', (4, ['flags', 'internaldate', 'rfc822.size', 'envelope'])],
- ['FULL', (5, ['flags', 'internaldate', 'rfc822.size', 'envelope', 'body'])],
- ['FAST', (3, ['flags', 'internaldate', 'rfc822.size'])],
- ]
-
- for (inp, outp) in cases:
- p = imap4._FetchParser()
- p.parseString(inp)
- self.assertEqual(len(p.result), outp[0])
- p = [str(p).lower() for p in p.result]
- p.sort()
- outp[1].sort()
- self.assertEqual(p, outp[1])
-
-
- def test_fetchParserBody(self):
- P = imap4._FetchParser
-
- p = P()
- p.parseString('BODY')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, False)
- self.assertEqual(p.result[0].header, None)
- self.assertEqual(str(p.result[0]), 'BODY')
-
- p = P()
- p.parseString('BODY.PEEK')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, True)
- self.assertEqual(str(p.result[0]), 'BODY')
-
- p = P()
- p.parseString('BODY[]')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].empty, True)
- self.assertEqual(str(p.result[0]), 'BODY[]')
-
- p = P()
- p.parseString('BODY[HEADER]')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, False)
- self.failUnless(isinstance(p.result[0].header, p.Header))
- self.assertEqual(p.result[0].header.negate, True)
- self.assertEqual(p.result[0].header.fields, ())
- self.assertEqual(p.result[0].empty, False)
- self.assertEqual(str(p.result[0]), 'BODY[HEADER]')
-
- p = P()
- p.parseString('BODY.PEEK[HEADER]')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, True)
- self.failUnless(isinstance(p.result[0].header, p.Header))
- self.assertEqual(p.result[0].header.negate, True)
- self.assertEqual(p.result[0].header.fields, ())
- self.assertEqual(p.result[0].empty, False)
- self.assertEqual(str(p.result[0]), 'BODY[HEADER]')
-
- p = P()
- p.parseString('BODY[HEADER.FIELDS (Subject Cc Message-Id)]')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, False)
- self.failUnless(isinstance(p.result[0].header, p.Header))
- self.assertEqual(p.result[0].header.negate, False)
- self.assertEqual(p.result[0].header.fields, ['SUBJECT', 'CC', 'MESSAGE-ID'])
- self.assertEqual(p.result[0].empty, False)
- self.assertEqual(str(p.result[0]), 'BODY[HEADER.FIELDS (Subject Cc Message-Id)]')
-
- p = P()
- p.parseString('BODY.PEEK[HEADER.FIELDS (Subject Cc Message-Id)]')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, True)
- self.failUnless(isinstance(p.result[0].header, p.Header))
- self.assertEqual(p.result[0].header.negate, False)
- self.assertEqual(p.result[0].header.fields, ['SUBJECT', 'CC', 'MESSAGE-ID'])
- self.assertEqual(p.result[0].empty, False)
- self.assertEqual(str(p.result[0]), 'BODY[HEADER.FIELDS (Subject Cc Message-Id)]')
-
- p = P()
- p.parseString('BODY.PEEK[HEADER.FIELDS.NOT (Subject Cc Message-Id)]')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, True)
- self.failUnless(isinstance(p.result[0].header, p.Header))
- self.assertEqual(p.result[0].header.negate, True)
- self.assertEqual(p.result[0].header.fields, ['SUBJECT', 'CC', 'MESSAGE-ID'])
- self.assertEqual(p.result[0].empty, False)
- self.assertEqual(str(p.result[0]), 'BODY[HEADER.FIELDS.NOT (Subject Cc Message-Id)]')
-
- p = P()
- p.parseString('BODY[1.MIME]<10.50>')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, False)
- self.failUnless(isinstance(p.result[0].mime, p.MIME))
- self.assertEqual(p.result[0].part, (0,))
- self.assertEqual(p.result[0].partialBegin, 10)
- self.assertEqual(p.result[0].partialLength, 50)
- self.assertEqual(p.result[0].empty, False)
- self.assertEqual(str(p.result[0]), 'BODY[1.MIME]<10.50>')
-
- p = P()
- p.parseString('BODY.PEEK[1.3.9.11.HEADER.FIELDS.NOT (Message-Id Date)]<103.69>')
- self.assertEqual(len(p.result), 1)
- self.failUnless(isinstance(p.result[0], p.Body))
- self.assertEqual(p.result[0].peek, True)
- self.failUnless(isinstance(p.result[0].header, p.Header))
- self.assertEqual(p.result[0].part, (0, 2, 8, 10))
- self.assertEqual(p.result[0].header.fields, ['MESSAGE-ID', 'DATE'])
- self.assertEqual(p.result[0].partialBegin, 103)
- self.assertEqual(p.result[0].partialLength, 69)
- self.assertEqual(p.result[0].empty, False)
- self.assertEqual(str(p.result[0]), 'BODY[1.3.9.11.HEADER.FIELDS.NOT (Message-Id Date)]<103.69>')
-
-
- def test_files(self):
- inputStructure = [
- 'foo', 'bar', 'baz', StringIO('this is a file\r\n'), 'buz'
- ]
-
- output = '"foo" "bar" "baz" {16}\r\nthis is a file\r\n "buz"'
-
- self.assertEqual(imap4.collapseNestedLists(inputStructure), output)
-
-
- def test_quoteAvoider(self):
- input = [
- 'foo', imap4.DontQuoteMe('bar'), "baz", StringIO('this is a file\r\n'),
- imap4.DontQuoteMe('buz'), ""
- ]
-
- output = '"foo" bar "baz" {16}\r\nthis is a file\r\n buz ""'
-
- self.assertEqual(imap4.collapseNestedLists(input), output)
-
-
- def test_literals(self):
- cases = [
- ('({10}\r\n0123456789)', [['0123456789']]),
- ]
-
- for (case, expected) in cases:
- self.assertEqual(imap4.parseNestedParens(case), expected)
-
-
- def test_queryBuilder(self):
- inputs = [
- imap4.Query(flagged=1),
- imap4.Query(sorted=1, unflagged=1, deleted=1),
- imap4.Or(imap4.Query(flagged=1), imap4.Query(deleted=1)),
- imap4.Query(before='today'),
- imap4.Or(
- imap4.Query(deleted=1),
- imap4.Query(unseen=1),
- imap4.Query(new=1)
- ),
- imap4.Or(
- imap4.Not(
- imap4.Or(
- imap4.Query(sorted=1, since='yesterday', smaller=1000),
- imap4.Query(sorted=1, before='tuesday', larger=10000),
- imap4.Query(sorted=1, unseen=1, deleted=1, before='today'),
- imap4.Not(
- imap4.Query(subject='spam')
- ),
- ),
- ),
- imap4.Not(
- imap4.Query(uid='1:5')
- ),
- )
- ]
-
- outputs = [
- 'FLAGGED',
- '(DELETED UNFLAGGED)',
- '(OR FLAGGED DELETED)',
- '(BEFORE "today")',
- '(OR DELETED (OR UNSEEN NEW))',
- '(OR (NOT (OR (SINCE "yesterday" SMALLER 1000) ' # Continuing
- '(OR (BEFORE "tuesday" LARGER 10000) (OR (BEFORE ' # Some more
- '"today" DELETED UNSEEN) (NOT (SUBJECT "spam")))))) ' # And more
- '(NOT (UID 1:5)))',
- ]
-
- for (query, expected) in zip(inputs, outputs):
- self.assertEqual(query, expected)
-
-
- def test_invalidIdListParser(self):
- """
- Trying to parse an invalid representation of a sequence range raises an
- L{IllegalIdentifierError}.
- """
- inputs = [
- '*:*',
- 'foo',
- '4:',
- 'bar:5'
- ]
-
- for input in inputs:
- self.assertRaises(imap4.IllegalIdentifierError,
- imap4.parseIdList, input, 12345)
-
-
- def test_invalidIdListParserNonPositive(self):
- """
- Zeroes and negative values are not accepted in id range expressions. RFC
- 3501 states that sequence numbers and sequence ranges consist of
- non-negative numbers (RFC 3501 section 9, the seq-number grammar item).
- """
- inputs = [
- '0:5',
- '0:0',
- '*:0',
- '0',
- '-3:5',
- '1:-2',
- '-1'
- ]
-
- for input in inputs:
- self.assertRaises(imap4.IllegalIdentifierError,
- imap4.parseIdList, input, 12345)
-
-
- def test_parseIdList(self):
- """
- The function to parse sequence ranges yields appropriate L{MessageSet}
- objects.
- """
- inputs = [
- '1:*',
- '5:*',
- '1:2,5:*',
- '*',
- '1',
- '1,2',
- '1,3,5',
- '1:10',
- '1:10,11',
- '1:5,10:20',
- '1,5:10',
- '1,5:10,15:20',
- '1:10,15,20:25',
- '4:2'
- ]
-
- outputs = [
- MessageSet(1, None),
- MessageSet(5, None),
- MessageSet(5, None) + MessageSet(1, 2),
- MessageSet(None, None),
- MessageSet(1),
- MessageSet(1, 2),
- MessageSet(1) + MessageSet(3) + MessageSet(5),
- MessageSet(1, 10),
- MessageSet(1, 11),
- MessageSet(1, 5) + MessageSet(10, 20),
- MessageSet(1) + MessageSet(5, 10),
- MessageSet(1) + MessageSet(5, 10) + MessageSet(15, 20),
- MessageSet(1, 10) + MessageSet(15) + MessageSet(20, 25),
- MessageSet(2, 4),
- ]
-
- lengths = [
- None, None, None,
- 1, 1, 2, 3, 10, 11, 16, 7, 13, 17, 3
- ]
-
- for (input, expected) in zip(inputs, outputs):
- self.assertEqual(imap4.parseIdList(input), expected)
-
- for (input, expected) in zip(inputs, lengths):
- if expected is None:
- self.assertRaises(TypeError, len, imap4.parseIdList(input))
- else:
- L = len(imap4.parseIdList(input))
- self.assertEqual(L, expected,
- "len(%r) = %r != %r" % (input, L, expected))
-
-class SimpleMailbox:
- implements(imap4.IMailboxInfo, imap4.IMailbox, imap4.ICloseableMailbox)
-
- flags = ('\\Flag1', 'Flag2', '\\AnotherSysFlag', 'LastFlag')
- messages = []
- mUID = 0
- rw = 1
- closed = False
-
- def __init__(self):
- self.listeners = []
- self.addListener = self.listeners.append
- self.removeListener = self.listeners.remove
-
- def getFlags(self):
- return self.flags
-
- def getUIDValidity(self):
- return 42
-
- def getUIDNext(self):
- return len(self.messages) + 1
-
- def getMessageCount(self):
- return 9
-
- def getRecentCount(self):
- return 3
-
- def getUnseenCount(self):
- return 4
-
- def isWriteable(self):
- return self.rw
-
- def destroy(self):
- pass
-
- def getHierarchicalDelimiter(self):
- return '/'
-
- def requestStatus(self, names):
- r = {}
- if 'MESSAGES' in names:
- r['MESSAGES'] = self.getMessageCount()
- if 'RECENT' in names:
- r['RECENT'] = self.getRecentCount()
- if 'UIDNEXT' in names:
- r['UIDNEXT'] = self.getMessageCount() + 1
- if 'UIDVALIDITY' in names:
- r['UIDVALIDITY'] = self.getUID()
- if 'UNSEEN' in names:
- r['UNSEEN'] = self.getUnseenCount()
- return defer.succeed(r)
-
- def addMessage(self, message, flags, date = None):
- self.messages.append((message, flags, date, self.mUID))
- self.mUID += 1
- return defer.succeed(None)
-
- def expunge(self):
- delete = []
- for i in self.messages:
- if '\\Deleted' in i[1]:
- delete.append(i)
- for i in delete:
- self.messages.remove(i)
- return [i[3] for i in delete]
-
- def close(self):
- self.closed = True
-
-class Account(imap4.MemoryAccount):
- mailboxFactory = SimpleMailbox
- def _emptyMailbox(self, name, id):
- return self.mailboxFactory()
-
- def select(self, name, rw=1):
- mbox = imap4.MemoryAccount.select(self, name)
- if mbox is not None:
- mbox.rw = rw
- return mbox
-
-class SimpleServer(imap4.IMAP4Server):
- def __init__(self, *args, **kw):
- imap4.IMAP4Server.__init__(self, *args, **kw)
- realm = TestRealm()
- realm.theAccount = Account('testuser')
- portal = cred.portal.Portal(realm)
- c = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()
- self.checker = c
- self.portal = portal
- portal.registerChecker(c)
- self.timeoutTest = False
-
- def lineReceived(self, line):
- if self.timeoutTest:
- #Do not send a respones
- return
-
- imap4.IMAP4Server.lineReceived(self, line)
-
- _username = 'testuser'
- _password = 'password-test'
- def authenticateLogin(self, username, password):
- if username == self._username and password == self._password:
- return imap4.IAccount, self.theAccount, lambda: None
- raise cred.error.UnauthorizedLogin()
-
-
-class SimpleClient(imap4.IMAP4Client):
- def __init__(self, deferred, contextFactory = None):
- imap4.IMAP4Client.__init__(self, contextFactory)
- self.deferred = deferred
- self.events = []
-
- def serverGreeting(self, caps):
- self.deferred.callback(None)
-
- def modeChanged(self, writeable):
- self.events.append(['modeChanged', writeable])
- self.transport.loseConnection()
-
- def flagsChanged(self, newFlags):
- self.events.append(['flagsChanged', newFlags])
- self.transport.loseConnection()
-
- def newMessages(self, exists, recent):
- self.events.append(['newMessages', exists, recent])
- self.transport.loseConnection()
-
-
-
-class IMAP4HelperMixin:
-
- serverCTX = None
- clientCTX = None
-
- def setUp(self):
- d = defer.Deferred()
- self.server = SimpleServer(contextFactory=self.serverCTX)
- self.client = SimpleClient(d, contextFactory=self.clientCTX)
- self.connected = d
-
- SimpleMailbox.messages = []
- theAccount = Account('testuser')
- theAccount.mboxType = SimpleMailbox
- SimpleServer.theAccount = theAccount
-
- def tearDown(self):
- del self.server
- del self.client
- del self.connected
-
- def _cbStopClient(self, ignore):
- self.client.transport.loseConnection()
-
- def _ebGeneral(self, failure):
- self.client.transport.loseConnection()
- self.server.transport.loseConnection()
- failure.raiseException()
-
- def loopback(self):
- return loopback.loopbackAsync(self.server, self.client)
-
-class IMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase):
- def testCapability(self):
- caps = {}
- def getCaps():
- def gotCaps(c):
- caps.update(c)
- self.server.transport.loseConnection()
- return self.client.getCapabilities().addCallback(gotCaps)
- d1 = self.connected.addCallback(strip(getCaps)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- expected = {'IMAP4rev1': None, 'NAMESPACE': None, 'IDLE': None}
- return d.addCallback(lambda _: self.assertEqual(expected, caps))
-
- def testCapabilityWithAuth(self):
- caps = {}
- self.server.challengers['CRAM-MD5'] = cred.credentials.CramMD5Credentials
- def getCaps():
- def gotCaps(c):
- caps.update(c)
- self.server.transport.loseConnection()
- return self.client.getCapabilities().addCallback(gotCaps)
- d1 = self.connected.addCallback(strip(getCaps)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
-
- expCap = {'IMAP4rev1': None, 'NAMESPACE': None,
- 'IDLE': None, 'AUTH': ['CRAM-MD5']}
-
- return d.addCallback(lambda _: self.assertEqual(expCap, caps))
-
- def testLogout(self):
- self.loggedOut = 0
- def logout():
- def setLoggedOut():
- self.loggedOut = 1
- self.client.logout().addCallback(strip(setLoggedOut))
- self.connected.addCallback(strip(logout)).addErrback(self._ebGeneral)
- d = self.loopback()
- return d.addCallback(lambda _: self.assertEqual(self.loggedOut, 1))
-
- def testNoop(self):
- self.responses = None
- def noop():
- def setResponses(responses):
- self.responses = responses
- self.server.transport.loseConnection()
- self.client.noop().addCallback(setResponses)
- self.connected.addCallback(strip(noop)).addErrback(self._ebGeneral)
- d = self.loopback()
- return d.addCallback(lambda _: self.assertEqual(self.responses, []))
-
- def testLogin(self):
- def login():
- d = self.client.login('testuser', 'password-test')
- d.addCallback(self._cbStopClient)
- d1 = self.connected.addCallback(strip(login)).addErrback(self._ebGeneral)
- d = defer.gatherResults([d1, self.loopback()])
- return d.addCallback(self._cbTestLogin)
-
- def _cbTestLogin(self, ignored):
- self.assertEqual(self.server.account, SimpleServer.theAccount)
- self.assertEqual(self.server.state, 'auth')
-
- def testFailedLogin(self):
- def login():
- d = self.client.login('testuser', 'wrong-password')
- d.addBoth(self._cbStopClient)
-
- d1 = self.connected.addCallback(strip(login)).addErrback(self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestFailedLogin)
-
- def _cbTestFailedLogin(self, ignored):
- self.assertEqual(self.server.account, None)
- self.assertEqual(self.server.state, 'unauth')
-
-
- def testLoginRequiringQuoting(self):
- self.server._username = '{test}user'
- self.server._password = '{test}password'
-
- def login():
- d = self.client.login('{test}user', '{test}password')
- d.addBoth(self._cbStopClient)
-
- d1 = self.connected.addCallback(strip(login)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestLoginRequiringQuoting)
-
- def _cbTestLoginRequiringQuoting(self, ignored):
- self.assertEqual(self.server.account, SimpleServer.theAccount)
- self.assertEqual(self.server.state, 'auth')
-
-
- def testNamespace(self):
- self.namespaceArgs = None
- def login():
- return self.client.login('testuser', 'password-test')
- def namespace():
- def gotNamespace(args):
- self.namespaceArgs = args
- self._cbStopClient(None)
- return self.client.namespace().addCallback(gotNamespace)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(namespace))
- d1.addErrback(self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _: self.assertEqual(self.namespaceArgs,
- [[['', '/']], [], []]))
- return d
-
- def testSelect(self):
- SimpleServer.theAccount.addMailbox('test-mailbox')
- self.selectedArgs = None
- def login():
- return self.client.login('testuser', 'password-test')
- def select():
- def selected(args):
- self.selectedArgs = args
- self._cbStopClient(None)
- d = self.client.select('test-mailbox')
- d.addCallback(selected)
- return d
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(select))
- d1.addErrback(self._ebGeneral)
- d2 = self.loopback()
- return defer.gatherResults([d1, d2]).addCallback(self._cbTestSelect)
-
- def _cbTestSelect(self, ignored):
- mbox = SimpleServer.theAccount.mailboxes['TEST-MAILBOX']
- self.assertEqual(self.server.mbox, mbox)
- self.assertEqual(self.selectedArgs, {
- 'EXISTS': 9, 'RECENT': 3, 'UIDVALIDITY': 42,
- 'FLAGS': ('\\Flag1', 'Flag2', '\\AnotherSysFlag', 'LastFlag'),
- 'READ-WRITE': 1
- })
-
-
- def test_examine(self):
- """
- L{IMAP4Client.examine} issues an I{EXAMINE} command to the server and
- returns a L{Deferred} which fires with a C{dict} with as many of the
- following keys as the server includes in its response: C{'FLAGS'},
- C{'EXISTS'}, C{'RECENT'}, C{'UNSEEN'}, C{'READ-WRITE'}, C{'READ-ONLY'},
- C{'UIDVALIDITY'}, and C{'PERMANENTFLAGS'}.
-
- Unfortunately the server doesn't generate all of these so it's hard to
- test the client's handling of them here. See
- L{IMAP4ClientExamineTests} below.
-
- See U{RFC 3501<http://www.faqs.org/rfcs/rfc3501.html>}, section 6.3.2,
- for details.
- """
- SimpleServer.theAccount.addMailbox('test-mailbox')
- self.examinedArgs = None
- def login():
- return self.client.login('testuser', 'password-test')
- def examine():
- def examined(args):
- self.examinedArgs = args
- self._cbStopClient(None)
- d = self.client.examine('test-mailbox')
- d.addCallback(examined)
- return d
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(examine))
- d1.addErrback(self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestExamine)
-
-
- def _cbTestExamine(self, ignored):
- mbox = SimpleServer.theAccount.mailboxes['TEST-MAILBOX']
- self.assertEqual(self.server.mbox, mbox)
- self.assertEqual(self.examinedArgs, {
- 'EXISTS': 9, 'RECENT': 3, 'UIDVALIDITY': 42,
- 'FLAGS': ('\\Flag1', 'Flag2', '\\AnotherSysFlag', 'LastFlag'),
- 'READ-WRITE': False})
-
-
- def testCreate(self):
- succeed = ('testbox', 'test/box', 'test/', 'test/box/box', 'INBOX')
- fail = ('testbox', 'test/box')
-
- def cb(): self.result.append(1)
- def eb(failure): self.result.append(0)
-
- def login():
- return self.client.login('testuser', 'password-test')
- def create():
- for name in succeed + fail:
- d = self.client.create(name)
- d.addCallback(strip(cb)).addErrback(eb)
- d.addCallbacks(self._cbStopClient, self._ebGeneral)
-
- self.result = []
- d1 = self.connected.addCallback(strip(login)).addCallback(strip(create))
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestCreate, succeed, fail)
-
- def _cbTestCreate(self, ignored, succeed, fail):
- self.assertEqual(self.result, [1] * len(succeed) + [0] * len(fail))
- mbox = SimpleServer.theAccount.mailboxes.keys()
- answers = ['inbox', 'testbox', 'test/box', 'test', 'test/box/box']
- mbox.sort()
- answers.sort()
- self.assertEqual(mbox, [a.upper() for a in answers])
-
- def testDelete(self):
- SimpleServer.theAccount.addMailbox('delete/me')
-
- def login():
- return self.client.login('testuser', 'password-test')
- def delete():
- return self.client.delete('delete/me')
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(delete), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _:
- self.assertEqual(SimpleServer.theAccount.mailboxes.keys(), []))
- return d
-
- def testIllegalInboxDelete(self):
- self.stashed = None
- def login():
- return self.client.login('testuser', 'password-test')
- def delete():
- return self.client.delete('inbox')
- def stash(result):
- self.stashed = result
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(delete), self._ebGeneral)
- d1.addBoth(stash)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _: self.failUnless(isinstance(self.stashed,
- failure.Failure)))
- return d
-
-
- def testNonExistentDelete(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def delete():
- return self.client.delete('delete/me')
- def deleteFailed(failure):
- self.failure = failure
-
- self.failure = None
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(delete)).addErrback(deleteFailed)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _: self.assertEqual(str(self.failure.value),
- 'No such mailbox'))
- return d
-
-
- def testIllegalDelete(self):
- m = SimpleMailbox()
- m.flags = (r'\Noselect',)
- SimpleServer.theAccount.addMailbox('delete', m)
- SimpleServer.theAccount.addMailbox('delete/me')
-
- def login():
- return self.client.login('testuser', 'password-test')
- def delete():
- return self.client.delete('delete')
- def deleteFailed(failure):
- self.failure = failure
-
- self.failure = None
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(delete)).addErrback(deleteFailed)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- expected = "Hierarchically inferior mailboxes exist and \\Noselect is set"
- d.addCallback(lambda _:
- self.assertEqual(str(self.failure.value), expected))
- return d
-
- def testRename(self):
- SimpleServer.theAccount.addMailbox('oldmbox')
- def login():
- return self.client.login('testuser', 'password-test')
- def rename():
- return self.client.rename('oldmbox', 'newname')
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(rename), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _:
- self.assertEqual(SimpleServer.theAccount.mailboxes.keys(),
- ['NEWNAME']))
- return d
-
- def testIllegalInboxRename(self):
- self.stashed = None
- def login():
- return self.client.login('testuser', 'password-test')
- def rename():
- return self.client.rename('inbox', 'frotz')
- def stash(stuff):
- self.stashed = stuff
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(rename), self._ebGeneral)
- d1.addBoth(stash)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _:
- self.failUnless(isinstance(self.stashed, failure.Failure)))
- return d
-
- def testHierarchicalRename(self):
- SimpleServer.theAccount.create('oldmbox/m1')
- SimpleServer.theAccount.create('oldmbox/m2')
- def login():
- return self.client.login('testuser', 'password-test')
- def rename():
- return self.client.rename('oldmbox', 'newname')
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(rename), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestHierarchicalRename)
-
- def _cbTestHierarchicalRename(self, ignored):
- mboxes = SimpleServer.theAccount.mailboxes.keys()
- expected = ['newname', 'newname/m1', 'newname/m2']
- mboxes.sort()
- self.assertEqual(mboxes, [s.upper() for s in expected])
-
- def testSubscribe(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def subscribe():
- return self.client.subscribe('this/mbox')
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(subscribe), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _:
- self.assertEqual(SimpleServer.theAccount.subscriptions,
- ['THIS/MBOX']))
- return d
-
- def testUnsubscribe(self):
- SimpleServer.theAccount.subscriptions = ['THIS/MBOX', 'THAT/MBOX']
- def login():
- return self.client.login('testuser', 'password-test')
- def unsubscribe():
- return self.client.unsubscribe('this/mbox')
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(unsubscribe), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _:
- self.assertEqual(SimpleServer.theAccount.subscriptions,
- ['THAT/MBOX']))
- return d
-
- def _listSetup(self, f):
- SimpleServer.theAccount.addMailbox('root/subthing')
- SimpleServer.theAccount.addMailbox('root/another-thing')
- SimpleServer.theAccount.addMailbox('non-root/subthing')
-
- def login():
- return self.client.login('testuser', 'password-test')
- def listed(answers):
- self.listed = answers
-
- self.listed = None
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(f), self._ebGeneral)
- d1.addCallbacks(listed, self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- return defer.gatherResults([d1, d2]).addCallback(lambda _: self.listed)
-
- def testList(self):
- def list():
- return self.client.list('root', '%')
- d = self._listSetup(list)
- d.addCallback(lambda listed: self.assertEqual(
- sortNest(listed),
- sortNest([
- (SimpleMailbox.flags, "/", "ROOT/SUBTHING"),
- (SimpleMailbox.flags, "/", "ROOT/ANOTHER-THING")
- ])
- ))
- return d
-
- def testLSub(self):
- SimpleServer.theAccount.subscribe('ROOT/SUBTHING')
- def lsub():
- return self.client.lsub('root', '%')
- d = self._listSetup(lsub)
- d.addCallback(self.assertEqual,
- [(SimpleMailbox.flags, "/", "ROOT/SUBTHING")])
- return d
-
- def testStatus(self):
- SimpleServer.theAccount.addMailbox('root/subthing')
- def login():
- return self.client.login('testuser', 'password-test')
- def status():
- return self.client.status('root/subthing', 'MESSAGES', 'UIDNEXT', 'UNSEEN')
- def statused(result):
- self.statused = result
-
- self.statused = None
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(status), self._ebGeneral)
- d1.addCallbacks(statused, self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- d.addCallback(lambda _: self.assertEqual(
- self.statused,
- {'MESSAGES': 9, 'UIDNEXT': '10', 'UNSEEN': 4}
- ))
- return d
-
- def testFailedStatus(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def status():
- return self.client.status('root/nonexistent', 'MESSAGES', 'UIDNEXT', 'UNSEEN')
- def statused(result):
- self.statused = result
- def failed(failure):
- self.failure = failure
-
- self.statused = self.failure = None
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(status), self._ebGeneral)
- d1.addCallbacks(statused, failed)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- return defer.gatherResults([d1, d2]).addCallback(self._cbTestFailedStatus)
-
- def _cbTestFailedStatus(self, ignored):
- self.assertEqual(
- self.statused, None
- )
- self.assertEqual(
- self.failure.value.args,
- ('Could not open mailbox',)
- )
-
- def testFullAppend(self):
- infile = util.sibpath(__file__, 'rfc822.message')
- message = open(infile)
- SimpleServer.theAccount.addMailbox('root/subthing')
- def login():
- return self.client.login('testuser', 'password-test')
- def append():
- return self.client.append(
- 'root/subthing',
- message,
- ('\\SEEN', '\\DELETED'),
- 'Tue, 17 Jun 2003 11:22:16 -0600 (MDT)',
- )
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(append), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestFullAppend, infile)
-
- def _cbTestFullAppend(self, ignored, infile):
- mb = SimpleServer.theAccount.mailboxes['ROOT/SUBTHING']
- self.assertEqual(1, len(mb.messages))
- self.assertEqual(
- (['\\SEEN', '\\DELETED'], 'Tue, 17 Jun 2003 11:22:16 -0600 (MDT)', 0),
- mb.messages[0][1:]
- )
- self.assertEqual(open(infile).read(), mb.messages[0][0].getvalue())
-
- def testPartialAppend(self):
- infile = util.sibpath(__file__, 'rfc822.message')
- message = open(infile)
- SimpleServer.theAccount.addMailbox('PARTIAL/SUBTHING')
- def login():
- return self.client.login('testuser', 'password-test')
- def append():
- message = file(infile)
- return self.client.sendCommand(
- imap4.Command(
- 'APPEND',
- 'PARTIAL/SUBTHING (\\SEEN) "Right now" {%d}' % os.path.getsize(infile),
- (), self.client._IMAP4Client__cbContinueAppend, message
- )
- )
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(append), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestPartialAppend, infile)
-
- def _cbTestPartialAppend(self, ignored, infile):
- mb = SimpleServer.theAccount.mailboxes['PARTIAL/SUBTHING']
- self.assertEqual(1, len(mb.messages))
- self.assertEqual(
- (['\\SEEN'], 'Right now', 0),
- mb.messages[0][1:]
- )
- self.assertEqual(open(infile).read(), mb.messages[0][0].getvalue())
-
- def testCheck(self):
- SimpleServer.theAccount.addMailbox('root/subthing')
- def login():
- return self.client.login('testuser', 'password-test')
- def select():
- return self.client.select('root/subthing')
- def check():
- return self.client.check()
-
- d = self.connected.addCallback(strip(login))
- d.addCallbacks(strip(select), self._ebGeneral)
- d.addCallbacks(strip(check), self._ebGeneral)
- d.addCallbacks(self._cbStopClient, self._ebGeneral)
- return self.loopback()
-
- # Okay, that was fun
-
- def testClose(self):
- m = SimpleMailbox()
- m.messages = [
- ('Message 1', ('\\Deleted', 'AnotherFlag'), None, 0),
- ('Message 2', ('AnotherFlag',), None, 1),
- ('Message 3', ('\\Deleted',), None, 2),
- ]
- SimpleServer.theAccount.addMailbox('mailbox', m)
- def login():
- return self.client.login('testuser', 'password-test')
- def select():
- return self.client.select('mailbox')
- def close():
- return self.client.close()
-
- d = self.connected.addCallback(strip(login))
- d.addCallbacks(strip(select), self._ebGeneral)
- d.addCallbacks(strip(close), self._ebGeneral)
- d.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- return defer.gatherResults([d, d2]).addCallback(self._cbTestClose, m)
-
- def _cbTestClose(self, ignored, m):
- self.assertEqual(len(m.messages), 1)
- self.assertEqual(m.messages[0], ('Message 2', ('AnotherFlag',), None, 1))
- self.failUnless(m.closed)
-
- def testExpunge(self):
- m = SimpleMailbox()
- m.messages = [
- ('Message 1', ('\\Deleted', 'AnotherFlag'), None, 0),
- ('Message 2', ('AnotherFlag',), None, 1),
- ('Message 3', ('\\Deleted',), None, 2),
- ]
- SimpleServer.theAccount.addMailbox('mailbox', m)
- def login():
- return self.client.login('testuser', 'password-test')
- def select():
- return self.client.select('mailbox')
- def expunge():
- return self.client.expunge()
- def expunged(results):
- self.failIf(self.server.mbox is None)
- self.results = results
-
- self.results = None
- d1 = self.connected.addCallback(strip(login))
- d1.addCallbacks(strip(select), self._ebGeneral)
- d1.addCallbacks(strip(expunge), self._ebGeneral)
- d1.addCallbacks(expunged, self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestExpunge, m)
-
- def _cbTestExpunge(self, ignored, m):
- self.assertEqual(len(m.messages), 1)
- self.assertEqual(m.messages[0], ('Message 2', ('AnotherFlag',), None, 1))
-
- self.assertEqual(self.results, [0, 2])
-
-
-
-class IMAP4ServerSearchTestCase(IMAP4HelperMixin, unittest.TestCase):
- """
- Tests for the behavior of the search_* functions in L{imap4.IMAP4Server}.
- """
- def setUp(self):
- IMAP4HelperMixin.setUp(self)
- self.earlierQuery = ["10-Dec-2009"]
- self.sameDateQuery = ["13-Dec-2009"]
- self.laterQuery = ["16-Dec-2009"]
- self.seq = 0
- self.msg = FakeyMessage({"date" : "Mon, 13 Dec 2009 21:25:10 GMT"}, [],
- '', '', 1234, None)
-
-
- def test_searchSentBefore(self):
- """
- L{imap4.IMAP4Server.search_SENTBEFORE} returns True if the message date
- is earlier than the query date.
- """
- self.assertFalse(
- self.server.search_SENTBEFORE(self.earlierQuery, self.seq, self.msg))
- self.assertTrue(
- self.server.search_SENTBEFORE(self.laterQuery, self.seq, self.msg))
-
- def test_searchWildcard(self):
- """
- L{imap4.IMAP4Server.search_UID} returns True if the message UID is in
- the search range.
- """
- self.assertFalse(
- self.server.search_UID(['2:3'], self.seq, self.msg, (1, 1234)))
- # 2:* should get translated to 2:<max UID> and then to 1:2
- self.assertTrue(
- self.server.search_UID(['2:*'], self.seq, self.msg, (1, 1234)))
- self.assertTrue(
- self.server.search_UID(['*'], self.seq, self.msg, (1, 1234)))
-
- def test_searchWildcardHigh(self):
- """
- L{imap4.IMAP4Server.search_UID} should return True if there is a
- wildcard, because a wildcard means "highest UID in the mailbox".
- """
- self.assertTrue(
- self.server.search_UID(['1235:*'], self.seq, self.msg, (1234, 1)))
-
- def test_reversedSearchTerms(self):
- """
- L{imap4.IMAP4Server.search_SENTON} returns True if the message date is
- the same as the query date.
- """
- msgset = imap4.parseIdList('4:2')
- self.assertEqual(list(msgset), [2, 3, 4])
-
- def test_searchSentOn(self):
- """
- L{imap4.IMAP4Server.search_SENTON} returns True if the message date is
- the same as the query date.
- """
- self.assertFalse(
- self.server.search_SENTON(self.earlierQuery, self.seq, self.msg))
- self.assertTrue(
- self.server.search_SENTON(self.sameDateQuery, self.seq, self.msg))
- self.assertFalse(
- self.server.search_SENTON(self.laterQuery, self.seq, self.msg))
-
-
- def test_searchSentSince(self):
- """
- L{imap4.IMAP4Server.search_SENTSINCE} returns True if the message date
- is later than the query date.
- """
- self.assertTrue(
- self.server.search_SENTSINCE(self.earlierQuery, self.seq, self.msg))
- self.assertFalse(
- self.server.search_SENTSINCE(self.laterQuery, self.seq, self.msg))
-
-
- def test_searchOr(self):
- """
- L{imap4.IMAP4Server.search_OR} returns true if either of the two
- expressions supplied to it returns true and returns false if neither
- does.
- """
- self.assertTrue(
- self.server.search_OR(
- ["SENTSINCE"] + self.earlierQuery +
- ["SENTSINCE"] + self.laterQuery,
- self.seq, self.msg, (None, None)))
- self.assertTrue(
- self.server.search_OR(
- ["SENTSINCE"] + self.laterQuery +
- ["SENTSINCE"] + self.earlierQuery,
- self.seq, self.msg, (None, None)))
- self.assertFalse(
- self.server.search_OR(
- ["SENTON"] + self.laterQuery +
- ["SENTSINCE"] + self.laterQuery,
- self.seq, self.msg, (None, None)))
-
-
- def test_searchNot(self):
- """
- L{imap4.IMAP4Server.search_NOT} returns the negation of the result
- of the expression supplied to it.
- """
- self.assertFalse(self.server.search_NOT(
- ["SENTSINCE"] + self.earlierQuery, self.seq, self.msg,
- (None, None)))
- self.assertTrue(self.server.search_NOT(
- ["SENTON"] + self.laterQuery, self.seq, self.msg,
- (None, None)))
-
-
-
-class TestRealm:
- theAccount = None
-
- def requestAvatar(self, avatarId, mind, *interfaces):
- return imap4.IAccount, self.theAccount, lambda: None
-
-class TestChecker:
- credentialInterfaces = (cred.credentials.IUsernameHashedPassword, cred.credentials.IUsernamePassword)
-
- users = {
- 'testuser': 'secret'
- }
-
- def requestAvatarId(self, credentials):
- if credentials.username in self.users:
- return defer.maybeDeferred(
- credentials.checkPassword, self.users[credentials.username]
- ).addCallback(self._cbCheck, credentials.username)
-
- def _cbCheck(self, result, username):
- if result:
- return username
- raise cred.error.UnauthorizedLogin()
-
-class AuthenticatorTestCase(IMAP4HelperMixin, unittest.TestCase):
- def setUp(self):
- IMAP4HelperMixin.setUp(self)
-
- realm = TestRealm()
- realm.theAccount = Account('testuser')
- portal = cred.portal.Portal(realm)
- portal.registerChecker(TestChecker())
- self.server.portal = portal
-
- self.authenticated = 0
- self.account = realm.theAccount
-
- def testCramMD5(self):
- self.server.challengers['CRAM-MD5'] = cred.credentials.CramMD5Credentials
- cAuth = imap4.CramMD5ClientAuthenticator('testuser')
- self.client.registerAuthenticator(cAuth)
-
- def auth():
- return self.client.authenticate('secret')
- def authed():
- self.authenticated = 1
-
- d1 = self.connected.addCallback(strip(auth))
- d1.addCallbacks(strip(authed), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d2 = self.loopback()
- d = defer.gatherResults([d1, d2])
- return d.addCallback(self._cbTestCramMD5)
-
- def _cbTestCramMD5(self, ignored):
- self.assertEqual(self.authenticated, 1)
- self.assertEqual(self.server.account, self.account)
-
- def testFailedCramMD5(self):
- self.server.challengers['CRAM-MD5'] = cred.credentials.CramMD5Credentials
- cAuth = imap4.CramMD5ClientAuthenticator('testuser')
- self.client.registerAuthenticator(cAuth)
-
- def misauth():
- return self.client.authenticate('not the secret')
- def authed():
- self.authenticated = 1
- def misauthed():
- self.authenticated = -1
-
- d1 = self.connected.addCallback(strip(misauth))
- d1.addCallbacks(strip(authed), strip(misauthed))
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestFailedCramMD5)
-
- def _cbTestFailedCramMD5(self, ignored):
- self.assertEqual(self.authenticated, -1)
- self.assertEqual(self.server.account, None)
-
- def testLOGIN(self):
- self.server.challengers['LOGIN'] = imap4.LOGINCredentials
- cAuth = imap4.LOGINAuthenticator('testuser')
- self.client.registerAuthenticator(cAuth)
-
- def auth():
- return self.client.authenticate('secret')
- def authed():
- self.authenticated = 1
-
- d1 = self.connected.addCallback(strip(auth))
- d1.addCallbacks(strip(authed), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestLOGIN)
-
- def _cbTestLOGIN(self, ignored):
- self.assertEqual(self.authenticated, 1)
- self.assertEqual(self.server.account, self.account)
-
- def testFailedLOGIN(self):
- self.server.challengers['LOGIN'] = imap4.LOGINCredentials
- cAuth = imap4.LOGINAuthenticator('testuser')
- self.client.registerAuthenticator(cAuth)
-
- def misauth():
- return self.client.authenticate('not the secret')
- def authed():
- self.authenticated = 1
- def misauthed():
- self.authenticated = -1
-
- d1 = self.connected.addCallback(strip(misauth))
- d1.addCallbacks(strip(authed), strip(misauthed))
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestFailedLOGIN)
-
- def _cbTestFailedLOGIN(self, ignored):
- self.assertEqual(self.authenticated, -1)
- self.assertEqual(self.server.account, None)
-
- def testPLAIN(self):
- self.server.challengers['PLAIN'] = imap4.PLAINCredentials
- cAuth = imap4.PLAINAuthenticator('testuser')
- self.client.registerAuthenticator(cAuth)
-
- def auth():
- return self.client.authenticate('secret')
- def authed():
- self.authenticated = 1
-
- d1 = self.connected.addCallback(strip(auth))
- d1.addCallbacks(strip(authed), self._ebGeneral)
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestPLAIN)
-
- def _cbTestPLAIN(self, ignored):
- self.assertEqual(self.authenticated, 1)
- self.assertEqual(self.server.account, self.account)
-
- def testFailedPLAIN(self):
- self.server.challengers['PLAIN'] = imap4.PLAINCredentials
- cAuth = imap4.PLAINAuthenticator('testuser')
- self.client.registerAuthenticator(cAuth)
-
- def misauth():
- return self.client.authenticate('not the secret')
- def authed():
- self.authenticated = 1
- def misauthed():
- self.authenticated = -1
-
- d1 = self.connected.addCallback(strip(misauth))
- d1.addCallbacks(strip(authed), strip(misauthed))
- d1.addCallbacks(self._cbStopClient, self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestFailedPLAIN)
-
- def _cbTestFailedPLAIN(self, ignored):
- self.assertEqual(self.authenticated, -1)
- self.assertEqual(self.server.account, None)
-
-
-
-class SASLPLAINTestCase(unittest.TestCase):
- """
- Tests for I{SASL PLAIN} authentication, as implemented by
- L{imap4.PLAINAuthenticator} and L{imap4.PLAINCredentials}.
-
- @see: U{http://www.faqs.org/rfcs/rfc2595.html}
- @see: U{http://www.faqs.org/rfcs/rfc4616.html}
- """
- def test_authenticatorChallengeResponse(self):
- """
- L{PLAINAuthenticator.challengeResponse} returns challenge strings of
- the form::
-
- NUL<authn-id>NUL<secret>
- """
- username = 'testuser'
- secret = 'secret'
- chal = 'challenge'
- cAuth = imap4.PLAINAuthenticator(username)
- response = cAuth.challengeResponse(secret, chal)
- self.assertEqual(response, '\0%s\0%s' % (username, secret))
-
-
- def test_credentialsSetResponse(self):
- """
- L{PLAINCredentials.setResponse} parses challenge strings of the
- form::
-
- NUL<authn-id>NUL<secret>
- """
- cred = imap4.PLAINCredentials()
- cred.setResponse('\0testuser\0secret')
- self.assertEqual(cred.username, 'testuser')
- self.assertEqual(cred.password, 'secret')
-
-
- def test_credentialsInvalidResponse(self):
- """
- L{PLAINCredentials.setResponse} raises L{imap4.IllegalClientResponse}
- when passed a string not of the expected form.
- """
- cred = imap4.PLAINCredentials()
- self.assertRaises(
- imap4.IllegalClientResponse, cred.setResponse, 'hello')
- self.assertRaises(
- imap4.IllegalClientResponse, cred.setResponse, 'hello\0world')
- self.assertRaises(
- imap4.IllegalClientResponse, cred.setResponse,
- 'hello\0world\0Zoom!\0')
-
-
-
-class UnsolicitedResponseTestCase(IMAP4HelperMixin, unittest.TestCase):
- def testReadWrite(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def loggedIn():
- self.server.modeChanged(1)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestReadWrite)
-
- def _cbTestReadWrite(self, ignored):
- E = self.client.events
- self.assertEqual(E, [['modeChanged', 1]])
-
- def testReadOnly(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def loggedIn():
- self.server.modeChanged(0)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestReadOnly)
-
- def _cbTestReadOnly(self, ignored):
- E = self.client.events
- self.assertEqual(E, [['modeChanged', 0]])
-
- def testFlagChange(self):
- flags = {
- 1: ['\\Answered', '\\Deleted'],
- 5: [],
- 10: ['\\Recent']
- }
- def login():
- return self.client.login('testuser', 'password-test')
- def loggedIn():
- self.server.flagsChanged(flags)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestFlagChange, flags)
-
- def _cbTestFlagChange(self, ignored, flags):
- E = self.client.events
- expect = [['flagsChanged', {x[0]: x[1]}] for x in flags.items()]
- E.sort()
- expect.sort()
- self.assertEqual(E, expect)
-
- def testNewMessages(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def loggedIn():
- self.server.newMessages(10, None)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestNewMessages)
-
- def _cbTestNewMessages(self, ignored):
- E = self.client.events
- self.assertEqual(E, [['newMessages', 10, None]])
-
- def testNewRecentMessages(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def loggedIn():
- self.server.newMessages(None, 10)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestNewRecentMessages)
-
- def _cbTestNewRecentMessages(self, ignored):
- E = self.client.events
- self.assertEqual(E, [['newMessages', None, 10]])
-
- def testNewMessagesAndRecent(self):
- def login():
- return self.client.login('testuser', 'password-test')
- def loggedIn():
- self.server.newMessages(20, 10)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(loggedIn)).addErrback(self._ebGeneral)
- d = defer.gatherResults([self.loopback(), d1])
- return d.addCallback(self._cbTestNewMessagesAndRecent)
-
- def _cbTestNewMessagesAndRecent(self, ignored):
- E = self.client.events
- self.assertEqual(E, [['newMessages', 20, None], ['newMessages', None, 10]])
-
-
-class ClientCapabilityTests(unittest.TestCase):
- """
- Tests for issuance of the CAPABILITY command and handling of its response.
- """
- def setUp(self):
- """
- Create an L{imap4.IMAP4Client} connected to a L{StringTransport}.
- """
- self.transport = StringTransport()
- self.protocol = imap4.IMAP4Client()
- self.protocol.makeConnection(self.transport)
- self.protocol.dataReceived('* OK [IMAP4rev1]\r\n')
-
-
- def test_simpleAtoms(self):
- """
- A capability response consisting only of atoms without C{'='} in them
- should result in a dict mapping those atoms to C{None}.
- """
- capabilitiesResult = self.protocol.getCapabilities(useCache=False)
- self.protocol.dataReceived('* CAPABILITY IMAP4rev1 LOGINDISABLED\r\n')
- self.protocol.dataReceived('0001 OK Capability completed.\r\n')
- def gotCapabilities(capabilities):
- self.assertEqual(
- capabilities, {'IMAP4rev1': None, 'LOGINDISABLED': None})
- capabilitiesResult.addCallback(gotCapabilities)
- return capabilitiesResult
-
-
- def test_categoryAtoms(self):
- """
- A capability response consisting of atoms including C{'='} should have
- those atoms split on that byte and have capabilities in the same
- category aggregated into lists in the resulting dictionary.
-
- (n.b. - I made up the word "category atom"; the protocol has no notion
- of structure here, but rather allows each capability to define the
- semantics of its entry in the capability response in a freeform manner.
- If I had realized this earlier, the API for capabilities would look
- different. As it is, we can hope that no one defines any crazy
- semantics which are incompatible with this API, or try to figure out a
- better API when someone does. -exarkun)
- """
- capabilitiesResult = self.protocol.getCapabilities(useCache=False)
- self.protocol.dataReceived('* CAPABILITY IMAP4rev1 AUTH=LOGIN AUTH=PLAIN\r\n')
- self.protocol.dataReceived('0001 OK Capability completed.\r\n')
- def gotCapabilities(capabilities):
- self.assertEqual(
- capabilities, {'IMAP4rev1': None, 'AUTH': ['LOGIN', 'PLAIN']})
- capabilitiesResult.addCallback(gotCapabilities)
- return capabilitiesResult
-
-
- def test_mixedAtoms(self):
- """
- A capability response consisting of both simple and category atoms of
- the same type should result in a list containing C{None} as well as the
- values for the category.
- """
- capabilitiesResult = self.protocol.getCapabilities(useCache=False)
- # Exercise codepath for both orderings of =-having and =-missing
- # capabilities.
- self.protocol.dataReceived(
- '* CAPABILITY IMAP4rev1 FOO FOO=BAR BAR=FOO BAR\r\n')
- self.protocol.dataReceived('0001 OK Capability completed.\r\n')
- def gotCapabilities(capabilities):
- self.assertEqual(capabilities, {'IMAP4rev1': None,
- 'FOO': [None, 'BAR'],
- 'BAR': ['FOO', None]})
- capabilitiesResult.addCallback(gotCapabilities)
- return capabilitiesResult
-
-
-
-class StillSimplerClient(imap4.IMAP4Client):
- """
- An IMAP4 client which keeps track of unsolicited flag changes.
- """
- def __init__(self):
- imap4.IMAP4Client.__init__(self)
- self.flags = {}
-
-
- def flagsChanged(self, newFlags):
- self.flags.update(newFlags)
-
-
-
-class HandCraftedTestCase(IMAP4HelperMixin, unittest.TestCase):
- def testTrailingLiteral(self):
- transport = StringTransport()
- c = imap4.IMAP4Client()
- c.makeConnection(transport)
- c.lineReceived('* OK [IMAP4rev1]')
-
- def cbSelect(ignored):
- d = c.fetchMessage('1')
- c.dataReceived('* 1 FETCH (RFC822 {10}\r\n0123456789\r\n RFC822.SIZE 10)\r\n')
- c.dataReceived('0003 OK FETCH\r\n')
- return d
-
- def cbLogin(ignored):
- d = c.select('inbox')
- c.lineReceived('0002 OK SELECT')
- d.addCallback(cbSelect)
- return d
-
- d = c.login('blah', 'blah')
- c.dataReceived('0001 OK LOGIN\r\n')
- d.addCallback(cbLogin)
- return d
-
-
- def testPathelogicalScatteringOfLiterals(self):
- self.server.checker.addUser('testuser', 'password-test')
- transport = StringTransport()
- self.server.makeConnection(transport)
-
- transport.clear()
- self.server.dataReceived("01 LOGIN {8}\r\n")
- self.assertEqual(transport.value(), "+ Ready for 8 octets of text\r\n")
-
- transport.clear()
- self.server.dataReceived("testuser {13}\r\n")
- self.assertEqual(transport.value(), "+ Ready for 13 octets of text\r\n")
-
- transport.clear()
- self.server.dataReceived("password-test\r\n")
- self.assertEqual(transport.value(), "01 OK LOGIN succeeded\r\n")
- self.assertEqual(self.server.state, 'auth')
-
- self.server.connectionLost(error.ConnectionDone("Connection done."))
-
-
- def test_unsolicitedResponseMixedWithSolicitedResponse(self):
- """
- If unsolicited data is received along with solicited data in the
- response to a I{FETCH} command issued by L{IMAP4Client.fetchSpecific},
- the unsolicited data is passed to the appropriate callback and not
- included in the result with wihch the L{Deferred} returned by
- L{IMAP4Client.fetchSpecific} fires.
- """
- transport = StringTransport()
- c = StillSimplerClient()
- c.makeConnection(transport)
- c.lineReceived('* OK [IMAP4rev1]')
-
- def login():
- d = c.login('blah', 'blah')
- c.dataReceived('0001 OK LOGIN\r\n')
- return d
- def select():
- d = c.select('inbox')
- c.lineReceived('0002 OK SELECT')
- return d
- def fetch():
- d = c.fetchSpecific('1:*',
- headerType='HEADER.FIELDS',
- headerArgs=['SUBJECT'])
- c.dataReceived('* 1 FETCH (BODY[HEADER.FIELDS ("SUBJECT")] {38}\r\n')
- c.dataReceived('Subject: Suprise for your woman...\r\n')
- c.dataReceived('\r\n')
- c.dataReceived(')\r\n')
- c.dataReceived('* 1 FETCH (FLAGS (\Seen))\r\n')
- c.dataReceived('* 2 FETCH (BODY[HEADER.FIELDS ("SUBJECT")] {75}\r\n')
- c.dataReceived('Subject: What you been doing. Order your meds here . ,. handcuff madsen\r\n')
- c.dataReceived('\r\n')
- c.dataReceived(')\r\n')
- c.dataReceived('0003 OK FETCH completed\r\n')
- return d
- def test(res):
- self.assertEqual(res, {
- 1: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
- 'Subject: Suprise for your woman...\r\n\r\n']],
- 2: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
- 'Subject: What you been doing. Order your meds here . ,. handcuff madsen\r\n\r\n']]
- })
-
- self.assertEqual(c.flags, {1: ['\\Seen']})
-
- return login(
- ).addCallback(strip(select)
- ).addCallback(strip(fetch)
- ).addCallback(test)
-
-
- def test_literalWithoutPrecedingWhitespace(self):
- """
- Literals should be recognized even when they are not preceded by
- whitespace.
- """
- transport = StringTransport()
- protocol = imap4.IMAP4Client()
-
- protocol.makeConnection(transport)
- protocol.lineReceived('* OK [IMAP4rev1]')
-
- def login():
- d = protocol.login('blah', 'blah')
- protocol.dataReceived('0001 OK LOGIN\r\n')
- return d
- def select():
- d = protocol.select('inbox')
- protocol.lineReceived('0002 OK SELECT')
- return d
- def fetch():
- d = protocol.fetchSpecific('1:*',
- headerType='HEADER.FIELDS',
- headerArgs=['SUBJECT'])
- protocol.dataReceived(
- '* 1 FETCH (BODY[HEADER.FIELDS ({7}\r\nSUBJECT)] "Hello")\r\n')
- protocol.dataReceived('0003 OK FETCH completed\r\n')
- return d
- def test(result):
- self.assertEqual(
- result, {1: [['BODY', ['HEADER.FIELDS', ['SUBJECT']], 'Hello']]})
-
- d = login()
- d.addCallback(strip(select))
- d.addCallback(strip(fetch))
- d.addCallback(test)
- return d
-
-
- def test_nonIntegerLiteralLength(self):
- """
- If the server sends a literal length which cannot be parsed as an
- integer, L{IMAP4Client.lineReceived} should cause the protocol to be
- disconnected by raising L{imap4.IllegalServerResponse}.
- """
- transport = StringTransport()
- protocol = imap4.IMAP4Client()
-
- protocol.makeConnection(transport)
- protocol.lineReceived('* OK [IMAP4rev1]')
-
- def login():
- d = protocol.login('blah', 'blah')
- protocol.dataReceived('0001 OK LOGIN\r\n')
- return d
- def select():
- d = protocol.select('inbox')
- protocol.lineReceived('0002 OK SELECT')
- return d
- def fetch():
- d = protocol.fetchSpecific('1:*',
- headerType='HEADER.FIELDS',
- headerArgs=['SUBJECT'])
- self.assertRaises(
- imap4.IllegalServerResponse,
- protocol.dataReceived,
- '* 1 FETCH {xyz}\r\n...')
- d = login()
- d.addCallback(strip(select))
- d.addCallback(strip(fetch))
- return d
-
-
- def test_flagsChangedInsideFetchSpecificResponse(self):
- """
- Any unrequested flag information received along with other requested
- information in an untagged I{FETCH} received in response to a request
- issued with L{IMAP4Client.fetchSpecific} is passed to the
- C{flagsChanged} callback.
- """
- transport = StringTransport()
- c = StillSimplerClient()
- c.makeConnection(transport)
- c.lineReceived('* OK [IMAP4rev1]')
-
- def login():
- d = c.login('blah', 'blah')
- c.dataReceived('0001 OK LOGIN\r\n')
- return d
- def select():
- d = c.select('inbox')
- c.lineReceived('0002 OK SELECT')
- return d
- def fetch():
- d = c.fetchSpecific('1:*',
- headerType='HEADER.FIELDS',
- headerArgs=['SUBJECT'])
- # This response includes FLAGS after the requested data.
- c.dataReceived('* 1 FETCH (BODY[HEADER.FIELDS ("SUBJECT")] {22}\r\n')
- c.dataReceived('Subject: subject one\r\n')
- c.dataReceived(' FLAGS (\\Recent))\r\n')
- # And this one includes it before! Either is possible.
- c.dataReceived('* 2 FETCH (FLAGS (\\Seen) BODY[HEADER.FIELDS ("SUBJECT")] {22}\r\n')
- c.dataReceived('Subject: subject two\r\n')
- c.dataReceived(')\r\n')
- c.dataReceived('0003 OK FETCH completed\r\n')
- return d
-
- def test(res):
- self.assertEqual(res, {
- 1: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
- 'Subject: subject one\r\n']],
- 2: [['BODY', ['HEADER.FIELDS', ['SUBJECT']],
- 'Subject: subject two\r\n']]
- })
-
- self.assertEqual(c.flags, {1: ['\\Recent'], 2: ['\\Seen']})
-
- return login(
- ).addCallback(strip(select)
- ).addCallback(strip(fetch)
- ).addCallback(test)
-
-
- def test_flagsChangedInsideFetchMessageResponse(self):
- """
- Any unrequested flag information received along with other requested
- information in an untagged I{FETCH} received in response to a request
- issued with L{IMAP4Client.fetchMessage} is passed to the
- C{flagsChanged} callback.
- """
- transport = StringTransport()
- c = StillSimplerClient()
- c.makeConnection(transport)
- c.lineReceived('* OK [IMAP4rev1]')
-
- def login():
- d = c.login('blah', 'blah')
- c.dataReceived('0001 OK LOGIN\r\n')
- return d
- def select():
- d = c.select('inbox')
- c.lineReceived('0002 OK SELECT')
- return d
- def fetch():
- d = c.fetchMessage('1:*')
- c.dataReceived('* 1 FETCH (RFC822 {24}\r\n')
- c.dataReceived('Subject: first subject\r\n')
- c.dataReceived(' FLAGS (\Seen))\r\n')
- c.dataReceived('* 2 FETCH (FLAGS (\Recent \Seen) RFC822 {25}\r\n')
- c.dataReceived('Subject: second subject\r\n')
- c.dataReceived(')\r\n')
- c.dataReceived('0003 OK FETCH completed\r\n')
- return d
-
- def test(res):
- self.assertEqual(res, {
- 1: {'RFC822': 'Subject: first subject\r\n'},
- 2: {'RFC822': 'Subject: second subject\r\n'}})
-
- self.assertEqual(
- c.flags, {1: ['\\Seen'], 2: ['\\Recent', '\\Seen']})
-
- return login(
- ).addCallback(strip(select)
- ).addCallback(strip(fetch)
- ).addCallback(test)
-
-
-
-class PreauthIMAP4ClientMixin:
- """
- Mixin for L{unittest.TestCase} subclasses which provides a C{setUp} method
- which creates an L{IMAP4Client} connected to a L{StringTransport} and puts
- it into the I{authenticated} state.
-
- @ivar transport: A L{StringTransport} to which C{client} is connected.
- @ivar client: An L{IMAP4Client} which is connected to C{transport}.
- """
- clientProtocol = imap4.IMAP4Client
-
- def setUp(self):
- """
- Create an IMAP4Client connected to a fake transport and in the
- authenticated state.
- """
- self.transport = StringTransport()
- self.client = self.clientProtocol()
- self.client.makeConnection(self.transport)
- self.client.dataReceived('* PREAUTH Hello unittest\r\n')
-
-
- def _extractDeferredResult(self, d):
- """
- Synchronously extract the result of the given L{Deferred}. Fail the
- test if that is not possible.
- """
- result = []
- error = []
- d.addCallbacks(result.append, error.append)
- if result:
- return result[0]
- elif error:
- error[0].raiseException()
- else:
- self.fail("Expected result not available")
-
-
-
-class SelectionTestsMixin(PreauthIMAP4ClientMixin):
- """
- Mixin for test cases which defines tests which apply to both I{EXAMINE} and
- I{SELECT} support.
- """
- def _examineOrSelect(self):
- """
- Issue either an I{EXAMINE} or I{SELECT} command (depending on
- C{self.method}), assert that the correct bytes are written to the
- transport, and return the L{Deferred} returned by whichever method was
- called.
- """
- d = getattr(self.client, self.method)('foobox')
- self.assertEqual(
- self.transport.value(), '0001 %s foobox\r\n' % (self.command,))
- return d
-
-
- def _response(self, *lines):
- """
- Deliver the given (unterminated) response lines to C{self.client} and
- then deliver a tagged SELECT or EXAMINE completion line to finish the
- SELECT or EXAMINE response.
- """
- for line in lines:
- self.client.dataReceived(line + '\r\n')
- self.client.dataReceived(
- '0001 OK [READ-ONLY] %s completed\r\n' % (self.command,))
-
-
- def test_exists(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{EXISTS} response, the L{Deferred} return by L{IMAP4Client.select} or
- L{IMAP4Client.examine} fires with a C{dict} including the value
- associated with the C{'EXISTS'} key.
- """
- d = self._examineOrSelect()
- self._response('* 3 EXISTS')
- self.assertEqual(
- self._extractDeferredResult(d),
- {'READ-WRITE': False, 'EXISTS': 3})
-
-
- def test_nonIntegerExists(self):
- """
- If the server returns a non-integer EXISTS value in its response to a
- I{SELECT} or I{EXAMINE} command, the L{Deferred} returned by
- L{IMAP4Client.select} or L{IMAP4Client.examine} fails with
- L{IllegalServerResponse}.
- """
- d = self._examineOrSelect()
- self._response('* foo EXISTS')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_recent(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{RECENT} response, the L{Deferred} return by L{IMAP4Client.select} or
- L{IMAP4Client.examine} fires with a C{dict} including the value
- associated with the C{'RECENT'} key.
- """
- d = self._examineOrSelect()
- self._response('* 5 RECENT')
- self.assertEqual(
- self._extractDeferredResult(d),
- {'READ-WRITE': False, 'RECENT': 5})
-
-
- def test_nonIntegerRecent(self):
- """
- If the server returns a non-integer RECENT value in its response to a
- I{SELECT} or I{EXAMINE} command, the L{Deferred} returned by
- L{IMAP4Client.select} or L{IMAP4Client.examine} fails with
- L{IllegalServerResponse}.
- """
- d = self._examineOrSelect()
- self._response('* foo RECENT')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_unseen(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{UNSEEN} response, the L{Deferred} returned by L{IMAP4Client.select} or
- L{IMAP4Client.examine} fires with a C{dict} including the value
- associated with the C{'UNSEEN'} key.
- """
- d = self._examineOrSelect()
- self._response('* OK [UNSEEN 8] Message 8 is first unseen')
- self.assertEqual(
- self._extractDeferredResult(d),
- {'READ-WRITE': False, 'UNSEEN': 8})
-
-
- def test_nonIntegerUnseen(self):
- """
- If the server returns a non-integer UNSEEN value in its response to a
- I{SELECT} or I{EXAMINE} command, the L{Deferred} returned by
- L{IMAP4Client.select} or L{IMAP4Client.examine} fails with
- L{IllegalServerResponse}.
- """
- d = self._examineOrSelect()
- self._response('* OK [UNSEEN foo] Message foo is first unseen')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_uidvalidity(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{UIDVALIDITY} response, the L{Deferred} returned by
- L{IMAP4Client.select} or L{IMAP4Client.examine} fires with a C{dict}
- including the value associated with the C{'UIDVALIDITY'} key.
- """
- d = self._examineOrSelect()
- self._response('* OK [UIDVALIDITY 12345] UIDs valid')
- self.assertEqual(
- self._extractDeferredResult(d),
- {'READ-WRITE': False, 'UIDVALIDITY': 12345})
-
-
- def test_nonIntegerUIDVALIDITY(self):
- """
- If the server returns a non-integer UIDVALIDITY value in its response to
- a I{SELECT} or I{EXAMINE} command, the L{Deferred} returned by
- L{IMAP4Client.select} or L{IMAP4Client.examine} fails with
- L{IllegalServerResponse}.
- """
- d = self._examineOrSelect()
- self._response('* OK [UIDVALIDITY foo] UIDs valid')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_uidnext(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{UIDNEXT} response, the L{Deferred} returned by L{IMAP4Client.select}
- or L{IMAP4Client.examine} fires with a C{dict} including the value
- associated with the C{'UIDNEXT'} key.
- """
- d = self._examineOrSelect()
- self._response('* OK [UIDNEXT 4392] Predicted next UID')
- self.assertEqual(
- self._extractDeferredResult(d),
- {'READ-WRITE': False, 'UIDNEXT': 4392})
-
-
- def test_nonIntegerUIDNEXT(self):
- """
- If the server returns a non-integer UIDNEXT value in its response to a
- I{SELECT} or I{EXAMINE} command, the L{Deferred} returned by
- L{IMAP4Client.select} or L{IMAP4Client.examine} fails with
- L{IllegalServerResponse}.
- """
- d = self._examineOrSelect()
- self._response('* OK [UIDNEXT foo] Predicted next UID')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_flags(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{FLAGS} response, the L{Deferred} returned by L{IMAP4Client.select} or
- L{IMAP4Client.examine} fires with a C{dict} including the value
- associated with the C{'FLAGS'} key.
- """
- d = self._examineOrSelect()
- self._response(
- '* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)')
- self.assertEqual(
- self._extractDeferredResult(d), {
- 'READ-WRITE': False,
- 'FLAGS': ('\\Answered', '\\Flagged', '\\Deleted', '\\Seen',
- '\\Draft')})
-
-
- def test_permanentflags(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{FLAGS} response, the L{Deferred} returned by L{IMAP4Client.select} or
- L{IMAP4Client.examine} fires with a C{dict} including the value
- associated with the C{'FLAGS'} key.
- """
- d = self._examineOrSelect()
- self._response(
- '* OK [PERMANENTFLAGS (\\Starred)] Just one permanent flag in '
- 'that list up there')
- self.assertEqual(
- self._extractDeferredResult(d), {
- 'READ-WRITE': False,
- 'PERMANENTFLAGS': ('\\Starred',)})
-
-
- def test_unrecognizedOk(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{OK} with unrecognized response code text, parsing does not fail.
- """
- d = self._examineOrSelect()
- self._response(
- '* OK [X-MADE-UP] I just made this response text up.')
- # The value won't show up in the result. It would be okay if it did
- # someday, perhaps. This shouldn't ever happen, though.
- self.assertEqual(
- self._extractDeferredResult(d), {'READ-WRITE': False})
-
-
- def test_bareOk(self):
- """
- If the server response to a I{SELECT} or I{EXAMINE} command includes an
- I{OK} with no response code text, parsing does not fail.
- """
- d = self._examineOrSelect()
- self._response('* OK')
- self.assertEqual(
- self._extractDeferredResult(d), {'READ-WRITE': False})
-
-
-
-class IMAP4ClientExamineTests(SelectionTestsMixin, unittest.TestCase):
- """
- Tests for the L{IMAP4Client.examine} method.
-
- An example of usage of the EXAMINE command from RFC 3501, section 6.3.2::
-
- S: * 17 EXISTS
- S: * 2 RECENT
- S: * OK [UNSEEN 8] Message 8 is first unseen
- S: * OK [UIDVALIDITY 3857529045] UIDs valid
- S: * OK [UIDNEXT 4392] Predicted next UID
- S: * FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)
- S: * OK [PERMANENTFLAGS ()] No permanent flags permitted
- S: A932 OK [READ-ONLY] EXAMINE completed
- """
- method = 'examine'
- command = 'EXAMINE'
-
-
-
-
-class IMAP4ClientSelectTests(SelectionTestsMixin, unittest.TestCase):
- """
- Tests for the L{IMAP4Client.select} method.
-
- An example of usage of the SELECT command from RFC 3501, section 6.3.1::
-
- C: A142 SELECT INBOX
- S: * 172 EXISTS
- S: * 1 RECENT
- S: * OK [UNSEEN 12] Message 12 is first unseen
- S: * OK [UIDVALIDITY 3857529045] UIDs valid
- S: * OK [UIDNEXT 4392] Predicted next UID
- S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
- S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited
- S: A142 OK [READ-WRITE] SELECT completed
- """
- method = 'select'
- command = 'SELECT'
-
-
-
-class IMAP4ClientExpungeTests(PreauthIMAP4ClientMixin, unittest.TestCase):
- """
- Tests for the L{IMAP4Client.expunge} method.
-
- An example of usage of the EXPUNGE command from RFC 3501, section 6.4.3::
-
- C: A202 EXPUNGE
- S: * 3 EXPUNGE
- S: * 3 EXPUNGE
- S: * 5 EXPUNGE
- S: * 8 EXPUNGE
- S: A202 OK EXPUNGE completed
- """
- def _expunge(self):
- d = self.client.expunge()
- self.assertEqual(self.transport.value(), '0001 EXPUNGE\r\n')
- self.transport.clear()
- return d
-
-
- def _response(self, sequenceNumbers):
- for number in sequenceNumbers:
- self.client.lineReceived('* %s EXPUNGE' % (number,))
- self.client.lineReceived('0001 OK EXPUNGE COMPLETED')
-
-
- def test_expunge(self):
- """
- L{IMAP4Client.expunge} sends the I{EXPUNGE} command and returns a
- L{Deferred} which fires with a C{list} of message sequence numbers
- given by the server's response.
- """
- d = self._expunge()
- self._response([3, 3, 5, 8])
- self.assertEqual(self._extractDeferredResult(d), [3, 3, 5, 8])
-
-
- def test_nonIntegerExpunged(self):
- """
- If the server responds with a non-integer where a message sequence
- number is expected, the L{Deferred} returned by L{IMAP4Client.expunge}
- fails with L{IllegalServerResponse}.
- """
- d = self._expunge()
- self._response([3, 3, 'foo', 8])
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
-
-class IMAP4ClientSearchTests(PreauthIMAP4ClientMixin, unittest.TestCase):
- """
- Tests for the L{IMAP4Client.search} method.
-
- An example of usage of the SEARCH command from RFC 3501, section 6.4.4::
-
- C: A282 SEARCH FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"
- S: * SEARCH 2 84 882
- S: A282 OK SEARCH completed
- C: A283 SEARCH TEXT "string not in mailbox"
- S: * SEARCH
- S: A283 OK SEARCH completed
- C: A284 SEARCH CHARSET UTF-8 TEXT {6}
- C: XXXXXX
- S: * SEARCH 43
- S: A284 OK SEARCH completed
- """
- def _search(self):
- d = self.client.search(imap4.Query(text="ABCDEF"))
- self.assertEqual(
- self.transport.value(), '0001 SEARCH (TEXT "ABCDEF")\r\n')
- return d
-
-
- def _response(self, messageNumbers):
- self.client.lineReceived(
- "* SEARCH " + " ".join(map(str, messageNumbers)))
- self.client.lineReceived("0001 OK SEARCH completed")
-
-
- def test_search(self):
- """
- L{IMAP4Client.search} sends the I{SEARCH} command and returns a
- L{Deferred} which fires with a C{list} of message sequence numbers
- given by the server's response.
- """
- d = self._search()
- self._response([2, 5, 10])
- self.assertEqual(self._extractDeferredResult(d), [2, 5, 10])
-
-
- def test_nonIntegerFound(self):
- """
- If the server responds with a non-integer where a message sequence
- number is expected, the L{Deferred} returned by L{IMAP4Client.search}
- fails with L{IllegalServerResponse}.
- """
- d = self._search()
- self._response([2, "foo", 10])
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
-
-class IMAP4ClientFetchTests(PreauthIMAP4ClientMixin, unittest.TestCase):
- """
- Tests for the L{IMAP4Client.fetch} method.
-
- See RFC 3501, section 6.4.5.
- """
- def test_fetchUID(self):
- """
- L{IMAP4Client.fetchUID} sends the I{FETCH UID} command and returns a
- L{Deferred} which fires with a C{dict} mapping message sequence numbers
- to C{dict}s mapping C{'UID'} to that message's I{UID} in the server's
- response.
- """
- d = self.client.fetchUID('1:7')
- self.assertEqual(self.transport.value(), '0001 FETCH 1:7 (UID)\r\n')
- self.client.lineReceived('* 2 FETCH (UID 22)')
- self.client.lineReceived('* 3 FETCH (UID 23)')
- self.client.lineReceived('* 4 FETCH (UID 24)')
- self.client.lineReceived('* 5 FETCH (UID 25)')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d), {
- 2: {'UID': '22'},
- 3: {'UID': '23'},
- 4: {'UID': '24'},
- 5: {'UID': '25'}})
-
-
- def test_fetchUIDNonIntegerFound(self):
- """
- If the server responds with a non-integer where a message sequence
- number is expected, the L{Deferred} returned by L{IMAP4Client.fetchUID}
- fails with L{IllegalServerResponse}.
- """
- d = self.client.fetchUID('1')
- self.assertEqual(self.transport.value(), '0001 FETCH 1 (UID)\r\n')
- self.client.lineReceived('* foo FETCH (UID 22)')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_incompleteFetchUIDResponse(self):
- """
- If the server responds with an incomplete I{FETCH} response line, the
- L{Deferred} returned by L{IMAP4Client.fetchUID} fails with
- L{IllegalServerResponse}.
- """
- d = self.client.fetchUID('1:7')
- self.assertEqual(self.transport.value(), '0001 FETCH 1:7 (UID)\r\n')
- self.client.lineReceived('* 2 FETCH (UID 22)')
- self.client.lineReceived('* 3 FETCH (UID)')
- self.client.lineReceived('* 4 FETCH (UID 24)')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_fetchBody(self):
- """
- L{IMAP4Client.fetchBody} sends the I{FETCH BODY} command and returns a
- L{Deferred} which fires with a C{dict} mapping message sequence numbers
- to C{dict}s mapping C{'RFC822.TEXT'} to that message's body as given in
- the server's response.
- """
- d = self.client.fetchBody('3')
- self.assertEqual(
- self.transport.value(), '0001 FETCH 3 (RFC822.TEXT)\r\n')
- self.client.lineReceived('* 3 FETCH (RFC822.TEXT "Message text")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d),
- {3: {'RFC822.TEXT': 'Message text'}})
-
-
- def test_fetchSpecific(self):
- """
- L{IMAP4Client.fetchSpecific} sends the I{BODY[]} command if no
- parameters beyond the message set to retrieve are given. It returns a
- L{Deferred} which fires with a C{dict} mapping message sequence numbers
- to C{list}s of corresponding message data given by the server's
- response.
- """
- d = self.client.fetchSpecific('7')
- self.assertEqual(
- self.transport.value(), '0001 FETCH 7 BODY[]\r\n')
- self.client.lineReceived('* 7 FETCH (BODY[] "Some body")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d), {7: [['BODY', [], "Some body"]]})
-
-
- def test_fetchSpecificPeek(self):
- """
- L{IMAP4Client.fetchSpecific} issues a I{BODY.PEEK[]} command if passed
- C{True} for the C{peek} parameter.
- """
- d = self.client.fetchSpecific('6', peek=True)
- self.assertEqual(
- self.transport.value(), '0001 FETCH 6 BODY.PEEK[]\r\n')
- # BODY.PEEK responses are just BODY
- self.client.lineReceived('* 6 FETCH (BODY[] "Some body")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d), {6: [['BODY', [], "Some body"]]})
-
-
- def test_fetchSpecificNumbered(self):
- """
- L{IMAP4Client.fetchSpecific}, when passed a sequence for for
- C{headerNumber}, sends the I{BODY[N.M]} command. It returns a
- L{Deferred} which fires with a C{dict} mapping message sequence numbers
- to C{list}s of corresponding message data given by the server's
- response.
- """
- d = self.client.fetchSpecific('7', headerNumber=(1, 2, 3))
- self.assertEqual(
- self.transport.value(), '0001 FETCH 7 BODY[1.2.3]\r\n')
- self.client.lineReceived('* 7 FETCH (BODY[1.2.3] "Some body")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d),
- {7: [['BODY', ['1.2.3'], "Some body"]]})
-
-
- def test_fetchSpecificText(self):
- """
- L{IMAP4Client.fetchSpecific}, when passed C{'TEXT'} for C{headerType},
- sends the I{BODY[TEXT]} command. It returns a L{Deferred} which fires
- with a C{dict} mapping message sequence numbers to C{list}s of
- corresponding message data given by the server's response.
- """
- d = self.client.fetchSpecific('8', headerType='TEXT')
- self.assertEqual(
- self.transport.value(), '0001 FETCH 8 BODY[TEXT]\r\n')
- self.client.lineReceived('* 8 FETCH (BODY[TEXT] "Some body")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d),
- {8: [['BODY', ['TEXT'], "Some body"]]})
-
-
- def test_fetchSpecificNumberedText(self):
- """
- If passed a value for the C{headerNumber} parameter and C{'TEXT'} for
- the C{headerType} parameter, L{IMAP4Client.fetchSpecific} sends a
- I{BODY[number.TEXT]} request and returns a L{Deferred} which fires with
- a C{dict} mapping message sequence numbers to C{list}s of message data
- given by the server's response.
- """
- d = self.client.fetchSpecific('4', headerType='TEXT', headerNumber=7)
- self.assertEqual(
- self.transport.value(), '0001 FETCH 4 BODY[7.TEXT]\r\n')
- self.client.lineReceived('* 4 FETCH (BODY[7.TEXT] "Some body")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d),
- {4: [['BODY', ['7.TEXT'], "Some body"]]})
-
-
- def test_incompleteFetchSpecificTextResponse(self):
- """
- If the server responds to a I{BODY[TEXT]} request with a I{FETCH} line
- which is truncated after the I{BODY[TEXT]} tokens, the L{Deferred}
- returned by L{IMAP4Client.fetchUID} fails with
- L{IllegalServerResponse}.
- """
- d = self.client.fetchSpecific('8', headerType='TEXT')
- self.assertEqual(
- self.transport.value(), '0001 FETCH 8 BODY[TEXT]\r\n')
- self.client.lineReceived('* 8 FETCH (BODY[TEXT])')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_fetchSpecificMIME(self):
- """
- L{IMAP4Client.fetchSpecific}, when passed C{'MIME'} for C{headerType},
- sends the I{BODY[MIME]} command. It returns a L{Deferred} which fires
- with a C{dict} mapping message sequence numbers to C{list}s of
- corresponding message data given by the server's response.
- """
- d = self.client.fetchSpecific('8', headerType='MIME')
- self.assertEqual(
- self.transport.value(), '0001 FETCH 8 BODY[MIME]\r\n')
- self.client.lineReceived('* 8 FETCH (BODY[MIME] "Some body")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d),
- {8: [['BODY', ['MIME'], "Some body"]]})
-
-
- def test_fetchSpecificPartial(self):
- """
- L{IMAP4Client.fetchSpecific}, when passed C{offset} and C{length},
- sends a partial content request (like I{BODY[TEXT]<offset.length>}).
- It returns a L{Deferred} which fires with a C{dict} mapping message
- sequence numbers to C{list}s of corresponding message data given by the
- server's response.
- """
- d = self.client.fetchSpecific(
- '9', headerType='TEXT', offset=17, length=3)
- self.assertEqual(
- self.transport.value(), '0001 FETCH 9 BODY[TEXT]<17.3>\r\n')
- self.client.lineReceived('* 9 FETCH (BODY[TEXT]<17> "foo")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d),
- {9: [['BODY', ['TEXT'], '<17>', 'foo']]})
-
-
- def test_incompleteFetchSpecificPartialResponse(self):
- """
- If the server responds to a I{BODY[TEXT]} request with a I{FETCH} line
- which is truncated after the I{BODY[TEXT]<offset>} tokens, the
- L{Deferred} returned by L{IMAP4Client.fetchUID} fails with
- L{IllegalServerResponse}.
- """
- d = self.client.fetchSpecific('8', headerType='TEXT')
- self.assertEqual(
- self.transport.value(), '0001 FETCH 8 BODY[TEXT]\r\n')
- self.client.lineReceived('* 8 FETCH (BODY[TEXT]<17>)')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertRaises(
- imap4.IllegalServerResponse, self._extractDeferredResult, d)
-
-
- def test_fetchSpecificHTML(self):
- """
- If the body of a message begins with I{<} and ends with I{>} (as,
- for example, HTML bodies typically will), this is still interpreted
- as the body by L{IMAP4Client.fetchSpecific} (and particularly, not
- as a length indicator for a response to a request for a partial
- body).
- """
- d = self.client.fetchSpecific('7')
- self.assertEqual(
- self.transport.value(), '0001 FETCH 7 BODY[]\r\n')
- self.client.lineReceived('* 7 FETCH (BODY[] "<html>test</html>")')
- self.client.lineReceived('0001 OK FETCH completed')
- self.assertEqual(
- self._extractDeferredResult(d), {7: [['BODY', [], "<html>test</html>"]]})
-
-
-
-class IMAP4ClientStoreTests(PreauthIMAP4ClientMixin, unittest.TestCase):
- """
- Tests for the L{IMAP4Client.setFlags}, L{IMAP4Client.addFlags}, and
- L{IMAP4Client.removeFlags} methods.
-
- An example of usage of the STORE command, in terms of which these three
- methods are implemented, from RFC 3501, section 6.4.6::
-
- C: A003 STORE 2:4 +FLAGS (\Deleted)
- S: * 2 FETCH (FLAGS (\Deleted \Seen))
- S: * 3 FETCH (FLAGS (\Deleted))
- S: * 4 FETCH (FLAGS (\Deleted \Flagged \Seen))
- S: A003 OK STORE completed
- """
- clientProtocol = StillSimplerClient
-
- def _flagsTest(self, method, item):
- """
- Test a non-silent flag modifying method. Call the method, assert that
- the correct bytes are sent, deliver a I{FETCH} response, and assert
- that the result of the Deferred returned by the method is correct.
-
- @param method: The name of the method to test.
- @param item: The data item which is expected to be specified.
- """
- d = getattr(self.client, method)('3', ('\\Read', '\\Seen'), False)
- self.assertEqual(
- self.transport.value(),
- '0001 STORE 3 ' + item + ' (\\Read \\Seen)\r\n')
- self.client.lineReceived('* 3 FETCH (FLAGS (\\Read \\Seen))')
- self.client.lineReceived('0001 OK STORE completed')
- self.assertEqual(
- self._extractDeferredResult(d),
- {3: {'FLAGS': ['\\Read', '\\Seen']}})
-
-
- def _flagsSilentlyTest(self, method, item):
- """
- Test a silent flag modifying method. Call the method, assert that the
- correct bytes are sent, deliver an I{OK} response, and assert that the
- result of the Deferred returned by the method is correct.
-
- @param method: The name of the method to test.
- @param item: The data item which is expected to be specified.
- """
- d = getattr(self.client, method)('3', ('\\Read', '\\Seen'), True)
- self.assertEqual(
- self.transport.value(),
- '0001 STORE 3 ' + item + ' (\\Read \\Seen)\r\n')
- self.client.lineReceived('0001 OK STORE completed')
- self.assertEqual(self._extractDeferredResult(d), {})
-
-
- def _flagsSilentlyWithUnsolicitedDataTest(self, method, item):
- """
- Test unsolicited data received in response to a silent flag modifying
- method. Call the method, assert that the correct bytes are sent,
- deliver the unsolicited I{FETCH} response, and assert that the result
- of the Deferred returned by the method is correct.
-
- @param method: The name of the method to test.
- @param item: The data item which is expected to be specified.
- """
- d = getattr(self.client, method)('3', ('\\Read', '\\Seen'), True)
- self.assertEqual(
- self.transport.value(),
- '0001 STORE 3 ' + item + ' (\\Read \\Seen)\r\n')
- self.client.lineReceived('* 2 FETCH (FLAGS (\\Read \\Seen))')
- self.client.lineReceived('0001 OK STORE completed')
- self.assertEqual(self._extractDeferredResult(d), {})
- self.assertEqual(self.client.flags, {2: ['\\Read', '\\Seen']})
-
-
- def test_setFlags(self):
- """
- When passed a C{False} value for the C{silent} parameter,
- L{IMAP4Client.setFlags} sends the I{STORE} command with a I{FLAGS} data
- item and returns a L{Deferred} which fires with a C{dict} mapping
- message sequence numbers to C{dict}s mapping C{'FLAGS'} to the new
- flags of those messages.
- """
- self._flagsTest('setFlags', 'FLAGS')
-
-
- def test_setFlagsSilently(self):
- """
- When passed a C{True} value for the C{silent} parameter,
- L{IMAP4Client.setFlags} sends the I{STORE} command with a
- I{FLAGS.SILENT} data item and returns a L{Deferred} which fires with an
- empty dictionary.
- """
- self._flagsSilentlyTest('setFlags', 'FLAGS.SILENT')
-
-
- def test_setFlagsSilentlyWithUnsolicitedData(self):
- """
- If unsolicited flag data is received in response to a I{STORE}
- I{FLAGS.SILENT} request, that data is passed to the C{flagsChanged}
- callback.
- """
- self._flagsSilentlyWithUnsolicitedDataTest('setFlags', 'FLAGS.SILENT')
-
-
- def test_addFlags(self):
- """
- L{IMAP4Client.addFlags} is like L{IMAP4Client.setFlags}, but sends
- I{+FLAGS} instead of I{FLAGS}.
- """
- self._flagsTest('addFlags', '+FLAGS')
-
-
- def test_addFlagsSilently(self):
- """
- L{IMAP4Client.addFlags} with a C{True} value for C{silent} behaves like
- L{IMAP4Client.setFlags} with a C{True} value for C{silent}, but it
- sends I{+FLAGS.SILENT} instead of I{FLAGS.SILENT}.
- """
- self._flagsSilentlyTest('addFlags', '+FLAGS.SILENT')
-
-
- def test_addFlagsSilentlyWithUnsolicitedData(self):
- """
- L{IMAP4Client.addFlags} behaves like L{IMAP4Client.setFlags} when used
- in silent mode and unsolicited data is received.
- """
- self._flagsSilentlyWithUnsolicitedDataTest('addFlags', '+FLAGS.SILENT')
-
-
- def test_removeFlags(self):
- """
- L{IMAP4Client.removeFlags} is like L{IMAP4Client.setFlags}, but sends
- I{-FLAGS} instead of I{FLAGS}.
- """
- self._flagsTest('removeFlags', '-FLAGS')
-
-
- def test_removeFlagsSilently(self):
- """
- L{IMAP4Client.removeFlags} with a C{True} value for C{silent} behaves
- like L{IMAP4Client.setFlags} with a C{True} value for C{silent}, but it
- sends I{-FLAGS.SILENT} instead of I{FLAGS.SILENT}.
- """
- self._flagsSilentlyTest('removeFlags', '-FLAGS.SILENT')
-
-
- def test_removeFlagsSilentlyWithUnsolicitedData(self):
- """
- L{IMAP4Client.removeFlags} behaves like L{IMAP4Client.setFlags} when
- used in silent mode and unsolicited data is received.
- """
- self._flagsSilentlyWithUnsolicitedDataTest('removeFlags', '-FLAGS.SILENT')
-
-
-
-class FakeyServer(imap4.IMAP4Server):
- state = 'select'
- timeout = None
-
- def sendServerGreeting(self):
- pass
-
-class FakeyMessage(util.FancyStrMixin):
- implements(imap4.IMessage)
-
- showAttributes = ('headers', 'flags', 'date', 'body', 'uid')
-
- def __init__(self, headers, flags, date, body, uid, subpart):
- self.headers = headers
- self.flags = flags
- self._body = body
- self.size = len(body)
- self.date = date
- self.uid = uid
- self.subpart = subpart
-
- def getHeaders(self, negate, *names):
- self.got_headers = negate, names
- return self.headers
-
- def getFlags(self):
- return self.flags
-
- def getInternalDate(self):
- return self.date
-
- def getBodyFile(self):
- return StringIO(self._body)
-
- def getSize(self):
- return self.size
-
- def getUID(self):
- return self.uid
-
- def isMultipart(self):
- return self.subpart is not None
-
- def getSubPart(self, part):
- self.got_subpart = part
- return self.subpart[part]
-
-class NewStoreTestCase(unittest.TestCase, IMAP4HelperMixin):
- result = None
- storeArgs = None
-
- def setUp(self):
- self.received_messages = self.received_uid = None
-
- self.server = imap4.IMAP4Server()
- self.server.state = 'select'
- self.server.mbox = self
- self.connected = defer.Deferred()
- self.client = SimpleClient(self.connected)
-
- def addListener(self, x):
- pass
- def removeListener(self, x):
- pass
-
- def store(self, *args, **kw):
- self.storeArgs = args, kw
- return self.response
-
- def _storeWork(self):
- def connected():
- return self.function(self.messages, self.flags, self.silent, self.uid)
- def result(R):
- self.result = R
-
- self.connected.addCallback(strip(connected)
- ).addCallback(result
- ).addCallback(self._cbStopClient
- ).addErrback(self._ebGeneral)
-
- def check(ignored):
- self.assertEqual(self.result, self.expected)
- self.assertEqual(self.storeArgs, self.expectedArgs)
- d = loopback.loopbackTCP(self.server, self.client, noisy=False)
- d.addCallback(check)
- return d
-
- def testSetFlags(self, uid=0):
- self.function = self.client.setFlags
- self.messages = '1,5,9'
- self.flags = ['\\A', '\\B', 'C']
- self.silent = False
- self.uid = uid
- self.response = {
- 1: ['\\A', '\\B', 'C'],
- 5: ['\\A', '\\B', 'C'],
- 9: ['\\A', '\\B', 'C'],
- }
- self.expected = {
- 1: {'FLAGS': ['\\A', '\\B', 'C']},
- 5: {'FLAGS': ['\\A', '\\B', 'C']},
- 9: {'FLAGS': ['\\A', '\\B', 'C']},
- }
- msg = imap4.MessageSet()
- msg.add(1)
- msg.add(5)
- msg.add(9)
- self.expectedArgs = ((msg, ['\\A', '\\B', 'C'], 0), {'uid': 0})
- return self._storeWork()
-
-
-
-class GetBodyStructureTests(unittest.TestCase):
- """
- Tests for L{imap4.getBodyStructure}, a helper for constructing a list which
- directly corresponds to the wire information needed for a I{BODY} or
- I{BODYSTRUCTURE} response.
- """
- def test_singlePart(self):
- """
- L{imap4.getBodyStructure} accepts a L{IMessagePart} provider and returns
- a list giving the basic fields for the I{BODY} response for that
- message.
- """
- body = 'hello, world'
- major = 'image'
- minor = 'jpeg'
- charset = 'us-ascii'
- identifier = 'some kind of id'
- description = 'great justice'
- encoding = 'maximum'
- msg = FakeyMessage({
- 'content-type': '%s/%s; charset=%s; x=y' % (
- major, minor, charset),
- 'content-id': identifier,
- 'content-description': description,
- 'content-transfer-encoding': encoding,
- }, (), '', body, 123, None)
- structure = imap4.getBodyStructure(msg)
- self.assertEqual(
- [major, minor, ["charset", charset, 'x', 'y'], identifier,
- description, encoding, len(body)],
- structure)
-
-
- def test_singlePartExtended(self):
- """
- L{imap4.getBodyStructure} returns a list giving the basic and extended
- fields for a I{BODYSTRUCTURE} response if passed C{True} for the
- C{extended} parameter.
- """
- body = 'hello, world'
- major = 'image'
- minor = 'jpeg'
- charset = 'us-ascii'
- identifier = 'some kind of id'
- description = 'great justice'
- encoding = 'maximum'
- md5 = 'abcdefabcdef'
- msg = FakeyMessage({
- 'content-type': '%s/%s; charset=%s; x=y' % (
- major, minor, charset),
- 'content-id': identifier,
- 'content-description': description,
- 'content-transfer-encoding': encoding,
- 'content-md5': md5,
- 'content-disposition': 'attachment; name=foo; size=bar',
- 'content-language': 'fr',
- 'content-location': 'France',
- }, (), '', body, 123, None)
- structure = imap4.getBodyStructure(msg, extended=True)
- self.assertEqual(
- [major, minor, ["charset", charset, 'x', 'y'], identifier,
- description, encoding, len(body), md5,
- ['attachment', ['name', 'foo', 'size', 'bar']], 'fr', 'France'],
- structure)
-
-
- def test_singlePartWithMissing(self):
- """
- For fields with no information contained in the message headers,
- L{imap4.getBodyStructure} fills in C{None} values in its result.
- """
- major = 'image'
- minor = 'jpeg'
- body = 'hello, world'
- msg = FakeyMessage({
- 'content-type': '%s/%s' % (major, minor),
- }, (), '', body, 123, None)
- structure = imap4.getBodyStructure(msg, extended=True)
- self.assertEqual(
- [major, minor, None, None, None, None, len(body), None, None,
- None, None],
- structure)
-
-
- def test_textPart(self):
- """
- For a I{text/*} message, the number of lines in the message body are
- included after the common single-part basic fields.
- """
- body = 'hello, world\nhow are you?\ngoodbye\n'
- major = 'text'
- minor = 'jpeg'
- charset = 'us-ascii'
- identifier = 'some kind of id'
- description = 'great justice'
- encoding = 'maximum'
- msg = FakeyMessage({
- 'content-type': '%s/%s; charset=%s; x=y' % (
- major, minor, charset),
- 'content-id': identifier,
- 'content-description': description,
- 'content-transfer-encoding': encoding,
- }, (), '', body, 123, None)
- structure = imap4.getBodyStructure(msg)
- self.assertEqual(
- [major, minor, ["charset", charset, 'x', 'y'], identifier,
- description, encoding, len(body), len(body.splitlines())],
- structure)
-
-
- def test_rfc822Message(self):
- """
- For a I{message/rfc822} message, the common basic fields are followed
- by information about the contained message.
- """
- body = 'hello, world\nhow are you?\ngoodbye\n'
- major = 'text'
- minor = 'jpeg'
- charset = 'us-ascii'
- identifier = 'some kind of id'
- description = 'great justice'
- encoding = 'maximum'
- msg = FakeyMessage({
- 'content-type': '%s/%s; charset=%s; x=y' % (
- major, minor, charset),
- 'from': 'Alice <alice@example.com>',
- 'to': 'Bob <bob@example.com>',
- 'content-id': identifier,
- 'content-description': description,
- 'content-transfer-encoding': encoding,
- }, (), '', body, 123, None)
-
- container = FakeyMessage({
- 'content-type': 'message/rfc822',
- }, (), '', '', 123, [msg])
-
- structure = imap4.getBodyStructure(container)
- self.assertEqual(
- ['message', 'rfc822', None, None, None, None, 0,
- imap4.getEnvelope(msg), imap4.getBodyStructure(msg), 3],
- structure)
-
-
- def test_multiPart(self):
- """
- For a I{multipart/*} message, L{imap4.getBodyStructure} returns a list
- containing the body structure information for each part of the message
- followed by an element giving the MIME subtype of the message.
- """
- oneSubPart = FakeyMessage({
- 'content-type': 'image/jpeg; x=y',
- 'content-id': 'some kind of id',
- 'content-description': 'great justice',
- 'content-transfer-encoding': 'maximum',
- }, (), '', 'hello world', 123, None)
-
- anotherSubPart = FakeyMessage({
- 'content-type': 'text/plain; charset=us-ascii',
- }, (), '', 'some stuff', 321, None)
-
- container = FakeyMessage({
- 'content-type': 'multipart/related',
- }, (), '', '', 555, [oneSubPart, anotherSubPart])
-
- self.assertEqual(
- [imap4.getBodyStructure(oneSubPart),
- imap4.getBodyStructure(anotherSubPart),
- 'related'],
- imap4.getBodyStructure(container))
-
-
- def test_multiPartExtended(self):
- """
- When passed a I{multipart/*} message and C{True} for the C{extended}
- argument, L{imap4.getBodyStructure} includes extended structure
- information from the parts of the multipart message and extended
- structure information about the multipart message itself.
- """
- oneSubPart = FakeyMessage({
- 'content-type': 'image/jpeg; x=y',
- 'content-id': 'some kind of id',
- 'content-description': 'great justice',
- 'content-transfer-encoding': 'maximum',
- }, (), '', 'hello world', 123, None)
-
- anotherSubPart = FakeyMessage({
- 'content-type': 'text/plain; charset=us-ascii',
- }, (), '', 'some stuff', 321, None)
-
- container = FakeyMessage({
- 'content-type': 'multipart/related; foo=bar',
- 'content-language': 'es',
- 'content-location': 'Spain',
- 'content-disposition': 'attachment; name=monkeys',
- }, (), '', '', 555, [oneSubPart, anotherSubPart])
-
- self.assertEqual(
- [imap4.getBodyStructure(oneSubPart, extended=True),
- imap4.getBodyStructure(anotherSubPart, extended=True),
- 'related', ['foo', 'bar'], ['attachment', ['name', 'monkeys']],
- 'es', 'Spain'],
- imap4.getBodyStructure(container, extended=True))
-
-
-
-class NewFetchTestCase(unittest.TestCase, IMAP4HelperMixin):
- def setUp(self):
- self.received_messages = self.received_uid = None
- self.result = None
-
- self.server = imap4.IMAP4Server()
- self.server.state = 'select'
- self.server.mbox = self
- self.connected = defer.Deferred()
- self.client = SimpleClient(self.connected)
-
- def addListener(self, x):
- pass
- def removeListener(self, x):
- pass
-
- def fetch(self, messages, uid):
- self.received_messages = messages
- self.received_uid = uid
- return iter(zip(range(len(self.msgObjs)), self.msgObjs))
-
- def _fetchWork(self, uid):
- if uid:
- for (i, msg) in zip(range(len(self.msgObjs)), self.msgObjs):
- self.expected[i]['UID'] = str(msg.getUID())
-
- def result(R):
- self.result = R
-
- self.connected.addCallback(lambda _: self.function(self.messages, uid)
- ).addCallback(result
- ).addCallback(self._cbStopClient
- ).addErrback(self._ebGeneral)
-
- d = loopback.loopbackTCP(self.server, self.client, noisy=False)
- d.addCallback(lambda x : self.assertEqual(self.result, self.expected))
- return d
-
- def testFetchUID(self):
- self.function = lambda m, u: self.client.fetchUID(m)
-
- self.messages = '7'
- self.msgObjs = [
- FakeyMessage({}, (), '', '', 12345, None),
- FakeyMessage({}, (), '', '', 999, None),
- FakeyMessage({}, (), '', '', 10101, None),
- ]
- self.expected = {
- 0: {'UID': '12345'},
- 1: {'UID': '999'},
- 2: {'UID': '10101'},
- }
- return self._fetchWork(0)
-
- def testFetchFlags(self, uid=0):
- self.function = self.client.fetchFlags
- self.messages = '9'
- self.msgObjs = [
- FakeyMessage({}, ['FlagA', 'FlagB', '\\FlagC'], '', '', 54321, None),
- FakeyMessage({}, ['\\FlagC', 'FlagA', 'FlagB'], '', '', 12345, None),
- ]
- self.expected = {
- 0: {'FLAGS': ['FlagA', 'FlagB', '\\FlagC']},
- 1: {'FLAGS': ['\\FlagC', 'FlagA', 'FlagB']},
- }
- return self._fetchWork(uid)
-
- def testFetchFlagsUID(self):
- return self.testFetchFlags(1)
-
- def testFetchInternalDate(self, uid=0):
- self.function = self.client.fetchInternalDate
- self.messages = '13'
- self.msgObjs = [
- FakeyMessage({}, (), 'Fri, 02 Nov 2003 21:25:10 GMT', '', 23232, None),
- FakeyMessage({}, (), 'Thu, 29 Dec 2013 11:31:52 EST', '', 101, None),
- FakeyMessage({}, (), 'Mon, 10 Mar 1992 02:44:30 CST', '', 202, None),
- FakeyMessage({}, (), 'Sat, 11 Jan 2000 14:40:24 PST', '', 303, None),
- ]
- self.expected = {
- 0: {'INTERNALDATE': '02-Nov-2003 21:25:10 +0000'},
- 1: {'INTERNALDATE': '29-Dec-2013 11:31:52 -0500'},
- 2: {'INTERNALDATE': '10-Mar-1992 02:44:30 -0600'},
- 3: {'INTERNALDATE': '11-Jan-2000 14:40:24 -0800'},
- }
- return self._fetchWork(uid)
-
- def testFetchInternalDateUID(self):
- return self.testFetchInternalDate(1)
-
-
- def test_fetchInternalDateLocaleIndependent(self):
- """
- The month name in the date is locale independent.
- """
- # Fake that we're in a language where December is not Dec
- currentLocale = locale.setlocale(locale.LC_ALL, None)
- locale.setlocale(locale.LC_ALL, "es_AR.UTF8")
- self.addCleanup(locale.setlocale, locale.LC_ALL, currentLocale)
- return self.testFetchInternalDate(1)
-
- # if alternate locale is not available, the previous test will be skipped,
- # please install this locale for it to run. Avoid using locale.getlocale to
- # learn the current locale; its values don't round-trip well on all
- # platforms. Fortunately setlocale returns a value which does round-trip
- # well.
- currentLocale = locale.setlocale(locale.LC_ALL, None)
- try:
- locale.setlocale(locale.LC_ALL, "es_AR.UTF8")
- except locale.Error:
- test_fetchInternalDateLocaleIndependent.skip = (
- "The es_AR.UTF8 locale is not installed.")
- else:
- locale.setlocale(locale.LC_ALL, currentLocale)
-
-
- def testFetchEnvelope(self, uid=0):
- self.function = self.client.fetchEnvelope
- self.messages = '15'
- self.msgObjs = [
- FakeyMessage({
- 'from': 'user@domain', 'to': 'resu@domain',
- 'date': 'thursday', 'subject': 'it is a message',
- 'message-id': 'id-id-id-yayaya'}, (), '', '', 65656,
- None),
- ]
- self.expected = {
- 0: {'ENVELOPE':
- ['thursday', 'it is a message',
- [[None, None, 'user', 'domain']],
- [[None, None, 'user', 'domain']],
- [[None, None, 'user', 'domain']],
- [[None, None, 'resu', 'domain']],
- None, None, None, 'id-id-id-yayaya']
- }
- }
- return self._fetchWork(uid)
-
- def testFetchEnvelopeUID(self):
- return self.testFetchEnvelope(1)
-
-
- def test_fetchBodyStructure(self, uid=0):
- """
- L{IMAP4Client.fetchBodyStructure} issues a I{FETCH BODYSTRUCTURE}
- command and returns a Deferred which fires with a structure giving the
- result of parsing the server's response. The structure is a list
- reflecting the parenthesized data sent by the server, as described by
- RFC 3501, section 7.4.2.
- """
- self.function = self.client.fetchBodyStructure
- self.messages = '3:9,10:*'
- self.msgObjs = [FakeyMessage({
- 'content-type': 'text/plain; name=thing; key="value"',
- 'content-id': 'this-is-the-content-id',
- 'content-description': 'describing-the-content-goes-here!',
- 'content-transfer-encoding': '8BIT',
- 'content-md5': 'abcdef123456',
- 'content-disposition': 'attachment; filename=monkeys',
- 'content-language': 'es',
- 'content-location': 'http://example.com/monkeys',
- }, (), '', 'Body\nText\nGoes\nHere\n', 919293, None)]
- self.expected = {0: {'BODYSTRUCTURE': [
- 'text', 'plain', ['key', 'value', 'name', 'thing'],
- 'this-is-the-content-id', 'describing-the-content-goes-here!',
- '8BIT', '20', '4', 'abcdef123456',
- ['attachment', ['filename', 'monkeys']], 'es',
- 'http://example.com/monkeys']}}
- return self._fetchWork(uid)
-
-
- def testFetchBodyStructureUID(self):
- """
- If passed C{True} for the C{uid} argument, C{fetchBodyStructure} can
- also issue a I{UID FETCH BODYSTRUCTURE} command.
- """
- return self.test_fetchBodyStructure(1)
-
-
- def test_fetchBodyStructureMultipart(self, uid=0):
- """
- L{IMAP4Client.fetchBodyStructure} can also parse the response to a
- I{FETCH BODYSTRUCTURE} command for a multipart message.
- """
- self.function = self.client.fetchBodyStructure
- self.messages = '3:9,10:*'
- innerMessage = FakeyMessage({
- 'content-type': 'text/plain; name=thing; key="value"',
- 'content-id': 'this-is-the-content-id',
- 'content-description': 'describing-the-content-goes-here!',
- 'content-transfer-encoding': '8BIT',
- 'content-language': 'fr',
- 'content-md5': '123456abcdef',
- 'content-disposition': 'inline',
- 'content-location': 'outer space',
- }, (), '', 'Body\nText\nGoes\nHere\n', 919293, None)
- self.msgObjs = [FakeyMessage({
- 'content-type': 'multipart/mixed; boundary="xyz"',
- 'content-language': 'en',
- 'content-location': 'nearby',
- }, (), '', '', 919293, [innerMessage])]
- self.expected = {0: {'BODYSTRUCTURE': [
- ['text', 'plain', ['key', 'value', 'name', 'thing'],
- 'this-is-the-content-id', 'describing-the-content-goes-here!',
- '8BIT', '20', '4', '123456abcdef', ['inline', None], 'fr',
- 'outer space'],
- 'mixed', ['boundary', 'xyz'], None, 'en', 'nearby'
- ]}}
- return self._fetchWork(uid)
-
-
- def testFetchSimplifiedBody(self, uid=0):
- self.function = self.client.fetchSimplifiedBody
- self.messages = '21'
- self.msgObjs = [FakeyMessage({}, (), '', 'Yea whatever', 91825,
- [FakeyMessage({'content-type': 'image/jpg'}, (), '',
- 'Body Body Body', None, None
- )]
- )]
- self.expected = {0:
- {'BODY':
- [None, None, None, None, None, None,
- '12'
- ]
- }
- }
-
- return self._fetchWork(uid)
-
- def testFetchSimplifiedBodyUID(self):
- return self.testFetchSimplifiedBody(1)
-
- def testFetchSimplifiedBodyText(self, uid=0):
- self.function = self.client.fetchSimplifiedBody
- self.messages = '21'
- self.msgObjs = [FakeyMessage({'content-type': 'text/plain'},
- (), '', 'Yea whatever', 91825, None)]
- self.expected = {0:
- {'BODY':
- ['text', 'plain', None, None, None, None,
- '12', '1'
- ]
- }
- }
-
- return self._fetchWork(uid)
-
- def testFetchSimplifiedBodyTextUID(self):
- return self.testFetchSimplifiedBodyText(1)
-
- def testFetchSimplifiedBodyRFC822(self, uid=0):
- self.function = self.client.fetchSimplifiedBody
- self.messages = '21'
- self.msgObjs = [FakeyMessage({'content-type': 'message/rfc822'},
- (), '', 'Yea whatever', 91825,
- [FakeyMessage({'content-type': 'image/jpg'}, (), '',
- 'Body Body Body', None, None
- )]
- )]
- self.expected = {0:
- {'BODY':
- ['message', 'rfc822', None, None, None, None,
- '12', [None, None, [[None, None, None]],
- [[None, None, None]], None, None, None,
- None, None, None], ['image', 'jpg', None,
- None, None, None, '14'], '1'
- ]
- }
- }
-
- return self._fetchWork(uid)
-
- def testFetchSimplifiedBodyRFC822UID(self):
- return self.testFetchSimplifiedBodyRFC822(1)
-
- def testFetchMessage(self, uid=0):
- self.function = self.client.fetchMessage
- self.messages = '1,3,7,10101'
- self.msgObjs = [
- FakeyMessage({'Header': 'Value'}, (), '', 'BODY TEXT\r\n', 91, None),
- ]
- self.expected = {
- 0: {'RFC822': 'Header: Value\r\n\r\nBODY TEXT\r\n'}
- }
- return self._fetchWork(uid)
-
- def testFetchMessageUID(self):
- return self.testFetchMessage(1)
-
- def testFetchHeaders(self, uid=0):
- self.function = self.client.fetchHeaders
- self.messages = '9,6,2'
- self.msgObjs = [
- FakeyMessage({'H1': 'V1', 'H2': 'V2'}, (), '', '', 99, None),
- ]
- self.expected = {
- 0: {'RFC822.HEADER': imap4._formatHeaders({'H1': 'V1', 'H2': 'V2'})},
- }
- return self._fetchWork(uid)
-
- def testFetchHeadersUID(self):
- return self.testFetchHeaders(1)
-
- def testFetchBody(self, uid=0):
- self.function = self.client.fetchBody
- self.messages = '1,2,3,4,5,6,7'
- self.msgObjs = [
- FakeyMessage({'Header': 'Value'}, (), '', 'Body goes here\r\n', 171, None),
- ]
- self.expected = {
- 0: {'RFC822.TEXT': 'Body goes here\r\n'},
- }
- return self._fetchWork(uid)
-
- def testFetchBodyUID(self):
- return self.testFetchBody(1)
-
- def testFetchBodyParts(self):
- """
- Test the server's handling of requests for specific body sections.
- """
- self.function = self.client.fetchSpecific
- self.messages = '1'
- outerBody = ''
- innerBody1 = 'Contained body message text. Squarge.'
- innerBody2 = 'Secondary <i>message</i> text of squarge body.'
- headers = util.OrderedDict()
- headers['from'] = 'sender@host'
- headers['to'] = 'recipient@domain'
- headers['subject'] = 'booga booga boo'
- headers['content-type'] = 'multipart/alternative; boundary="xyz"'
- innerHeaders = util.OrderedDict()
- innerHeaders['subject'] = 'this is subject text'
- innerHeaders['content-type'] = 'text/plain'
- innerHeaders2 = util.OrderedDict()
- innerHeaders2['subject'] = '<b>this is subject</b>'
- innerHeaders2['content-type'] = 'text/html'
- self.msgObjs = [FakeyMessage(
- headers, (), None, outerBody, 123,
- [FakeyMessage(innerHeaders, (), None, innerBody1, None, None),
- FakeyMessage(innerHeaders2, (), None, innerBody2, None, None)])]
- self.expected = {
- 0: [['BODY', ['1'], 'Contained body message text. Squarge.']]}
-
- def result(R):
- self.result = R
-
- self.connected.addCallback(
- lambda _: self.function(self.messages, headerNumber=1))
- self.connected.addCallback(result)
- self.connected.addCallback(self._cbStopClient)
- self.connected.addErrback(self._ebGeneral)
-
- d = loopback.loopbackTCP(self.server, self.client, noisy=False)
- d.addCallback(lambda ign: self.assertEqual(self.result, self.expected))
- return d
-
-
- def test_fetchBodyPartOfNonMultipart(self):
- """
- Single-part messages have an implicit first part which clients
- should be able to retrieve explicitly. Test that a client
- requesting part 1 of a text/plain message receives the body of the
- text/plain part.
- """
- self.function = self.client.fetchSpecific
- self.messages = '1'
- parts = [1]
- outerBody = 'DA body'
- headers = util.OrderedDict()
- headers['from'] = 'sender@host'
- headers['to'] = 'recipient@domain'
- headers['subject'] = 'booga booga boo'
- headers['content-type'] = 'text/plain'
- self.msgObjs = [FakeyMessage(
- headers, (), None, outerBody, 123, None)]
-
- self.expected = {0: [['BODY', ['1'], 'DA body']]}
-
- def result(R):
- self.result = R
-
- self.connected.addCallback(
- lambda _: self.function(self.messages, headerNumber=parts))
- self.connected.addCallback(result)
- self.connected.addCallback(self._cbStopClient)
- self.connected.addErrback(self._ebGeneral)
-
- d = loopback.loopbackTCP(self.server, self.client, noisy=False)
- d.addCallback(lambda ign: self.assertEqual(self.result, self.expected))
- return d
-
-
- def testFetchSize(self, uid=0):
- self.function = self.client.fetchSize
- self.messages = '1:100,2:*'
- self.msgObjs = [
- FakeyMessage({}, (), '', 'x' * 20, 123, None),
- ]
- self.expected = {
- 0: {'RFC822.SIZE': '20'},
- }
- return self._fetchWork(uid)
-
- def testFetchSizeUID(self):
- return self.testFetchSize(1)
-
- def testFetchFull(self, uid=0):
- self.function = self.client.fetchFull
- self.messages = '1,3'
- self.msgObjs = [
- FakeyMessage({}, ('\\XYZ', '\\YZX', 'Abc'),
- 'Sun, 25 Jul 2010 06:20:30 -0400 (EDT)',
- 'xyz' * 2, 654, None),
- FakeyMessage({}, ('\\One', '\\Two', 'Three'),
- 'Mon, 14 Apr 2003 19:43:44 -0400',
- 'abc' * 4, 555, None),
- ]
- self.expected = {
- 0: {'FLAGS': ['\\XYZ', '\\YZX', 'Abc'],
- 'INTERNALDATE': '25-Jul-2010 06:20:30 -0400',
- 'RFC822.SIZE': '6',
- 'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
- 'BODY': [None, None, None, None, None, None, '6']},
- 1: {'FLAGS': ['\\One', '\\Two', 'Three'],
- 'INTERNALDATE': '14-Apr-2003 19:43:44 -0400',
- 'RFC822.SIZE': '12',
- 'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
- 'BODY': [None, None, None, None, None, None, '12']},
- }
- return self._fetchWork(uid)
-
- def testFetchFullUID(self):
- return self.testFetchFull(1)
-
- def testFetchAll(self, uid=0):
- self.function = self.client.fetchAll
- self.messages = '1,2:3'
- self.msgObjs = [
- FakeyMessage({}, (), 'Mon, 14 Apr 2003 19:43:44 +0400',
- 'Lalala', 10101, None),
- FakeyMessage({}, (), 'Tue, 15 Apr 2003 19:43:44 +0200',
- 'Alalal', 20202, None),
- ]
- self.expected = {
- 0: {'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
- 'RFC822.SIZE': '6',
- 'INTERNALDATE': '14-Apr-2003 19:43:44 +0400',
- 'FLAGS': []},
- 1: {'ENVELOPE': [None, None, [[None, None, None]], [[None, None, None]], None, None, None, None, None, None],
- 'RFC822.SIZE': '6',
- 'INTERNALDATE': '15-Apr-2003 19:43:44 +0200',
- 'FLAGS': []},
- }
- return self._fetchWork(uid)
-
- def testFetchAllUID(self):
- return self.testFetchAll(1)
-
- def testFetchFast(self, uid=0):
- self.function = self.client.fetchFast
- self.messages = '1'
- self.msgObjs = [
- FakeyMessage({}, ('\\X',), '19 Mar 2003 19:22:21 -0500', '', 9, None),
- ]
- self.expected = {
- 0: {'FLAGS': ['\\X'],
- 'INTERNALDATE': '19-Mar-2003 19:22:21 -0500',
- 'RFC822.SIZE': '0'},
- }
- return self._fetchWork(uid)
-
- def testFetchFastUID(self):
- return self.testFetchFast(1)
-
-
-
-class DefaultSearchTestCase(IMAP4HelperMixin, unittest.TestCase):
- """
- Test the behavior of the server's SEARCH implementation, particularly in
- the face of unhandled search terms.
- """
- def setUp(self):
- self.server = imap4.IMAP4Server()
- self.server.state = 'select'
- self.server.mbox = self
- self.connected = defer.Deferred()
- self.client = SimpleClient(self.connected)
- self.msgObjs = [
- FakeyMessage({}, (), '', '', 999, None),
- FakeyMessage({}, (), '', '', 10101, None),
- FakeyMessage({}, (), '', '', 12345, None),
- FakeyMessage({}, (), '', '', 20001, None),
- FakeyMessage({}, (), '', '', 20002, None),
- ]
-
-
- def fetch(self, messages, uid):
- """
- Pretend to be a mailbox and let C{self.server} lookup messages on me.
- """
- return zip(range(1, len(self.msgObjs) + 1), self.msgObjs)
-
-
- def _messageSetSearchTest(self, queryTerms, expectedMessages):
- """
- Issue a search with given query and verify that the returned messages
- match the given expected messages.
-
- @param queryTerms: A string giving the search query.
- @param expectedMessages: A list of the message sequence numbers
- expected as the result of the search.
- @return: A L{Deferred} which fires when the test is complete.
- """
- def search():
- return self.client.search(queryTerms)
-
- d = self.connected.addCallback(strip(search))
- def searched(results):
- self.assertEqual(results, expectedMessages)
- d.addCallback(searched)
- d.addCallback(self._cbStopClient)
- d.addErrback(self._ebGeneral)
- self.loopback()
- return d
-
-
- def test_searchMessageSet(self):
- """
- Test that a search which starts with a message set properly limits
- the search results to messages in that set.
- """
- return self._messageSetSearchTest('1', [1])
-
-
- def test_searchMessageSetWithStar(self):
- """
- If the search filter ends with a star, all the message from the
- starting point are returned.
- """
- return self._messageSetSearchTest('2:*', [2, 3, 4, 5])
-
-
- def test_searchMessageSetWithStarFirst(self):
- """
- If the search filter starts with a star, the result should be identical
- with if the filter would end with a star.
- """
- return self._messageSetSearchTest('*:2', [2, 3, 4, 5])
-
-
- def test_searchMessageSetUIDWithStar(self):
- """
- If the search filter ends with a star, all the message from the
- starting point are returned (also for the SEARCH UID case).
- """
- return self._messageSetSearchTest('UID 10000:*', [2, 3, 4, 5])
-
-
- def test_searchMessageSetUIDWithStarFirst(self):
- """
- If the search filter starts with a star, the result should be identical
- with if the filter would end with a star (also for the SEARCH UID case).
- """
- return self._messageSetSearchTest('UID *:10000', [2, 3, 4, 5])
-
-
- def test_searchMessageSetUIDWithStarAndHighStart(self):
- """
- A search filter of 1234:* should include the UID of the last message in
- the mailbox, even if its UID is less than 1234.
- """
- # in our fake mbox the highest message UID is 20002
- return self._messageSetSearchTest('UID 30000:*', [5])
-
-
- def test_searchMessageSetWithList(self):
- """
- If the search filter contains nesting terms, one of which includes a
- message sequence set with a wildcard, IT ALL WORKS GOOD.
- """
- # 6 is bigger than the biggest message sequence number, but that's
- # okay, because N:* includes the biggest message sequence number even
- # if N is bigger than that (read the rfc nub).
- return self._messageSetSearchTest('(6:*)', [5])
-
-
- def test_searchOr(self):
- """
- If the search filter contains an I{OR} term, all messages
- which match either subexpression are returned.
- """
- return self._messageSetSearchTest('OR 1 2', [1, 2])
-
-
- def test_searchOrMessageSet(self):
- """
- If the search filter contains an I{OR} term with a
- subexpression which includes a message sequence set wildcard,
- all messages in that set are considered for inclusion in the
- results.
- """
- return self._messageSetSearchTest('OR 2:* 2:*', [2, 3, 4, 5])
-
-
- def test_searchNot(self):
- """
- If the search filter contains a I{NOT} term, all messages
- which do not match the subexpression are returned.
- """
- return self._messageSetSearchTest('NOT 3', [1, 2, 4, 5])
-
-
- def test_searchNotMessageSet(self):
- """
- If the search filter contains a I{NOT} term with a
- subexpression which includes a message sequence set wildcard,
- no messages in that set are considered for inclusion in the
- result.
- """
- return self._messageSetSearchTest('NOT 2:*', [1])
-
-
- def test_searchAndMessageSet(self):
- """
- If the search filter contains multiple terms implicitly
- conjoined with a message sequence set wildcard, only the
- intersection of the results of each term are returned.
- """
- return self._messageSetSearchTest('2:* 3', [3])
-
- def test_searchInvalidCriteria(self):
- """
- If the search criteria is not a valid key, a NO result is returned to
- the client (resulting in an error callback), and an IllegalQueryError is
- logged on the server side.
- """
- queryTerms = 'FOO'
- def search():
- return self.client.search(queryTerms)
-
- d = self.connected.addCallback(strip(search))
- d = self.assertFailure(d, imap4.IMAP4Exception)
-
- def errorReceived(results):
- """
- Verify that the server logs an IllegalQueryError and the
- client raises an IMAP4Exception with 'Search failed:...'
- """
- self.client.transport.loseConnection()
- self.server.transport.loseConnection()
-
- # Check what the server logs
- errors = self.flushLoggedErrors(imap4.IllegalQueryError)
- self.assertEqual(len(errors), 1)
-
- # Verify exception given to client has the correct message
- self.assertEqual(
- "SEARCH failed: Invalid search command FOO", str(results))
-
- d.addCallback(errorReceived)
- d.addErrback(self._ebGeneral)
- self.loopback()
- return d
-
-
-
-class FetchSearchStoreTestCase(unittest.TestCase, IMAP4HelperMixin):
- implements(imap4.ISearchableMailbox)
-
- def setUp(self):
- self.expected = self.result = None
- self.server_received_query = None
- self.server_received_uid = None
- self.server_received_parts = None
- self.server_received_messages = None
-
- self.server = imap4.IMAP4Server()
- self.server.state = 'select'
- self.server.mbox = self
- self.connected = defer.Deferred()
- self.client = SimpleClient(self.connected)
-
- def search(self, query, uid):
- # Look for a specific bad query, so we can verify we handle it properly
- if query == ['FOO']:
- raise imap4.IllegalQueryError("FOO is not a valid search criteria")
-
- self.server_received_query = query
- self.server_received_uid = uid
- return self.expected
-
- def addListener(self, *a, **kw):
- pass
- removeListener = addListener
-
- def _searchWork(self, uid):
- def search():
- return self.client.search(self.query, uid=uid)
- def result(R):
- self.result = R
-
- self.connected.addCallback(strip(search)
- ).addCallback(result
- ).addCallback(self._cbStopClient
- ).addErrback(self._ebGeneral)
-
- def check(ignored):
- # Ensure no short-circuiting wierdness is going on
- self.failIf(self.result is self.expected)
-
- self.assertEqual(self.result, self.expected)
- self.assertEqual(self.uid, self.server_received_uid)
- self.assertEqual(
- imap4.parseNestedParens(self.query),
- self.server_received_query
- )
- d = loopback.loopbackTCP(self.server, self.client, noisy=False)
- d.addCallback(check)
- return d
-
- def testSearch(self):
- self.query = imap4.Or(
- imap4.Query(header=('subject', 'substring')),
- imap4.Query(larger=1024, smaller=4096),
- )
- self.expected = [1, 4, 5, 7]
- self.uid = 0
- return self._searchWork(0)
-
- def testUIDSearch(self):
- self.query = imap4.Or(
- imap4.Query(header=('subject', 'substring')),
- imap4.Query(larger=1024, smaller=4096),
- )
- self.uid = 1
- self.expected = [1, 2, 3]
- return self._searchWork(1)
-
- def getUID(self, msg):
- try:
- return self.expected[msg]['UID']
- except (TypeError, IndexError):
- return self.expected[msg-1]
- except KeyError:
- return 42
-
- def fetch(self, messages, uid):
- self.server_received_uid = uid
- self.server_received_messages = str(messages)
- return self.expected
-
- def _fetchWork(self, fetch):
- def result(R):
- self.result = R
-
- self.connected.addCallback(strip(fetch)
- ).addCallback(result
- ).addCallback(self._cbStopClient
- ).addErrback(self._ebGeneral)
-
- def check(ignored):
- # Ensure no short-circuiting wierdness is going on
- self.failIf(self.result is self.expected)
-
- self.parts and self.parts.sort()
- self.server_received_parts and self.server_received_parts.sort()
-
- if self.uid:
- for (k, v) in self.expected.items():
- v['UID'] = str(k)
-
- self.assertEqual(self.result, self.expected)
- self.assertEqual(self.uid, self.server_received_uid)
- self.assertEqual(self.parts, self.server_received_parts)
- self.assertEqual(imap4.parseIdList(self.messages),
- imap4.parseIdList(self.server_received_messages))
-
- d = loopback.loopbackTCP(self.server, self.client, noisy=False)
- d.addCallback(check)
- return d
-
-
- def test_invalidTerm(self):
- """
- If, as part of a search, an ISearchableMailbox raises an
- IllegalQueryError (e.g. due to invalid search criteria), client sees a
- failure response, and an IllegalQueryError is logged on the server.
- """
- query = 'FOO'
-
- def search():
- return self.client.search(query)
-
- d = self.connected.addCallback(strip(search))
- d = self.assertFailure(d, imap4.IMAP4Exception)
-
- def errorReceived(results):
- """
- Verify that the server logs an IllegalQueryError and the
- client raises an IMAP4Exception with 'Search failed:...'
- """
- self.client.transport.loseConnection()
- self.server.transport.loseConnection()
-
- # Check what the server logs
- errors = self.flushLoggedErrors(imap4.IllegalQueryError)
- self.assertEqual(len(errors), 1)
-
- # Verify exception given to client has the correct message
- self.assertEqual(
- "SEARCH failed: FOO is not a valid search criteria",
- str(results))
-
- d.addCallback(errorReceived)
- d.addErrback(self._ebGeneral)
- self.loopback()
- return d
-
-
-
-class FakeMailbox:
- def __init__(self):
- self.args = []
- def addMessage(self, body, flags, date):
- self.args.append((body, flags, date))
- return defer.succeed(None)
-
-class FeaturefulMessage:
- implements(imap4.IMessageFile)
-
- def getFlags(self):
- return 'flags'
-
- def getInternalDate(self):
- return 'internaldate'
-
- def open(self):
- return StringIO("open")
-
-class MessageCopierMailbox:
- implements(imap4.IMessageCopier)
-
- def __init__(self):
- self.msgs = []
-
- def copy(self, msg):
- self.msgs.append(msg)
- return len(self.msgs)
-
-class CopyWorkerTestCase(unittest.TestCase):
- def testFeaturefulMessage(self):
- s = imap4.IMAP4Server()
-
- # Yes. I am grabbing this uber-non-public method to test it.
- # It is complex. It needs to be tested directly!
- # Perhaps it should be refactored, simplified, or split up into
- # not-so-private components, but that is a task for another day.
-
- # Ha ha! Addendum! Soon it will be split up, and this test will
- # be re-written to just use the default adapter for IMailbox to
- # IMessageCopier and call .copy on that adapter.
- f = s._IMAP4Server__cbCopy
-
- m = FakeMailbox()
- d = f([(i, FeaturefulMessage()) for i in range(1, 11)], 'tag', m)
-
- def cbCopy(results):
- for a in m.args:
- self.assertEqual(a[0].read(), "open")
- self.assertEqual(a[1], "flags")
- self.assertEqual(a[2], "internaldate")
-
- for (status, result) in results:
- self.failUnless(status)
- self.assertEqual(result, None)
-
- return d.addCallback(cbCopy)
-
-
- def testUnfeaturefulMessage(self):
- s = imap4.IMAP4Server()
-
- # See above comment
- f = s._IMAP4Server__cbCopy
-
- m = FakeMailbox()
- msgs = [FakeyMessage({'Header-Counter': str(i)}, (), 'Date', 'Body %d' % (i,), i + 10, None) for i in range(1, 11)]
- d = f([im for im in zip(range(1, 11), msgs)], 'tag', m)
-
- def cbCopy(results):
- seen = []
- for a in m.args:
- seen.append(a[0].read())
- self.assertEqual(a[1], ())
- self.assertEqual(a[2], "Date")
-
- seen.sort()
- exp = ["Header-Counter: %d\r\n\r\nBody %d" % (i, i) for i in range(1, 11)]
- exp.sort()
- self.assertEqual(seen, exp)
-
- for (status, result) in results:
- self.failUnless(status)
- self.assertEqual(result, None)
-
- return d.addCallback(cbCopy)
-
- def testMessageCopier(self):
- s = imap4.IMAP4Server()
-
- # See above comment
- f = s._IMAP4Server__cbCopy
-
- m = MessageCopierMailbox()
- msgs = [object() for i in range(1, 11)]
- d = f([im for im in zip(range(1, 11), msgs)], 'tag', m)
-
- def cbCopy(results):
- self.assertEqual(results, zip([1] * 10, range(1, 11)))
- for (orig, new) in zip(msgs, m.msgs):
- self.assertIdentical(orig, new)
-
- return d.addCallback(cbCopy)
-
-
-class TLSTestCase(IMAP4HelperMixin, unittest.TestCase):
- serverCTX = ServerTLSContext and ServerTLSContext()
- clientCTX = ClientTLSContext and ClientTLSContext()
-
- def loopback(self):
- return loopback.loopbackTCP(self.server, self.client, noisy=False)
-
- def testAPileOfThings(self):
- SimpleServer.theAccount.addMailbox('inbox')
- called = []
- def login():
- called.append(None)
- return self.client.login('testuser', 'password-test')
- def list():
- called.append(None)
- return self.client.list('inbox', '%')
- def status():
- called.append(None)
- return self.client.status('inbox', 'UIDNEXT')
- def examine():
- called.append(None)
- return self.client.examine('inbox')
- def logout():
- called.append(None)
- return self.client.logout()
-
- self.client.requireTransportSecurity = True
-
- methods = [login, list, status, examine, logout]
- map(self.connected.addCallback, map(strip, methods))
- self.connected.addCallbacks(self._cbStopClient, self._ebGeneral)
- def check(ignored):
- self.assertEqual(self.server.startedTLS, True)
- self.assertEqual(self.client.startedTLS, True)
- self.assertEqual(len(called), len(methods))
- d = self.loopback()
- d.addCallback(check)
- return d
-
- def testLoginLogin(self):
- self.server.checker.addUser('testuser', 'password-test')
- success = []
- self.client.registerAuthenticator(imap4.LOGINAuthenticator('testuser'))
- self.connected.addCallback(
- lambda _: self.client.authenticate('password-test')
- ).addCallback(
- lambda _: self.client.logout()
- ).addCallback(success.append
- ).addCallback(self._cbStopClient
- ).addErrback(self._ebGeneral)
-
- d = self.loopback()
- d.addCallback(lambda x : self.assertEqual(len(success), 1))
- return d
-
-
- def test_startTLS(self):
- """
- L{IMAP4Client.startTLS} triggers TLS negotiation and returns a
- L{Deferred} which fires after the client's transport is using
- encryption.
- """
- success = []
- self.connected.addCallback(lambda _: self.client.startTLS())
- def checkSecure(ignored):
- self.assertTrue(
- interfaces.ISSLTransport.providedBy(self.client.transport))
- self.connected.addCallback(checkSecure)
- self.connected.addCallback(self._cbStopClient)
- self.connected.addCallback(success.append)
- self.connected.addErrback(self._ebGeneral)
-
- d = self.loopback()
- d.addCallback(lambda x : self.failUnless(success))
- return defer.gatherResults([d, self.connected])
-
-
- def testFailedStartTLS(self):
- failure = []
- def breakServerTLS(ign):
- self.server.canStartTLS = False
-
- self.connected.addCallback(breakServerTLS)
- self.connected.addCallback(lambda ign: self.client.startTLS())
- self.connected.addErrback(lambda err: failure.append(err.trap(imap4.IMAP4Exception)))
- self.connected.addCallback(self._cbStopClient)
- self.connected.addErrback(self._ebGeneral)
-
- def check(ignored):
- self.failUnless(failure)
- self.assertIdentical(failure[0], imap4.IMAP4Exception)
- return self.loopback().addCallback(check)
-
-
-
-class SlowMailbox(SimpleMailbox):
- howSlow = 2
- callLater = None
- fetchDeferred = None
-
- # Not a very nice implementation of fetch(), but it'll
- # do for the purposes of testing.
- def fetch(self, messages, uid):
- d = defer.Deferred()
- self.callLater(self.howSlow, d.callback, ())
- self.fetchDeferred.callback(None)
- return d
-
-class Timeout(IMAP4HelperMixin, unittest.TestCase):
-
- def test_serverTimeout(self):
- """
- The *client* has a timeout mechanism which will close connections that
- are inactive for a period.
- """
- c = Clock()
- self.server.timeoutTest = True
- self.client.timeout = 5 #seconds
- self.client.callLater = c.callLater
- self.selectedArgs = None
-
- def login():
- d = self.client.login('testuser', 'password-test')
- c.advance(5)
- d.addErrback(timedOut)
- return d
-
- def timedOut(failure):
- self._cbStopClient(None)
- failure.trap(error.TimeoutError)
-
- d = self.connected.addCallback(strip(login))
- d.addErrback(self._ebGeneral)
- return defer.gatherResults([d, self.loopback()])
-
-
- def test_longFetchDoesntTimeout(self):
- """
- The connection timeout does not take effect during fetches.
- """
- c = Clock()
- SlowMailbox.callLater = c.callLater
- SlowMailbox.fetchDeferred = defer.Deferred()
- self.server.callLater = c.callLater
- SimpleServer.theAccount.mailboxFactory = SlowMailbox
- SimpleServer.theAccount.addMailbox('mailbox-test')
-
- self.server.setTimeout(1)
-
- def login():
- return self.client.login('testuser', 'password-test')
- def select():
- self.server.setTimeout(1)
- return self.client.select('mailbox-test')
- def fetch():
- return self.client.fetchUID('1:*')
- def stillConnected():
- self.assertNotEquals(self.server.state, 'timeout')
-
- def cbAdvance(ignored):
- for i in xrange(4):
- c.advance(.5)
-
- SlowMailbox.fetchDeferred.addCallback(cbAdvance)
-
- d1 = self.connected.addCallback(strip(login))
- d1.addCallback(strip(select))
- d1.addCallback(strip(fetch))
- d1.addCallback(strip(stillConnected))
- d1.addCallback(self._cbStopClient)
- d1.addErrback(self._ebGeneral)
- d = defer.gatherResults([d1, self.loopback()])
- return d
-
-
- def test_idleClientDoesDisconnect(self):
- """
- The *server* has a timeout mechanism which will close connections that
- are inactive for a period.
- """
- c = Clock()
- # Hook up our server protocol
- transport = StringTransportWithDisconnection()
- transport.protocol = self.server
- self.server.callLater = c.callLater
- self.server.makeConnection(transport)
-
- # Make sure we can notice when the connection goes away
- lost = []
- connLost = self.server.connectionLost
- self.server.connectionLost = lambda reason: (lost.append(None), connLost(reason))[1]
-
- # 2/3rds of the idle timeout elapses...
- c.pump([0.0] + [self.server.timeOut / 3.0] * 2)
- self.failIf(lost, lost)
-
- # Now some more
- c.pump([0.0, self.server.timeOut / 2.0])
- self.failUnless(lost)
-
-
-
-class Disconnection(unittest.TestCase):
- def testClientDisconnectFailsDeferreds(self):
- c = imap4.IMAP4Client()
- t = StringTransportWithDisconnection()
- c.makeConnection(t)
- d = self.assertFailure(c.login('testuser', 'example.com'), error.ConnectionDone)
- c.connectionLost(error.ConnectionDone("Connection closed"))
- return d
-
-
-
-class SynchronousMailbox(object):
- """
- Trivial, in-memory mailbox implementation which can produce a message
- synchronously.
- """
- def __init__(self, messages):
- self.messages = messages
-
-
- def fetch(self, msgset, uid):
- assert not uid, "Cannot handle uid requests."
- for msg in msgset:
- yield msg, self.messages[msg - 1]
-
-
-
-class StringTransportConsumer(StringTransport):
- producer = None
- streaming = None
-
- def registerProducer(self, producer, streaming):
- self.producer = producer
- self.streaming = streaming
-
-
-
-class Pipelining(unittest.TestCase):
- """
- Tests for various aspects of the IMAP4 server's pipelining support.
- """
- messages = [
- FakeyMessage({}, [], '', '0', None, None),
- FakeyMessage({}, [], '', '1', None, None),
- FakeyMessage({}, [], '', '2', None, None),
- ]
-
- def setUp(self):
- self.iterators = []
-
- self.transport = StringTransportConsumer()
- self.server = imap4.IMAP4Server(None, None, self.iterateInReactor)
- self.server.makeConnection(self.transport)
-
-
- def iterateInReactor(self, iterator):
- d = defer.Deferred()
- self.iterators.append((iterator, d))
- return d
-
-
- def tearDown(self):
- self.server.connectionLost(failure.Failure(error.ConnectionDone()))
-
-
- def test_synchronousFetch(self):
- """
- Test that pipelined FETCH commands which can be responded to
- synchronously are responded to correctly.
- """
- mailbox = SynchronousMailbox(self.messages)
-
- # Skip over authentication and folder selection
- self.server.state = 'select'
- self.server.mbox = mailbox
-
- # Get rid of any greeting junk
- self.transport.clear()
-
- # Here's some pipelined stuff
- self.server.dataReceived(
- '01 FETCH 1 BODY[]\r\n'
- '02 FETCH 2 BODY[]\r\n'
- '03 FETCH 3 BODY[]\r\n')
-
- # Flush anything the server has scheduled to run
- while self.iterators:
- for e in self.iterators[0][0]:
- break
- else:
- self.iterators.pop(0)[1].callback(None)
-
- # The bodies are empty because we aren't simulating a transport
- # exactly correctly (we have StringTransportConsumer but we never
- # call resumeProducing on its producer). It doesn't matter: just
- # make sure the surrounding structure is okay, and that no
- # exceptions occurred.
- self.assertEqual(
- self.transport.value(),
- '* 1 FETCH (BODY[] )\r\n'
- '01 OK FETCH completed\r\n'
- '* 2 FETCH (BODY[] )\r\n'
- '02 OK FETCH completed\r\n'
- '* 3 FETCH (BODY[] )\r\n'
- '03 OK FETCH completed\r\n')
-
-
-
-if ClientTLSContext is None:
- for case in (TLSTestCase,):
- case.skip = "OpenSSL not present"
-elif interfaces.IReactorSSL(reactor, None) is None:
- for case in (TLSTestCase,):
- case.skip = "Reactor doesn't support SSL"
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mail.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mail.py
deleted file mode 100755
index 6d2bdddb..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mail.py
+++ /dev/null
@@ -1,2062 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for large portions of L{twisted.mail}.
-"""
-
-import os
-import errno
-import shutil
-import pickle
-import StringIO
-import rfc822
-import tempfile
-import signal
-
-from zope.interface import Interface, implements
-
-from twisted.trial import unittest
-from twisted.mail import smtp
-from twisted.mail import pop3
-from twisted.names import dns
-from twisted.internet import protocol
-from twisted.internet import defer
-from twisted.internet.defer import Deferred
-from twisted.internet import reactor
-from twisted.internet import interfaces
-from twisted.internet import task
-from twisted.internet.error import DNSLookupError, CannotListenError
-from twisted.internet.error import ProcessDone, ProcessTerminated
-from twisted.internet import address
-from twisted.python import failure
-from twisted.python.filepath import FilePath
-from twisted.python.hashlib import md5
-
-from twisted import mail
-import twisted.mail.mail
-import twisted.mail.maildir
-import twisted.mail.relay
-import twisted.mail.relaymanager
-import twisted.mail.protocols
-import twisted.mail.alias
-
-from twisted.names.error import DNSNameError
-from twisted.names.dns import RRHeader, Record_CNAME, Record_MX
-
-from twisted import cred
-import twisted.cred.credentials
-import twisted.cred.checkers
-import twisted.cred.portal
-
-from twisted.test.proto_helpers import LineSendingProtocol
-
-class DomainWithDefaultsTestCase(unittest.TestCase):
- def testMethods(self):
- d = dict([(x, x + 10) for x in range(10)])
- d = mail.mail.DomainWithDefaultDict(d, 'Default')
-
- self.assertEqual(len(d), 10)
- self.assertEqual(list(iter(d)), range(10))
- self.assertEqual(list(d.iterkeys()), list(iter(d)))
-
- items = list(d.iteritems())
- items.sort()
- self.assertEqual(items, [(x, x + 10) for x in range(10)])
-
- values = list(d.itervalues())
- values.sort()
- self.assertEqual(values, range(10, 20))
-
- items = d.items()
- items.sort()
- self.assertEqual(items, [(x, x + 10) for x in range(10)])
-
- values = d.values()
- values.sort()
- self.assertEqual(values, range(10, 20))
-
- for x in range(10):
- self.assertEqual(d[x], x + 10)
- self.assertEqual(d.get(x), x + 10)
- self.failUnless(x in d)
- self.failUnless(d.has_key(x))
-
- del d[2], d[4], d[6]
-
- self.assertEqual(len(d), 7)
- self.assertEqual(d[2], 'Default')
- self.assertEqual(d[4], 'Default')
- self.assertEqual(d[6], 'Default')
-
- d.update({'a': None, 'b': (), 'c': '*'})
- self.assertEqual(len(d), 10)
- self.assertEqual(d['a'], None)
- self.assertEqual(d['b'], ())
- self.assertEqual(d['c'], '*')
-
- d.clear()
- self.assertEqual(len(d), 0)
-
- self.assertEqual(d.setdefault('key', 'value'), 'value')
- self.assertEqual(d['key'], 'value')
-
- self.assertEqual(d.popitem(), ('key', 'value'))
- self.assertEqual(len(d), 0)
-
- dcopy = d.copy()
- self.assertEqual(d.domains, dcopy.domains)
- self.assertEqual(d.default, dcopy.default)
-
-
- def _stringificationTest(self, stringifier):
- """
- Assert that the class name of a L{mail.mail.DomainWithDefaultDict}
- instance and the string-formatted underlying domain dictionary both
- appear in the string produced by the given string-returning function.
-
- @type stringifier: one-argument callable
- @param stringifier: either C{str} or C{repr}, to be used to get a
- string to make assertions against.
- """
- domain = mail.mail.DomainWithDefaultDict({}, 'Default')
- self.assertIn(domain.__class__.__name__, stringifier(domain))
- domain['key'] = 'value'
- self.assertIn(str({'key': 'value'}), stringifier(domain))
-
-
- def test_str(self):
- """
- L{DomainWithDefaultDict.__str__} should return a string including
- the class name and the domain mapping held by the instance.
- """
- self._stringificationTest(str)
-
-
- def test_repr(self):
- """
- L{DomainWithDefaultDict.__repr__} should return a string including
- the class name and the domain mapping held by the instance.
- """
- self._stringificationTest(repr)
-
-
-
-class BounceTestCase(unittest.TestCase):
- def setUp(self):
- self.domain = mail.mail.BounceDomain()
-
- def testExists(self):
- self.assertRaises(smtp.AddressError, self.domain.exists, "any user")
-
- def testRelay(self):
- self.assertEqual(
- self.domain.willRelay("random q emailer", "protocol"),
- False
- )
-
- def testMessage(self):
- self.assertRaises(NotImplementedError, self.domain.startMessage, "whomever")
-
- def testAddUser(self):
- self.domain.addUser("bob", "password")
- self.assertRaises(smtp.SMTPBadRcpt, self.domain.exists, "bob")
-
-class FileMessageTestCase(unittest.TestCase):
- def setUp(self):
- self.name = "fileMessage.testFile"
- self.final = "final.fileMessage.testFile"
- self.f = file(self.name, 'w')
- self.fp = mail.mail.FileMessage(self.f, self.name, self.final)
-
- def tearDown(self):
- try:
- self.f.close()
- except:
- pass
- try:
- os.remove(self.name)
- except:
- pass
- try:
- os.remove(self.final)
- except:
- pass
-
- def testFinalName(self):
- return self.fp.eomReceived().addCallback(self._cbFinalName)
-
- def _cbFinalName(self, result):
- self.assertEqual(result, self.final)
- self.failUnless(self.f.closed)
- self.failIf(os.path.exists(self.name))
-
- def testContents(self):
- contents = "first line\nsecond line\nthird line\n"
- for line in contents.splitlines():
- self.fp.lineReceived(line)
- self.fp.eomReceived()
- self.assertEqual(file(self.final).read(), contents)
-
- def testInterrupted(self):
- contents = "first line\nsecond line\n"
- for line in contents.splitlines():
- self.fp.lineReceived(line)
- self.fp.connectionLost()
- self.failIf(os.path.exists(self.name))
- self.failIf(os.path.exists(self.final))
-
-class MailServiceTestCase(unittest.TestCase):
- def setUp(self):
- self.service = mail.mail.MailService()
-
- def testFactories(self):
- f = self.service.getPOP3Factory()
- self.failUnless(isinstance(f, protocol.ServerFactory))
- self.failUnless(f.buildProtocol(('127.0.0.1', 12345)), pop3.POP3)
-
- f = self.service.getSMTPFactory()
- self.failUnless(isinstance(f, protocol.ServerFactory))
- self.failUnless(f.buildProtocol(('127.0.0.1', 12345)), smtp.SMTP)
-
- f = self.service.getESMTPFactory()
- self.failUnless(isinstance(f, protocol.ServerFactory))
- self.failUnless(f.buildProtocol(('127.0.0.1', 12345)), smtp.ESMTP)
-
- def testPortals(self):
- o1 = object()
- o2 = object()
- self.service.portals['domain'] = o1
- self.service.portals[''] = o2
-
- self.failUnless(self.service.lookupPortal('domain') is o1)
- self.failUnless(self.service.defaultPortal() is o2)
-
-
-class StringListMailboxTests(unittest.TestCase):
- """
- Tests for L{StringListMailbox}, an in-memory only implementation of
- L{pop3.IMailbox}.
- """
- def test_listOneMessage(self):
- """
- L{StringListMailbox.listMessages} returns the length of the message at
- the offset into the mailbox passed to it.
- """
- mailbox = mail.maildir.StringListMailbox(["abc", "ab", "a"])
- self.assertEqual(mailbox.listMessages(0), 3)
- self.assertEqual(mailbox.listMessages(1), 2)
- self.assertEqual(mailbox.listMessages(2), 1)
-
-
- def test_listAllMessages(self):
- """
- L{StringListMailbox.listMessages} returns a list of the lengths of all
- messages if not passed an index.
- """
- mailbox = mail.maildir.StringListMailbox(["a", "abc", "ab"])
- self.assertEqual(mailbox.listMessages(), [1, 3, 2])
-
-
- def test_getMessage(self):
- """
- L{StringListMailbox.getMessage} returns a file-like object from which
- the contents of the message at the given offset into the mailbox can be
- read.
- """
- mailbox = mail.maildir.StringListMailbox(["foo", "real contents"])
- self.assertEqual(mailbox.getMessage(1).read(), "real contents")
-
-
- def test_getUidl(self):
- """
- L{StringListMailbox.getUidl} returns a unique identifier for the
- message at the given offset into the mailbox.
- """
- mailbox = mail.maildir.StringListMailbox(["foo", "bar"])
- self.assertNotEqual(mailbox.getUidl(0), mailbox.getUidl(1))
-
-
- def test_deleteMessage(self):
- """
- L{StringListMailbox.deleteMessage} marks a message for deletion causing
- further requests for its length to return 0.
- """
- mailbox = mail.maildir.StringListMailbox(["foo"])
- mailbox.deleteMessage(0)
- self.assertEqual(mailbox.listMessages(0), 0)
- self.assertEqual(mailbox.listMessages(), [0])
-
-
- def test_undeleteMessages(self):
- """
- L{StringListMailbox.undeleteMessages} causes any messages marked for
- deletion to be returned to their original state.
- """
- mailbox = mail.maildir.StringListMailbox(["foo"])
- mailbox.deleteMessage(0)
- mailbox.undeleteMessages()
- self.assertEqual(mailbox.listMessages(0), 3)
- self.assertEqual(mailbox.listMessages(), [3])
-
-
- def test_sync(self):
- """
- L{StringListMailbox.sync} causes any messages as marked for deletion to
- be permanently deleted.
- """
- mailbox = mail.maildir.StringListMailbox(["foo"])
- mailbox.deleteMessage(0)
- mailbox.sync()
- mailbox.undeleteMessages()
- self.assertEqual(mailbox.listMessages(0), 0)
- self.assertEqual(mailbox.listMessages(), [0])
-
-
-
-class FailingMaildirMailboxAppendMessageTask(mail.maildir._MaildirMailboxAppendMessageTask):
- _openstate = True
- _writestate = True
- _renamestate = True
- def osopen(self, fn, attr, mode):
- if self._openstate:
- return os.open(fn, attr, mode)
- else:
- raise OSError(errno.EPERM, "Faked Permission Problem")
- def oswrite(self, fh, data):
- if self._writestate:
- return os.write(fh, data)
- else:
- raise OSError(errno.ENOSPC, "Faked Space problem")
- def osrename(self, oldname, newname):
- if self._renamestate:
- return os.rename(oldname, newname)
- else:
- raise OSError(errno.EPERM, "Faked Permission Problem")
-
-
-class _AppendTestMixin(object):
- """
- Mixin for L{MaildirMailbox.appendMessage} test cases which defines a helper
- for serially appending multiple messages to a mailbox.
- """
- def _appendMessages(self, mbox, messages):
- """
- Deliver the given messages one at a time. Delivery is serialized to
- guarantee a predictable order in the mailbox (overlapped message deliver
- makes no guarantees about which message which appear first).
- """
- results = []
- def append():
- for m in messages:
- d = mbox.appendMessage(m)
- d.addCallback(results.append)
- yield d
- d = task.cooperate(append()).whenDone()
- d.addCallback(lambda ignored: results)
- return d
-
-
-
-class MaildirAppendStringTestCase(unittest.TestCase, _AppendTestMixin):
- """
- Tests for L{MaildirMailbox.appendMessage} when invoked with a C{str}.
- """
- def setUp(self):
- self.d = self.mktemp()
- mail.maildir.initializeMaildir(self.d)
-
-
- def _append(self, ignored, mbox):
- d = mbox.appendMessage('TEST')
- return self.assertFailure(d, Exception)
-
-
- def _setState(self, ignored, mbox, rename=None, write=None, open=None):
- """
- Change the behavior of future C{rename}, C{write}, or C{open} calls made
- by the mailbox C{mbox}.
-
- @param rename: If not C{None}, a new value for the C{_renamestate}
- attribute of the mailbox's append factory. The original value will
- be restored at the end of the test.
-
- @param write: Like C{rename}, but for the C{_writestate} attribute.
-
- @param open: Like C{rename}, but for the C{_openstate} attribute.
- """
- if rename is not None:
- self.addCleanup(
- setattr, mbox.AppendFactory, '_renamestate',
- mbox.AppendFactory._renamestate)
- mbox.AppendFactory._renamestate = rename
- if write is not None:
- self.addCleanup(
- setattr, mbox.AppendFactory, '_writestate',
- mbox.AppendFactory._writestate)
- mbox.AppendFactory._writestate = write
- if open is not None:
- self.addCleanup(
- setattr, mbox.AppendFactory, '_openstate',
- mbox.AppendFactory._openstate)
- mbox.AppendFactory._openstate = open
-
-
- def test_append(self):
- """
- L{MaildirMailbox.appendMessage} returns a L{Deferred} which fires when
- the message has been added to the end of the mailbox.
- """
- mbox = mail.maildir.MaildirMailbox(self.d)
- mbox.AppendFactory = FailingMaildirMailboxAppendMessageTask
-
- d = self._appendMessages(mbox, ["X" * i for i in range(1, 11)])
- d.addCallback(self.assertEqual, [None] * 10)
- d.addCallback(self._cbTestAppend, mbox)
- return d
-
-
- def _cbTestAppend(self, ignored, mbox):
- """
- Check that the mailbox has the expected number (ten) of messages in it,
- and that each has the expected contents, and that they are in the same
- order as that in which they were appended.
- """
- self.assertEqual(len(mbox.listMessages()), 10)
- self.assertEqual(
- [len(mbox.getMessage(i).read()) for i in range(10)],
- range(1, 11))
- # test in the right order: last to first error location.
- self._setState(None, mbox, rename=False)
- d = self._append(None, mbox)
- d.addCallback(self._setState, mbox, rename=True, write=False)
- d.addCallback(self._append, mbox)
- d.addCallback(self._setState, mbox, write=True, open=False)
- d.addCallback(self._append, mbox)
- d.addCallback(self._setState, mbox, open=True)
- return d
-
-
-
-class MaildirAppendFileTestCase(unittest.TestCase, _AppendTestMixin):
- """
- Tests for L{MaildirMailbox.appendMessage} when invoked with a C{str}.
- """
- def setUp(self):
- self.d = self.mktemp()
- mail.maildir.initializeMaildir(self.d)
-
-
- def test_append(self):
- """
- L{MaildirMailbox.appendMessage} returns a L{Deferred} which fires when
- the message has been added to the end of the mailbox.
- """
- mbox = mail.maildir.MaildirMailbox(self.d)
- messages = []
- for i in xrange(1, 11):
- temp = tempfile.TemporaryFile()
- temp.write("X" * i)
- temp.seek(0, 0)
- messages.append(temp)
- self.addCleanup(temp.close)
-
- d = self._appendMessages(mbox, messages)
- d.addCallback(self._cbTestAppend, mbox)
- return d
-
-
- def _cbTestAppend(self, result, mbox):
- """
- Check that the mailbox has the expected number (ten) of messages in it,
- and that each has the expected contents, and that they are in the same
- order as that in which they were appended.
- """
- self.assertEqual(len(mbox.listMessages()), 10)
- self.assertEqual(
- [len(mbox.getMessage(i).read()) for i in range(10)],
- range(1, 11))
-
-
-
-class MaildirTestCase(unittest.TestCase):
- def setUp(self):
- self.d = self.mktemp()
- mail.maildir.initializeMaildir(self.d)
-
- def tearDown(self):
- shutil.rmtree(self.d)
-
- def testInitializer(self):
- d = self.d
- trash = os.path.join(d, '.Trash')
-
- self.failUnless(os.path.exists(d) and os.path.isdir(d))
- self.failUnless(os.path.exists(os.path.join(d, 'new')))
- self.failUnless(os.path.exists(os.path.join(d, 'cur')))
- self.failUnless(os.path.exists(os.path.join(d, 'tmp')))
- self.failUnless(os.path.isdir(os.path.join(d, 'new')))
- self.failUnless(os.path.isdir(os.path.join(d, 'cur')))
- self.failUnless(os.path.isdir(os.path.join(d, 'tmp')))
-
- self.failUnless(os.path.exists(os.path.join(trash, 'new')))
- self.failUnless(os.path.exists(os.path.join(trash, 'cur')))
- self.failUnless(os.path.exists(os.path.join(trash, 'tmp')))
- self.failUnless(os.path.isdir(os.path.join(trash, 'new')))
- self.failUnless(os.path.isdir(os.path.join(trash, 'cur')))
- self.failUnless(os.path.isdir(os.path.join(trash, 'tmp')))
-
-
- def test_nameGenerator(self):
- """
- Each call to L{_MaildirNameGenerator.generate} returns a unique
- string suitable for use as the basename of a new message file. The
- names are ordered such that those generated earlier sort less than
- those generated later.
- """
- clock = task.Clock()
- clock.advance(0.05)
- generator = mail.maildir._MaildirNameGenerator(clock)
-
- firstName = generator.generate()
- clock.advance(0.05)
- secondName = generator.generate()
-
- self.assertTrue(firstName < secondName)
-
-
- def test_mailbox(self):
- """
- Exercise the methods of L{IMailbox} as implemented by
- L{MaildirMailbox}.
- """
- j = os.path.join
- n = mail.maildir._generateMaildirName
- msgs = [j(b, n()) for b in ('cur', 'new') for x in range(5)]
-
- # Toss a few files into the mailbox
- i = 1
- for f in msgs:
- fObj = file(j(self.d, f), 'w')
- fObj.write('x' * i)
- fObj.close()
- i = i + 1
-
- mb = mail.maildir.MaildirMailbox(self.d)
- self.assertEqual(mb.listMessages(), range(1, 11))
- self.assertEqual(mb.listMessages(1), 2)
- self.assertEqual(mb.listMessages(5), 6)
-
- self.assertEqual(mb.getMessage(6).read(), 'x' * 7)
- self.assertEqual(mb.getMessage(1).read(), 'x' * 2)
-
- d = {}
- for i in range(10):
- u = mb.getUidl(i)
- self.failIf(u in d)
- d[u] = None
-
- p, f = os.path.split(msgs[5])
-
- mb.deleteMessage(5)
- self.assertEqual(mb.listMessages(5), 0)
- self.failUnless(os.path.exists(j(self.d, '.Trash', 'cur', f)))
- self.failIf(os.path.exists(j(self.d, msgs[5])))
-
- mb.undeleteMessages()
- self.assertEqual(mb.listMessages(5), 6)
- self.failIf(os.path.exists(j(self.d, '.Trash', 'cur', f)))
- self.failUnless(os.path.exists(j(self.d, msgs[5])))
-
-class MaildirDirdbmDomainTestCase(unittest.TestCase):
- def setUp(self):
- self.P = self.mktemp()
- self.S = mail.mail.MailService()
- self.D = mail.maildir.MaildirDirdbmDomain(self.S, self.P)
-
- def tearDown(self):
- shutil.rmtree(self.P)
-
- def testAddUser(self):
- toAdd = (('user1', 'pwd1'), ('user2', 'pwd2'), ('user3', 'pwd3'))
- for (u, p) in toAdd:
- self.D.addUser(u, p)
-
- for (u, p) in toAdd:
- self.failUnless(u in self.D.dbm)
- self.assertEqual(self.D.dbm[u], p)
- self.failUnless(os.path.exists(os.path.join(self.P, u)))
-
- def testCredentials(self):
- creds = self.D.getCredentialsCheckers()
-
- self.assertEqual(len(creds), 1)
- self.failUnless(cred.checkers.ICredentialsChecker.providedBy(creds[0]))
- self.failUnless(cred.credentials.IUsernamePassword in creds[0].credentialInterfaces)
-
- def testRequestAvatar(self):
- class ISomething(Interface):
- pass
-
- self.D.addUser('user', 'password')
- self.assertRaises(
- NotImplementedError,
- self.D.requestAvatar, 'user', None, ISomething
- )
-
- t = self.D.requestAvatar('user', None, pop3.IMailbox)
- self.assertEqual(len(t), 3)
- self.failUnless(t[0] is pop3.IMailbox)
- self.failUnless(pop3.IMailbox.providedBy(t[1]))
-
- t[2]()
-
- def testRequestAvatarId(self):
- self.D.addUser('user', 'password')
- database = self.D.getCredentialsCheckers()[0]
-
- creds = cred.credentials.UsernamePassword('user', 'wrong password')
- self.assertRaises(
- cred.error.UnauthorizedLogin,
- database.requestAvatarId, creds
- )
-
- creds = cred.credentials.UsernamePassword('user', 'password')
- self.assertEqual(database.requestAvatarId(creds), 'user')
-
-
-class StubAliasableDomain(object):
- """
- Minimal testable implementation of IAliasableDomain.
- """
- implements(mail.mail.IAliasableDomain)
-
- def exists(self, user):
- """
- No test coverage for invocations of this method on domain objects,
- so we just won't implement it.
- """
- raise NotImplementedError()
-
-
- def addUser(self, user, password):
- """
- No test coverage for invocations of this method on domain objects,
- so we just won't implement it.
- """
- raise NotImplementedError()
-
-
- def getCredentialsCheckers(self):
- """
- This needs to succeed in order for other tests to complete
- successfully, but we don't actually assert anything about its
- behavior. Return an empty list. Sometime later we should return
- something else and assert that a portal got set up properly.
- """
- return []
-
-
- def setAliasGroup(self, aliases):
- """
- Just record the value so the test can check it later.
- """
- self.aliasGroup = aliases
-
-
-class ServiceDomainTestCase(unittest.TestCase):
- def setUp(self):
- self.S = mail.mail.MailService()
- self.D = mail.protocols.DomainDeliveryBase(self.S, None)
- self.D.service = self.S
- self.D.protocolName = 'TEST'
- self.D.host = 'hostname'
-
- self.tmpdir = self.mktemp()
- domain = mail.maildir.MaildirDirdbmDomain(self.S, self.tmpdir)
- domain.addUser('user', 'password')
- self.S.addDomain('test.domain', domain)
-
- def tearDown(self):
- shutil.rmtree(self.tmpdir)
-
-
- def testAddAliasableDomain(self):
- """
- Test that adding an IAliasableDomain to a mail service properly sets
- up alias group references and such.
- """
- aliases = object()
- domain = StubAliasableDomain()
- self.S.aliases = aliases
- self.S.addDomain('example.com', domain)
- self.assertIdentical(domain.aliasGroup, aliases)
-
-
- def testReceivedHeader(self):
- hdr = self.D.receivedHeader(
- ('remotehost', '123.232.101.234'),
- smtp.Address('<someguy@somplace>'),
- ['user@host.name']
- )
- fp = StringIO.StringIO(hdr)
- m = rfc822.Message(fp)
- self.assertEqual(len(m.items()), 1)
- self.failUnless(m.has_key('Received'))
-
- def testValidateTo(self):
- user = smtp.User('user@test.domain', 'helo', None, 'wherever@whatever')
- return defer.maybeDeferred(self.D.validateTo, user
- ).addCallback(self._cbValidateTo
- )
-
- def _cbValidateTo(self, result):
- self.failUnless(callable(result))
-
- def testValidateToBadUsername(self):
- user = smtp.User('resu@test.domain', 'helo', None, 'wherever@whatever')
- return self.assertFailure(
- defer.maybeDeferred(self.D.validateTo, user),
- smtp.SMTPBadRcpt)
-
- def testValidateToBadDomain(self):
- user = smtp.User('user@domain.test', 'helo', None, 'wherever@whatever')
- return self.assertFailure(
- defer.maybeDeferred(self.D.validateTo, user),
- smtp.SMTPBadRcpt)
-
- def testValidateFrom(self):
- helo = ('hostname', '127.0.0.1')
- origin = smtp.Address('<user@hostname>')
- self.failUnless(self.D.validateFrom(helo, origin) is origin)
-
- helo = ('hostname', '1.2.3.4')
- origin = smtp.Address('<user@hostname>')
- self.failUnless(self.D.validateFrom(helo, origin) is origin)
-
- helo = ('hostname', '1.2.3.4')
- origin = smtp.Address('<>')
- self.failUnless(self.D.validateFrom(helo, origin) is origin)
-
- self.assertRaises(
- smtp.SMTPBadSender,
- self.D.validateFrom, None, origin
- )
-
-class VirtualPOP3TestCase(unittest.TestCase):
- def setUp(self):
- self.tmpdir = self.mktemp()
- self.S = mail.mail.MailService()
- self.D = mail.maildir.MaildirDirdbmDomain(self.S, self.tmpdir)
- self.D.addUser('user', 'password')
- self.S.addDomain('test.domain', self.D)
-
- portal = cred.portal.Portal(self.D)
- map(portal.registerChecker, self.D.getCredentialsCheckers())
- self.S.portals[''] = self.S.portals['test.domain'] = portal
-
- self.P = mail.protocols.VirtualPOP3()
- self.P.service = self.S
- self.P.magic = '<unit test magic>'
-
- def tearDown(self):
- shutil.rmtree(self.tmpdir)
-
- def testAuthenticateAPOP(self):
- resp = md5(self.P.magic + 'password').hexdigest()
- return self.P.authenticateUserAPOP('user', resp
- ).addCallback(self._cbAuthenticateAPOP
- )
-
- def _cbAuthenticateAPOP(self, result):
- self.assertEqual(len(result), 3)
- self.assertEqual(result[0], pop3.IMailbox)
- self.failUnless(pop3.IMailbox.providedBy(result[1]))
- result[2]()
-
- def testAuthenticateIncorrectUserAPOP(self):
- resp = md5(self.P.magic + 'password').hexdigest()
- return self.assertFailure(
- self.P.authenticateUserAPOP('resu', resp),
- cred.error.UnauthorizedLogin)
-
- def testAuthenticateIncorrectResponseAPOP(self):
- resp = md5('wrong digest').hexdigest()
- return self.assertFailure(
- self.P.authenticateUserAPOP('user', resp),
- cred.error.UnauthorizedLogin)
-
- def testAuthenticatePASS(self):
- return self.P.authenticateUserPASS('user', 'password'
- ).addCallback(self._cbAuthenticatePASS
- )
-
- def _cbAuthenticatePASS(self, result):
- self.assertEqual(len(result), 3)
- self.assertEqual(result[0], pop3.IMailbox)
- self.failUnless(pop3.IMailbox.providedBy(result[1]))
- result[2]()
-
- def testAuthenticateBadUserPASS(self):
- return self.assertFailure(
- self.P.authenticateUserPASS('resu', 'password'),
- cred.error.UnauthorizedLogin)
-
- def testAuthenticateBadPasswordPASS(self):
- return self.assertFailure(
- self.P.authenticateUserPASS('user', 'wrong password'),
- cred.error.UnauthorizedLogin)
-
-class empty(smtp.User):
- def __init__(self):
- pass
-
-class RelayTestCase(unittest.TestCase):
- def testExists(self):
- service = mail.mail.MailService()
- domain = mail.relay.DomainQueuer(service)
-
- doRelay = [
- address.UNIXAddress('/var/run/mail-relay'),
- address.IPv4Address('TCP', '127.0.0.1', 12345),
- ]
-
- dontRelay = [
- address.IPv4Address('TCP', '192.168.2.1', 62),
- address.IPv4Address('TCP', '1.2.3.4', 1943),
- ]
-
- for peer in doRelay:
- user = empty()
- user.orig = 'user@host'
- user.dest = 'tsoh@resu'
- user.protocol = empty()
- user.protocol.transport = empty()
- user.protocol.transport.getPeer = lambda: peer
-
- self.failUnless(callable(domain.exists(user)))
-
- for peer in dontRelay:
- user = empty()
- user.orig = 'some@place'
- user.protocol = empty()
- user.protocol.transport = empty()
- user.protocol.transport.getPeer = lambda: peer
- user.dest = 'who@cares'
-
- self.assertRaises(smtp.SMTPBadRcpt, domain.exists, user)
-
-class RelayerTestCase(unittest.TestCase):
- def setUp(self):
- self.tmpdir = self.mktemp()
- os.mkdir(self.tmpdir)
- self.messageFiles = []
- for i in range(10):
- name = os.path.join(self.tmpdir, 'body-%d' % (i,))
- f = file(name + '-H', 'w')
- pickle.dump(['from-%d' % (i,), 'to-%d' % (i,)], f)
- f.close()
-
- f = file(name + '-D', 'w')
- f.write(name)
- f.seek(0, 0)
- self.messageFiles.append(name)
-
- self.R = mail.relay.RelayerMixin()
- self.R.loadMessages(self.messageFiles)
-
- def tearDown(self):
- shutil.rmtree(self.tmpdir)
-
- def testMailFrom(self):
- for i in range(10):
- self.assertEqual(self.R.getMailFrom(), 'from-%d' % (i,))
- self.R.sentMail(250, None, None, None, None)
- self.assertEqual(self.R.getMailFrom(), None)
-
- def testMailTo(self):
- for i in range(10):
- self.assertEqual(self.R.getMailTo(), ['to-%d' % (i,)])
- self.R.sentMail(250, None, None, None, None)
- self.assertEqual(self.R.getMailTo(), None)
-
- def testMailData(self):
- for i in range(10):
- name = os.path.join(self.tmpdir, 'body-%d' % (i,))
- self.assertEqual(self.R.getMailData().read(), name)
- self.R.sentMail(250, None, None, None, None)
- self.assertEqual(self.R.getMailData(), None)
-
-class Manager:
- def __init__(self):
- self.success = []
- self.failure = []
- self.done = []
-
- def notifySuccess(self, factory, message):
- self.success.append((factory, message))
-
- def notifyFailure(self, factory, message):
- self.failure.append((factory, message))
-
- def notifyDone(self, factory):
- self.done.append(factory)
-
-class ManagedRelayerTestCase(unittest.TestCase):
- def setUp(self):
- self.manager = Manager()
- self.messages = range(0, 20, 2)
- self.factory = object()
- self.relay = mail.relaymanager.ManagedRelayerMixin(self.manager)
- self.relay.messages = self.messages[:]
- self.relay.names = self.messages[:]
- self.relay.factory = self.factory
-
- def testSuccessfulSentMail(self):
- for i in self.messages:
- self.relay.sentMail(250, None, None, None, None)
-
- self.assertEqual(
- self.manager.success,
- [(self.factory, m) for m in self.messages]
- )
-
- def testFailedSentMail(self):
- for i in self.messages:
- self.relay.sentMail(550, None, None, None, None)
-
- self.assertEqual(
- self.manager.failure,
- [(self.factory, m) for m in self.messages]
- )
-
- def testConnectionLost(self):
- self.relay.connectionLost(failure.Failure(Exception()))
- self.assertEqual(self.manager.done, [self.factory])
-
-class DirectoryQueueTestCase(unittest.TestCase):
- def setUp(self):
- # This is almost a test case itself.
- self.tmpdir = self.mktemp()
- os.mkdir(self.tmpdir)
- self.queue = mail.relaymanager.Queue(self.tmpdir)
- self.queue.noisy = False
- for m in range(25):
- hdrF, msgF = self.queue.createNewMessage()
- pickle.dump(['header', m], hdrF)
- hdrF.close()
- msgF.lineReceived('body: %d' % (m,))
- msgF.eomReceived()
- self.queue.readDirectory()
-
- def tearDown(self):
- shutil.rmtree(self.tmpdir)
-
- def testWaiting(self):
- self.failUnless(self.queue.hasWaiting())
- self.assertEqual(len(self.queue.getWaiting()), 25)
-
- waiting = self.queue.getWaiting()
- self.queue.setRelaying(waiting[0])
- self.assertEqual(len(self.queue.getWaiting()), 24)
-
- self.queue.setWaiting(waiting[0])
- self.assertEqual(len(self.queue.getWaiting()), 25)
-
- def testRelaying(self):
- for m in self.queue.getWaiting():
- self.queue.setRelaying(m)
- self.assertEqual(
- len(self.queue.getRelayed()),
- 25 - len(self.queue.getWaiting())
- )
-
- self.failIf(self.queue.hasWaiting())
-
- relayed = self.queue.getRelayed()
- self.queue.setWaiting(relayed[0])
- self.assertEqual(len(self.queue.getWaiting()), 1)
- self.assertEqual(len(self.queue.getRelayed()), 24)
-
- def testDone(self):
- msg = self.queue.getWaiting()[0]
- self.queue.setRelaying(msg)
- self.queue.done(msg)
-
- self.assertEqual(len(self.queue.getWaiting()), 24)
- self.assertEqual(len(self.queue.getRelayed()), 0)
-
- self.failIf(msg in self.queue.getWaiting())
- self.failIf(msg in self.queue.getRelayed())
-
- def testEnvelope(self):
- envelopes = []
-
- for msg in self.queue.getWaiting():
- envelopes.append(self.queue.getEnvelope(msg))
-
- envelopes.sort()
- for i in range(25):
- self.assertEqual(
- envelopes.pop(0),
- ['header', i]
- )
-
-from twisted.names import server
-from twisted.names import client
-from twisted.names import common
-
-class TestAuthority(common.ResolverBase):
- def __init__(self):
- common.ResolverBase.__init__(self)
- self.addresses = {}
-
- def _lookup(self, name, cls, type, timeout = None):
- if name in self.addresses and type == dns.MX:
- results = []
- for a in self.addresses[name]:
- hdr = dns.RRHeader(
- name, dns.MX, dns.IN, 60, dns.Record_MX(0, a)
- )
- results.append(hdr)
- return defer.succeed((results, [], []))
- return defer.fail(failure.Failure(dns.DomainError(name)))
-
-def setUpDNS(self):
- self.auth = TestAuthority()
- factory = server.DNSServerFactory([self.auth])
- protocol = dns.DNSDatagramProtocol(factory)
- while 1:
- self.port = reactor.listenTCP(0, factory, interface='127.0.0.1')
- portNumber = self.port.getHost().port
-
- try:
- self.udpPort = reactor.listenUDP(portNumber, protocol, interface='127.0.0.1')
- except CannotListenError:
- self.port.stopListening()
- else:
- break
- self.resolver = client.Resolver(servers=[('127.0.0.1', portNumber)])
-
-
-def tearDownDNS(self):
- dl = []
- dl.append(defer.maybeDeferred(self.port.stopListening))
- dl.append(defer.maybeDeferred(self.udpPort.stopListening))
- if self.resolver.protocol.transport is not None:
- dl.append(defer.maybeDeferred(self.resolver.protocol.transport.stopListening))
- try:
- self.resolver._parseCall.cancel()
- except:
- pass
- return defer.DeferredList(dl)
-
-class MXTestCase(unittest.TestCase):
- """
- Tests for L{mail.relaymanager.MXCalculator}.
- """
- def setUp(self):
- setUpDNS(self)
- self.clock = task.Clock()
- self.mx = mail.relaymanager.MXCalculator(self.resolver, self.clock)
-
- def tearDown(self):
- return tearDownDNS(self)
-
-
- def test_defaultClock(self):
- """
- L{MXCalculator}'s default clock is C{twisted.internet.reactor}.
- """
- self.assertIdentical(
- mail.relaymanager.MXCalculator(self.resolver).clock,
- reactor)
-
-
- def testSimpleSuccess(self):
- self.auth.addresses['test.domain'] = ['the.email.test.domain']
- return self.mx.getMX('test.domain').addCallback(self._cbSimpleSuccess)
-
- def _cbSimpleSuccess(self, mx):
- self.assertEqual(mx.preference, 0)
- self.assertEqual(str(mx.name), 'the.email.test.domain')
-
- def testSimpleFailure(self):
- self.mx.fallbackToDomain = False
- return self.assertFailure(self.mx.getMX('test.domain'), IOError)
-
- def testSimpleFailureWithFallback(self):
- return self.assertFailure(self.mx.getMX('test.domain'), DNSLookupError)
-
-
- def _exchangeTest(self, domain, records, correctMailExchange):
- """
- Issue an MX request for the given domain and arrange for it to be
- responded to with the given records. Verify that the resulting mail
- exchange is the indicated host.
-
- @type domain: C{str}
- @type records: C{list} of L{RRHeader}
- @type correctMailExchange: C{str}
- @rtype: L{Deferred}
- """
- class DummyResolver(object):
- def lookupMailExchange(self, name):
- if name == domain:
- return defer.succeed((
- records,
- [],
- []))
- return defer.fail(DNSNameError(domain))
-
- self.mx.resolver = DummyResolver()
- d = self.mx.getMX(domain)
- def gotMailExchange(record):
- self.assertEqual(str(record.name), correctMailExchange)
- d.addCallback(gotMailExchange)
- return d
-
-
- def test_mailExchangePreference(self):
- """
- The MX record with the lowest preference is returned by
- L{MXCalculator.getMX}.
- """
- domain = "example.com"
- good = "good.example.com"
- bad = "bad.example.com"
-
- records = [
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(1, bad)),
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(0, good)),
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(2, bad))]
- return self._exchangeTest(domain, records, good)
-
-
- def test_badExchangeExcluded(self):
- """
- L{MXCalculator.getMX} returns the MX record with the lowest preference
- which is not also marked as bad.
- """
- domain = "example.com"
- good = "good.example.com"
- bad = "bad.example.com"
-
- records = [
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(0, bad)),
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(1, good))]
- self.mx.markBad(bad)
- return self._exchangeTest(domain, records, good)
-
-
- def test_fallbackForAllBadExchanges(self):
- """
- L{MXCalculator.getMX} returns the MX record with the lowest preference
- if all the MX records in the response have been marked bad.
- """
- domain = "example.com"
- bad = "bad.example.com"
- worse = "worse.example.com"
-
- records = [
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(0, bad)),
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(1, worse))]
- self.mx.markBad(bad)
- self.mx.markBad(worse)
- return self._exchangeTest(domain, records, bad)
-
-
- def test_badExchangeExpires(self):
- """
- L{MXCalculator.getMX} returns the MX record with the lowest preference
- if it was last marked bad longer than L{MXCalculator.timeOutBadMX}
- seconds ago.
- """
- domain = "example.com"
- good = "good.example.com"
- previouslyBad = "bad.example.com"
-
- records = [
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(0, previouslyBad)),
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(1, good))]
- self.mx.markBad(previouslyBad)
- self.clock.advance(self.mx.timeOutBadMX)
- return self._exchangeTest(domain, records, previouslyBad)
-
-
- def test_goodExchangeUsed(self):
- """
- L{MXCalculator.getMX} returns the MX record with the lowest preference
- if it was marked good after it was marked bad.
- """
- domain = "example.com"
- good = "good.example.com"
- previouslyBad = "bad.example.com"
-
- records = [
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(0, previouslyBad)),
- RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(1, good))]
- self.mx.markBad(previouslyBad)
- self.mx.markGood(previouslyBad)
- self.clock.advance(self.mx.timeOutBadMX)
- return self._exchangeTest(domain, records, previouslyBad)
-
-
- def test_successWithoutResults(self):
- """
- If an MX lookup succeeds but the result set is empty,
- L{MXCalculator.getMX} should try to look up an I{A} record for the
- requested name and call back its returned Deferred with that
- address.
- """
- ip = '1.2.3.4'
- domain = 'example.org'
-
- class DummyResolver(object):
- """
- Fake resolver which will respond to an MX lookup with an empty
- result set.
-
- @ivar mx: A dictionary mapping hostnames to three-tuples of
- results to be returned from I{MX} lookups.
-
- @ivar a: A dictionary mapping hostnames to addresses to be
- returned from I{A} lookups.
- """
- mx = {domain: ([], [], [])}
- a = {domain: ip}
-
- def lookupMailExchange(self, domain):
- return defer.succeed(self.mx[domain])
-
- def getHostByName(self, domain):
- return defer.succeed(self.a[domain])
-
- self.mx.resolver = DummyResolver()
- d = self.mx.getMX(domain)
- d.addCallback(self.assertEqual, Record_MX(name=ip))
- return d
-
-
- def test_failureWithSuccessfulFallback(self):
- """
- Test that if the MX record lookup fails, fallback is enabled, and an A
- record is available for the name, then the Deferred returned by
- L{MXCalculator.getMX} ultimately fires with a Record_MX instance which
- gives the address in the A record for the name.
- """
- class DummyResolver(object):
- """
- Fake resolver which will fail an MX lookup but then succeed a
- getHostByName call.
- """
- def lookupMailExchange(self, domain):
- return defer.fail(DNSNameError())
-
- def getHostByName(self, domain):
- return defer.succeed("1.2.3.4")
-
- self.mx.resolver = DummyResolver()
- d = self.mx.getMX("domain")
- d.addCallback(self.assertEqual, Record_MX(name="1.2.3.4"))
- return d
-
-
- def test_cnameWithoutGlueRecords(self):
- """
- If an MX lookup returns a single CNAME record as a result, MXCalculator
- will perform an MX lookup for the canonical name indicated and return
- the MX record which results.
- """
- alias = "alias.example.com"
- canonical = "canonical.example.com"
- exchange = "mail.example.com"
-
- class DummyResolver(object):
- """
- Fake resolver which will return a CNAME for an MX lookup of a name
- which is an alias and an MX for an MX lookup of the canonical name.
- """
- def lookupMailExchange(self, domain):
- if domain == alias:
- return defer.succeed((
- [RRHeader(name=domain,
- type=Record_CNAME.TYPE,
- payload=Record_CNAME(canonical))],
- [], []))
- elif domain == canonical:
- return defer.succeed((
- [RRHeader(name=domain,
- type=Record_MX.TYPE,
- payload=Record_MX(0, exchange))],
- [], []))
- else:
- return defer.fail(DNSNameError(domain))
-
- self.mx.resolver = DummyResolver()
- d = self.mx.getMX(alias)
- d.addCallback(self.assertEqual, Record_MX(name=exchange))
- return d
-
-
- def test_cnameChain(self):
- """
- If L{MXCalculator.getMX} encounters a CNAME chain which is longer than
- the length specified, the returned L{Deferred} should errback with
- L{CanonicalNameChainTooLong}.
- """
- class DummyResolver(object):
- """
- Fake resolver which generates a CNAME chain of infinite length in
- response to MX lookups.
- """
- chainCounter = 0
-
- def lookupMailExchange(self, domain):
- self.chainCounter += 1
- name = 'x-%d.example.com' % (self.chainCounter,)
- return defer.succeed((
- [RRHeader(name=domain,
- type=Record_CNAME.TYPE,
- payload=Record_CNAME(name))],
- [], []))
-
- cnameLimit = 3
- self.mx.resolver = DummyResolver()
- d = self.mx.getMX("mail.example.com", cnameLimit)
- self.assertFailure(
- d, twisted.mail.relaymanager.CanonicalNameChainTooLong)
- def cbChainTooLong(error):
- self.assertEqual(error.args[0], Record_CNAME("x-%d.example.com" % (cnameLimit + 1,)))
- self.assertEqual(self.mx.resolver.chainCounter, cnameLimit + 1)
- d.addCallback(cbChainTooLong)
- return d
-
-
- def test_cnameWithGlueRecords(self):
- """
- If an MX lookup returns a CNAME and the MX record for the CNAME, the
- L{Deferred} returned by L{MXCalculator.getMX} should be called back
- with the name from the MX record without further lookups being
- attempted.
- """
- lookedUp = []
- alias = "alias.example.com"
- canonical = "canonical.example.com"
- exchange = "mail.example.com"
-
- class DummyResolver(object):
- def lookupMailExchange(self, domain):
- if domain != alias or lookedUp:
- # Don't give back any results for anything except the alias
- # or on any request after the first.
- return ([], [], [])
- return defer.succeed((
- [RRHeader(name=alias,
- type=Record_CNAME.TYPE,
- payload=Record_CNAME(canonical)),
- RRHeader(name=canonical,
- type=Record_MX.TYPE,
- payload=Record_MX(name=exchange))],
- [], []))
-
- self.mx.resolver = DummyResolver()
- d = self.mx.getMX(alias)
- d.addCallback(self.assertEqual, Record_MX(name=exchange))
- return d
-
-
- def test_cnameLoopWithGlueRecords(self):
- """
- If an MX lookup returns two CNAME records which point to each other,
- the loop should be detected and the L{Deferred} returned by
- L{MXCalculator.getMX} should be errbacked with L{CanonicalNameLoop}.
- """
- firstAlias = "cname1.example.com"
- secondAlias = "cname2.example.com"
-
- class DummyResolver(object):
- def lookupMailExchange(self, domain):
- return defer.succeed((
- [RRHeader(name=firstAlias,
- type=Record_CNAME.TYPE,
- payload=Record_CNAME(secondAlias)),
- RRHeader(name=secondAlias,
- type=Record_CNAME.TYPE,
- payload=Record_CNAME(firstAlias))],
- [], []))
-
- self.mx.resolver = DummyResolver()
- d = self.mx.getMX(firstAlias)
- self.assertFailure(d, twisted.mail.relaymanager.CanonicalNameLoop)
- return d
-
-
- def testManyRecords(self):
- self.auth.addresses['test.domain'] = [
- 'mx1.test.domain', 'mx2.test.domain', 'mx3.test.domain'
- ]
- return self.mx.getMX('test.domain'
- ).addCallback(self._cbManyRecordsSuccessfulLookup
- )
-
- def _cbManyRecordsSuccessfulLookup(self, mx):
- self.failUnless(str(mx.name).split('.', 1)[0] in ('mx1', 'mx2', 'mx3'))
- self.mx.markBad(str(mx.name))
- return self.mx.getMX('test.domain'
- ).addCallback(self._cbManyRecordsDifferentResult, mx
- )
-
- def _cbManyRecordsDifferentResult(self, nextMX, mx):
- self.assertNotEqual(str(mx.name), str(nextMX.name))
- self.mx.markBad(str(nextMX.name))
-
- return self.mx.getMX('test.domain'
- ).addCallback(self._cbManyRecordsLastResult, mx, nextMX
- )
-
- def _cbManyRecordsLastResult(self, lastMX, mx, nextMX):
- self.assertNotEqual(str(mx.name), str(lastMX.name))
- self.assertNotEqual(str(nextMX.name), str(lastMX.name))
-
- self.mx.markBad(str(lastMX.name))
- self.mx.markGood(str(nextMX.name))
-
- return self.mx.getMX('test.domain'
- ).addCallback(self._cbManyRecordsRepeatSpecificResult, nextMX
- )
-
- def _cbManyRecordsRepeatSpecificResult(self, againMX, nextMX):
- self.assertEqual(str(againMX.name), str(nextMX.name))
-
-class LiveFireExercise(unittest.TestCase):
- if interfaces.IReactorUDP(reactor, None) is None:
- skip = "UDP support is required to determining MX records"
-
- def setUp(self):
- setUpDNS(self)
- self.tmpdirs = [
- 'domainDir', 'insertionDomain', 'insertionQueue',
- 'destinationDomain', 'destinationQueue'
- ]
-
- def tearDown(self):
- for d in self.tmpdirs:
- if os.path.exists(d):
- shutil.rmtree(d)
- return tearDownDNS(self)
-
- def testLocalDelivery(self):
- service = mail.mail.MailService()
- service.smtpPortal.registerChecker(cred.checkers.AllowAnonymousAccess())
- domain = mail.maildir.MaildirDirdbmDomain(service, 'domainDir')
- domain.addUser('user', 'password')
- service.addDomain('test.domain', domain)
- service.portals[''] = service.portals['test.domain']
- map(service.portals[''].registerChecker, domain.getCredentialsCheckers())
-
- service.setQueue(mail.relay.DomainQueuer(service))
- manager = mail.relaymanager.SmartHostSMTPRelayingManager(service.queue, None)
- helper = mail.relaymanager.RelayStateHelper(manager, 1)
-
- f = service.getSMTPFactory()
-
- self.smtpServer = reactor.listenTCP(0, f, interface='127.0.0.1')
-
- client = LineSendingProtocol([
- 'HELO meson',
- 'MAIL FROM: <user@hostname>',
- 'RCPT TO: <user@test.domain>',
- 'DATA',
- 'This is the message',
- '.',
- 'QUIT'
- ])
-
- done = Deferred()
- f = protocol.ClientFactory()
- f.protocol = lambda: client
- f.clientConnectionLost = lambda *args: done.callback(None)
- reactor.connectTCP('127.0.0.1', self.smtpServer.getHost().port, f)
-
- def finished(ign):
- mbox = domain.requestAvatar('user', None, pop3.IMailbox)[1]
- msg = mbox.getMessage(0).read()
- self.failIfEqual(msg.find('This is the message'), -1)
-
- return self.smtpServer.stopListening()
- done.addCallback(finished)
- return done
-
-
- def testRelayDelivery(self):
- # Here is the service we will connect to and send mail from
- insServ = mail.mail.MailService()
- insServ.smtpPortal.registerChecker(cred.checkers.AllowAnonymousAccess())
- domain = mail.maildir.MaildirDirdbmDomain(insServ, 'insertionDomain')
- insServ.addDomain('insertion.domain', domain)
- os.mkdir('insertionQueue')
- insServ.setQueue(mail.relaymanager.Queue('insertionQueue'))
- insServ.domains.setDefaultDomain(mail.relay.DomainQueuer(insServ))
- manager = mail.relaymanager.SmartHostSMTPRelayingManager(insServ.queue)
- manager.fArgs += ('test.identity.hostname',)
- helper = mail.relaymanager.RelayStateHelper(manager, 1)
- # Yoink! Now the internet obeys OUR every whim!
- manager.mxcalc = mail.relaymanager.MXCalculator(self.resolver)
- # And this is our whim.
- self.auth.addresses['destination.domain'] = ['127.0.0.1']
-
- f = insServ.getSMTPFactory()
- self.insServer = reactor.listenTCP(0, f, interface='127.0.0.1')
-
- # Here is the service the previous one will connect to for final
- # delivery
- destServ = mail.mail.MailService()
- destServ.smtpPortal.registerChecker(cred.checkers.AllowAnonymousAccess())
- domain = mail.maildir.MaildirDirdbmDomain(destServ, 'destinationDomain')
- domain.addUser('user', 'password')
- destServ.addDomain('destination.domain', domain)
- os.mkdir('destinationQueue')
- destServ.setQueue(mail.relaymanager.Queue('destinationQueue'))
- manager2 = mail.relaymanager.SmartHostSMTPRelayingManager(destServ.queue)
- helper = mail.relaymanager.RelayStateHelper(manager, 1)
- helper.startService()
-
- f = destServ.getSMTPFactory()
- self.destServer = reactor.listenTCP(0, f, interface='127.0.0.1')
-
- # Update the port number the *first* relay will connect to, because we can't use
- # port 25
- manager.PORT = self.destServer.getHost().port
-
- client = LineSendingProtocol([
- 'HELO meson',
- 'MAIL FROM: <user@wherever>',
- 'RCPT TO: <user@destination.domain>',
- 'DATA',
- 'This is the message',
- '.',
- 'QUIT'
- ])
-
- done = Deferred()
- f = protocol.ClientFactory()
- f.protocol = lambda: client
- f.clientConnectionLost = lambda *args: done.callback(None)
- reactor.connectTCP('127.0.0.1', self.insServer.getHost().port, f)
-
- def finished(ign):
- # First part of the delivery is done. Poke the queue manually now
- # so we don't have to wait for the queue to be flushed.
- delivery = manager.checkState()
- def delivered(ign):
- mbox = domain.requestAvatar('user', None, pop3.IMailbox)[1]
- msg = mbox.getMessage(0).read()
- self.failIfEqual(msg.find('This is the message'), -1)
-
- self.insServer.stopListening()
- self.destServer.stopListening()
- helper.stopService()
- delivery.addCallback(delivered)
- return delivery
- done.addCallback(finished)
- return done
-
-
-aliasFile = StringIO.StringIO("""\
-# Here's a comment
- # woop another one
-testuser: address1,address2, address3,
- continuation@address, |/bin/process/this
-
-usertwo:thisaddress,thataddress, lastaddress
-lastuser: :/includable, /filename, |/program, address
-""")
-
-class LineBufferMessage:
- def __init__(self):
- self.lines = []
- self.eom = False
- self.lost = False
-
- def lineReceived(self, line):
- self.lines.append(line)
-
- def eomReceived(self):
- self.eom = True
- return defer.succeed('<Whatever>')
-
- def connectionLost(self):
- self.lost = True
-
-class AliasTestCase(unittest.TestCase):
- lines = [
- 'First line',
- 'Next line',
- '',
- 'After a blank line',
- 'Last line'
- ]
-
- def setUp(self):
- aliasFile.seek(0)
-
- def testHandle(self):
- result = {}
- lines = [
- 'user: another@host\n',
- 'nextuser: |/bin/program\n',
- 'user: me@again\n',
- 'moreusers: :/etc/include/filename\n',
- 'multiuser: first@host, second@host,last@anotherhost',
- ]
-
- for l in lines:
- mail.alias.handle(result, l, 'TestCase', None)
-
- self.assertEqual(result['user'], ['another@host', 'me@again'])
- self.assertEqual(result['nextuser'], ['|/bin/program'])
- self.assertEqual(result['moreusers'], [':/etc/include/filename'])
- self.assertEqual(result['multiuser'], ['first@host', 'second@host', 'last@anotherhost'])
-
- def testFileLoader(self):
- domains = {'': object()}
- result = mail.alias.loadAliasFile(domains, fp=aliasFile)
-
- self.assertEqual(len(result), 3)
-
- group = result['testuser']
- s = str(group)
- for a in ('address1', 'address2', 'address3', 'continuation@address', '/bin/process/this'):
- self.failIfEqual(s.find(a), -1)
- self.assertEqual(len(group), 5)
-
- group = result['usertwo']
- s = str(group)
- for a in ('thisaddress', 'thataddress', 'lastaddress'):
- self.failIfEqual(s.find(a), -1)
- self.assertEqual(len(group), 3)
-
- group = result['lastuser']
- s = str(group)
- self.assertEqual(s.find('/includable'), -1)
- for a in ('/filename', 'program', 'address'):
- self.failIfEqual(s.find(a), -1, '%s not found' % a)
- self.assertEqual(len(group), 3)
-
- def testMultiWrapper(self):
- msgs = LineBufferMessage(), LineBufferMessage(), LineBufferMessage()
- msg = mail.alias.MultiWrapper(msgs)
-
- for L in self.lines:
- msg.lineReceived(L)
- return msg.eomReceived().addCallback(self._cbMultiWrapper, msgs)
-
- def _cbMultiWrapper(self, ignored, msgs):
- for m in msgs:
- self.failUnless(m.eom)
- self.failIf(m.lost)
- self.assertEqual(self.lines, m.lines)
-
- def testFileAlias(self):
- tmpfile = self.mktemp()
- a = mail.alias.FileAlias(tmpfile, None, None)
- m = a.createMessageReceiver()
-
- for l in self.lines:
- m.lineReceived(l)
- return m.eomReceived().addCallback(self._cbTestFileAlias, tmpfile)
-
- def _cbTestFileAlias(self, ignored, tmpfile):
- lines = file(tmpfile).readlines()
- self.assertEqual([L[:-1] for L in lines], self.lines)
-
-
-
-class DummyProcess(object):
- __slots__ = ['onEnd']
-
-
-
-class MockProcessAlias(mail.alias.ProcessAlias):
- """
- A alias processor that doesn't actually launch processes.
- """
-
- def spawnProcess(self, proto, program, path):
- """
- Don't spawn a process.
- """
-
-
-
-class MockAliasGroup(mail.alias.AliasGroup):
- """
- An alias group using C{MockProcessAlias}.
- """
- processAliasFactory = MockProcessAlias
-
-
-
-class StubProcess(object):
- """
- Fake implementation of L{IProcessTransport}.
-
- @ivar signals: A list of all the signals which have been sent to this fake
- process.
- """
- def __init__(self):
- self.signals = []
-
-
- def loseConnection(self):
- """
- No-op implementation of disconnection.
- """
-
-
- def signalProcess(self, signal):
- """
- Record a signal sent to this process for later inspection.
- """
- self.signals.append(signal)
-
-
-
-class ProcessAliasTestCase(unittest.TestCase):
- """
- Tests for alias resolution.
- """
- if interfaces.IReactorProcess(reactor, None) is None:
- skip = "IReactorProcess not supported"
-
- lines = [
- 'First line',
- 'Next line',
- '',
- 'After a blank line',
- 'Last line'
- ]
-
- def exitStatus(self, code):
- """
- Construct a status from the given exit code.
-
- @type code: L{int} between 0 and 255 inclusive.
- @param code: The exit status which the code will represent.
-
- @rtype: L{int}
- @return: A status integer for the given exit code.
- """
- # /* Macros for constructing status values. */
- # #define __W_EXITCODE(ret, sig) ((ret) << 8 | (sig))
- status = (code << 8) | 0
-
- # Sanity check
- self.assertTrue(os.WIFEXITED(status))
- self.assertEqual(os.WEXITSTATUS(status), code)
- self.assertFalse(os.WIFSIGNALED(status))
-
- return status
-
-
- def signalStatus(self, signal):
- """
- Construct a status from the given signal.
-
- @type signal: L{int} between 0 and 255 inclusive.
- @param signal: The signal number which the status will represent.
-
- @rtype: L{int}
- @return: A status integer for the given signal.
- """
- # /* If WIFSIGNALED(STATUS), the terminating signal. */
- # #define __WTERMSIG(status) ((status) & 0x7f)
- # /* Nonzero if STATUS indicates termination by a signal. */
- # #define __WIFSIGNALED(status) \
- # (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
- status = signal
-
- # Sanity check
- self.assertTrue(os.WIFSIGNALED(status))
- self.assertEqual(os.WTERMSIG(status), signal)
- self.assertFalse(os.WIFEXITED(status))
-
- return status
-
-
- def setUp(self):
- """
- Replace L{smtp.DNSNAME} with a well-known value.
- """
- self.DNSNAME = smtp.DNSNAME
- smtp.DNSNAME = ''
-
-
- def tearDown(self):
- """
- Restore the original value of L{smtp.DNSNAME}.
- """
- smtp.DNSNAME = self.DNSNAME
-
-
- def test_processAlias(self):
- """
- Standard call to C{mail.alias.ProcessAlias}: check that the specified
- script is called, and that the input is correctly transferred to it.
- """
- sh = FilePath(self.mktemp())
- sh.setContent("""\
-#!/bin/sh
-rm -f process.alias.out
-while read i; do
- echo $i >> process.alias.out
-done""")
- os.chmod(sh.path, 0700)
- a = mail.alias.ProcessAlias(sh.path, None, None)
- m = a.createMessageReceiver()
-
- for l in self.lines:
- m.lineReceived(l)
-
- def _cbProcessAlias(ignored):
- lines = file('process.alias.out').readlines()
- self.assertEqual([L[:-1] for L in lines], self.lines)
-
- return m.eomReceived().addCallback(_cbProcessAlias)
-
-
- def test_processAliasTimeout(self):
- """
- If the alias child process does not exit within a particular period of
- time, the L{Deferred} returned by L{MessageWrapper.eomReceived} should
- fail with L{ProcessAliasTimeout} and send the I{KILL} signal to the
- child process..
- """
- reactor = task.Clock()
- transport = StubProcess()
- proto = mail.alias.ProcessAliasProtocol()
- proto.makeConnection(transport)
-
- receiver = mail.alias.MessageWrapper(proto, None, reactor)
- d = receiver.eomReceived()
- reactor.advance(receiver.completionTimeout)
- def timedOut(ignored):
- self.assertEqual(transport.signals, ['KILL'])
- # Now that it has been killed, disconnect the protocol associated
- # with it.
- proto.processEnded(
- ProcessTerminated(self.signalStatus(signal.SIGKILL)))
- self.assertFailure(d, mail.alias.ProcessAliasTimeout)
- d.addCallback(timedOut)
- return d
-
-
- def test_earlyProcessTermination(self):
- """
- If the process associated with an L{mail.alias.MessageWrapper} exits
- before I{eomReceived} is called, the L{Deferred} returned by
- I{eomReceived} should fail.
- """
- transport = StubProcess()
- protocol = mail.alias.ProcessAliasProtocol()
- protocol.makeConnection(transport)
- receiver = mail.alias.MessageWrapper(protocol, None, None)
- protocol.processEnded(failure.Failure(ProcessDone(0)))
- return self.assertFailure(receiver.eomReceived(), ProcessDone)
-
-
- def _terminationTest(self, status):
- """
- Verify that if the process associated with an
- L{mail.alias.MessageWrapper} exits with the given status, the
- L{Deferred} returned by I{eomReceived} fails with L{ProcessTerminated}.
- """
- transport = StubProcess()
- protocol = mail.alias.ProcessAliasProtocol()
- protocol.makeConnection(transport)
- receiver = mail.alias.MessageWrapper(protocol, None, None)
- protocol.processEnded(
- failure.Failure(ProcessTerminated(status)))
- return self.assertFailure(receiver.eomReceived(), ProcessTerminated)
-
-
- def test_errorProcessTermination(self):
- """
- If the process associated with an L{mail.alias.MessageWrapper} exits
- with a non-zero exit code, the L{Deferred} returned by I{eomReceived}
- should fail.
- """
- return self._terminationTest(self.exitStatus(1))
-
-
- def test_signalProcessTermination(self):
- """
- If the process associated with an L{mail.alias.MessageWrapper} exits
- because it received a signal, the L{Deferred} returned by
- I{eomReceived} should fail.
- """
- return self._terminationTest(self.signalStatus(signal.SIGHUP))
-
-
- def test_aliasResolution(self):
- """
- Check that the C{resolve} method of alias processors produce the correct
- set of objects:
- - direct alias with L{mail.alias.AddressAlias} if a simple input is passed
- - aliases in a file with L{mail.alias.FileWrapper} if an input in the format
- '/file' is given
- - aliases resulting of a process call wrapped by L{mail.alias.MessageWrapper}
- if the format is '|process'
- """
- aliases = {}
- domain = {'': TestDomain(aliases, ['user1', 'user2', 'user3'])}
- A1 = MockAliasGroup(['user1', '|echo', '/file'], domain, 'alias1')
- A2 = MockAliasGroup(['user2', 'user3'], domain, 'alias2')
- A3 = mail.alias.AddressAlias('alias1', domain, 'alias3')
- aliases.update({
- 'alias1': A1,
- 'alias2': A2,
- 'alias3': A3,
- })
-
- res1 = A1.resolve(aliases)
- r1 = map(str, res1.objs)
- r1.sort()
- expected = map(str, [
- mail.alias.AddressAlias('user1', None, None),
- mail.alias.MessageWrapper(DummyProcess(), 'echo'),
- mail.alias.FileWrapper('/file'),
- ])
- expected.sort()
- self.assertEqual(r1, expected)
-
- res2 = A2.resolve(aliases)
- r2 = map(str, res2.objs)
- r2.sort()
- expected = map(str, [
- mail.alias.AddressAlias('user2', None, None),
- mail.alias.AddressAlias('user3', None, None)
- ])
- expected.sort()
- self.assertEqual(r2, expected)
-
- res3 = A3.resolve(aliases)
- r3 = map(str, res3.objs)
- r3.sort()
- expected = map(str, [
- mail.alias.AddressAlias('user1', None, None),
- mail.alias.MessageWrapper(DummyProcess(), 'echo'),
- mail.alias.FileWrapper('/file'),
- ])
- expected.sort()
- self.assertEqual(r3, expected)
-
-
- def test_cyclicAlias(self):
- """
- Check that a cycle in alias resolution is correctly handled.
- """
- aliases = {}
- domain = {'': TestDomain(aliases, [])}
- A1 = mail.alias.AddressAlias('alias2', domain, 'alias1')
- A2 = mail.alias.AddressAlias('alias3', domain, 'alias2')
- A3 = mail.alias.AddressAlias('alias1', domain, 'alias3')
- aliases.update({
- 'alias1': A1,
- 'alias2': A2,
- 'alias3': A3
- })
-
- self.assertEqual(aliases['alias1'].resolve(aliases), None)
- self.assertEqual(aliases['alias2'].resolve(aliases), None)
- self.assertEqual(aliases['alias3'].resolve(aliases), None)
-
- A4 = MockAliasGroup(['|echo', 'alias1'], domain, 'alias4')
- aliases['alias4'] = A4
-
- res = A4.resolve(aliases)
- r = map(str, res.objs)
- r.sort()
- expected = map(str, [
- mail.alias.MessageWrapper(DummyProcess(), 'echo')
- ])
- expected.sort()
- self.assertEqual(r, expected)
-
-
-
-
-
-
-class TestDomain:
- def __init__(self, aliases, users):
- self.aliases = aliases
- self.users = users
-
- def exists(self, user, memo=None):
- user = user.dest.local
- if user in self.users:
- return lambda: mail.alias.AddressAlias(user, None, None)
- try:
- a = self.aliases[user]
- except:
- raise smtp.SMTPBadRcpt(user)
- else:
- aliases = a.resolve(self.aliases, memo)
- if aliases:
- return lambda: aliases
- raise smtp.SMTPBadRcpt(user)
-
-
-
-class SSLContextFactoryTests(unittest.TestCase):
- """
- Tests for twisted.mail.protocols.SSLContextFactory.
- """
- def test_deprecation(self):
- """
- Accessing L{twisted.mail.protocols.SSLContextFactory} emits a
- deprecation warning recommending the use of the more general SSL context
- factory from L{twisted.internet.ssl}.
- """
- mail.protocols.SSLContextFactory
- warningsShown = self.flushWarnings([self.test_deprecation])
- self.assertEqual(len(warningsShown), 1)
- self.assertIdentical(warningsShown[0]['category'], DeprecationWarning)
- self.assertEqual(
- warningsShown[0]['message'],
- 'twisted.mail.protocols.SSLContextFactory was deprecated in '
- 'Twisted 12.2.0: Use twisted.internet.ssl.'
- 'DefaultOpenSSLContextFactory instead.')
-
-
-
-from twisted.python.runtime import platformType
-import types
-if platformType != "posix":
- for o in locals().values():
- if isinstance(o, (types.ClassType, type)) and issubclass(o, unittest.TestCase):
- o.skip = "twisted.mail only works on posix"
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mailmail.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mailmail.py
deleted file mode 100755
index 8b9e4d87..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_mailmail.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for L{twisted.mail.scripts.mailmail}, the implementation of the
-command line program I{mailmail}.
-"""
-
-import sys
-from StringIO import StringIO
-
-from twisted.trial.unittest import TestCase
-from twisted.mail.scripts.mailmail import parseOptions
-
-
-class OptionsTests(TestCase):
- """
- Tests for L{parseOptions} which parses command line arguments and reads
- message text from stdin to produce an L{Options} instance which can be
- used to send a message.
- """
- def test_unspecifiedRecipients(self):
- """
- If no recipients are given in the argument list and there is no
- recipient header in the message text, L{parseOptions} raises
- L{SystemExit} with a string describing the problem.
- """
- self.addCleanup(setattr, sys, 'stdin', sys.stdin)
- sys.stdin = StringIO(
- 'Subject: foo\n'
- '\n'
- 'Hello, goodbye.\n')
- exc = self.assertRaises(SystemExit, parseOptions, [])
- self.assertEqual(exc.args, ('No recipients specified.',))
-
-
- def test_listQueueInformation(self):
- """
- The I{-bp} option for listing queue information is unsupported and
- if it is passed to L{parseOptions}, L{SystemExit} is raised.
- """
- exc = self.assertRaises(SystemExit, parseOptions, ['-bp'])
- self.assertEqual(exc.args, ("Unsupported option.",))
-
-
- def test_stdioTransport(self):
- """
- The I{-bs} option for using stdin and stdout as the SMTP transport
- is unsupported and if it is passed to L{parseOptions}, L{SystemExit}
- is raised.
- """
- exc = self.assertRaises(SystemExit, parseOptions, ['-bs'])
- self.assertEqual(exc.args, ("Unsupported option.",))
-
-
- def test_ignoreFullStop(self):
- """
- The I{-i} and I{-oi} options for ignoring C{"."} by itself on a line
- are unsupported and if either is passed to L{parseOptions},
- L{SystemExit} is raised.
- """
- exc = self.assertRaises(SystemExit, parseOptions, ['-i'])
- self.assertEqual(exc.args, ("Unsupported option.",))
- exc = self.assertRaises(SystemExit, parseOptions, ['-oi'])
- self.assertEqual(exc.args, ("Unsupported option.",))
-
-
- def test_copyAliasedSender(self):
- """
- The I{-om} option for copying the sender if they appear in an alias
- expansion is unsupported and if it is passed to L{parseOptions},
- L{SystemExit} is raised.
- """
- exc = self.assertRaises(SystemExit, parseOptions, ['-om'])
- self.assertEqual(exc.args, ("Unsupported option.",))
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_options.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_options.py
deleted file mode 100755
index daee5d26..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_options.py
+++ /dev/null
@@ -1,247 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for L{twisted.mail.tap}.
-"""
-
-from twisted.trial.unittest import TestCase
-
-from twisted.python.usage import UsageError
-from twisted.mail import protocols
-from twisted.mail.tap import Options, makeService
-from twisted.python.filepath import FilePath
-from twisted.internet import endpoints, defer
-from twisted.python import util
-
-try:
- import OpenSSL
-except ImportError, e:
- sslSkip = str(e)
-else:
- sslSkip = None
-
-
-class OptionsTestCase(TestCase):
- """
- Tests for the command line option parser used for I{twistd mail}.
- """
- def setUp(self):
- self.aliasFilename = self.mktemp()
- aliasFile = file(self.aliasFilename, 'w')
- aliasFile.write('someuser:\tdifferentuser\n')
- aliasFile.close()
-
-
- def testAliasesWithoutDomain(self):
- """
- Test that adding an aliases(5) file before adding a domain raises a
- UsageError.
- """
- self.assertRaises(
- UsageError,
- Options().parseOptions,
- ['--aliases', self.aliasFilename])
-
-
- def testAliases(self):
- """
- Test that adding an aliases(5) file to an IAliasableDomain at least
- doesn't raise an unhandled exception.
- """
- Options().parseOptions([
- '--maildirdbmdomain', 'example.com=example.com',
- '--aliases', self.aliasFilename])
-
-
- def test_barePort(self):
- """
- A bare port passed to I{--pop3} results in deprecation warning in
- addition to a TCP4ServerEndpoint.
- """
- options = Options()
- options.parseOptions(['--pop3', '8110'])
- self.assertEqual(len(options['pop3']), 1)
- self.assertIsInstance(
- options['pop3'][0], endpoints.TCP4ServerEndpoint)
- warnings = self.flushWarnings([options.opt_pop3])
- self.assertEqual(len(warnings), 1)
- self.assertEqual(warnings[0]['category'], DeprecationWarning)
- self.assertEqual(
- warnings[0]['message'],
- "Specifying plain ports and/or a certificate is deprecated since "
- "Twisted 11.0; use endpoint descriptions instead.")
-
-
- def _endpointTest(self, service):
- """
- Use L{Options} to parse a single service configuration parameter and
- verify that an endpoint of the correct type is added to the list for
- that service.
- """
- options = Options()
- options.parseOptions(['--' + service, 'tcp:1234'])
- self.assertEqual(len(options[service]), 1)
- self.assertIsInstance(
- options[service][0], endpoints.TCP4ServerEndpoint)
-
-
- def test_endpointSMTP(self):
- """
- When I{--smtp} is given a TCP endpoint description as an argument, a
- TCPServerEndpoint is added to the list of SMTP endpoints.
- """
- self._endpointTest('smtp')
-
-
- def test_endpointPOP3(self):
- """
- When I{--pop3} is given a TCP endpoint description as an argument, a
- TCPServerEndpoint is added to the list of POP3 endpoints.
- """
- self._endpointTest('pop3')
-
-
- def test_protoDefaults(self):
- """
- POP3 and SMTP each listen on a TCP4ServerEndpoint by default.
- """
- options = Options()
- options.parseOptions([])
-
- self.assertEqual(len(options['pop3']), 1)
- self.assertIsInstance(
- options['pop3'][0], endpoints.TCP4ServerEndpoint)
-
- self.assertEqual(len(options['smtp']), 1)
- self.assertIsInstance(
- options['smtp'][0], endpoints.TCP4ServerEndpoint)
-
-
- def test_protoDisable(self):
- """
- The I{--no-pop3} and I{--no-smtp} options disable POP3 and SMTP
- respectively.
- """
- options = Options()
- options.parseOptions(['--no-pop3'])
- self.assertEqual(options._getEndpoints(None, 'pop3'), [])
- self.assertNotEquals(options._getEndpoints(None, 'smtp'), [])
-
- options = Options()
- options.parseOptions(['--no-smtp'])
- self.assertNotEquals(options._getEndpoints(None, 'pop3'), [])
- self.assertEqual(options._getEndpoints(None, 'smtp'), [])
-
-
- def test_allProtosDisabledError(self):
- """
- If all protocols are disabled, L{UsageError} is raised.
- """
- options = Options()
- self.assertRaises(
- UsageError, options.parseOptions, (['--no-pop3', '--no-smtp']))
-
-
- def test_pop3sBackwardCompatibility(self):
- """
- The deprecated I{--pop3s} and I{--certificate} options set up a POP3 SSL
- server.
- """
- cert = FilePath(__file__).sibling("server.pem")
- options = Options()
- options.parseOptions(['--pop3s', '8995',
- '--certificate', cert.path])
- self.assertEqual(len(options['pop3']), 2)
- self.assertIsInstance(
- options['pop3'][0], endpoints.SSL4ServerEndpoint)
- self.assertIsInstance(
- options['pop3'][1], endpoints.TCP4ServerEndpoint)
-
- warnings = self.flushWarnings([options.postOptions])
- self.assertEqual(len(warnings), 1)
- self.assertEqual(warnings[0]['category'], DeprecationWarning)
- self.assertEqual(
- warnings[0]['message'],
- "Specifying plain ports and/or a certificate is deprecated since "
- "Twisted 11.0; use endpoint descriptions instead.")
- if sslSkip is not None:
- test_pop3sBackwardCompatibility.skip = sslSkip
-
-
- def test_esmtpWithoutHostname(self):
- """
- If I{--esmtp} is given without I{--hostname}, L{Options.parseOptions}
- raises L{UsageError}.
- """
- options = Options()
- exc = self.assertRaises(UsageError, options.parseOptions, ['--esmtp'])
- self.assertEqual("--esmtp requires --hostname", str(exc))
-
-
- def test_auth(self):
- """
- Tests that the --auth option registers a checker.
- """
- options = Options()
- options.parseOptions(['--auth', 'memory:admin:admin:bob:password'])
- self.assertEqual(len(options['credCheckers']), 1)
- checker = options['credCheckers'][0]
- interfaces = checker.credentialInterfaces
- registered_checkers = options.service.smtpPortal.checkers
- for iface in interfaces:
- self.assertEqual(checker, registered_checkers[iface])
-
-
-
-class SpyEndpoint(object):
- """
- SpyEndpoint remembers what factory it is told to listen with.
- """
- listeningWith = None
- def listen(self, factory):
- self.listeningWith = factory
- return defer.succeed(None)
-
-
-
-class MakeServiceTests(TestCase):
- """
- Tests for L{twisted.mail.tap.makeService}
- """
- def _endpointServerTest(self, key, factoryClass):
- """
- Configure a service with two endpoints for the protocol associated with
- C{key} and verify that when the service is started a factory of type
- C{factoryClass} is used to listen on each of them.
- """
- cleartext = SpyEndpoint()
- secure = SpyEndpoint()
- config = Options()
- config[key] = [cleartext, secure]
- service = makeService(config)
- service.privilegedStartService()
- service.startService()
- self.addCleanup(service.stopService)
- self.assertIsInstance(cleartext.listeningWith, factoryClass)
- self.assertIsInstance(secure.listeningWith, factoryClass)
-
-
- def test_pop3(self):
- """
- If one or more endpoints is included in the configuration passed to
- L{makeService} for the C{"pop3"} key, a service for starting a POP3
- server is constructed for each of them and attached to the returned
- service.
- """
- self._endpointServerTest("pop3", protocols.POP3Factory)
-
-
- def test_smtp(self):
- """
- If one or more endpoints is included in the configuration passed to
- L{makeService} for the C{"smtp"} key, a service for starting an SMTP
- server is constructed for each of them and attached to the returned
- service.
- """
- self._endpointServerTest("smtp", protocols.SMTPFactory)
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3.py
deleted file mode 100755
index 4379a1e3..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3.py
+++ /dev/null
@@ -1,1071 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Test cases for Ltwisted.mail.pop3} module.
-"""
-
-import StringIO
-import hmac
-import base64
-import itertools
-
-from zope.interface import implements
-
-from twisted.internet import defer
-
-from twisted.trial import unittest, util
-from twisted import mail
-import twisted.mail.protocols
-import twisted.mail.pop3
-import twisted.internet.protocol
-from twisted import internet
-from twisted.mail import pop3
-from twisted.protocols import loopback
-from twisted.python import failure
-from twisted.python.util import OrderedDict
-
-from twisted import cred
-import twisted.cred.portal
-import twisted.cred.checkers
-import twisted.cred.credentials
-
-from twisted.test.proto_helpers import LineSendingProtocol
-
-
-class UtilityTestCase(unittest.TestCase):
- """
- Test the various helper functions and classes used by the POP3 server
- protocol implementation.
- """
-
- def testLineBuffering(self):
- """
- Test creating a LineBuffer and feeding it some lines. The lines should
- build up in its internal buffer for a while and then get spat out to
- the writer.
- """
- output = []
- input = iter(itertools.cycle(['012', '345', '6', '7', '8', '9']))
- c = pop3._IteratorBuffer(output.extend, input, 6)
- i = iter(c)
- self.assertEqual(output, []) # nothing is buffer
- i.next()
- self.assertEqual(output, []) # '012' is buffered
- i.next()
- self.assertEqual(output, []) # '012345' is buffered
- i.next()
- self.assertEqual(output, ['012', '345', '6']) # nothing is buffered
- for n in range(5):
- i.next()
- self.assertEqual(output, ['012', '345', '6', '7', '8', '9', '012', '345'])
-
-
- def testFinishLineBuffering(self):
- """
- Test that a LineBuffer flushes everything when its iterator is
- exhausted, and itself raises StopIteration.
- """
- output = []
- input = iter(['a', 'b', 'c'])
- c = pop3._IteratorBuffer(output.extend, input, 5)
- for i in c:
- pass
- self.assertEqual(output, ['a', 'b', 'c'])
-
-
- def testSuccessResponseFormatter(self):
- """
- Test that the thing that spits out POP3 'success responses' works
- right.
- """
- self.assertEqual(
- pop3.successResponse('Great.'),
- '+OK Great.\r\n')
-
-
- def testStatLineFormatter(self):
- """
- Test that the function which formats stat lines does so appropriately.
- """
- statLine = list(pop3.formatStatResponse([]))[-1]
- self.assertEqual(statLine, '+OK 0 0\r\n')
-
- statLine = list(pop3.formatStatResponse([10, 31, 0, 10101]))[-1]
- self.assertEqual(statLine, '+OK 4 10142\r\n')
-
-
- def testListLineFormatter(self):
- """
- Test that the function which formats the lines in response to a LIST
- command does so appropriately.
- """
- listLines = list(pop3.formatListResponse([]))
- self.assertEqual(
- listLines,
- ['+OK 0\r\n', '.\r\n'])
-
- listLines = list(pop3.formatListResponse([1, 2, 3, 100]))
- self.assertEqual(
- listLines,
- ['+OK 4\r\n', '1 1\r\n', '2 2\r\n', '3 3\r\n', '4 100\r\n', '.\r\n'])
-
-
-
- def testUIDListLineFormatter(self):
- """
- Test that the function which formats lines in response to a UIDL
- command does so appropriately.
- """
- UIDs = ['abc', 'def', 'ghi']
- listLines = list(pop3.formatUIDListResponse([], UIDs.__getitem__))
- self.assertEqual(
- listLines,
- ['+OK \r\n', '.\r\n'])
-
- listLines = list(pop3.formatUIDListResponse([123, 431, 591], UIDs.__getitem__))
- self.assertEqual(
- listLines,
- ['+OK \r\n', '1 abc\r\n', '2 def\r\n', '3 ghi\r\n', '.\r\n'])
-
- listLines = list(pop3.formatUIDListResponse([0, None, 591], UIDs.__getitem__))
- self.assertEqual(
- listLines,
- ['+OK \r\n', '1 abc\r\n', '3 ghi\r\n', '.\r\n'])
-
-
-
-class MyVirtualPOP3(mail.protocols.VirtualPOP3):
-
- magic = '<moshez>'
-
- def authenticateUserAPOP(self, user, digest):
- user, domain = self.lookupDomain(user)
- return self.service.domains['baz.com'].authenticateUserAPOP(user, digest, self.magic, domain)
-
-class DummyDomain:
-
- def __init__(self):
- self.users = {}
-
- def addUser(self, name):
- self.users[name] = []
-
- def addMessage(self, name, message):
- self.users[name].append(message)
-
- def authenticateUserAPOP(self, name, digest, magic, domain):
- return pop3.IMailbox, ListMailbox(self.users[name]), lambda: None
-
-
-class ListMailbox:
-
- def __init__(self, list):
- self.list = list
-
- def listMessages(self, i=None):
- if i is None:
- return map(len, self.list)
- return len(self.list[i])
-
- def getMessage(self, i):
- return StringIO.StringIO(self.list[i])
-
- def getUidl(self, i):
- return i
-
- def deleteMessage(self, i):
- self.list[i] = ''
-
- def sync(self):
- pass
-
-class MyPOP3Downloader(pop3.POP3Client):
-
- def handle_WELCOME(self, line):
- pop3.POP3Client.handle_WELCOME(self, line)
- self.apop('hello@baz.com', 'world')
-
- def handle_APOP(self, line):
- parts = line.split()
- code = parts[0]
- data = (parts[1:] or ['NONE'])[0]
- if code != '+OK':
- print parts
- raise AssertionError, 'code is ' + code
- self.lines = []
- self.retr(1)
-
- def handle_RETR_continue(self, line):
- self.lines.append(line)
-
- def handle_RETR_end(self):
- self.message = '\n'.join(self.lines) + '\n'
- self.quit()
-
- def handle_QUIT(self, line):
- if line[:3] != '+OK':
- raise AssertionError, 'code is ' + line
-
-
-class POP3TestCase(unittest.TestCase):
-
- message = '''\
-Subject: urgent
-
-Someone set up us the bomb!
-'''
-
- expectedOutput = '''\
-+OK <moshez>\015
-+OK Authentication succeeded\015
-+OK \015
-1 0\015
-.\015
-+OK %d\015
-Subject: urgent\015
-\015
-Someone set up us the bomb!\015
-.\015
-+OK \015
-''' % len(message)
-
- def setUp(self):
- self.factory = internet.protocol.Factory()
- self.factory.domains = {}
- self.factory.domains['baz.com'] = DummyDomain()
- self.factory.domains['baz.com'].addUser('hello')
- self.factory.domains['baz.com'].addMessage('hello', self.message)
-
- def testMessages(self):
- client = LineSendingProtocol([
- 'APOP hello@baz.com world',
- 'UIDL',
- 'RETR 1',
- 'QUIT',
- ])
- server = MyVirtualPOP3()
- server.service = self.factory
- def check(ignored):
- output = '\r\n'.join(client.response) + '\r\n'
- self.assertEqual(output, self.expectedOutput)
- return loopback.loopbackTCP(server, client).addCallback(check)
-
- def testLoopback(self):
- protocol = MyVirtualPOP3()
- protocol.service = self.factory
- clientProtocol = MyPOP3Downloader()
- def check(ignored):
- self.assertEqual(clientProtocol.message, self.message)
- protocol.connectionLost(
- failure.Failure(Exception("Test harness disconnect")))
- d = loopback.loopbackAsync(protocol, clientProtocol)
- return d.addCallback(check)
- testLoopback.suppress = [util.suppress(message="twisted.mail.pop3.POP3Client is deprecated")]
-
-
-
-class DummyPOP3(pop3.POP3):
-
- magic = '<moshez>'
-
- def authenticateUserAPOP(self, user, password):
- return pop3.IMailbox, DummyMailbox(ValueError), lambda: None
-
-
-
-class DummyMailbox(pop3.Mailbox):
-
- messages = ['From: moshe\nTo: moshe\n\nHow are you, friend?\n']
-
- def __init__(self, exceptionType):
- self.messages = DummyMailbox.messages[:]
- self.exceptionType = exceptionType
-
- def listMessages(self, i=None):
- if i is None:
- return map(len, self.messages)
- if i >= len(self.messages):
- raise self.exceptionType()
- return len(self.messages[i])
-
- def getMessage(self, i):
- return StringIO.StringIO(self.messages[i])
-
- def getUidl(self, i):
- if i >= len(self.messages):
- raise self.exceptionType()
- return str(i)
-
- def deleteMessage(self, i):
- self.messages[i] = ''
-
-
-class AnotherPOP3TestCase(unittest.TestCase):
-
- def runTest(self, lines, expectedOutput):
- dummy = DummyPOP3()
- client = LineSendingProtocol(lines)
- d = loopback.loopbackAsync(dummy, client)
- return d.addCallback(self._cbRunTest, client, dummy, expectedOutput)
-
-
- def _cbRunTest(self, ignored, client, dummy, expectedOutput):
- self.assertEqual('\r\n'.join(expectedOutput),
- '\r\n'.join(client.response))
- dummy.connectionLost(failure.Failure(Exception("Test harness disconnect")))
- return ignored
-
-
- def test_buffer(self):
- """
- Test a lot of different POP3 commands in an extremely pipelined
- scenario.
-
- This test may cover legitimate behavior, but the intent and
- granularity are not very good. It would likely be an improvement to
- split it into a number of smaller, more focused tests.
- """
- return self.runTest(
- ["APOP moshez dummy",
- "LIST",
- "UIDL",
- "RETR 1",
- "RETR 2",
- "DELE 1",
- "RETR 1",
- "QUIT"],
- ['+OK <moshez>',
- '+OK Authentication succeeded',
- '+OK 1',
- '1 44',
- '.',
- '+OK ',
- '1 0',
- '.',
- '+OK 44',
- 'From: moshe',
- 'To: moshe',
- '',
- 'How are you, friend?',
- '.',
- '-ERR Bad message number argument',
- '+OK ',
- '-ERR message deleted',
- '+OK '])
-
-
- def test_noop(self):
- """
- Test the no-op command.
- """
- return self.runTest(
- ['APOP spiv dummy',
- 'NOOP',
- 'QUIT'],
- ['+OK <moshez>',
- '+OK Authentication succeeded',
- '+OK ',
- '+OK '])
-
-
- def testAuthListing(self):
- p = DummyPOP3()
- p.factory = internet.protocol.Factory()
- p.factory.challengers = {'Auth1': None, 'secondAuth': None, 'authLast': None}
- client = LineSendingProtocol([
- "AUTH",
- "QUIT",
- ])
-
- d = loopback.loopbackAsync(p, client)
- return d.addCallback(self._cbTestAuthListing, client)
-
- def _cbTestAuthListing(self, ignored, client):
- self.failUnless(client.response[1].startswith('+OK'))
- self.assertEqual(sorted(client.response[2:5]),
- ["AUTH1", "AUTHLAST", "SECONDAUTH"])
- self.assertEqual(client.response[5], ".")
-
- def testIllegalPASS(self):
- dummy = DummyPOP3()
- client = LineSendingProtocol([
- "PASS fooz",
- "QUIT"
- ])
- d = loopback.loopbackAsync(dummy, client)
- return d.addCallback(self._cbTestIllegalPASS, client, dummy)
-
- def _cbTestIllegalPASS(self, ignored, client, dummy):
- expected_output = '+OK <moshez>\r\n-ERR USER required before PASS\r\n+OK \r\n'
- self.assertEqual(expected_output, '\r\n'.join(client.response) + '\r\n')
- dummy.connectionLost(failure.Failure(Exception("Test harness disconnect")))
-
- def testEmptyPASS(self):
- dummy = DummyPOP3()
- client = LineSendingProtocol([
- "PASS ",
- "QUIT"
- ])
- d = loopback.loopbackAsync(dummy, client)
- return d.addCallback(self._cbTestEmptyPASS, client, dummy)
-
- def _cbTestEmptyPASS(self, ignored, client, dummy):
- expected_output = '+OK <moshez>\r\n-ERR USER required before PASS\r\n+OK \r\n'
- self.assertEqual(expected_output, '\r\n'.join(client.response) + '\r\n')
- dummy.connectionLost(failure.Failure(Exception("Test harness disconnect")))
-
-
-class TestServerFactory:
- implements(pop3.IServerFactory)
-
- def cap_IMPLEMENTATION(self):
- return "Test Implementation String"
-
- def cap_EXPIRE(self):
- return 60
-
- challengers = OrderedDict([("SCHEME_1", None), ("SCHEME_2", None)])
-
- def cap_LOGIN_DELAY(self):
- return 120
-
- pue = True
- def perUserExpiration(self):
- return self.pue
-
- puld = True
- def perUserLoginDelay(self):
- return self.puld
-
-
-class TestMailbox:
- loginDelay = 100
- messageExpiration = 25
-
-
-class CapabilityTestCase(unittest.TestCase):
- def setUp(self):
- s = StringIO.StringIO()
- p = pop3.POP3()
- p.factory = TestServerFactory()
- p.transport = internet.protocol.FileWrapper(s)
- p.connectionMade()
- p.do_CAPA()
-
- self.caps = p.listCapabilities()
- self.pcaps = s.getvalue().splitlines()
-
- s = StringIO.StringIO()
- p.mbox = TestMailbox()
- p.transport = internet.protocol.FileWrapper(s)
- p.do_CAPA()
-
- self.lpcaps = s.getvalue().splitlines()
- p.connectionLost(failure.Failure(Exception("Test harness disconnect")))
-
- def contained(self, s, *caps):
- for c in caps:
- self.assertIn(s, c)
-
- def testUIDL(self):
- self.contained("UIDL", self.caps, self.pcaps, self.lpcaps)
-
- def testTOP(self):
- self.contained("TOP", self.caps, self.pcaps, self.lpcaps)
-
- def testUSER(self):
- self.contained("USER", self.caps, self.pcaps, self.lpcaps)
-
- def testEXPIRE(self):
- self.contained("EXPIRE 60 USER", self.caps, self.pcaps)
- self.contained("EXPIRE 25", self.lpcaps)
-
- def testIMPLEMENTATION(self):
- self.contained(
- "IMPLEMENTATION Test Implementation String",
- self.caps, self.pcaps, self.lpcaps
- )
-
- def testSASL(self):
- self.contained(
- "SASL SCHEME_1 SCHEME_2",
- self.caps, self.pcaps, self.lpcaps
- )
-
- def testLOGIN_DELAY(self):
- self.contained("LOGIN-DELAY 120 USER", self.caps, self.pcaps)
- self.assertIn("LOGIN-DELAY 100", self.lpcaps)
-
-
-
-class GlobalCapabilitiesTestCase(unittest.TestCase):
- def setUp(self):
- s = StringIO.StringIO()
- p = pop3.POP3()
- p.factory = TestServerFactory()
- p.factory.pue = p.factory.puld = False
- p.transport = internet.protocol.FileWrapper(s)
- p.connectionMade()
- p.do_CAPA()
-
- self.caps = p.listCapabilities()
- self.pcaps = s.getvalue().splitlines()
-
- s = StringIO.StringIO()
- p.mbox = TestMailbox()
- p.transport = internet.protocol.FileWrapper(s)
- p.do_CAPA()
-
- self.lpcaps = s.getvalue().splitlines()
- p.connectionLost(failure.Failure(Exception("Test harness disconnect")))
-
- def contained(self, s, *caps):
- for c in caps:
- self.assertIn(s, c)
-
- def testEXPIRE(self):
- self.contained("EXPIRE 60", self.caps, self.pcaps, self.lpcaps)
-
- def testLOGIN_DELAY(self):
- self.contained("LOGIN-DELAY 120", self.caps, self.pcaps, self.lpcaps)
-
-
-
-class TestRealm:
- def requestAvatar(self, avatarId, mind, *interfaces):
- if avatarId == 'testuser':
- return pop3.IMailbox, DummyMailbox(ValueError), lambda: None
- assert False
-
-
-
-class SASLTestCase(unittest.TestCase):
- def testValidLogin(self):
- p = pop3.POP3()
- p.factory = TestServerFactory()
- p.factory.challengers = {'CRAM-MD5': cred.credentials.CramMD5Credentials}
- p.portal = cred.portal.Portal(TestRealm())
- ch = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()
- ch.addUser('testuser', 'testpassword')
- p.portal.registerChecker(ch)
-
- s = StringIO.StringIO()
- p.transport = internet.protocol.FileWrapper(s)
- p.connectionMade()
-
- p.lineReceived("CAPA")
- self.failUnless(s.getvalue().find("SASL CRAM-MD5") >= 0)
-
- p.lineReceived("AUTH CRAM-MD5")
- chal = s.getvalue().splitlines()[-1][2:]
- chal = base64.decodestring(chal)
- response = hmac.HMAC('testpassword', chal).hexdigest()
-
- p.lineReceived(base64.encodestring('testuser ' + response).rstrip('\n'))
- self.failUnless(p.mbox)
- self.failUnless(s.getvalue().splitlines()[-1].find("+OK") >= 0)
- p.connectionLost(failure.Failure(Exception("Test harness disconnect")))
-
-
-
-class CommandMixin:
- """
- Tests for all the commands a POP3 server is allowed to receive.
- """
-
- extraMessage = '''\
-From: guy
-To: fellow
-
-More message text for you.
-'''
-
-
- def setUp(self):
- """
- Make a POP3 server protocol instance hooked up to a simple mailbox and
- a transport that buffers output to a StringIO.
- """
- p = pop3.POP3()
- p.mbox = self.mailboxType(self.exceptionType)
- p.schedule = list
- self.pop3Server = p
-
- s = StringIO.StringIO()
- p.transport = internet.protocol.FileWrapper(s)
- p.connectionMade()
- s.truncate(0)
- self.pop3Transport = s
-
-
- def tearDown(self):
- """
- Disconnect the server protocol so it can clean up anything it might
- need to clean up.
- """
- self.pop3Server.connectionLost(failure.Failure(Exception("Test harness disconnect")))
-
-
- def _flush(self):
- """
- Do some of the things that the reactor would take care of, if the
- reactor were actually running.
- """
- # Oh man FileWrapper is pooh.
- self.pop3Server.transport._checkProducer()
-
-
- def testLIST(self):
- """
- Test the two forms of list: with a message index number, which should
- return a short-form response, and without a message index number, which
- should return a long-form response, one line per message.
- """
- p = self.pop3Server
- s = self.pop3Transport
-
- p.lineReceived("LIST 1")
- self._flush()
- self.assertEqual(s.getvalue(), "+OK 1 44\r\n")
- s.truncate(0)
-
- p.lineReceived("LIST")
- self._flush()
- self.assertEqual(s.getvalue(), "+OK 1\r\n1 44\r\n.\r\n")
-
-
- def testLISTWithBadArgument(self):
- """
- Test that non-integers and out-of-bound integers produce appropriate
- error responses.
- """
- p = self.pop3Server
- s = self.pop3Transport
-
- p.lineReceived("LIST a")
- self.assertEqual(
- s.getvalue(),
- "-ERR Invalid message-number: 'a'\r\n")
- s.truncate(0)
-
- p.lineReceived("LIST 0")
- self.assertEqual(
- s.getvalue(),
- "-ERR Invalid message-number: 0\r\n")
- s.truncate(0)
-
- p.lineReceived("LIST 2")
- self.assertEqual(
- s.getvalue(),
- "-ERR Invalid message-number: 2\r\n")
- s.truncate(0)
-
-
- def testUIDL(self):
- """
- Test the two forms of the UIDL command. These are just like the two
- forms of the LIST command.
- """
- p = self.pop3Server
- s = self.pop3Transport
-
- p.lineReceived("UIDL 1")
- self.assertEqual(s.getvalue(), "+OK 0\r\n")
- s.truncate(0)
-
- p.lineReceived("UIDL")
- self._flush()
- self.assertEqual(s.getvalue(), "+OK \r\n1 0\r\n.\r\n")
-
-
- def testUIDLWithBadArgument(self):
- """
- Test that UIDL with a non-integer or an out-of-bounds integer produces
- the appropriate error response.
- """
- p = self.pop3Server
- s = self.pop3Transport
-
- p.lineReceived("UIDL a")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
- p.lineReceived("UIDL 0")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
- p.lineReceived("UIDL 2")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
-
- def testSTAT(self):
- """
- Test the single form of the STAT command, which returns a short-form
- response of the number of messages in the mailbox and their total size.
- """
- p = self.pop3Server
- s = self.pop3Transport
-
- p.lineReceived("STAT")
- self._flush()
- self.assertEqual(s.getvalue(), "+OK 1 44\r\n")
-
-
- def testRETR(self):
- """
- Test downloading a message.
- """
- p = self.pop3Server
- s = self.pop3Transport
-
- p.lineReceived("RETR 1")
- self._flush()
- self.assertEqual(
- s.getvalue(),
- "+OK 44\r\n"
- "From: moshe\r\n"
- "To: moshe\r\n"
- "\r\n"
- "How are you, friend?\r\n"
- ".\r\n")
- s.truncate(0)
-
-
- def testRETRWithBadArgument(self):
- """
- Test that trying to download a message with a bad argument, either not
- an integer or an out-of-bounds integer, fails with the appropriate
- error response.
- """
- p = self.pop3Server
- s = self.pop3Transport
-
- p.lineReceived("RETR a")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
- p.lineReceived("RETR 0")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
- p.lineReceived("RETR 2")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
-
- def testTOP(self):
- """
- Test downloading the headers and part of the body of a message.
- """
- p = self.pop3Server
- s = self.pop3Transport
- p.mbox.messages.append(self.extraMessage)
-
- p.lineReceived("TOP 1 0")
- self._flush()
- self.assertEqual(
- s.getvalue(),
- "+OK Top of message follows\r\n"
- "From: moshe\r\n"
- "To: moshe\r\n"
- "\r\n"
- ".\r\n")
-
-
- def testTOPWithBadArgument(self):
- """
- Test that trying to download a message with a bad argument, either a
- message number which isn't an integer or is an out-of-bounds integer or
- a number of lines which isn't an integer or is a negative integer,
- fails with the appropriate error response.
- """
- p = self.pop3Server
- s = self.pop3Transport
- p.mbox.messages.append(self.extraMessage)
-
- p.lineReceived("TOP 1 a")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad line count argument\r\n")
- s.truncate(0)
-
- p.lineReceived("TOP 1 -1")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad line count argument\r\n")
- s.truncate(0)
-
- p.lineReceived("TOP a 1")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
- p.lineReceived("TOP 0 1")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
- p.lineReceived("TOP 3 1")
- self.assertEqual(
- s.getvalue(),
- "-ERR Bad message number argument\r\n")
- s.truncate(0)
-
-
- def testLAST(self):
- """
- Test the exceedingly pointless LAST command, which tells you the
- highest message index which you have already downloaded.
- """
- p = self.pop3Server
- s = self.pop3Transport
- p.mbox.messages.append(self.extraMessage)
-
- p.lineReceived('LAST')
- self.assertEqual(
- s.getvalue(),
- "+OK 0\r\n")
- s.truncate(0)
-
-
- def testRetrieveUpdatesHighest(self):
- """
- Test that issuing a RETR command updates the LAST response.
- """
- p = self.pop3Server
- s = self.pop3Transport
- p.mbox.messages.append(self.extraMessage)
-
- p.lineReceived('RETR 2')
- self._flush()
- s.truncate(0)
- p.lineReceived('LAST')
- self.assertEqual(
- s.getvalue(),
- '+OK 2\r\n')
- s.truncate(0)
-
-
- def testTopUpdatesHighest(self):
- """
- Test that issuing a TOP command updates the LAST response.
- """
- p = self.pop3Server
- s = self.pop3Transport
- p.mbox.messages.append(self.extraMessage)
-
- p.lineReceived('TOP 2 10')
- self._flush()
- s.truncate(0)
- p.lineReceived('LAST')
- self.assertEqual(
- s.getvalue(),
- '+OK 2\r\n')
-
-
- def testHighestOnlyProgresses(self):
- """
- Test that downloading a message with a smaller index than the current
- LAST response doesn't change the LAST response.
- """
- p = self.pop3Server
- s = self.pop3Transport
- p.mbox.messages.append(self.extraMessage)
-
- p.lineReceived('RETR 2')
- self._flush()
- p.lineReceived('TOP 1 10')
- self._flush()
- s.truncate(0)
- p.lineReceived('LAST')
- self.assertEqual(
- s.getvalue(),
- '+OK 2\r\n')
-
-
- def testResetClearsHighest(self):
- """
- Test that issuing RSET changes the LAST response to 0.
- """
- p = self.pop3Server
- s = self.pop3Transport
- p.mbox.messages.append(self.extraMessage)
-
- p.lineReceived('RETR 2')
- self._flush()
- p.lineReceived('RSET')
- s.truncate(0)
- p.lineReceived('LAST')
- self.assertEqual(
- s.getvalue(),
- '+OK 0\r\n')
-
-
-
-_listMessageDeprecation = (
- "twisted.mail.pop3.IMailbox.listMessages may not "
- "raise IndexError for out-of-bounds message numbers: "
- "raise ValueError instead.")
-_listMessageSuppression = util.suppress(
- message=_listMessageDeprecation,
- category=PendingDeprecationWarning)
-
-_getUidlDeprecation = (
- "twisted.mail.pop3.IMailbox.getUidl may not "
- "raise IndexError for out-of-bounds message numbers: "
- "raise ValueError instead.")
-_getUidlSuppression = util.suppress(
- message=_getUidlDeprecation,
- category=PendingDeprecationWarning)
-
-class IndexErrorCommandTestCase(CommandMixin, unittest.TestCase):
- """
- Run all of the command tests against a mailbox which raises IndexError
- when an out of bounds request is made. This behavior will be deprecated
- shortly and then removed.
- """
- exceptionType = IndexError
- mailboxType = DummyMailbox
-
- def testLISTWithBadArgument(self):
- return CommandMixin.testLISTWithBadArgument(self)
- testLISTWithBadArgument.suppress = [_listMessageSuppression]
-
-
- def testUIDLWithBadArgument(self):
- return CommandMixin.testUIDLWithBadArgument(self)
- testUIDLWithBadArgument.suppress = [_getUidlSuppression]
-
-
- def testTOPWithBadArgument(self):
- return CommandMixin.testTOPWithBadArgument(self)
- testTOPWithBadArgument.suppress = [_listMessageSuppression]
-
-
- def testRETRWithBadArgument(self):
- return CommandMixin.testRETRWithBadArgument(self)
- testRETRWithBadArgument.suppress = [_listMessageSuppression]
-
-
-
-class ValueErrorCommandTestCase(CommandMixin, unittest.TestCase):
- """
- Run all of the command tests against a mailbox which raises ValueError
- when an out of bounds request is made. This is the correct behavior and
- after support for mailboxes which raise IndexError is removed, this will
- become just C{CommandTestCase}.
- """
- exceptionType = ValueError
- mailboxType = DummyMailbox
-
-
-
-class SyncDeferredMailbox(DummyMailbox):
- """
- Mailbox which has a listMessages implementation which returns a Deferred
- which has already fired.
- """
- def listMessages(self, n=None):
- return defer.succeed(DummyMailbox.listMessages(self, n))
-
-
-
-class IndexErrorSyncDeferredCommandTestCase(IndexErrorCommandTestCase):
- """
- Run all of the L{IndexErrorCommandTestCase} tests with a
- synchronous-Deferred returning IMailbox implementation.
- """
- mailboxType = SyncDeferredMailbox
-
-
-
-class ValueErrorSyncDeferredCommandTestCase(ValueErrorCommandTestCase):
- """
- Run all of the L{ValueErrorCommandTestCase} tests with a
- synchronous-Deferred returning IMailbox implementation.
- """
- mailboxType = SyncDeferredMailbox
-
-
-
-class AsyncDeferredMailbox(DummyMailbox):
- """
- Mailbox which has a listMessages implementation which returns a Deferred
- which has not yet fired.
- """
- def __init__(self, *a, **kw):
- self.waiting = []
- DummyMailbox.__init__(self, *a, **kw)
-
-
- def listMessages(self, n=None):
- d = defer.Deferred()
- # See AsyncDeferredMailbox._flush
- self.waiting.append((d, DummyMailbox.listMessages(self, n)))
- return d
-
-
-
-class IndexErrorAsyncDeferredCommandTestCase(IndexErrorCommandTestCase):
- """
- Run all of the L{IndexErrorCommandTestCase} tests with an asynchronous-Deferred
- returning IMailbox implementation.
- """
- mailboxType = AsyncDeferredMailbox
-
- def _flush(self):
- """
- Fire whatever Deferreds we've built up in our mailbox.
- """
- while self.pop3Server.mbox.waiting:
- d, a = self.pop3Server.mbox.waiting.pop()
- d.callback(a)
- IndexErrorCommandTestCase._flush(self)
-
-
-
-class ValueErrorAsyncDeferredCommandTestCase(ValueErrorCommandTestCase):
- """
- Run all of the L{IndexErrorCommandTestCase} tests with an asynchronous-Deferred
- returning IMailbox implementation.
- """
- mailboxType = AsyncDeferredMailbox
-
- def _flush(self):
- """
- Fire whatever Deferreds we've built up in our mailbox.
- """
- while self.pop3Server.mbox.waiting:
- d, a = self.pop3Server.mbox.waiting.pop()
- d.callback(a)
- ValueErrorCommandTestCase._flush(self)
-
-class POP3MiscTestCase(unittest.TestCase):
- """
- Miscellaneous tests more to do with module/package structure than
- anything to do with the Post Office Protocol.
- """
- def test_all(self):
- """
- This test checks that all names listed in
- twisted.mail.pop3.__all__ are actually present in the module.
- """
- mod = twisted.mail.pop3
- for attr in mod.__all__:
- self.failUnless(hasattr(mod, attr))
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3client.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3client.py
deleted file mode 100755
index 502aae82..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_pop3client.py
+++ /dev/null
@@ -1,582 +0,0 @@
-# -*- test-case-name: twisted.mail.test.test_pop3client -*-
-# Copyright (c) 2001-2004 Divmod Inc.
-# See LICENSE for details.
-
-from zope.interface import directlyProvides
-
-from twisted.mail.pop3 import AdvancedPOP3Client as POP3Client
-from twisted.mail.pop3 import InsecureAuthenticationDisallowed
-from twisted.mail.pop3 import ServerErrorResponse
-from twisted.protocols import loopback
-from twisted.internet import reactor, defer, error, protocol, interfaces
-from twisted.python import log
-
-from twisted.trial import unittest
-from twisted.test.proto_helpers import StringTransport
-from twisted.protocols import basic
-
-from twisted.mail.test import pop3testserver
-
-try:
- from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
-except ImportError:
- ClientTLSContext = ServerTLSContext = None
-
-
-class StringTransportWithConnectionLosing(StringTransport):
- def loseConnection(self):
- self.protocol.connectionLost(error.ConnectionDone())
-
-
-capCache = {"TOP": None, "LOGIN-DELAY": "180", "UIDL": None, \
- "STLS": None, "USER": None, "SASL": "LOGIN"}
-def setUp(greet=True):
- p = POP3Client()
-
- # Skip the CAPA login will issue if it doesn't already have a
- # capability cache
- p._capCache = capCache
-
- t = StringTransportWithConnectionLosing()
- t.protocol = p
- p.makeConnection(t)
-
- if greet:
- p.dataReceived('+OK Hello!\r\n')
-
- return p, t
-
-def strip(f):
- return lambda result, f=f: f()
-
-class POP3ClientLoginTestCase(unittest.TestCase):
- def testNegativeGreeting(self):
- p, t = setUp(greet=False)
- p.allowInsecureLogin = True
- d = p.login("username", "password")
- p.dataReceived('-ERR Offline for maintenance\r\n')
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "Offline for maintenance"))
-
-
- def testOkUser(self):
- p, t = setUp()
- d = p.user("username")
- self.assertEqual(t.value(), "USER username\r\n")
- p.dataReceived("+OK send password\r\n")
- return d.addCallback(self.assertEqual, "send password")
-
- def testBadUser(self):
- p, t = setUp()
- d = p.user("username")
- self.assertEqual(t.value(), "USER username\r\n")
- p.dataReceived("-ERR account suspended\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "account suspended"))
-
- def testOkPass(self):
- p, t = setUp()
- d = p.password("password")
- self.assertEqual(t.value(), "PASS password\r\n")
- p.dataReceived("+OK you're in!\r\n")
- return d.addCallback(self.assertEqual, "you're in!")
-
- def testBadPass(self):
- p, t = setUp()
- d = p.password("password")
- self.assertEqual(t.value(), "PASS password\r\n")
- p.dataReceived("-ERR go away\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "go away"))
-
- def testOkLogin(self):
- p, t = setUp()
- p.allowInsecureLogin = True
- d = p.login("username", "password")
- self.assertEqual(t.value(), "USER username\r\n")
- p.dataReceived("+OK go ahead\r\n")
- self.assertEqual(t.value(), "USER username\r\nPASS password\r\n")
- p.dataReceived("+OK password accepted\r\n")
- return d.addCallback(self.assertEqual, "password accepted")
-
- def testBadPasswordLogin(self):
- p, t = setUp()
- p.allowInsecureLogin = True
- d = p.login("username", "password")
- self.assertEqual(t.value(), "USER username\r\n")
- p.dataReceived("+OK waiting on you\r\n")
- self.assertEqual(t.value(), "USER username\r\nPASS password\r\n")
- p.dataReceived("-ERR bogus login\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "bogus login"))
-
- def testBadUsernameLogin(self):
- p, t = setUp()
- p.allowInsecureLogin = True
- d = p.login("username", "password")
- self.assertEqual(t.value(), "USER username\r\n")
- p.dataReceived("-ERR bogus login\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "bogus login"))
-
- def testServerGreeting(self):
- p, t = setUp(greet=False)
- p.dataReceived("+OK lalala this has no challenge\r\n")
- self.assertEqual(p.serverChallenge, None)
-
- def testServerGreetingWithChallenge(self):
- p, t = setUp(greet=False)
- p.dataReceived("+OK <here is the challenge>\r\n")
- self.assertEqual(p.serverChallenge, "<here is the challenge>")
-
- def testAPOP(self):
- p, t = setUp(greet=False)
- p.dataReceived("+OK <challenge string goes here>\r\n")
- d = p.login("username", "password")
- self.assertEqual(t.value(), "APOP username f34f1e464d0d7927607753129cabe39a\r\n")
- p.dataReceived("+OK Welcome!\r\n")
- return d.addCallback(self.assertEqual, "Welcome!")
-
- def testInsecureLoginRaisesException(self):
- p, t = setUp(greet=False)
- p.dataReceived("+OK Howdy\r\n")
- d = p.login("username", "password")
- self.failIf(t.value())
- return self.assertFailure(
- d, InsecureAuthenticationDisallowed)
-
-
- def testSSLTransportConsideredSecure(self):
- """
- If a server doesn't offer APOP but the transport is secured using
- SSL or TLS, a plaintext login should be allowed, not rejected with
- an InsecureAuthenticationDisallowed exception.
- """
- p, t = setUp(greet=False)
- directlyProvides(t, interfaces.ISSLTransport)
- p.dataReceived("+OK Howdy\r\n")
- d = p.login("username", "password")
- self.assertEqual(t.value(), "USER username\r\n")
- t.clear()
- p.dataReceived("+OK\r\n")
- self.assertEqual(t.value(), "PASS password\r\n")
- p.dataReceived("+OK\r\n")
- return d
-
-
-
-class ListConsumer:
- def __init__(self):
- self.data = {}
-
- def consume(self, (item, value)):
- self.data.setdefault(item, []).append(value)
-
-class MessageConsumer:
- def __init__(self):
- self.data = []
-
- def consume(self, line):
- self.data.append(line)
-
-class POP3ClientListTestCase(unittest.TestCase):
- def testListSize(self):
- p, t = setUp()
- d = p.listSize()
- self.assertEqual(t.value(), "LIST\r\n")
- p.dataReceived("+OK Here it comes\r\n")
- p.dataReceived("1 3\r\n2 2\r\n3 1\r\n.\r\n")
- return d.addCallback(self.assertEqual, [3, 2, 1])
-
- def testListSizeWithConsumer(self):
- p, t = setUp()
- c = ListConsumer()
- f = c.consume
- d = p.listSize(f)
- self.assertEqual(t.value(), "LIST\r\n")
- p.dataReceived("+OK Here it comes\r\n")
- p.dataReceived("1 3\r\n2 2\r\n3 1\r\n")
- self.assertEqual(c.data, {0: [3], 1: [2], 2: [1]})
- p.dataReceived("5 3\r\n6 2\r\n7 1\r\n")
- self.assertEqual(c.data, {0: [3], 1: [2], 2: [1], 4: [3], 5: [2], 6: [1]})
- p.dataReceived(".\r\n")
- return d.addCallback(self.assertIdentical, f)
-
- def testFailedListSize(self):
- p, t = setUp()
- d = p.listSize()
- self.assertEqual(t.value(), "LIST\r\n")
- p.dataReceived("-ERR Fatal doom server exploded\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "Fatal doom server exploded"))
-
- def testListUID(self):
- p, t = setUp()
- d = p.listUID()
- self.assertEqual(t.value(), "UIDL\r\n")
- p.dataReceived("+OK Here it comes\r\n")
- p.dataReceived("1 abc\r\n2 def\r\n3 ghi\r\n.\r\n")
- return d.addCallback(self.assertEqual, ["abc", "def", "ghi"])
-
- def testListUIDWithConsumer(self):
- p, t = setUp()
- c = ListConsumer()
- f = c.consume
- d = p.listUID(f)
- self.assertEqual(t.value(), "UIDL\r\n")
- p.dataReceived("+OK Here it comes\r\n")
- p.dataReceived("1 xyz\r\n2 abc\r\n5 mno\r\n")
- self.assertEqual(c.data, {0: ["xyz"], 1: ["abc"], 4: ["mno"]})
- p.dataReceived(".\r\n")
- return d.addCallback(self.assertIdentical, f)
-
- def testFailedListUID(self):
- p, t = setUp()
- d = p.listUID()
- self.assertEqual(t.value(), "UIDL\r\n")
- p.dataReceived("-ERR Fatal doom server exploded\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "Fatal doom server exploded"))
-
-class POP3ClientMessageTestCase(unittest.TestCase):
- def testRetrieve(self):
- p, t = setUp()
- d = p.retrieve(7)
- self.assertEqual(t.value(), "RETR 8\r\n")
- p.dataReceived("+OK Message incoming\r\n")
- p.dataReceived("La la la here is message text\r\n")
- p.dataReceived("..Further message text tra la la\r\n")
- p.dataReceived(".\r\n")
- return d.addCallback(
- self.assertEqual,
- ["La la la here is message text",
- ".Further message text tra la la"])
-
- def testRetrieveWithConsumer(self):
- p, t = setUp()
- c = MessageConsumer()
- f = c.consume
- d = p.retrieve(7, f)
- self.assertEqual(t.value(), "RETR 8\r\n")
- p.dataReceived("+OK Message incoming\r\n")
- p.dataReceived("La la la here is message text\r\n")
- p.dataReceived("..Further message text\r\n.\r\n")
- return d.addCallback(self._cbTestRetrieveWithConsumer, f, c)
-
- def _cbTestRetrieveWithConsumer(self, result, f, c):
- self.assertIdentical(result, f)
- self.assertEqual(c.data, ["La la la here is message text",
- ".Further message text"])
-
- def testPartialRetrieve(self):
- p, t = setUp()
- d = p.retrieve(7, lines=2)
- self.assertEqual(t.value(), "TOP 8 2\r\n")
- p.dataReceived("+OK 2 lines on the way\r\n")
- p.dataReceived("Line the first! Woop\r\n")
- p.dataReceived("Line the last! Bye\r\n")
- p.dataReceived(".\r\n")
- return d.addCallback(
- self.assertEqual,
- ["Line the first! Woop",
- "Line the last! Bye"])
-
- def testPartialRetrieveWithConsumer(self):
- p, t = setUp()
- c = MessageConsumer()
- f = c.consume
- d = p.retrieve(7, f, lines=2)
- self.assertEqual(t.value(), "TOP 8 2\r\n")
- p.dataReceived("+OK 2 lines on the way\r\n")
- p.dataReceived("Line the first! Woop\r\n")
- p.dataReceived("Line the last! Bye\r\n")
- p.dataReceived(".\r\n")
- return d.addCallback(self._cbTestPartialRetrieveWithConsumer, f, c)
-
- def _cbTestPartialRetrieveWithConsumer(self, result, f, c):
- self.assertIdentical(result, f)
- self.assertEqual(c.data, ["Line the first! Woop",
- "Line the last! Bye"])
-
- def testFailedRetrieve(self):
- p, t = setUp()
- d = p.retrieve(0)
- self.assertEqual(t.value(), "RETR 1\r\n")
- p.dataReceived("-ERR Fatal doom server exploded\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "Fatal doom server exploded"))
-
-
- def test_concurrentRetrieves(self):
- """
- Issue three retrieve calls immediately without waiting for any to
- succeed and make sure they all do succeed eventually.
- """
- p, t = setUp()
- messages = [
- p.retrieve(i).addCallback(
- self.assertEqual,
- ["First line of %d." % (i + 1,),
- "Second line of %d." % (i + 1,)])
- for i
- in range(3)]
-
- for i in range(1, 4):
- self.assertEqual(t.value(), "RETR %d\r\n" % (i,))
- t.clear()
- p.dataReceived("+OK 2 lines on the way\r\n")
- p.dataReceived("First line of %d.\r\n" % (i,))
- p.dataReceived("Second line of %d.\r\n" % (i,))
- self.assertEqual(t.value(), "")
- p.dataReceived(".\r\n")
-
- return defer.DeferredList(messages, fireOnOneErrback=True)
-
-
-
-class POP3ClientMiscTestCase(unittest.TestCase):
- def testCapability(self):
- p, t = setUp()
- d = p.capabilities(useCache=0)
- self.assertEqual(t.value(), "CAPA\r\n")
- p.dataReceived("+OK Capabilities on the way\r\n")
- p.dataReceived("X\r\nY\r\nZ\r\nA 1 2 3\r\nB 1 2\r\nC 1\r\n.\r\n")
- return d.addCallback(
- self.assertEqual,
- {"X": None, "Y": None, "Z": None,
- "A": ["1", "2", "3"],
- "B": ["1", "2"],
- "C": ["1"]})
-
- def testCapabilityError(self):
- p, t = setUp()
- d = p.capabilities(useCache=0)
- self.assertEqual(t.value(), "CAPA\r\n")
- p.dataReceived("-ERR This server is lame!\r\n")
- return d.addCallback(self.assertEqual, {})
-
- def testStat(self):
- p, t = setUp()
- d = p.stat()
- self.assertEqual(t.value(), "STAT\r\n")
- p.dataReceived("+OK 1 1212\r\n")
- return d.addCallback(self.assertEqual, (1, 1212))
-
- def testStatError(self):
- p, t = setUp()
- d = p.stat()
- self.assertEqual(t.value(), "STAT\r\n")
- p.dataReceived("-ERR This server is lame!\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "This server is lame!"))
-
- def testNoop(self):
- p, t = setUp()
- d = p.noop()
- self.assertEqual(t.value(), "NOOP\r\n")
- p.dataReceived("+OK No-op to you too!\r\n")
- return d.addCallback(self.assertEqual, "No-op to you too!")
-
- def testNoopError(self):
- p, t = setUp()
- d = p.noop()
- self.assertEqual(t.value(), "NOOP\r\n")
- p.dataReceived("-ERR This server is lame!\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "This server is lame!"))
-
- def testRset(self):
- p, t = setUp()
- d = p.reset()
- self.assertEqual(t.value(), "RSET\r\n")
- p.dataReceived("+OK Reset state\r\n")
- return d.addCallback(self.assertEqual, "Reset state")
-
- def testRsetError(self):
- p, t = setUp()
- d = p.reset()
- self.assertEqual(t.value(), "RSET\r\n")
- p.dataReceived("-ERR This server is lame!\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "This server is lame!"))
-
- def testDelete(self):
- p, t = setUp()
- d = p.delete(3)
- self.assertEqual(t.value(), "DELE 4\r\n")
- p.dataReceived("+OK Hasta la vista\r\n")
- return d.addCallback(self.assertEqual, "Hasta la vista")
-
- def testDeleteError(self):
- p, t = setUp()
- d = p.delete(3)
- self.assertEqual(t.value(), "DELE 4\r\n")
- p.dataReceived("-ERR Winner is not you.\r\n")
- return self.assertFailure(
- d, ServerErrorResponse).addCallback(
- lambda exc: self.assertEqual(exc.args[0], "Winner is not you."))
-
-
-class SimpleClient(POP3Client):
- def __init__(self, deferred, contextFactory = None):
- self.deferred = deferred
- self.allowInsecureLogin = True
-
- def serverGreeting(self, challenge):
- self.deferred.callback(None)
-
-class POP3HelperMixin:
- serverCTX = None
- clientCTX = None
-
- def setUp(self):
- d = defer.Deferred()
- self.server = pop3testserver.POP3TestServer(contextFactory=self.serverCTX)
- self.client = SimpleClient(d, contextFactory=self.clientCTX)
- self.client.timeout = 30
- self.connected = d
-
- def tearDown(self):
- del self.server
- del self.client
- del self.connected
-
- def _cbStopClient(self, ignore):
- self.client.transport.loseConnection()
-
- def _ebGeneral(self, failure):
- self.client.transport.loseConnection()
- self.server.transport.loseConnection()
- return failure
-
- def loopback(self):
- return loopback.loopbackTCP(self.server, self.client, noisy=False)
-
-
-class TLSServerFactory(protocol.ServerFactory):
- class protocol(basic.LineReceiver):
- context = None
- output = []
- def connectionMade(self):
- self.factory.input = []
- self.output = self.output[:]
- map(self.sendLine, self.output.pop(0))
- def lineReceived(self, line):
- self.factory.input.append(line)
- map(self.sendLine, self.output.pop(0))
- if line == 'STLS':
- self.transport.startTLS(self.context)
-
-
-class POP3TLSTestCase(unittest.TestCase):
- """
- Tests for POP3Client's support for TLS connections.
- """
-
- def test_startTLS(self):
- """
- POP3Client.startTLS starts a TLS session over its existing TCP
- connection.
- """
- sf = TLSServerFactory()
- sf.protocol.output = [
- ['+OK'], # Server greeting
- ['+OK', 'STLS', '.'], # CAPA response
- ['+OK'], # STLS response
- ['+OK', '.'], # Second CAPA response
- ['+OK'] # QUIT response
- ]
- sf.protocol.context = ServerTLSContext()
- port = reactor.listenTCP(0, sf, interface='127.0.0.1')
- self.addCleanup(port.stopListening)
- H = port.getHost().host
- P = port.getHost().port
-
- connLostDeferred = defer.Deferred()
- cp = SimpleClient(defer.Deferred(), ClientTLSContext())
- def connectionLost(reason):
- SimpleClient.connectionLost(cp, reason)
- connLostDeferred.callback(None)
- cp.connectionLost = connectionLost
- cf = protocol.ClientFactory()
- cf.protocol = lambda: cp
-
- conn = reactor.connectTCP(H, P, cf)
-
- def cbConnected(ignored):
- log.msg("Connected to server; starting TLS")
- return cp.startTLS()
-
- def cbStartedTLS(ignored):
- log.msg("Started TLS; disconnecting")
- return cp.quit()
-
- def cbDisconnected(ign):
- log.msg("Disconnected; asserting correct input received")
- self.assertEqual(
- sf.input,
- ['CAPA', 'STLS', 'CAPA', 'QUIT'])
-
- def cleanup(result):
- log.msg("Asserted correct input; disconnecting client and shutting down server")
- conn.disconnect()
- return connLostDeferred
-
- cp.deferred.addCallback(cbConnected)
- cp.deferred.addCallback(cbStartedTLS)
- cp.deferred.addCallback(cbDisconnected)
- cp.deferred.addBoth(cleanup)
-
- return cp.deferred
-
-
-class POP3TimeoutTestCase(POP3HelperMixin, unittest.TestCase):
- def testTimeout(self):
- def login():
- d = self.client.login('test', 'twisted')
- d.addCallback(loggedIn)
- d.addErrback(timedOut)
- return d
-
- def loggedIn(result):
- self.fail("Successfully logged in!? Impossible!")
-
-
- def timedOut(failure):
- failure.trap(error.TimeoutError)
- self._cbStopClient(None)
-
- def quit():
- return self.client.quit()
-
- self.client.timeout = 0.01
-
- # Tell the server to not return a response to client. This
- # will trigger a timeout.
- pop3testserver.TIMEOUT_RESPONSE = True
-
- methods = [login, quit]
- map(self.connected.addCallback, map(strip, methods))
- self.connected.addCallback(self._cbStopClient)
- self.connected.addErrback(self._ebGeneral)
- return self.loopback()
-
-
-if ClientTLSContext is None:
- for case in (POP3TLSTestCase,):
- case.skip = "OpenSSL not present"
-elif interfaces.IReactorSSL(reactor, None) is None:
- for case in (POP3TLSTestCase,):
- case.skip = "Reactor doesn't support SSL"
-
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_scripts.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_scripts.py
deleted file mode 100755
index cc14061f..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_scripts.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Tests for the command-line mailer tool provided by Twisted Mail.
-"""
-
-from twisted.trial.unittest import TestCase
-from twisted.scripts.test.test_scripts import ScriptTestsMixin
-
-
-
-class ScriptTests(TestCase, ScriptTestsMixin):
- """
- Tests for all one of mail's scripts.
- """
- def test_mailmail(self):
- self.scriptTest("mail/mailmail")
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_smtp.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_smtp.py
deleted file mode 100755
index 058bb8ec..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/test/test_smtp.py
+++ /dev/null
@@ -1,1520 +0,0 @@
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Test cases for twisted.mail.smtp module.
-"""
-
-from zope.interface import implements
-
-from twisted.python.util import LineLog
-from twisted.trial import unittest, util
-from twisted.protocols import basic, loopback
-from twisted.mail import smtp
-from twisted.internet import defer, protocol, reactor, interfaces
-from twisted.internet import address, error, task
-from twisted.test.proto_helpers import StringTransport
-
-from twisted import cred
-import twisted.cred.error
-import twisted.cred.portal
-import twisted.cred.checkers
-import twisted.cred.credentials
-
-from twisted.cred.portal import IRealm, Portal
-from twisted.cred.checkers import ICredentialsChecker, AllowAnonymousAccess
-from twisted.cred.credentials import IAnonymous
-from twisted.cred.error import UnauthorizedLogin
-
-from twisted.mail import imap4
-
-
-try:
- from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
-except ImportError:
- ClientTLSContext = ServerTLSContext = None
-
-import re
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-
-def spameater(*spam, **eggs):
- return None
-
-
-
-class BrokenMessage(object):
- """
- L{BrokenMessage} is an L{IMessage} which raises an unexpected exception
- from its C{eomReceived} method. This is useful for creating a server which
- can be used to test client retry behavior.
- """
- implements(smtp.IMessage)
-
- def __init__(self, user):
- pass
-
-
- def lineReceived(self, line):
- pass
-
-
- def eomReceived(self):
- raise RuntimeError("Some problem, delivery is failing.")
-
-
- def connectionLost(self):
- pass
-
-
-
-class DummyMessage(object):
- """
- L{BrokenMessage} is an L{IMessage} which saves the message delivered to it
- to its domain object.
-
- @ivar domain: A L{DummyDomain} which will be used to store the message once
- it is received.
- """
- def __init__(self, domain, user):
- self.domain = domain
- self.user = user
- self.buffer = []
-
-
- def lineReceived(self, line):
- # Throw away the generated Received: header
- if not re.match('Received: From yyy.com \(\[.*\]\) by localhost;', line):
- self.buffer.append(line)
-
-
- def eomReceived(self):
- message = '\n'.join(self.buffer) + '\n'
- self.domain.messages[self.user.dest.local].append(message)
- deferred = defer.Deferred()
- deferred.callback("saved")
- return deferred
-
-
-
-class DummyDomain(object):
- """
- L{DummyDomain} is an L{IDomain} which keeps track of messages delivered to
- it in memory.
- """
- def __init__(self, names):
- self.messages = {}
- for name in names:
- self.messages[name] = []
-
-
- def exists(self, user):
- if user.dest.local in self.messages:
- return defer.succeed(lambda: self.startMessage(user))
- return defer.fail(smtp.SMTPBadRcpt(user))
-
-
- def startMessage(self, user):
- return DummyMessage(self, user)
-
-
-
-class SMTPTestCase(unittest.TestCase):
-
- messages = [('foo@bar.com', ['foo@baz.com', 'qux@baz.com'], '''\
-Subject: urgent\015
-\015
-Someone set up us the bomb!\015
-''')]
-
- mbox = {'foo': ['Subject: urgent\n\nSomeone set up us the bomb!\n']}
-
- def setUp(self):
- """
- Create an in-memory mail domain to which messages may be delivered by
- tests and create a factory and transport to do the delivering.
- """
- self.factory = smtp.SMTPFactory()
- self.factory.domains = {}
- self.factory.domains['baz.com'] = DummyDomain(['foo'])
- self.transport = StringTransport()
-
-
- def testMessages(self):
- from twisted.mail import protocols
- protocol = protocols.DomainSMTP()
- protocol.service = self.factory
- protocol.factory = self.factory
- protocol.receivedHeader = spameater
- protocol.makeConnection(self.transport)
- protocol.lineReceived('HELO yyy.com')
- for message in self.messages:
- protocol.lineReceived('MAIL FROM:<%s>' % message[0])
- for target in message[1]:
- protocol.lineReceived('RCPT TO:<%s>' % target)
- protocol.lineReceived('DATA')
- protocol.dataReceived(message[2])
- protocol.lineReceived('.')
- protocol.lineReceived('QUIT')
- if self.mbox != self.factory.domains['baz.com'].messages:
- raise AssertionError(self.factory.domains['baz.com'].messages)
- protocol.setTimeout(None)
-
- testMessages.suppress = [util.suppress(message='DomainSMTP', category=DeprecationWarning)]
-
-mail = '''\
-Subject: hello
-
-Goodbye
-'''
-
-class MyClient:
- def __init__(self, messageInfo=None):
- if messageInfo is None:
- messageInfo = (
- 'moshez@foo.bar', ['moshez@foo.bar'], StringIO(mail))
- self._sender = messageInfo[0]
- self._recipient = messageInfo[1]
- self._data = messageInfo[2]
-
-
- def getMailFrom(self):
- return self._sender
-
-
- def getMailTo(self):
- return self._recipient
-
-
- def getMailData(self):
- return self._data
-
-
- def sendError(self, exc):
- self._error = exc
-
-
- def sentMail(self, code, resp, numOk, addresses, log):
- # Prevent another mail from being sent.
- self._sender = None
- self._recipient = None
- self._data = None
-
-
-
-class MySMTPClient(MyClient, smtp.SMTPClient):
- def __init__(self, messageInfo=None):
- smtp.SMTPClient.__init__(self, 'foo.baz')
- MyClient.__init__(self, messageInfo)
-
-class MyESMTPClient(MyClient, smtp.ESMTPClient):
- def __init__(self, secret = '', contextFactory = None):
- smtp.ESMTPClient.__init__(self, secret, contextFactory, 'foo.baz')
- MyClient.__init__(self)
-
-class LoopbackMixin:
- def loopback(self, server, client):
- return loopback.loopbackTCP(server, client)
-
-class LoopbackTestCase(LoopbackMixin):
- def testMessages(self):
- factory = smtp.SMTPFactory()
- factory.domains = {}
- factory.domains['foo.bar'] = DummyDomain(['moshez'])
- from twisted.mail.protocols import DomainSMTP
- protocol = DomainSMTP()
- protocol.service = factory
- protocol.factory = factory
- clientProtocol = self.clientClass()
- return self.loopback(protocol, clientProtocol)
- testMessages.suppress = [util.suppress(message='DomainSMTP', category=DeprecationWarning)]
-
-class LoopbackSMTPTestCase(LoopbackTestCase, unittest.TestCase):
- clientClass = MySMTPClient
-
-class LoopbackESMTPTestCase(LoopbackTestCase, unittest.TestCase):
- clientClass = MyESMTPClient
-
-
-class FakeSMTPServer(basic.LineReceiver):
-
- clientData = [
- '220 hello', '250 nice to meet you',
- '250 great', '250 great', '354 go on, lad'
- ]
-
- def connectionMade(self):
- self.buffer = []
- self.clientData = self.clientData[:]
- self.clientData.reverse()
- self.sendLine(self.clientData.pop())
-
- def lineReceived(self, line):
- self.buffer.append(line)
- if line == "QUIT":
- self.transport.write("221 see ya around\r\n")
- self.transport.loseConnection()
- elif line == ".":
- self.transport.write("250 gotcha\r\n")
- elif line == "RSET":
- self.transport.loseConnection()
-
- if self.clientData:
- self.sendLine(self.clientData.pop())
-
-
-class SMTPClientTestCase(unittest.TestCase, LoopbackMixin):
- """
- Tests for L{smtp.SMTPClient}.
- """
-
- def test_timeoutConnection(self):
- """
- L{smtp.SMTPClient.timeoutConnection} calls the C{sendError} hook with a
- fatal L{SMTPTimeoutError} with the current line log.
- """
- error = []
- client = MySMTPClient()
- client.sendError = error.append
- client.makeConnection(StringTransport())
- client.lineReceived("220 hello")
- client.timeoutConnection()
- self.assertIsInstance(error[0], smtp.SMTPTimeoutError)
- self.assertTrue(error[0].isFatal)
- self.assertEqual(
- str(error[0]),
- "Timeout waiting for SMTP server response\n"
- "<<< 220 hello\n"
- ">>> HELO foo.baz\n")
-
-
- expected_output = [
- 'HELO foo.baz', 'MAIL FROM:<moshez@foo.bar>',
- 'RCPT TO:<moshez@foo.bar>', 'DATA',
- 'Subject: hello', '', 'Goodbye', '.', 'RSET'
- ]
-
- def test_messages(self):
- """
- L{smtp.SMTPClient} sends I{HELO}, I{MAIL FROM}, I{RCPT TO}, and I{DATA}
- commands based on the return values of its C{getMailFrom},
- C{getMailTo}, and C{getMailData} methods.
- """
- client = MySMTPClient()
- server = FakeSMTPServer()
- d = self.loopback(server, client)
- d.addCallback(lambda x :
- self.assertEqual(server.buffer, self.expected_output))
- return d
-
-
- def test_transferError(self):
- """
- If there is an error while producing the message body to the
- connection, the C{sendError} callback is invoked.
- """
- client = MySMTPClient(
- ('alice@example.com', ['bob@example.com'], StringIO("foo")))
- transport = StringTransport()
- client.makeConnection(transport)
- client.dataReceived(
- '220 Ok\r\n' # Greeting
- '250 Ok\r\n' # EHLO response
- '250 Ok\r\n' # MAIL FROM response
- '250 Ok\r\n' # RCPT TO response
- '354 Ok\r\n' # DATA response
- )
-
- # Sanity check - a pull producer should be registered now.
- self.assertNotIdentical(transport.producer, None)
- self.assertFalse(transport.streaming)
-
- # Now stop the producer prematurely, meaning the message was not sent.
- transport.producer.stopProducing()
-
- # The sendError hook should have been invoked as a result.
- self.assertIsInstance(client._error, Exception)
-
-
- def test_sendFatalError(self):
- """
- If L{smtp.SMTPClient.sendError} is called with an L{SMTPClientError}
- which is fatal, it disconnects its transport without writing anything
- more to it.
- """
- client = smtp.SMTPClient(None)
- transport = StringTransport()
- client.makeConnection(transport)
- client.sendError(smtp.SMTPClientError(123, "foo", isFatal=True))
- self.assertEqual(transport.value(), "")
- self.assertTrue(transport.disconnecting)
-
-
- def test_sendNonFatalError(self):
- """
- If L{smtp.SMTPClient.sendError} is called with an L{SMTPClientError}
- which is not fatal, it sends C{"QUIT"} and waits for the server to
- close the connection.
- """
- client = smtp.SMTPClient(None)
- transport = StringTransport()
- client.makeConnection(transport)
- client.sendError(smtp.SMTPClientError(123, "foo", isFatal=False))
- self.assertEqual(transport.value(), "QUIT\r\n")
- self.assertFalse(transport.disconnecting)
-
-
- def test_sendOtherError(self):
- """
- If L{smtp.SMTPClient.sendError} is called with an exception which is
- not an L{SMTPClientError}, it disconnects its transport without
- writing anything more to it.
- """
- client = smtp.SMTPClient(None)
- transport = StringTransport()
- client.makeConnection(transport)
- client.sendError(Exception("foo"))
- self.assertEqual(transport.value(), "")
- self.assertTrue(transport.disconnecting)
-
-
-
-class DummySMTPMessage:
-
- def __init__(self, protocol, users):
- self.protocol = protocol
- self.users = users
- self.buffer = []
-
- def lineReceived(self, line):
- self.buffer.append(line)
-
- def eomReceived(self):
- message = '\n'.join(self.buffer) + '\n'
- helo, origin = self.users[0].helo[0], str(self.users[0].orig)
- recipients = []
- for user in self.users:
- recipients.append(str(user))
- self.protocol.message[tuple(recipients)] = (helo, origin, recipients, message)
- return defer.succeed("saved")
-
-
-
-class DummyProto:
- def connectionMade(self):
- self.dummyMixinBase.connectionMade(self)
- self.message = {}
-
- def startMessage(self, users):
- return DummySMTPMessage(self, users)
-
- def receivedHeader(*spam):
- return None
-
- def validateTo(self, user):
- self.delivery = SimpleDelivery(None)
- return lambda: self.startMessage([user])
-
- def validateFrom(self, helo, origin):
- return origin
-
-
-
-class DummySMTP(DummyProto, smtp.SMTP):
- dummyMixinBase = smtp.SMTP
-
-class DummyESMTP(DummyProto, smtp.ESMTP):
- dummyMixinBase = smtp.ESMTP
-
-class AnotherTestCase:
- serverClass = None
- clientClass = None
-
- messages = [ ('foo.com', 'moshez@foo.com', ['moshez@bar.com'],
- 'moshez@foo.com', ['moshez@bar.com'], '''\
-From: Moshe
-To: Moshe
-
-Hi,
-how are you?
-'''),
- ('foo.com', 'tttt@rrr.com', ['uuu@ooo', 'yyy@eee'],
- 'tttt@rrr.com', ['uuu@ooo', 'yyy@eee'], '''\
-Subject: pass
-
-..rrrr..
-'''),
- ('foo.com', '@this,@is,@ignored:foo@bar.com',
- ['@ignore,@this,@too:bar@foo.com'],
- 'foo@bar.com', ['bar@foo.com'], '''\
-Subject: apa
-To: foo
-
-123
-.
-456
-'''),
- ]
-
- data = [
- ('', '220.*\r\n$', None, None),
- ('HELO foo.com\r\n', '250.*\r\n$', None, None),
- ('RSET\r\n', '250.*\r\n$', None, None),
- ]
- for helo_, from_, to_, realfrom, realto, msg in messages:
- data.append(('MAIL FROM:<%s>\r\n' % from_, '250.*\r\n',
- None, None))
- for rcpt in to_:
- data.append(('RCPT TO:<%s>\r\n' % rcpt, '250.*\r\n',
- None, None))
-
- data.append(('DATA\r\n','354.*\r\n',
- msg, ('250.*\r\n',
- (helo_, realfrom, realto, msg))))
-
-
- def test_buffer(self):
- """
- Exercise a lot of the SMTP client code. This is a "shotgun" style unit
- test. It does a lot of things and hopes that something will go really
- wrong if it is going to go wrong. This test should be replaced with a
- suite of nicer tests.
- """
- transport = StringTransport()
- a = self.serverClass()
- class fooFactory:
- domain = 'foo.com'
-
- a.factory = fooFactory()
- a.makeConnection(transport)
- for (send, expect, msg, msgexpect) in self.data:
- if send:
- a.dataReceived(send)
- data = transport.value()
- transport.clear()
- if not re.match(expect, data):
- raise AssertionError, (send, expect, data)
- if data[:3] == '354':
- for line in msg.splitlines():
- if line and line[0] == '.':
- line = '.' + line
- a.dataReceived(line + '\r\n')
- a.dataReceived('.\r\n')
- # Special case for DATA. Now we want a 250, and then
- # we compare the messages
- data = transport.value()
- transport.clear()
- resp, msgdata = msgexpect
- if not re.match(resp, data):
- raise AssertionError, (resp, data)
- for recip in msgdata[2]:
- expected = list(msgdata[:])
- expected[2] = [recip]
- self.assertEqual(
- a.message[(recip,)],
- tuple(expected)
- )
- a.setTimeout(None)
-
-
-class AnotherESMTPTestCase(AnotherTestCase, unittest.TestCase):
- serverClass = DummyESMTP
- clientClass = MyESMTPClient
-
-class AnotherSMTPTestCase(AnotherTestCase, unittest.TestCase):
- serverClass = DummySMTP
- clientClass = MySMTPClient
-
-
-
-class DummyChecker:
- implements(cred.checkers.ICredentialsChecker)
-
- users = {
- 'testuser': 'testpassword'
- }
-
- credentialInterfaces = (cred.credentials.IUsernamePassword,
- cred.credentials.IUsernameHashedPassword)
-
- def requestAvatarId(self, credentials):
- return defer.maybeDeferred(
- credentials.checkPassword, self.users[credentials.username]
- ).addCallback(self._cbCheck, credentials.username)
-
- def _cbCheck(self, result, username):
- if result:
- return username
- raise cred.error.UnauthorizedLogin()
-
-
-
-class SimpleDelivery(object):
- """
- L{SimpleDelivery} is a message delivery factory with no interesting
- behavior.
- """
- implements(smtp.IMessageDelivery)
-
- def __init__(self, messageFactory):
- self._messageFactory = messageFactory
-
-
- def receivedHeader(self, helo, origin, recipients):
- return None
-
-
- def validateFrom(self, helo, origin):
- return origin
-
-
- def validateTo(self, user):
- return lambda: self._messageFactory(user)
-
-
-
-class DummyRealm:
- def requestAvatar(self, avatarId, mind, *interfaces):
- return smtp.IMessageDelivery, SimpleDelivery(None), lambda: None
-
-
-
-class AuthTestCase(unittest.TestCase, LoopbackMixin):
- def test_crammd5Auth(self):
- """
- L{ESMTPClient} can authenticate using the I{CRAM-MD5} SASL mechanism.
-
- @see: U{http://tools.ietf.org/html/rfc2195}
- """
- realm = DummyRealm()
- p = cred.portal.Portal(realm)
- p.registerChecker(DummyChecker())
-
- server = DummyESMTP({'CRAM-MD5': cred.credentials.CramMD5Credentials})
- server.portal = p
- client = MyESMTPClient('testpassword')
-
- cAuth = smtp.CramMD5ClientAuthenticator('testuser')
- client.registerAuthenticator(cAuth)
-
- d = self.loopback(server, client)
- d.addCallback(lambda x : self.assertEqual(server.authenticated, 1))
- return d
-
-
- def test_loginAuth(self):
- """
- L{ESMTPClient} can authenticate using the I{LOGIN} SASL mechanism.
-
- @see: U{http://sepp.oetiker.ch/sasl-2.1.19-ds/draft-murchison-sasl-login-00.txt}
- """
- realm = DummyRealm()
- p = cred.portal.Portal(realm)
- p.registerChecker(DummyChecker())
-
- server = DummyESMTP({'LOGIN': imap4.LOGINCredentials})
- server.portal = p
- client = MyESMTPClient('testpassword')
-
- cAuth = smtp.LOGINAuthenticator('testuser')
- client.registerAuthenticator(cAuth)
-
- d = self.loopback(server, client)
- d.addCallback(lambda x: self.assertTrue(server.authenticated))
- return d
-
-
- def test_loginAgainstWeirdServer(self):
- """
- When communicating with a server which implements the I{LOGIN} SASL
- mechanism using C{"Username:"} as the challenge (rather than C{"User
- Name\\0"}), L{ESMTPClient} can still authenticate successfully using
- the I{LOGIN} mechanism.
- """
- realm = DummyRealm()
- p = cred.portal.Portal(realm)
- p.registerChecker(DummyChecker())
-
- server = DummyESMTP({'LOGIN': smtp.LOGINCredentials})
- server.portal = p
-
- client = MyESMTPClient('testpassword')
- cAuth = smtp.LOGINAuthenticator('testuser')
- client.registerAuthenticator(cAuth)
-
- d = self.loopback(server, client)
- d.addCallback(lambda x: self.assertTrue(server.authenticated))
- return d
-
-
-
-class SMTPHelperTestCase(unittest.TestCase):
- def testMessageID(self):
- d = {}
- for i in range(1000):
- m = smtp.messageid('testcase')
- self.failIf(m in d)
- d[m] = None
-
- def testQuoteAddr(self):
- cases = [
- ['user@host.name', '<user@host.name>'],
- ['"User Name" <user@host.name>', '<user@host.name>'],
- [smtp.Address('someguy@someplace'), '<someguy@someplace>'],
- ['', '<>'],
- [smtp.Address(''), '<>'],
- ]
-
- for (c, e) in cases:
- self.assertEqual(smtp.quoteaddr(c), e)
-
- def testUser(self):
- u = smtp.User('user@host', 'helo.host.name', None, None)
- self.assertEqual(str(u), 'user@host')
-
- def testXtextEncoding(self):
- cases = [
- ('Hello world', 'Hello+20world'),
- ('Hello+world', 'Hello+2Bworld'),
- ('\0\1\2\3\4\5', '+00+01+02+03+04+05'),
- ('e=mc2@example.com', 'e+3Dmc2@example.com')
- ]
-
- for (case, expected) in cases:
- self.assertEqual(smtp.xtext_encode(case), (expected, len(case)))
- self.assertEqual(case.encode('xtext'), expected)
- self.assertEqual(
- smtp.xtext_decode(expected), (case, len(expected)))
- self.assertEqual(expected.decode('xtext'), case)
-
-
- def test_encodeWithErrors(self):
- """
- Specifying an error policy to C{unicode.encode} with the
- I{xtext} codec should produce the same result as not
- specifying the error policy.
- """
- text = u'Hello world'
- self.assertEqual(
- smtp.xtext_encode(text, 'strict'),
- (text.encode('xtext'), len(text)))
- self.assertEqual(
- text.encode('xtext', 'strict'),
- text.encode('xtext'))
-
-
- def test_decodeWithErrors(self):
- """
- Similar to L{test_encodeWithErrors}, but for C{str.decode}.
- """
- bytes = 'Hello world'
- self.assertEqual(
- smtp.xtext_decode(bytes, 'strict'),
- (bytes.decode('xtext'), len(bytes)))
- self.assertEqual(
- bytes.decode('xtext', 'strict'),
- bytes.decode('xtext'))
-
-
-
-class NoticeTLSClient(MyESMTPClient):
- tls = False
-
- def esmtpState_starttls(self, code, resp):
- MyESMTPClient.esmtpState_starttls(self, code, resp)
- self.tls = True
-
-class TLSTestCase(unittest.TestCase, LoopbackMixin):
- def testTLS(self):
- clientCTX = ClientTLSContext()
- serverCTX = ServerTLSContext()
-
- client = NoticeTLSClient(contextFactory=clientCTX)
- server = DummyESMTP(contextFactory=serverCTX)
-
- def check(ignored):
- self.assertEqual(client.tls, True)
- self.assertEqual(server.startedTLS, True)
-
- return self.loopback(server, client).addCallback(check)
-
-if ClientTLSContext is None:
- for case in (TLSTestCase,):
- case.skip = "OpenSSL not present"
-
-if not interfaces.IReactorSSL.providedBy(reactor):
- for case in (TLSTestCase,):
- case.skip = "Reactor doesn't support SSL"
-
-class EmptyLineTestCase(unittest.TestCase):
- def test_emptyLineSyntaxError(self):
- """
- If L{smtp.SMTP} receives an empty line, it responds with a 500 error
- response code and a message about a syntax error.
- """
- proto = smtp.SMTP()
- transport = StringTransport()
- proto.makeConnection(transport)
- proto.lineReceived('')
- proto.setTimeout(None)
-
- out = transport.value().splitlines()
- self.assertEqual(len(out), 2)
- self.failUnless(out[0].startswith('220'))
- self.assertEqual(out[1], "500 Error: bad syntax")
-
-
-
-class TimeoutTestCase(unittest.TestCase, LoopbackMixin):
- """
- Check that SMTP client factories correctly use the timeout.
- """
-
- def _timeoutTest(self, onDone, clientFactory):
- """
- Connect the clientFactory, and check the timeout on the request.
- """
- clock = task.Clock()
- client = clientFactory.buildProtocol(
- address.IPv4Address('TCP', 'example.net', 25))
- client.callLater = clock.callLater
- t = StringTransport()
- client.makeConnection(t)
- t.protocol = client
- def check(ign):
- self.assertEqual(clock.seconds(), 0.5)
- d = self.assertFailure(onDone, smtp.SMTPTimeoutError
- ).addCallback(check)
- # The first call should not trigger the timeout
- clock.advance(0.1)
- # But this one should
- clock.advance(0.4)
- return d
-
-
- def test_SMTPClient(self):
- """
- Test timeout for L{smtp.SMTPSenderFactory}: the response L{Deferred}
- should be errback with a L{smtp.SMTPTimeoutError}.
- """
- onDone = defer.Deferred()
- clientFactory = smtp.SMTPSenderFactory(
- 'source@address', 'recipient@address',
- StringIO("Message body"), onDone,
- retries=0, timeout=0.5)
- return self._timeoutTest(onDone, clientFactory)
-
-
- def test_ESMTPClient(self):
- """
- Test timeout for L{smtp.ESMTPSenderFactory}: the response L{Deferred}
- should be errback with a L{smtp.SMTPTimeoutError}.
- """
- onDone = defer.Deferred()
- clientFactory = smtp.ESMTPSenderFactory(
- 'username', 'password',
- 'source@address', 'recipient@address',
- StringIO("Message body"), onDone,
- retries=0, timeout=0.5)
- return self._timeoutTest(onDone, clientFactory)
-
-
- def test_resetTimeoutWhileSending(self):
- """
- The timeout is not allowed to expire after the server has accepted a
- DATA command and the client is actively sending data to it.
- """
- class SlowFile:
- """
- A file-like which returns one byte from each read call until the
- specified number of bytes have been returned.
- """
- def __init__(self, size):
- self._size = size
-
- def read(self, max=None):
- if self._size:
- self._size -= 1
- return 'x'
- return ''
-
- failed = []
- onDone = defer.Deferred()
- onDone.addErrback(failed.append)
- clientFactory = smtp.SMTPSenderFactory(
- 'source@address', 'recipient@address',
- SlowFile(1), onDone, retries=0, timeout=3)
- clientFactory.domain = "example.org"
- clock = task.Clock()
- client = clientFactory.buildProtocol(
- address.IPv4Address('TCP', 'example.net', 25))
- client.callLater = clock.callLater
- transport = StringTransport()
- client.makeConnection(transport)
-
- client.dataReceived(
- "220 Ok\r\n" # Greet the client
- "250 Ok\r\n" # Respond to HELO
- "250 Ok\r\n" # Respond to MAIL FROM
- "250 Ok\r\n" # Respond to RCPT TO
- "354 Ok\r\n" # Respond to DATA
- )
-
- # Now the client is producing data to the server. Any time
- # resumeProducing is called on the producer, the timeout should be
- # extended. First, a sanity check. This test is only written to
- # handle pull producers.
- self.assertNotIdentical(transport.producer, None)
- self.assertFalse(transport.streaming)
-
- # Now, allow 2 seconds (1 less than the timeout of 3 seconds) to
- # elapse.
- clock.advance(2)
-
- # The timeout has not expired, so the failure should not have happened.
- self.assertEqual(failed, [])
-
- # Let some bytes be produced, extending the timeout. Then advance the
- # clock some more and verify that the timeout still hasn't happened.
- transport.producer.resumeProducing()
- clock.advance(2)
- self.assertEqual(failed, [])
-
- # The file has been completely produced - the next resume producing
- # finishes the upload, successfully.
- transport.producer.resumeProducing()
- client.dataReceived("250 Ok\r\n")
- self.assertEqual(failed, [])
-
- # Verify that the client actually did send the things expected.
- self.assertEqual(
- transport.value(),
- "HELO example.org\r\n"
- "MAIL FROM:<source@address>\r\n"
- "RCPT TO:<recipient@address>\r\n"
- "DATA\r\n"
- "x\r\n"
- ".\r\n"
- # This RSET is just an implementation detail. It's nice, but this
- # test doesn't really care about it.
- "RSET\r\n")
-
-
-
-class MultipleDeliveryFactorySMTPServerFactory(protocol.ServerFactory):
- """
- L{MultipleDeliveryFactorySMTPServerFactory} creates SMTP server protocol
- instances with message delivery factory objects supplied to it. Each
- factory is used for one connection and then discarded. Factories are used
- in the order they are supplied.
- """
- def __init__(self, messageFactories):
- self._messageFactories = messageFactories
-
-
- def buildProtocol(self, addr):
- p = protocol.ServerFactory.buildProtocol(self, addr)
- p.delivery = SimpleDelivery(self._messageFactories.pop(0))
- return p
-
-
-
-class SMTPSenderFactoryRetryTestCase(unittest.TestCase):
- """
- Tests for the retry behavior of L{smtp.SMTPSenderFactory}.
- """
- def test_retryAfterDisconnect(self):
- """
- If the protocol created by L{SMTPSenderFactory} loses its connection
- before receiving confirmation of message delivery, it reconnects and
- tries to deliver the message again.
- """
- recipient = 'alice'
- message = "some message text"
- domain = DummyDomain([recipient])
-
- class CleanSMTP(smtp.SMTP):
- """
- An SMTP subclass which ensures that its transport will be
- disconnected before the test ends.
- """
- def makeConnection(innerSelf, transport):
- self.addCleanup(transport.loseConnection)
- smtp.SMTP.makeConnection(innerSelf, transport)
-
- # Create a server which will fail the first message deliver attempt to
- # it with a 500 and a disconnect, but which will accept a message
- # delivered over the 2nd connection to it.
- serverFactory = MultipleDeliveryFactorySMTPServerFactory([
- BrokenMessage,
- lambda user: DummyMessage(domain, user)])
- serverFactory.protocol = CleanSMTP
- serverPort = reactor.listenTCP(0, serverFactory, interface='127.0.0.1')
- serverHost = serverPort.getHost()
- self.addCleanup(serverPort.stopListening)
-
- # Set up a client to try to deliver a message to the above created
- # server.
- sentDeferred = defer.Deferred()
- clientFactory = smtp.SMTPSenderFactory(
- "bob@example.org", recipient + "@example.com",
- StringIO(message), sentDeferred)
- clientFactory.domain = "example.org"
- clientConnector = reactor.connectTCP(
- serverHost.host, serverHost.port, clientFactory)
- self.addCleanup(clientConnector.disconnect)
-
- def cbSent(ignored):
- """
- Verify that the message was successfully delivered and flush the
- error which caused the first attempt to fail.
- """
- self.assertEqual(
- domain.messages,
- {recipient: ["\n%s\n" % (message,)]})
- # Flush the RuntimeError that BrokenMessage caused to be logged.
- self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
- sentDeferred.addCallback(cbSent)
- return sentDeferred
-
-
-
-class SingletonRealm(object):
- """
- Trivial realm implementation which is constructed with an interface and an
- avatar and returns that avatar when asked for that interface.
- """
- implements(IRealm)
-
- def __init__(self, interface, avatar):
- self.interface = interface
- self.avatar = avatar
-
-
- def requestAvatar(self, avatarId, mind, *interfaces):
- for iface in interfaces:
- if iface is self.interface:
- return iface, self.avatar, lambda: None
-
-
-
-class NotImplementedDelivery(object):
- """
- Non-implementation of L{smtp.IMessageDelivery} which only has methods which
- raise L{NotImplementedError}. Subclassed by various tests to provide the
- particular behavior being tested.
- """
- def validateFrom(self, helo, origin):
- raise NotImplementedError("This oughtn't be called in the course of this test.")
-
-
- def validateTo(self, user):
- raise NotImplementedError("This oughtn't be called in the course of this test.")
-
-
- def receivedHeader(self, helo, origin, recipients):
- raise NotImplementedError("This oughtn't be called in the course of this test.")
-
-
-
-class SMTPServerTestCase(unittest.TestCase):
- """
- Test various behaviors of L{twisted.mail.smtp.SMTP} and
- L{twisted.mail.smtp.ESMTP}.
- """
- def testSMTPGreetingHost(self, serverClass=smtp.SMTP):
- """
- Test that the specified hostname shows up in the SMTP server's
- greeting.
- """
- s = serverClass()
- s.host = "example.com"
- t = StringTransport()
- s.makeConnection(t)
- s.connectionLost(error.ConnectionDone())
- self.assertIn("example.com", t.value())
-
-
- def testSMTPGreetingNotExtended(self):
- """
- Test that the string "ESMTP" does not appear in the SMTP server's
- greeting since that string strongly suggests the presence of support
- for various SMTP extensions which are not supported by L{smtp.SMTP}.
- """
- s = smtp.SMTP()
- t = StringTransport()
- s.makeConnection(t)
- s.connectionLost(error.ConnectionDone())
- self.assertNotIn("ESMTP", t.value())
-
-
- def testESMTPGreetingHost(self):
- """
- Similar to testSMTPGreetingHost, but for the L{smtp.ESMTP} class.
- """
- self.testSMTPGreetingHost(smtp.ESMTP)
-
-
- def testESMTPGreetingExtended(self):
- """
- Test that the string "ESMTP" does appear in the ESMTP server's
- greeting since L{smtp.ESMTP} does support the SMTP extensions which
- that advertises to the client.
- """
- s = smtp.ESMTP()
- t = StringTransport()
- s.makeConnection(t)
- s.connectionLost(error.ConnectionDone())
- self.assertIn("ESMTP", t.value())
-
-
- def test_acceptSenderAddress(self):
- """
- Test that a C{MAIL FROM} command with an acceptable address is
- responded to with the correct success code.
- """
- class AcceptanceDelivery(NotImplementedDelivery):
- """
- Delivery object which accepts all senders as valid.
- """
- def validateFrom(self, helo, origin):
- return origin
-
- realm = SingletonRealm(smtp.IMessageDelivery, AcceptanceDelivery())
- portal = Portal(realm, [AllowAnonymousAccess()])
- proto = smtp.SMTP()
- proto.portal = portal
- trans = StringTransport()
- proto.makeConnection(trans)
-
- # Deal with the necessary preliminaries
- proto.dataReceived('HELO example.com\r\n')
- trans.clear()
-
- # Try to specify our sender address
- proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
-
- # Clean up the protocol before doing anything that might raise an
- # exception.
- proto.connectionLost(error.ConnectionLost())
-
- # Make sure that we received exactly the correct response
- self.assertEqual(
- trans.value(),
- '250 Sender address accepted\r\n')
-
-
- def test_deliveryRejectedSenderAddress(self):
- """
- Test that a C{MAIL FROM} command with an address rejected by a
- L{smtp.IMessageDelivery} instance is responded to with the correct
- error code.
- """
- class RejectionDelivery(NotImplementedDelivery):
- """
- Delivery object which rejects all senders as invalid.
- """
- def validateFrom(self, helo, origin):
- raise smtp.SMTPBadSender(origin)
-
- realm = SingletonRealm(smtp.IMessageDelivery, RejectionDelivery())
- portal = Portal(realm, [AllowAnonymousAccess()])
- proto = smtp.SMTP()
- proto.portal = portal
- trans = StringTransport()
- proto.makeConnection(trans)
-
- # Deal with the necessary preliminaries
- proto.dataReceived('HELO example.com\r\n')
- trans.clear()
-
- # Try to specify our sender address
- proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
-
- # Clean up the protocol before doing anything that might raise an
- # exception.
- proto.connectionLost(error.ConnectionLost())
-
- # Make sure that we received exactly the correct response
- self.assertEqual(
- trans.value(),
- '550 Cannot receive from specified address '
- '<alice@example.com>: Sender not acceptable\r\n')
-
-
- def test_portalRejectedSenderAddress(self):
- """
- Test that a C{MAIL FROM} command with an address rejected by an
- L{smtp.SMTP} instance's portal is responded to with the correct error
- code.
- """
- class DisallowAnonymousAccess(object):
- """
- Checker for L{IAnonymous} which rejects authentication attempts.
- """
- implements(ICredentialsChecker)
-
- credentialInterfaces = (IAnonymous,)
-
- def requestAvatarId(self, credentials):
- return defer.fail(UnauthorizedLogin())
-
- realm = SingletonRealm(smtp.IMessageDelivery, NotImplementedDelivery())
- portal = Portal(realm, [DisallowAnonymousAccess()])
- proto = smtp.SMTP()
- proto.portal = portal
- trans = StringTransport()
- proto.makeConnection(trans)
-
- # Deal with the necessary preliminaries
- proto.dataReceived('HELO example.com\r\n')
- trans.clear()
-
- # Try to specify our sender address
- proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
-
- # Clean up the protocol before doing anything that might raise an
- # exception.
- proto.connectionLost(error.ConnectionLost())
-
- # Make sure that we received exactly the correct response
- self.assertEqual(
- trans.value(),
- '550 Cannot receive from specified address '
- '<alice@example.com>: Sender not acceptable\r\n')
-
-
- def test_portalRejectedAnonymousSender(self):
- """
- Test that a C{MAIL FROM} command issued without first authenticating
- when a portal has been configured to disallow anonymous logins is
- responded to with the correct error code.
- """
- realm = SingletonRealm(smtp.IMessageDelivery, NotImplementedDelivery())
- portal = Portal(realm, [])
- proto = smtp.SMTP()
- proto.portal = portal
- trans = StringTransport()
- proto.makeConnection(trans)
-
- # Deal with the necessary preliminaries
- proto.dataReceived('HELO example.com\r\n')
- trans.clear()
-
- # Try to specify our sender address
- proto.dataReceived('MAIL FROM:<alice@example.com>\r\n')
-
- # Clean up the protocol before doing anything that might raise an
- # exception.
- proto.connectionLost(error.ConnectionLost())
-
- # Make sure that we received exactly the correct response
- self.assertEqual(
- trans.value(),
- '550 Cannot receive from specified address '
- '<alice@example.com>: Unauthenticated senders not allowed\r\n')
-
-
-
-class ESMTPAuthenticationTestCase(unittest.TestCase):
- def assertServerResponse(self, bytes, response):
- """
- Assert that when the given bytes are delivered to the ESMTP server
- instance, it responds with the indicated lines.
-
- @type bytes: str
- @type response: list of str
- """
- self.transport.clear()
- self.server.dataReceived(bytes)
- self.assertEqual(
- response,
- self.transport.value().splitlines())
-
-
- def assertServerAuthenticated(self, loginArgs, username="username", password="password"):
- """
- Assert that a login attempt has been made, that the credentials and
- interfaces passed to it are correct, and that when the login request
- is satisfied, a successful response is sent by the ESMTP server
- instance.
-
- @param loginArgs: A C{list} previously passed to L{portalFactory}.
- """
- d, credentials, mind, interfaces = loginArgs.pop()
- self.assertEqual(loginArgs, [])
- self.failUnless(twisted.cred.credentials.IUsernamePassword.providedBy(credentials))
- self.assertEqual(credentials.username, username)
- self.failUnless(credentials.checkPassword(password))
- self.assertIn(smtp.IMessageDeliveryFactory, interfaces)
- self.assertIn(smtp.IMessageDelivery, interfaces)
- d.callback((smtp.IMessageDeliveryFactory, None, lambda: None))
-
- self.assertEqual(
- ["235 Authentication successful."],
- self.transport.value().splitlines())
-
-
- def setUp(self):
- """
- Create an ESMTP instance attached to a StringTransport.
- """
- self.server = smtp.ESMTP({
- 'LOGIN': imap4.LOGINCredentials})
- self.server.host = 'localhost'
- self.transport = StringTransport(
- peerAddress=address.IPv4Address('TCP', '127.0.0.1', 12345))
- self.server.makeConnection(self.transport)
-
-
- def tearDown(self):
- """
- Disconnect the ESMTP instance to clean up its timeout DelayedCall.
- """
- self.server.connectionLost(error.ConnectionDone())
-
-
- def portalFactory(self, loginList):
- class DummyPortal:
- def login(self, credentials, mind, *interfaces):
- d = defer.Deferred()
- loginList.append((d, credentials, mind, interfaces))
- return d
- return DummyPortal()
-
-
- def test_authenticationCapabilityAdvertised(self):
- """
- Test that AUTH is advertised to clients which issue an EHLO command.
- """
- self.transport.clear()
- self.server.dataReceived('EHLO\r\n')
- responseLines = self.transport.value().splitlines()
- self.assertEqual(
- responseLines[0],
- "250-localhost Hello 127.0.0.1, nice to meet you")
- self.assertEqual(
- responseLines[1],
- "250 AUTH LOGIN")
- self.assertEqual(len(responseLines), 2)
-
-
- def test_plainAuthentication(self):
- """
- Test that the LOGIN authentication mechanism can be used
- """
- loginArgs = []
- self.server.portal = self.portalFactory(loginArgs)
-
- self.server.dataReceived('EHLO\r\n')
- self.transport.clear()
-
- self.assertServerResponse(
- 'AUTH LOGIN\r\n',
- ["334 " + "User Name\0".encode('base64').strip()])
-
- self.assertServerResponse(
- 'username'.encode('base64') + '\r\n',
- ["334 " + "Password\0".encode('base64').strip()])
-
- self.assertServerResponse(
- 'password'.encode('base64').strip() + '\r\n',
- [])
-
- self.assertServerAuthenticated(loginArgs)
-
-
- def test_plainAuthenticationEmptyPassword(self):
- """
- Test that giving an empty password for plain auth succeeds.
- """
- loginArgs = []
- self.server.portal = self.portalFactory(loginArgs)
-
- self.server.dataReceived('EHLO\r\n')
- self.transport.clear()
-
- self.assertServerResponse(
- 'AUTH LOGIN\r\n',
- ["334 " + "User Name\0".encode('base64').strip()])
-
- self.assertServerResponse(
- 'username'.encode('base64') + '\r\n',
- ["334 " + "Password\0".encode('base64').strip()])
-
- self.assertServerResponse('\r\n', [])
- self.assertServerAuthenticated(loginArgs, password='')
-
-
- def test_plainAuthenticationInitialResponse(self):
- """
- The response to the first challenge may be included on the AUTH command
- line. Test that this is also supported.
- """
- loginArgs = []
- self.server.portal = self.portalFactory(loginArgs)
-
- self.server.dataReceived('EHLO\r\n')
- self.transport.clear()
-
- self.assertServerResponse(
- 'AUTH LOGIN ' + "username".encode('base64').strip() + '\r\n',
- ["334 " + "Password\0".encode('base64').strip()])
-
- self.assertServerResponse(
- 'password'.encode('base64').strip() + '\r\n',
- [])
-
- self.assertServerAuthenticated(loginArgs)
-
-
- def test_abortAuthentication(self):
- """
- Test that a challenge/response sequence can be aborted by the client.
- """
- loginArgs = []
- self.server.portal = self.portalFactory(loginArgs)
-
- self.server.dataReceived('EHLO\r\n')
- self.server.dataReceived('AUTH LOGIN\r\n')
-
- self.assertServerResponse(
- '*\r\n',
- ['501 Authentication aborted'])
-
-
- def test_invalidBase64EncodedResponse(self):
- """
- Test that a response which is not properly Base64 encoded results in
- the appropriate error code.
- """
- loginArgs = []
- self.server.portal = self.portalFactory(loginArgs)
-
- self.server.dataReceived('EHLO\r\n')
- self.server.dataReceived('AUTH LOGIN\r\n')
-
- self.assertServerResponse(
- 'x\r\n',
- ['501 Syntax error in parameters or arguments'])
-
- self.assertEqual(loginArgs, [])
-
-
- def test_invalidBase64EncodedInitialResponse(self):
- """
- Like L{test_invalidBase64EncodedResponse} but for the case of an
- initial response included with the C{AUTH} command.
- """
- loginArgs = []
- self.server.portal = self.portalFactory(loginArgs)
-
- self.server.dataReceived('EHLO\r\n')
- self.assertServerResponse(
- 'AUTH LOGIN x\r\n',
- ['501 Syntax error in parameters or arguments'])
-
- self.assertEqual(loginArgs, [])
-
-
- def test_unexpectedLoginFailure(self):
- """
- If the L{Deferred} returned by L{Portal.login} fires with an
- exception of any type other than L{UnauthorizedLogin}, the exception
- is logged and the client is informed that the authentication attempt
- has failed.
- """
- loginArgs = []
- self.server.portal = self.portalFactory(loginArgs)
-
- self.server.dataReceived('EHLO\r\n')
- self.transport.clear()
-
- self.assertServerResponse(
- 'AUTH LOGIN ' + 'username'.encode('base64').strip() + '\r\n',
- ['334 ' + 'Password\0'.encode('base64').strip()])
- self.assertServerResponse(
- 'password'.encode('base64').strip() + '\r\n',
- [])
-
- d, credentials, mind, interfaces = loginArgs.pop()
- d.errback(RuntimeError("Something wrong with the server"))
-
- self.assertEqual(
- '451 Requested action aborted: local error in processing\r\n',
- self.transport.value())
-
- self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
-
-
-
-class SMTPClientErrorTestCase(unittest.TestCase):
- """
- Tests for L{smtp.SMTPClientError}.
- """
- def test_str(self):
- """
- The string representation of a L{SMTPClientError} instance includes
- the response code and response string.
- """
- err = smtp.SMTPClientError(123, "some text")
- self.assertEqual(str(err), "123 some text")
-
-
- def test_strWithNegativeCode(self):
- """
- If the response code supplied to L{SMTPClientError} is negative, it
- is excluded from the string representation.
- """
- err = smtp.SMTPClientError(-1, "foo bar")
- self.assertEqual(str(err), "foo bar")
-
-
- def test_strWithLog(self):
- """
- If a line log is supplied to L{SMTPClientError}, its contents are
- included in the string representation of the exception instance.
- """
- log = LineLog(10)
- log.append("testlog")
- log.append("secondline")
- err = smtp.SMTPClientError(100, "test error", log=log.str())
- self.assertEqual(
- str(err),
- "100 test error\n"
- "testlog\n"
- "secondline\n")
-
-
-
-class SenderMixinSentMailTests(unittest.TestCase):
- """
- Tests for L{smtp.SenderMixin.sentMail}, used in particular by
- L{smtp.SMTPSenderFactory} and L{smtp.ESMTPSenderFactory}.
- """
- def test_onlyLogFailedAddresses(self):
- """
- L{smtp.SenderMixin.sentMail} adds only the addresses with failing
- SMTP response codes to the log passed to the factory's errback.
- """
- onDone = self.assertFailure(defer.Deferred(), smtp.SMTPDeliveryError)
- onDone.addCallback(lambda e: self.assertEqual(
- e.log, "bob@example.com: 199 Error in sending.\n"))
-
- clientFactory = smtp.SMTPSenderFactory(
- 'source@address', 'recipient@address',
- StringIO("Message body"), onDone,
- retries=0, timeout=0.5)
-
- client = clientFactory.buildProtocol(
- address.IPv4Address('TCP', 'example.net', 25))
-
- addresses = [("alice@example.com", 200, "No errors here!"),
- ("bob@example.com", 199, "Error in sending.")]
- client.sentMail(199, "Test response", 1, addresses, client.log)
-
- return onDone
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/NEWS b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/NEWS
deleted file mode 100644
index a1468dec..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/NEWS
+++ /dev/null
@@ -1,309 +0,0 @@
-Ticket numbers in this file can be looked up by visiting
-http://twistedmatrix.com/trac/ticket/<number>
-
-Twisted Mail 12.2.0 (2012-08-26)
-================================
-
-Bugfixes
---------
- - twisted.mail.imap4.IMAP4Server will now generate an error response
- when it receives an illegal SEARCH term from a client. (#4080)
- - twisted.mail.imap4 now serves BODYSTRUCTURE responses which provide
- more information and conform to the IMAP4 RFC more closely. (#5763)
-
-Deprecations and Removals
--------------------------
- - twisted.mail.protocols.SSLContextFactory is now deprecated. (#4963)
- - The --passwordfile option to twistd mail is now removed. (#5541)
-
-Other
------
- - #5697, #5750, #5751, #5783
-
-
-Twisted Mail 12.1.0 (2012-06-02)
-================================
-
-Bugfixes
---------
- - twistd mail --auth, broken in 11.0, now correctly connects
- authentication to the portal being used (#5219)
-
-Other
------
- - #5686
-
-
-Twisted Mail 12.0.0 (2012-02-10)
-================================
-
-No significant changes have been made for this release.
-
-
-Twisted Mail 11.1.0 (2011-11-15)
-================================
-
-Features
---------
- - twisted.mail.smtp.LOGINCredentials now generates challenges with
- ":" instead of "\0" for interoperability with Microsoft Outlook.
- (#4692)
-
-Bugfixes
---------
- - When run from an unpacked source tarball or a VCS checkout,
- bin/mail/mailmail will now use the version of Twisted it is part
- of. (#3526)
-
-Other
------
- - #4796, #5006
-
-
-Twisted Mail 11.0.0 (2011-04-01)
-================================
-
-Features
---------
- - The `twistd mail` command line now accepts endpoint descriptions
- for POP3 and SMTP servers. (#4739)
- - The twistd mail plugin now accepts new authentication options via
- strcred.AuthOptionMixin. These include --auth, --auth-help, and
- authentication type-specific help options. (#4740)
-
-Bugfixes
---------
- - twisted.mail.imap4.IMAP4Server now generates INTERNALDATE strings
- which do not consider the locale. (#4937)
-
-Improved Documentation
-----------------------
- - Added a simple SMTP example, showing how to use sendmail. (#4042)
-
-Other
------
-
- - #4162
-
-
-Twisted Mail 10.2.0 (2010-11-29)
-================================
-
-Improved Documentation
-----------------------
- - The email server example now demonstrates how to set up
- authentication and authorization using twisted.cred. (#4609)
-
-Deprecations and Removals
--------------------------
- - twisted.mail.smtp.sendEmail, deprecated since mid 2003 (before
- Twisted 2.0), has been removed. (#4529)
-
-Other
------
- - #4038, #4572
-
-
-Twisted Mail 10.1.0 (2010-06-27)
-================================
-
-Bugfixes
---------
- - twisted.mail.imap4.IMAP4Server no longer fails on search queries
- that contain wildcards. (#2278)
- - A case which would cause twisted.mail.imap4.IMAP4Server to loop
- indefinitely when handling a search command has been fixed. (#4385)
-
-Other
------
- - #4069, #4271, #4467
-
-
-Twisted Mail 10.0.0 (2010-03-01)
-================================
-
-Bugfixes
---------
- - twisted.mail.smtp.ESMTPClient and
- twisted.mail.smtp.LOGINAuthenticator now implement the (obsolete)
- LOGIN SASL mechanism according to the draft specification. (#4031)
-
- - twisted.mail.imap4.IMAP4Client will no longer misparse all html-
- formatted message bodies received in response to a fetch command.
- (#4049)
-
- - The regression in IMAP4 search handling of "OR" and "NOT" terms has
- been fixed. (#4178)
-
-Other
------
- - #4028, #4170, #4200
-
-
-Twisted Mail 9.0.0 (2009-11-24)
-===============================
-
-Features
---------
- - maildir.StringListMailbox, an in-memory maildir mailbox, now supports
- deletion, undeletion, and syncing (#3547)
- - SMTPClient's callbacks are now more completely documented (#684)
-
-Fixes
------
- - Parse UNSEEN response data and include it in the result of
- IMAP4Client.examine (#3550)
- - The IMAP4 client now delivers more unsolicited server responses to callbacks
- rather than ignoring them, and also won't ignore solicited responses that
- arrive on the same line as an unsolicited one (#1105)
- - Several bugs in the SMTP client's idle timeout support were fixed (#3641,
- #1219)
- - A case where the SMTP client could skip some recipients when retrying
- delivery has been fixed (#3638)
- - Errors during certain data transfers will no longer be swallowed. They will
- now bubble up to the higher-level API (such as the sendmail function) (#3642)
- - Escape sequences inside quoted strings in IMAP4 should now be parsed
- correctly by the IMAP4 server protocol (#3659)
- - The "imap4-utf-7" codec that is registered by twisted.mail.imap4 had a number
- of fixes that allow it to work better with the Python codecs system, and to
- actually work (#3663)
- - The Maildir implementation now ensures time-based ordering of filenames so
- that the lexical sorting of messages matches the order in which they were
- received (#3812)
- - SASL PLAIN credentials generated by the IMAP4 protocol implementations
- (client and server) should now be RFC-compliant (#3939)
- - Searching for a set of sequences using the IMAP4 "SEARCH" command should
- now work on the IMAP4 server protocol implementation. This at least improves
- support for the Pine mail client (#1977)
-
-Other
------
- - #2763, #3647, #3750, #3819, #3540, #3846, #2023, #4050
-
-
-Mail 8.2.0 (2008-12-16)
-=======================
-
-Fixes
------
- - The mailmail tool now provides better error messages for usage errors (#3339)
- - The SMTP protocol implementation now works on PyPy (#2976)
-
-Other
------
- - #3475
-
-
-8.1.0 (2008-05-18)
-==================
-
-Fixes
------
- - The deprecated mktap API is no longer used (#3127)
-
-
-8.0.0 (2008-03-17)
-==================
-
-Features
---------
- - Support CAPABILITY responses that include atoms of the form "FOO" and
- "FOO=BAR" in IMAP4 (#2695)
- - Parameterize error handling behavior of imap4.encoder and imap4.decoder.
- (#2929)
-
-Fixes
------
- - Handle empty passwords in SMTP auth. (#2521)
- - Fix IMAP4Client's parsing of literals which are not preceeded by whitespace.
- (#2700)
- - Handle MX lookup suceeding without answers. (#2807)
- - Fix issues with aliases(5) process support. (#2729)
-
-Misc
-----
- - #2371, #2123, #2378, #739, #2640, #2746, #1917, #2266, #2864, #2832, #2063,
- #2865, #2847
-
-
-0.4.0 (2007-01-06)
-==================
-
-Features
---------
- - Plaintext POP3 logins are now possible over SSL or TLS (#1809)
-
-Fixes
------
- - ESMTP servers now greet with an "ESMTP" string (#1891)
- - The POP3 client can now correctly deal with concurrent POP3
- retrievals (#1988, #1691)
- - In the IMAP4 server, a bug involving retrieving the first part
- of a single-part message was fixed. This improves compatibility
- with Pine (#1978)
- - A bug in the IMAP4 server which caused corruption under heavy
- pipelining was fixed (#1992)
- - More strict support for the AUTH command was added to the SMTP
- server, to support the AUTH <mechanism>
- <initial-authentication-data> form of the command (#1552)
- - An SMTP bug involving the interaction with validateFrom, which
- caused multiple conflicting SMTP messages to be sent over the wire,
- was fixed (#2158)
-
-Misc
-----
- - #1648, #1801, #1636, #2003, #1936, #1202, #2051, #2072, #2248, #2250
-
-0.3.0 (2006-05-21)
-==================
-
-Features
---------
- - Support Deferred results from POP3's IMailbox.listMessages (#1701).
-
-Fixes
------
- - Quote usernames and passwords automatically in the IMAP client (#1411).
- - Improved parsing of literals in IMAP4 client and server (#1417).
- - Recognize unsolicted FLAGS response in IMAP4 client (#1105).
- - Parse and respond to requests with multiple BODY arguments in IMAP4
- server (#1307).
- - Misc: #1356, #1290, #1602
-
-0.2.0:
- - SMTP server:
- - Now gives application-level code opportunity to set a different
- Received header for each recipient of a multi-recipient message.
- - IMAP client:
- - New `startTLS' method to allow explicit negotiation of transport
- security.
-- POP client:
- - Support for per-command timeouts
- - New `startTLS' method, similar to the one added to the IMAP
- client.
- - NOOP, RSET, and STAT support added
-- POP server:
- - Bug handling passwords of "" fixed
-
-
-0.1.0:
- - Tons of bugfixes in IMAP4, POP3, and SMTP protocols
- - Maildir append support
- - Brand new, improved POP3 client (twisted.mail.pop3.AdvancedPOP3Client)
- - Deprecated the old POP3 client (twisted.mail.pop3.POP3Client)
- - SMTP client:
- - Support SMTP AUTH
- - Allow user to supply SSL context
- - Improved error handling, via new exception classes and an overridable
- hook to customize handling.
- - Order to try the authenication schemes is user-definable.
- - Timeout support.
- - SMTP server:
- - Properly understand <> sender.
- - Parameterize remote port
- - IMAP4:
- - LOGIN authentication compatibility improved
- - Improved unicode mailbox support
- - Fix parsing/handling of "FETCH BODY[HEADER]"
- - Many many quoting fixes
- - Timeout support on client
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/README b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/README
deleted file mode 100644
index b302614f..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/mail/topfiles/README
+++ /dev/null
@@ -1,6 +0,0 @@
-Twisted Mail 12.2.0
-
-Twisted Mail depends on Twisted Core and (sometimes) Twisted Names. For TLS
-support, pyOpenSSL (<http://launchpad.net/pyopenssl>) is also required. Aside
-from protocol implementations, much of Twisted Mail also only runs on POSIX
-platforms.