zope.sendmail-3.7.5/0000775000175000017500000000000011757162772014517 5ustar pangolinpangolinzope.sendmail-3.7.5/src/0000775000175000017500000000000011757162772015306 5ustar pangolinpangolinzope.sendmail-3.7.5/src/zope/0000775000175000017500000000000011757162772016263 5ustar pangolinpangolinzope.sendmail-3.7.5/src/zope/sendmail/0000775000175000017500000000000011757162772020057 5ustar pangolinpangolinzope.sendmail-3.7.5/src/zope/sendmail/delivery.py0000664000175000017500000001035211757162701022245 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mail Delivery utility implementation This module contains various implementations of `MailDeliverys`. $Id: delivery.py 108107 2010-01-13 13:32:26Z kobold $ """ __docformat__ = 'restructuredtext' import os import rfc822 from cStringIO import StringIO from random import randrange from time import strftime from socket import gethostname from zope.interface import implements from zope.sendmail.interfaces import IDirectMailDelivery, IQueuedMailDelivery from zope.sendmail.maildir import Maildir from transaction.interfaces import IDataManager import transaction # BBB: this import is needed for backward compatibility with older versions of # zope.sendmail which defined QueueProcessorThread in this module from zope.sendmail.queue import QueueProcessorThread class MailDataManager(object): implements(IDataManager) def __init__(self, callable, args=(), onAbort=None): self.callable = callable self.args = args self.onAbort = onAbort # Use the default thread transaction manager. self.transaction_manager = transaction.manager def commit(self, transaction): pass def abort(self, transaction): if self.onAbort: self.onAbort() def sortKey(self): return id(self) # No subtransaction support. def abort_sub(self, transaction): pass commit_sub = abort_sub def beforeCompletion(self, transaction): pass afterCompletion = beforeCompletion def tpc_begin(self, transaction, subtransaction=False): assert not subtransaction def tpc_vote(self, transaction): pass def tpc_finish(self, transaction): self.callable(*self.args) tpc_abort = abort class AbstractMailDelivery(object): def newMessageId(self): """Generates a new message ID according to RFC 2822 rules""" randmax = 0x7fffffff left_part = '%s.%d.%d' % (strftime('%Y%m%d%H%M%S'), os.getpid(), randrange(0, randmax)) return "%s@%s" % (left_part, gethostname()) def send(self, fromaddr, toaddrs, message): parser = rfc822.Message(StringIO(message)) messageid = parser.getheader('Message-Id') if messageid: if not messageid.startswith('<') or not messageid.endswith('>'): raise ValueError('Malformed Message-Id header') messageid = messageid[1:-1] else: messageid = self.newMessageId() message = 'Message-Id: <%s>\n%s' % (messageid, message) transaction.get().join( self.createDataManager(fromaddr, toaddrs, message)) return messageid class DirectMailDelivery(AbstractMailDelivery): __doc__ = IDirectMailDelivery.__doc__ implements(IDirectMailDelivery) def __init__(self, mailer): self.mailer = mailer def createDataManager(self, fromaddr, toaddrs, message): return MailDataManager(self.mailer.send, args=(fromaddr, toaddrs, message)) class QueuedMailDelivery(AbstractMailDelivery): __doc__ = IQueuedMailDelivery.__doc__ implements(IQueuedMailDelivery) def __init__(self, queuePath): self._queuePath = queuePath queuePath = property(lambda self: self._queuePath) def createDataManager(self, fromaddr, toaddrs, message): maildir = Maildir(self.queuePath, True) msg = maildir.newMessage() msg.write('X-Zope-From: %s\n' % fromaddr) msg.write('X-Zope-To: %s\n' % ", ".join(toaddrs)) msg.write(message) msg.close() return MailDataManager(msg.commit, onAbort=msg.abort) zope.sendmail-3.7.5/src/zope/sendmail/maildir.py0000664000175000017500000001355011757162701022046 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Read/write access to `Maildir` folders. $Id: maildir.py 109588 2010-03-03 09:57:07Z kobold $ """ __docformat__ = 'restructuredtext' import os import errno import socket import time import random from zope.interface import implements, classProvides from zope.sendmail.interfaces import \ IMaildirFactory, IMaildir, IMaildirMessageWriter class Maildir(object): """See `zope.sendmail.interfaces.IMaildir`""" classProvides(IMaildirFactory) implements(IMaildir) def __init__(self, path, create=False): "See `zope.sendmail.interfaces.IMaildirFactory`" self.path = path def access(path): return os.access(path, os.F_OK) subdir_cur = os.path.join(path, 'cur') subdir_new = os.path.join(path, 'new') subdir_tmp = os.path.join(path, 'tmp') if create and not access(path): os.mkdir(path) os.mkdir(subdir_cur) os.mkdir(subdir_new) os.mkdir(subdir_tmp) maildir = True else: maildir = (os.path.isdir(subdir_cur) and os.path.isdir(subdir_new) and os.path.isdir(subdir_tmp)) if not maildir: raise ValueError('%s is not a Maildir folder' % path) def __iter__(self): "See `zope.sendmail.interfaces.IMaildir`" join = os.path.join subdir_cur = join(self.path, 'cur') subdir_new = join(self.path, 'new') # http://www.qmail.org/man/man5/maildir.html says: # "It is a good idea for readers to skip all filenames in new # and cur starting with a dot. Other than this, readers # should not attempt to parse filenames." new_messages = [join(subdir_new, x) for x in os.listdir(subdir_new) if not x.startswith('.')] cur_messages = [join(subdir_cur, x) for x in os.listdir(subdir_cur) if not x.startswith('.')] # Sort by modification time so earlier messages are sent before # later messages during queue processing. msgs_sorted = [(m, os.path.getmtime(m)) for m in new_messages + cur_messages] msgs_sorted.sort(key=lambda x: x[1]) return iter([m[0] for m in msgs_sorted]) def newMessage(self): "See `zope.sendmail.interfaces.IMaildir`" # NOTE: http://www.qmail.org/man/man5/maildir.html says, that the first # step of the delivery process should be a chdir. Chdirs and # threading do not mix. Is that chdir really necessary? join = os.path.join subdir_tmp = join(self.path, 'tmp') subdir_new = join(self.path, 'new') pid = os.getpid() host = socket.gethostname() randmax = 0x7fffffff counter = 0 while True: timestamp = int(time.time()) unique = '%d.%d.%s.%d' % (timestamp, pid, host, random.randrange(randmax)) filename = join(subdir_tmp, unique) try: fd = os.open(filename, os.O_CREAT|os.O_EXCL|os.O_WRONLY, 0600) except OSError, e: if e.errno != errno.EEXIST: raise # File exists counter += 1 if counter >= 1000: raise RuntimeError("Failed to create unique file name" " in %s, are we under a DoS attack?" % subdir_tmp) # NOTE: maildir.html (see above) says I should sleep for 2 time.sleep(0.1) else: break return MaildirMessageWriter(os.fdopen(fd, 'w'), filename, join(subdir_new, unique)) def _encode_utf8(s): if isinstance(s, unicode): s = s.encode('utf-8') return s class MaildirMessageWriter(object): """See `zope.sendmail.interfaces.IMaildirMessageWriter`""" implements(IMaildirMessageWriter) def __init__(self, fd, filename, new_filename): self._filename = filename self._new_filename = new_filename self._fd = fd self._finished = False self._aborted = False def write(self, data): self._fd.write(_encode_utf8(data)) def writelines(self, lines): lines = map(_encode_utf8, lines) self._fd.writelines(lines) def close(self): self._fd.close() def commit(self): if self._aborted: raise RuntimeError('Cannot commit, message already aborted') elif not self._finished: self._finished = True self._fd.close() os.rename(self._filename, self._new_filename) # NOTE: the same maildir.html says it should be a link, followed by # unlink. But Win32 does not necessarily have hardlinks! def abort(self): # XXX mgedmin: I think it is dangerous to have an abort() that does # nothing when commit() already succeeded. But the tests currently # test that expectation. if not self._finished: self._finished = True self._aborted = True self._fd.close() os.unlink(self._filename) # XXX: should there be a __del__ that calls abort()? zope.sendmail-3.7.5/src/zope/sendmail/queue.py0000664000175000017500000004740211757162701021554 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Queue processor thread This module contains the queue processor thread. $Id: queue.py 126451 2012-05-23 12:23:18Z tseaver $ """ __docformat__ = 'restructuredtext' import atexit import ConfigParser import logging import os import smtplib import stat import threading import time from zope.sendmail.maildir import Maildir from zope.sendmail.mailer import SMTPMailer import sys if sys.platform == 'win32': import win32file _os_link = lambda src, dst: win32file.CreateHardLink(dst, src, None) else: _os_link = os.link # The longest time sending a file is expected to take. Longer than this and # the send attempt will be assumed to have failed. This means that sending # very large files or using very slow mail servers could result in duplicate # messages sent. MAX_SEND_TIME = 60*60*3 # The below diagram depicts the operations performed while sending a message in # the ``run`` method of ``QueueProcessorThread``. This sequence of operations # will be performed for each file in the maildir each time the thread "wakes # up" to send messages. # # Any error conditions not depected on the diagram will provoke the catch-all # exception logging of the ``run`` method. # # In the diagram the "message file" is the file in the maildir's "cur" directory # that contains the message and "tmp file" is a hard link to the message file # created in the maildir's "tmp" directory. # # ( start trying to deliver a message ) # | # | # V # +-----( get tmp file mtime ) # | | # | | file exists # | V # | ( check age )-----------------------------+ # tmp file | | file is new | # does not | | file is old | # exist | | | # | ( unlink tmp file )-----------------------+ | # | | file does | | # | | file unlinked not exist | | # | V | | # +---->( touch message file )------------------+ | | # | file does | | | # | not exist | | | # V | | | # ( link message file to tmp file )----------+ | | | # | tmp file | | | | # | already exists | | | | # | | | | | # V V V V V # ( send message ) ( skip this message ) # | # V # ( unlink message file )---------+ # | | # | file unlinked | file no longer exists # | | # | +-----------------+ # | | # | V # ( unlink tmp file )------------+ # | | # | file unlinked | file no longer exists # V | # ( message delivered )<---------+ class QueueProcessorThread(threading.Thread): """This thread is started at configuration time from the `mail:queuedDelivery` directive handler if processorThread is True. """ log = logging.getLogger("QueueProcessorThread") _stopped = False interval = 3.0 # process queue every X second def __init__(self, interval=3.0): threading.Thread.__init__(self) self.interval = interval self._lock = threading.Lock() self.setDaemon(True) def setMaildir(self, maildir): """Set the maildir. This method is used just to provide a `maildir` stubs .""" self.maildir = maildir def setQueuePath(self, path): self.maildir = Maildir(path, True) def setMailer(self, mailer): self.mailer = mailer def _parseMessage(self, message): """Extract fromaddr and toaddrs from the first two lines of the `message`. Returns a fromaddr string, a toaddrs tuple and the message string. """ fromaddr = "" toaddrs = () rest = "" try: first, second, rest = message.split('\n', 2) except ValueError: return fromaddr, toaddrs, message if first.startswith("X-Zope-From: "): i = len("X-Zope-From: ") fromaddr = first[i:] if second.startswith("X-Zope-To: "): i = len("X-Zope-To: ") toaddrs = tuple(second[i:].split(", ")) return fromaddr, toaddrs, rest def run(self, forever=True): atexit.register(self.stop) while not self._stopped: for filename in self.maildir: # if we are asked to stop while sending messages, do so if self._stopped: break fromaddr = '' toaddrs = () head, tail = os.path.split(filename) tmp_filename = os.path.join(head, '.sending-' + tail) rejected_filename = os.path.join(head, '.rejected-' + tail) try: # perform a series of operations in an attempt to ensure # that no two threads/processes send this message # simultaneously as well as attempting to not generate # spurious failure messages in the log; a diagram that # represents these operations is included in a # comment above this class try: # find the age of the tmp file (if it exists) age = None mtime = os.stat(tmp_filename)[stat.ST_MTIME] age = time.time() - mtime except OSError, e: if e.errno == 2: # file does not exist # the tmp file could not be stated because it # doesn't exist, that's fine, keep going pass else: # the tmp file could not be stated for some reason # other than not existing; we'll report the error raise # if the tmp file exists, check it's age if age is not None: try: if age > MAX_SEND_TIME: # the tmp file is "too old"; this suggests # that during an attemt to send it, the # process died; remove the tmp file so we # can try again os.unlink(tmp_filename) else: # the tmp file is "new", so someone else may # be sending this message, try again later continue # if we get here, the file existed, but was too # old, so it was unlinked except OSError, e: if e.errno == 2: # file does not exist # it looks like someone else removed the tmp # file, that's fine, we'll try to deliver the # message again later continue # now we know that the tmp file doesn't exist, we need to # "touch" the message before we create the tmp file so the # mtime will reflect the fact that the file is being # processed (there is a race here, but it's OK for two or # more processes to touch the file "simultaneously") try: os.utime(filename, None) except OSError, e: if e.errno == 2: # file does not exist # someone removed the message before we could # touch it, no need to complain, we'll just keep # going continue # creating this hard link will fail if another process is # also sending this message try: #os.link(filename, tmp_filename) _os_link(filename, tmp_filename) except OSError, e: if e.errno == 17: # file exists, *nix # it looks like someone else is sending this # message too; we'll try again later continue except Exception, e: if e[0] == 183 and e[1] == 'CreateHardLink': # file exists, win32 continue # read message file and send contents file = open(filename) message = file.read() file.close() fromaddr, toaddrs, message = self._parseMessage(message) # The next block is the only one that is sensitive to # interruptions. Everywhere else, if this daemon thread # stops, we should be able to correctly handle a restart. # In this block, if we send the message, but we are # stopped before we unlink the file, we will resend the # message when we are restarted. We limit the likelihood # of this somewhat by using a lock to link the two # operations. When the process gets an interrupt, it # will call the atexit that we registered (``stop`` # below). This will try to get the same lock before it # lets go. Because this can cause the daemon thread to # continue (that is, to not act like a daemon thread), we # still use the _stopped flag to communicate. self._lock.acquire() try: try: self.mailer.send(fromaddr, toaddrs, message) except smtplib.SMTPResponseException, e: if 500 <= e.smtp_code <= 599: # permanent error, ditch the message self.log.error( "Discarding email from %s to %s due to" " a permanent error: %s", fromaddr, ", ".join(toaddrs), str(e)) #os.link(filename, rejected_filename) _os_link(filename, rejected_filename) else: # Log an error and retry later raise except smtplib.SMTPRecipientsRefused, e: # All recipients are refused by smtp # server. Dont try to redeliver the message. self.log.error("Email recipients refused: %s", ', '.join(e.recipients)) _os_link(filename, rejected_filename) try: os.unlink(filename) except OSError, e: if e.errno == 2: # file does not exist # someone else unlinked the file; oh well pass else: # something bad happend, log it raise finally: self._lock.release() try: os.unlink(tmp_filename) except OSError, e: if e.errno == 2: # file does not exist # someone else unlinked the file; oh well pass else: # something bad happend, log it raise # TODO: maybe log the Message-Id of the message sent self.log.info("Mail from %s to %s sent.", fromaddr, ", ".join(toaddrs)) # Blanket except because we don't want # this thread to ever die except: if fromaddr != '' or toaddrs != (): self.log.error( "Error while sending mail from %s to %s.", fromaddr, ", ".join(toaddrs), exc_info=True) else: self.log.error( "Error while sending mail : %s ", filename, exc_info=True) else: if forever: time.sleep(self.interval) # A testing plug if not forever: break def stop(self): self._stopped = True self._lock.acquire() self._lock.release() def boolean(s): s = str(s).lower() return s.startswith("t") or s.startswith("y") or s.startswith("1") def string_or_none(s): if s == 'None': return None return s class ConsoleApp(object): """Allows running of Queue Processor from the console.""" _usage = """%(script_name)s [OPTIONS] path/to/maildir OPTIONS: --daemon Run in daemon mode, periodically checking queue and sending messages. Default is to send all messages in queue once and exit. --interval <#secs> How often to check queue when in daemon mode. Default is 3 seconds. --hostname Name of smtp host to use for delivery. Default is localhost. --port Which port on smtp server to deliver mail to. Default is 25. --username Username to use to log in to smtp server. Default is none. --password Password to use to log in to smtp server. Must be specified if username is specified. --force-tls Do not connect if TLS is not available. Not enabled by default. --no-tls Do not use TLS even if is available. Not enabled by default. --config Get configuration from specificed ini file; it must contain a section [app:zope-sendmail]. """ _error = False daemon = False interval = 3 hostname = 'localhost' port = 25 username = None password = None force_tls = False no_tls = False queue_path = None def __init__(self, argv=sys.argv, verbose=True): self.script_name = argv[0] self.verbose = verbose self._process_args(argv[1:]) self.mailer = SMTPMailer(self.hostname, self.port, self.username, self.password, self.no_tls, self.force_tls) def main(self): if self._error: return queue = QueueProcessorThread(self.interval) queue.setMailer(self.mailer) queue.setQueuePath(self.queue_path) queue.run(forever=self.daemon) def _process_args(self, args): got_queue_path = False while args: arg = args.pop(0) if arg == "--daemon": self.daemon = True elif arg == "--interval": try: self.interval = float(args.pop(0)) except: self._error_usage() elif arg == "--hostname": if not args: self._error_usage() self.hostname = args.pop(0) elif arg == "--port": try: self.port = int(args.pop(0)) except: self._error_usage() elif arg == "--username": if not args: self._error_usage() self.username = args.pop(0) elif arg == "--password": if not args: self._error_usage() self.password = args.pop(0) elif arg == "--force-tls": self.force_tls = True elif arg == "--no-tls": self.no_tls = True elif arg == "--config": if not args: self._error_usage() self._load_config(args.pop(0)) elif arg.startswith("-") or got_queue_path: self._error_usage() else: self.queue_path = arg got_queue_path = True if not self.queue_path: self._error_usage() if (self.username or self.password) and \ not (self.username and self.password): if self.verbose: print >>sys.stderr, "Must use username and password together." self._error = True if self.force_tls and self.no_tls: if self.verbose: print >>sys.stderr, \ "--force-tls and --no-tls are mutually exclusive." self._error = True def _load_config(self, path): section = "app:zope-sendmail" names = [ "interval", "hostname", "port", "username", "password", "force_tls", "no_tls", "queue_path", ] defaults = dict([(name, str(getattr(self, name))) for name in names]) config = ConfigParser.ConfigParser(defaults) config.read(path) self.interval = float(config.get(section, "interval")) self.hostname = config.get(section, "hostname") self.port = int(config.get(section, "port")) self.username = string_or_none(config.get(section, "username")) self.password = string_or_none(config.get(section, "password")) self.force_tls = boolean(config.get(section, "force_tls")) self.no_tls = boolean(config.get(section, "no_tls")) self.queue_path = string_or_none(config.get(section, "queue_path")) def _error_usage(self): if self.verbose: print >>sys.stderr, self._usage % {"script_name": self.script_name,} self._error = True def run(): logging.basicConfig() app = ConsoleApp() app.main() if __name__ == "__main__": run_console() zope.sendmail-3.7.5/src/zope/sendmail/README.txt0000664000175000017500000001040711757162701021547 0ustar pangolinpangolinUsing zope.sendmail =================== This package is useful when your Zope 3 application wants to send email. It integrates with the transaction mechanism and queues your emails to be sent on successful commits only. API --- An application that wants to send an email can do so by getting the appropriate IMailDelivery utility. The standard library's email module is useful for formatting the message according to RFC-2822:: import email.MIMEText import email.Header from zope.sendmail.interfaces import IMailDelivery from zope.component import getUtility def send_email(sender, recipient, subject, body): msg = email.MIMEText.MIMEText(body.encode('UTF-8'), 'plain', 'UTF-8') msg["From"] = sender msg["To"] = recipient msg["Subject"] = email.Header.Header(subject, 'UTF-8') mailer = getUtility(IMailDelivery, 'my-app.mailer') mailer.send(sender, [recipient], msg.as_string()) In real-world code you may need to do extra work to format the 'From' and 'To' headers correctly, if the addresses contain a real-name part with non-ASCII characters. You can find a recipe for that in this blog post: http://mg.pov.lt/blog/unicode-emails-in-python.html Configuration ------------- The code above used a named IMailDelivery utility. It is your responsibility to define one, as Zope 3 doesn't provide one by default. You can define an IMailDelivery utility in your site.zcml with a configuration directive:: The ``mail:queuedDelivery`` directive stores every email in a queue (a standard Maildir folder on the file system in a given directory) and sends them from a background thread. There's an alternative directive, ``mail:directDelivery``, that sends them from the same thread. This may slow down transaction commits (especially if the SMTP server is slow to respond) and increase the loading time of web pages. Mailers ------- The ``mailer`` argument of the ``mail:queuedDelivery`` utility chooses the appropriate IMailer utility that will be used to deliver email. There are alternative ways of doing that, for example, SMTP or piping the message to an external program. Currently ``zope.sendmail`` supports only plain SMTP. [#]_ .. [#] There was once a mailer utility that invoked /usr/sbin/sendmail, but it had security issues related to the difficulty of quoting command-line arguments in a portable way. If the same system that runs your Zope 3 server also has an SMTP server on port 25, you can use the default ``smtp`` mailer. If you want to use a different SMTP server, define your own utility like this:: Testing ------- Obviously, you don't want your automated unit/functional test runs to send real emails. You'll have to define a fake email delivery utility in your test layer. Something like this will do the trick:: class FakeMailDelivery(object): implements(IMailDelivery) def send(self, source, dest, body): print "*** Sending email from %s to %s:" % (source, dest) print body return 'fake-message-id@example.com' Register it with the standard ``utility`` directive:: Problems with zope.sendmail --------------------------- * The API is a bit inconvenient to use (e.g. you have to do the message formatting by yourself). * The configuration should be done in zope.conf, not in ZCML. * The IMailSentEvent and IMailErrorEvent events aren't used and can't be used (you don't want to send emails during the commit phase). zope.sendmail-3.7.5/src/zope/sendmail/interfaces.py0000664000175000017500000002227611757162701022555 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mailer interfaces Email sending from Zope 3 applications works as follows: - A Zope 3 application locates a mail delivery utility (`IMailDelivery`) and feeds a message to it. It gets back a unique message ID so it can keep track of the message by subscribing to `IMailEvent` events. - The utility registers with the transaction system to make sure the message is only sent when the transaction commits successfully. (Among other things this avoids duplicate messages on `ConflictErrors`.) - If the delivery utility is a `IQueuedMailDelivery`, it puts the message into a queue (a Maildir mailbox in the file system). A separate process or thread (`IMailQueueProcessor`) watches the queue and delivers messages asynchronously. Since the queue is located in the file system, it survives Zope restarts or crashes and the mail is not lost. The queue processor can implement batching to keep the server load low. - If the delivery utility is a `IDirectMailDelivery`, it delivers messages synchronously during the transaction commit. This is not a very good idea, as it makes the user wait. Note that transaction commits must not fail, but that is not a problem, because mail delivery problems dispatch an event instead of raising an exception. However, there is a problem -- sending events causes unknown code to be executed during the transaction commit phase. There should be a way to start a new transaction for event processing after this one is commited. - An `IMailQueueProcessor` or `IDirectMailDelivery` actually delivers the messages by using a mailer (`IMailer`) component that encapsulates the delivery process. There currently is only one mailer: - `ISMTPMailer` sends all messages to a relay host using SMTP - If mail delivery succeeds, an `IMailSentEvent` is dispatched by the mailer. If mail delivery fails, no exceptions are raised, but an `IMailErrorEvent` is dispatched by the mailer. $Id: interfaces.py 79091 2007-08-21 18:35:51Z andreasjung $ """ __docformat__ = 'restructuredtext' from zope.interface import Interface, Attribute from zope.schema import TextLine, Int, Password, Bool from zope.i18nmessageid import MessageFactory _ = MessageFactory('zope') class IMailDelivery(Interface): """A mail delivery utility allows someone to send an email to a group of people.""" def send(fromaddr, toaddrs, message): """Send an email message. `fromaddr` is the sender address (byte string), `toaddrs` is a sequence of recipient addresses (byte strings). `message` is a byte string that contains both headers and body formatted according to RFC 2822. If it does not contain a Message-Id header, it will be generated and added automatically. Returns the message ID. You can subscribe to `IMailEvent` events for notification about problems or successful delivery. Messages are actually sent during transaction commit. """ class IDirectMailDelivery(IMailDelivery): """A mail delivery utility that delivers messages synchronously during transaction commit. Not useful for production use, but simpler to set up and use. """ mailer = Attribute("IMailer that is used for message delivery") class IQueuedMailDelivery(IMailDelivery): """A mail delivery utility that puts all messages into a queue in the filesystem. Messages will be delivered asynchronously by a separate component. """ queuePath = TextLine( title=_(u"Queue path"), description=_(u"Pathname of the directory used to queue mail.")) class IMailQueueProcessor(Interface): """A mail queue processor that delivers queueud messages asynchronously. """ queuePath = TextLine( title=_(u"Queue Path"), description=_(u"Pathname of the directory used to queue mail.")) pollingInterval = Int( title=_(u"Polling Interval"), description=_(u"How often the queue is checked for new messages" " (in milliseconds)"), default=5000) mailer = Attribute("IMailer that is used for message delivery") class IMailer(Interface): """Mailer handles synchronous mail delivery.""" def send(fromaddr, toaddrs, message): """Send an email message. `fromaddr` is the sender address (unicode string), `toaddrs` is a sequence of recipient addresses (unicode strings). `message` contains both headers and body formatted according to RFC 2822. It should contain at least Date, From, To, and Message-Id headers. Messages are sent immediatelly. Dispatches an `IMailSentEvent` on successful delivery, otherwise an `IMailErrorEvent`. """ class ISMTPMailer(IMailer): """A mailer that delivers mail to a relay host via SMTP.""" hostname = TextLine( title=_(u"Hostname"), description=_(u"Name of server to be used as SMTP server.")) port = Int( title=_(u"Port"), description=_(u"Port of SMTP service"), default=25) username = TextLine( title=_(u"Username"), description=_(u"Username used for optional SMTP authentication.")) password = Password( title=_(u"Password"), description=_(u"Password used for optional SMTP authentication.")) no_tls = Bool( title=_(u"No TLS"), description=_(u"Never use TLS for sending email.")) force_tls = Bool( title=_(u"Force TLS"), description=_(u"Use TLS always for sending email.")) class IMailEvent(Interface): """Generic mail event.""" messageId = Attribute("Message id according to RFC 2822") class IMailSentEvent(IMailEvent): """Event that is fired when a message is succesfully sent. This does not mean that all the recipients have received it, it only means that the message left this system successfully. It is possible that a bounce message will arrive later from some remote mail server. """ class IMailErrorEvent(IMailEvent): """Event that is fired when a message cannot be delivered.""" errorMessage = Attribute("Error message") class IMaildirFactory(Interface): def __call__(dirname, create=False): """Opens a `Maildir` folder at a given filesystem path. If `create` is ``True``, the folder will be created when it does not exist. If `create` is ``False`` and the folder does not exist, an exception (``OSError``) will be raised. If path points to a file or an existing directory that is not a valid `Maildir` folder, an exception is raised regardless of the `create` argument. """ class IMaildir(Interface): """Read/write access to `Maildir` folders. See http://www.qmail.org/man/man5/maildir.html for detailed format description. """ def __iter__(): """Returns an iterator over the pathnames of messages in this folder. """ def newMessage(): """Creates a new message in the `maildir`. Returns a file-like object for a new file in the ``tmp`` subdirectory of the `Maildir`. After writing message contents to it, call the ``commit()`` or ``abort()`` method on it. The returned object implements `IMaildirMessageWriter`. """ class IMaildirMessageWriter(Interface): """A file-like object to a new message in a `Maildir`.""" def write(str): """Writes a string to the file. There is no return value. Due to buffering, the string may not actually show up in the file until the ``commit()`` method is called. """ def writelines(sequence): """Writes a sequence of strings to the file. The sequence can be any iterable object producing strings, typically a list of strings. There is no return value. ``writelines`` does not add any line separators. """ def close(): """Closes the message file. No further writes are allowed. You can call ``close()`` before calling ``commit()`` or ``abort()`` to avoid having too many open files. Calling ``close()`` more than once is allowed. """ def commit(): """Commits the new message using the `Maildir` protocol. First, the message file is flushed, closed, then it is moved from ``tmp`` into ``new`` subdirectory of the maildir. Calling ``commit()`` more than once is allowed. """ def abort(): """Aborts the new message. The message file is closed and removed from the ``tmp`` subdirectory of the `maildir`. Calling ``abort()`` more than once is allowed. """ zope.sendmail-3.7.5/src/zope/sendmail/meta.zcml0000664000175000017500000000125111757162701021663 0ustar pangolinpangolin zope.sendmail-3.7.5/src/zope/sendmail/tests/0000775000175000017500000000000011757162772021221 5ustar pangolinpangolinzope.sendmail-3.7.5/src/zope/sendmail/tests/test_vocabulary.py0000664000175000017500000000207011757162701024770 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mail delivery names vocabulary test $Id: test_vocabulary.py 110390 2010-04-01 12:33:34Z mgedmin $ """ import unittest from doctest import DocTestSuite from zope.component.testing import setUp, tearDown def test_suite(): return unittest.TestSuite([ DocTestSuite('zope.sendmail.vocabulary', setUp=setUp, tearDown=tearDown), ]) if __name__ == '__main__': unittest.main(defaultTest='test_suite') zope.sendmail-3.7.5/src/zope/sendmail/tests/test_maildir.py0000664000175000017500000002567611757162701024263 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Unit tests for zope.sendmail.maildir module $Id: test_maildir.py 109588 2010-03-03 09:57:07Z kobold $ """ import unittest import stat import os import errno from zope.interface.verify import verifyObject class FakeSocketModule(object): def gethostname(self): return 'myhostname' class FakeTimeModule(object): _timer = 1234500000 def time(self): return self._timer def sleep(self, n): self._timer += n class FakeOsPathModule(object): def __init__(self, files, dirs): self.files = files self.dirs = dirs mtimes = {} for t,f in enumerate(files): mtimes[f] = 9999 - t self._mtimes = mtimes def join(self, *args): return '/'.join(args) def isdir(self, dir): return dir in self.dirs def getmtime(self, f): return self._mtimes.get(f, 10000) class FakeOsModule(object): F_OK = 0 O_CREAT = os.O_CREAT O_EXCL = os.O_EXCL O_WRONLY = os.O_WRONLY _stat_mode = { '/path/to/maildir': stat.S_IFDIR, '/path/to/maildir/new': stat.S_IFDIR, '/path/to/maildir/new/1': stat.S_IFREG, '/path/to/maildir/new/2': stat.S_IFREG, '/path/to/maildir/cur': stat.S_IFDIR, '/path/to/maildir/cur/1': stat.S_IFREG, '/path/to/maildir/cur/2': stat.S_IFREG, '/path/to/maildir/tmp': stat.S_IFDIR, '/path/to/maildir/tmp/1': stat.S_IFREG, '/path/to/maildir/tmp/2': stat.S_IFREG, '/path/to/maildir/tmp/1234500000.4242.myhostname.*': stat.S_IFREG, '/path/to/maildir/tmp/1234500001.4242.myhostname.*': stat.S_IFREG, '/path/to/regularfile': stat.S_IFREG, '/path/to/emptydirectory': stat.S_IFDIR, } _listdir = { '/path/to/maildir/new': ['1', '2', '.svn'], '/path/to/maildir/cur': ['2', '1', '.tmp'], '/path/to/maildir/tmp': ['1', '2', '.ignore'], } path = FakeOsPathModule(_stat_mode, _listdir) _made_directories = () _removed_files = () _renamed_files = () _all_files_exist = False def __init__(self): self._descriptors = {} def access(self, path, mode): if self._all_files_exist: return True if path in self._stat_mode: return True if path.rsplit('.', 1)[0] + '.*' in self._stat_mode: return True return False def stat(self, path): if path in self._stat_mode: return (self._stat_mode[path], 0, 0, 1, 0, 0, 0, 0, 0, 0) raise OSError('%s does not exist' % path) def listdir(self, path): return self._listdir.get(path, []) def mkdir(self, path): self._made_directories += (path, ) def getpid(self): return 4242 def unlink(self, path): self._removed_files += (path, ) def rename(self, old, new): self._renamed_files += ((old, new), ) def open(self, filename, flags, mode=0777): if (flags & os.O_EXCL and flags & os.O_CREAT and self.access(filename, 0)): raise OSError(errno.EEXIST, 'file already exists') if not flags & os.O_CREAT and not self.access(filename, 0): raise OSError('file not found') fd = max(self._descriptors.keys() + [2]) + 1 self._descriptors[fd] = filename, flags, mode return fd def fdopen(self, fd, mode='r'): try: filename, flags, permissions = self._descriptors[fd] except KeyError: raise AssertionError('os.fdopen() called with an unknown' ' file descriptor') if mode == 'r': assert not flags & os.O_WRONLY assert not flags & os.O_RDWR elif mode == 'w': assert flags & os.O_WRONLY assert not flags & os.O_RDWR elif mode == 'r+': assert not flags & os.O_WRONLY assert flags & os.O_RDWR else: raise AssertionError("don't know how to verify if flags match" " mode %r" % mode) return FakeFile(filename, mode) class FakeFile(object): def __init__(self, filename, mode): self._filename = filename self._mode = mode self._written = '' self._closed = False def close(self): self._closed = True def write(self, data): self._written += data def writelines(self, lines): self._written += ''.join(lines) class TestMaildir(unittest.TestCase): def setUp(self): import zope.sendmail.maildir as maildir_module self.maildir_module = maildir_module self.old_os_module = maildir_module.os self.old_time_module = maildir_module.time self.old_socket_module = maildir_module.socket maildir_module.os = self.fake_os_module = FakeOsModule() maildir_module.time = FakeTimeModule() maildir_module.socket = FakeSocketModule() def tearDown(self): self.maildir_module.os = self.old_os_module self.maildir_module.time = self.old_time_module self.maildir_module.socket = self.old_socket_module self.fake_os_module._stat_never_fails = False self.fake_os_module._all_files_exist = False def test_factory(self): from zope.sendmail.interfaces import IMaildirFactory, IMaildir from zope.sendmail.maildir import Maildir verifyObject(IMaildirFactory, Maildir) # Case 1: normal maildir m = Maildir('/path/to/maildir') verifyObject(IMaildir, m) # Case 2a: directory does not exist, create = False self.assertRaises(ValueError, Maildir, '/path/to/nosuchfolder', False) # Case 2b: directory does not exist, create = True m = Maildir('/path/to/nosuchfolder', True) verifyObject(IMaildir, m) dirs = list(self.fake_os_module._made_directories) dirs.sort() self.assertEquals(dirs, ['/path/to/nosuchfolder', '/path/to/nosuchfolder/cur', '/path/to/nosuchfolder/new', '/path/to/nosuchfolder/tmp']) # Case 3: it is a file, not a directory self.assertRaises(ValueError, Maildir, '/path/to/regularfile', False) self.assertRaises(ValueError, Maildir, '/path/to/regularfile', True) # Case 4: it is a directory, but not a maildir self.assertRaises(ValueError, Maildir, '/path/to/emptydirectory', False) self.assertRaises(ValueError, Maildir, '/path/to/emptydirectory', True) def test_iteration(self): from zope.sendmail.maildir import Maildir m = Maildir('/path/to/maildir') messages = list(m) self.assertEquals(messages, ['/path/to/maildir/cur/2', '/path/to/maildir/cur/1', '/path/to/maildir/new/2', '/path/to/maildir/new/1']) def test_newMessage(self): from zope.sendmail.maildir import Maildir from zope.sendmail.interfaces import IMaildirMessageWriter m = Maildir('/path/to/maildir') fd = m.newMessage() verifyObject(IMaildirMessageWriter, fd) self.assert_(fd._filename.startswith( '/path/to/maildir/tmp/1234500002.4242.myhostname.')) def test_newMessage_never_loops(self): from zope.sendmail.maildir import Maildir from zope.sendmail.interfaces import IMaildirMessageWriter self.fake_os_module._all_files_exist = True m = Maildir('/path/to/maildir') self.assertRaises(RuntimeError, m.newMessage) def test_message_writer_and_abort(self): from zope.sendmail.maildir import MaildirMessageWriter filename1 = '/path/to/maildir/tmp/1234500002.4242.myhostname' filename2 = '/path/to/maildir/new/1234500002.4242.myhostname' fd = FakeFile(filename1, 'w') writer = MaildirMessageWriter(fd, filename1, filename2) self.assertEquals(writer._fd._filename, filename1) self.assertEquals(writer._fd._mode, 'w') # TODO or 'wb'? print >> writer, 'fee', writer.write(' fie') writer.writelines([' foe', ' foo']) self.assertEquals(writer._fd._written, 'fee fie foe foo') writer.abort() self.assertEquals(writer._fd._closed, True) self.assert_(filename1 in self.fake_os_module._removed_files) # Once aborted, abort does nothing self.fake_os_module._removed_files = () writer.abort() writer.abort() self.assertEquals(self.fake_os_module._removed_files, ()) # Once aborted, commit fails self.assertRaises(RuntimeError, writer.commit) def test_message_writer_commit(self): from zope.sendmail.maildir import MaildirMessageWriter filename1 = '/path/to/maildir/tmp/1234500002.4242.myhostname' filename2 = '/path/to/maildir/new/1234500002.4242.myhostname' fd = FakeFile(filename1, 'w') writer = MaildirMessageWriter(fd, filename1, filename2) writer.commit() self.assertEquals(writer._fd._closed, True) self.assert_((filename1, filename2) in self.fake_os_module._renamed_files) # Once commited, commit does nothing self.fake_os_module._renamed_files = () writer.commit() writer.commit() self.assertEquals(self.fake_os_module._renamed_files, ()) # Once commited, abort does nothing writer.abort() writer.abort() self.assertEquals(self.fake_os_module._renamed_files, ()) def test_message_writer_unicode(self): from zope.sendmail.maildir import MaildirMessageWriter filename1 = '/path/to/maildir/tmp/1234500002.4242.myhostname' filename2 = '/path/to/maildir/new/1234500002.4242.myhostname' fd = FakeFile(filename1, 'w') writer = MaildirMessageWriter(fd, filename1, filename2) self.assertEquals(writer._fd._filename, filename1) self.assertEquals(writer._fd._mode, 'w') # TODO or 'wb'? print >> writer, u'fe\xe8', writer.write(u' fi\xe8') writer.writelines([u' fo\xe8', u' fo\xf2']) self.assertEquals(writer._fd._written, 'fe\xc3\xa8 fi\xc3\xa8 fo\xc3\xa8 fo\xc3\xb2') def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestMaildir)) return suite if __name__ == '__main__': unittest.main() zope.sendmail-3.7.5/src/zope/sendmail/tests/test_directives.py0000664000175000017500000000662211757162701024771 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test the gts ZCML namespace directives. $Id: test_directives.py 107770 2010-01-07 07:11:43Z kobold $ """ import os import shutil import unittest import threading import tempfile import time import zope.component from zope.component.testing import PlacelessSetup from zope.configuration import xmlconfig from zope.interface import implements from zope.sendmail.interfaces import \ IMailDelivery, IMailer, ISMTPMailer from zope.sendmail import delivery from zope.sendmail.queue import QueueProcessorThread import zope.sendmail.tests class MaildirStub(object): def __init__(self, path, create=False): self.path = path self.create = create def __iter__(self): return iter(()) def newMessage(self): return None class Mailer(object): implements(IMailer) class DirectivesTest(PlacelessSetup, unittest.TestCase): def setUp(self): self.mailbox = os.path.join(tempfile.mkdtemp(), "mailbox") super(DirectivesTest, self).setUp() self.testMailer = Mailer() gsm = zope.component.getGlobalSiteManager() gsm.registerUtility(Mailer(), IMailer, "test.smtp") gsm.registerUtility(self.testMailer, IMailer, "test.mailer") here = os.path.dirname(__file__) zcmlfile = open(os.path.join(here, "mail.zcml"), 'r') zcml = zcmlfile.read() zcmlfile.close() self.context = xmlconfig.string( zcml.replace('path/to/tmp/mailbox', self.mailbox)) self.orig_maildir = delivery.Maildir delivery.Maildir = MaildirStub def tearDown(self): delivery.Maildir = self.orig_maildir # Tear down the mail queue processor thread. # Give the other thread a chance to start: time.sleep(0.001) threads = list(threading.enumerate()) for thread in threads: if isinstance(thread, QueueProcessorThread): thread.stop() thread.join() shutil.rmtree(self.mailbox, True) super(DirectivesTest, self).tearDown() def testQueuedDelivery(self): delivery = zope.component.getUtility(IMailDelivery, "Mail") self.assertEqual('QueuedMailDelivery', delivery.__class__.__name__) self.assertEqual(self.mailbox, delivery.queuePath) def testDirectDelivery(self): delivery = zope.component.getUtility(IMailDelivery, "Mail2") self.assertEqual('DirectMailDelivery', delivery.__class__.__name__) self.assert_(self.testMailer is delivery.mailer) def testSMTPMailer(self): mailer = zope.component.getUtility(IMailer, "smtp") self.assert_(ISMTPMailer.providedBy(mailer)) def test_suite(): return unittest.TestSuite(( unittest.makeSuite(DirectivesTest), )) if __name__ == '__main__': unittest.main() zope.sendmail-3.7.5/src/zope/sendmail/tests/test_queue.py0000664000175000017500000002723411757162701023756 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mail Delivery Tests Simple implementation of the MailDelivery, Mailers and MailEvents. $Id: test_queue.py 126451 2012-05-23 12:23:18Z tseaver $ """ import os.path import shutil from tempfile import mkdtemp from unittest import TestCase, TestSuite, makeSuite, main from zope.sendmail.queue import ConsoleApp from zope.sendmail.tests.test_delivery import MaildirStub, LoggerStub, \ BrokenMailerStub, SMTPResponseExceptionMailerStub, MailerStub class TestQueueProcessorThread(TestCase): def setUp(self): from zope.sendmail.queue import QueueProcessorThread self.md = MaildirStub('/foo/bar/baz') self.thread = QueueProcessorThread() self.thread.setMaildir(self.md) self.mailer = MailerStub() self.thread.setMailer(self.mailer) self.thread.log = LoggerStub() self.dir = mkdtemp() def tearDown(self): shutil.rmtree(self.dir) def test_parseMessage(self): hdr = ('X-Zope-From: foo@example.com\n' 'X-Zope-To: bar@example.com, baz@example.com\n') msg = ('Header: value\n' '\n' 'Body\n') f, t, m = self.thread._parseMessage(hdr + msg) self.assertEquals(f, 'foo@example.com') self.assertEquals(t, ('bar@example.com', 'baz@example.com')) self.assertEquals(m, msg) def test_deliveration(self): self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write('X-Zope-From: foo@example.com\n' 'X-Zope-To: bar@example.com, baz@example.com\n' 'Header: value\n\nBody\n') temp.close() self.md.files.append(self.filename) self.thread.run(forever=False) self.assertEquals(self.mailer.sent_messages, [('foo@example.com', ('bar@example.com', 'baz@example.com'), 'Header: value\n\nBody\n')]) self.failIf(os.path.exists(self.filename), 'File exists') self.assertEquals(self.thread.log.infos, [('Mail from %s to %s sent.', ('foo@example.com', 'bar@example.com, baz@example.com'), {})]) def test_error_logging(self): self.thread.setMailer(BrokenMailerStub()) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write('X-Zope-From: foo@example.com\n' 'X-Zope-To: bar@example.com, baz@example.com\n' 'Header: value\n\nBody\n') temp.close() self.md.files.append(self.filename) self.thread.run(forever=False) self.assertEquals(self.thread.log.errors, [('Error while sending mail from %s to %s.', ('foo@example.com', 'bar@example.com, baz@example.com'), {'exc_info': 1})]) def test_smtp_response_error_transient(self): # Test a transient error self.thread.setMailer(SMTPResponseExceptionMailerStub(451)) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write('X-Zope-From: foo@example.com\n' 'X-Zope-To: bar@example.com, baz@example.com\n' 'Header: value\n\nBody\n') temp.close() self.md.files.append(self.filename) self.thread.run(forever=False) # File must remail were it was, so it will be retried self.failUnless(os.path.exists(self.filename)) self.assertEquals(self.thread.log.errors, [('Error while sending mail from %s to %s.', ('foo@example.com', 'bar@example.com, baz@example.com'), {'exc_info': 1})]) def test_smtp_response_error_permanent(self): # Test a permanent error self.thread.setMailer(SMTPResponseExceptionMailerStub(550)) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write('X-Zope-From: foo@example.com\n' 'X-Zope-To: bar@example.com, baz@example.com\n' 'Header: value\n\nBody\n') temp.close() self.md.files.append(self.filename) self.thread.run(forever=False) # File must be moved aside self.failIf(os.path.exists(self.filename)) self.failUnless(os.path.exists(os.path.join(self.dir, '.rejected-message'))) self.assertEquals(self.thread.log.errors, [('Discarding email from %s to %s due to a ' 'permanent error: %s', ('foo@example.com', 'bar@example.com, baz@example.com', "(550, 'Serious Error')"), {})]) def test_smtp_recipients_refused(self): # Test a permanent error self.thread.setMailer(SMTPRecipientsRefusedMailerStub( ['bar@example.com'])) self.filename = os.path.join(self.dir, 'message') temp = open(self.filename, "w+b") temp.write('X-Zope-From: foo@example.com\n' 'X-Zope-To: bar@example.com, baz@example.com\n' 'Header: value\n\nBody\n') temp.close() self.md.files.append(self.filename) self.thread.run(forever=False) # File must be moved aside self.failIf(os.path.exists(self.filename)) self.failUnless(os.path.exists(os.path.join(self.dir, '.rejected-message'))) self.assertEquals(self.thread.log.errors, [('Email recipients refused: %s', ('bar@example.com',), {})]) test_ini = """[app:zope-sendmail] interval = 33 hostname = testhost port = 2525 username = Chris password = Rossi force_tls = False no_tls = True queue_path = hammer/dont/hurt/em """ class TestConsoleApp(TestCase): def setUp(self): from zope.sendmail.delivery import QueuedMailDelivery from zope.sendmail.maildir import Maildir self.dir = mkdtemp() self.queue_dir = os.path.join(self.dir, "queue") self.delivery = QueuedMailDelivery(self.queue_dir) self.maildir = Maildir(self.queue_dir, True) self.mailer = MailerStub() def tearDown(self): shutil.rmtree(self.dir) def test_args_processing(self): # simplest case that works cmdline = "zope-sendmail %s" % self.dir app = ConsoleApp(cmdline.split(), verbose=False) self.assertEquals("zope-sendmail", app.script_name) self.assertFalse(app._error) self.assertEquals(self.dir, app.queue_path) self.assertFalse(app.daemon) self.assertEquals(3, app.interval) self.assertEquals("localhost", app.hostname) self.assertEquals(25, app.port) self.assertEquals(None, app.username) self.assertEquals(None, app.password) self.assertFalse(app.force_tls) self.assertFalse(app.no_tls) # simplest case that doesn't work cmdline = "zope-sendmail" app = ConsoleApp(cmdline.split(), verbose=False) self.assertEquals("zope-sendmail", app.script_name) self.assertTrue(app._error) self.assertEquals(None, app.queue_path) self.assertFalse(app.daemon) self.assertEquals(3, app.interval) self.assertEquals("localhost", app.hostname) self.assertEquals(25, app.port) self.assertEquals(None, app.username) self.assertEquals(None, app.password) self.assertFalse(app.force_tls) self.assertFalse(app.no_tls) # use (almost) all of the options cmdline = "zope-sendmail --daemon --interval 7 --hostname foo --port 75 " \ "--username chris --password rossi --force-tls " \ "%s" % self.dir app = ConsoleApp(cmdline.split(), verbose=False) self.assertEquals("zope-sendmail", app.script_name) self.assertFalse(app._error) self.assertEquals(self.dir, app.queue_path) self.assertTrue(app.daemon) self.assertEquals(7, app.interval) self.assertEquals("foo", app.hostname) self.assertEquals(75, app.port) self.assertEquals("chris", app.username) self.assertEquals("rossi", app.password) self.assertTrue(app.force_tls) self.assertFalse(app.no_tls) # test username without password cmdline = "zope-sendmail --username chris %s" % self.dir app = ConsoleApp(cmdline.split(), verbose=False) self.assertTrue(app._error) # test --tls and --no-tls together cmdline = "zope-sendmail --tls --no-tls %s" % self.dir app = ConsoleApp(cmdline.split(), verbose=False) self.assertTrue(app._error) # test force_tls and no_tls comdline = "zope-sendmail --force-tls --no-tls %s" % self.dir self.assertTrue(app._error) def test_ini_parse(self): ini_path = os.path.join(self.dir, "zope-sendmail.ini") f = open(ini_path, "w") f.write(test_ini) f.close() # override most everything cmdline = """zope-sendmail --config %s""" % ini_path app = ConsoleApp(cmdline.split(), verbose=False) self.assertEquals("zope-sendmail", app.script_name) self.assertFalse(app._error) self.assertEquals("hammer/dont/hurt/em", app.queue_path) self.assertFalse(app.daemon) self.assertEquals(33, app.interval) self.assertEquals("testhost", app.hostname) self.assertEquals(2525, app.port) self.assertEquals("Chris", app.username) self.assertEquals("Rossi", app.password) self.assertFalse(app.force_tls) self.assertTrue(app.no_tls) # override nothing, make sure defaults come through f = open(ini_path, "w") f.write("[app:zope-sendmail]\n\nqueue_path=foo\n") f.close() cmdline = """zope-sendmail --config %s %s""" % (ini_path, self.dir) app = ConsoleApp(cmdline.split(), verbose=False) self.assertEquals("zope-sendmail", app.script_name) self.assertFalse(app._error) self.assertEquals(self.dir, app.queue_path) self.assertFalse(app.daemon) self.assertEquals(3, app.interval) self.assertEquals("localhost", app.hostname) self.assertEquals(25, app.port) self.assertEquals(None, app.username) self.assertEquals(None, app.password) self.assertFalse(app.force_tls) self.assertFalse(app.no_tls) class SMTPRecipientsRefusedMailerStub(object): def __init__(self, recipients): self.recipients = recipients def send(self, fromaddr, toaddrs, message): import smtplib raise smtplib.SMTPRecipientsRefused(self.recipients) def test_suite(): return TestSuite(( makeSuite(TestQueueProcessorThread), makeSuite(TestConsoleApp), )) if __name__ == '__main__': main() zope.sendmail-3.7.5/src/zope/sendmail/tests/test_mailer.py0000664000175000017500000001551611757162701024103 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for mailers. $Id: test_mailer.py 117103 2010-10-01 09:28:29Z mj $ """ from StringIO import StringIO from zope.interface.verify import verifyObject from zope.sendmail.interfaces import ISMTPMailer from zope.sendmail.mailer import SMTPMailer import socket import unittest class TestSMTPMailer(unittest.TestCase): def setUp(self, port=None): global SMTP class SMTP(object): fail_on_quit = False def __init__(myself, h, p): myself.hostname = h myself.port = p myself.quitted = False myself.closed = False if type(p) == type(u""): raise socket.error("Int or String expected") self.smtp = myself def sendmail(self, f, t, m): self.fromaddr = f self.toaddrs = t self.msgtext = m def login(self, username, password): self.username = username self.password = password def quit(self): if self.fail_on_quit: raise socket.sslerror("dang") self.quitted = True self.close() def close(self): self.closed = True def has_extn(self, ext): return True def ehlo(self): self.does_esmtp = True return (200, 'Hello, I am your stupid MTA mock') def starttls(self): pass if port is None: self.mailer = SMTPMailer() else: self.mailer = SMTPMailer(u'localhost', port) self.mailer.smtp = SMTP def test_interface(self): verifyObject(ISMTPMailer, self.mailer) def test_send(self): for run in (1,2): if run == 2: self.setUp(u'25') fromaddr = 'me@example.com' toaddrs = ('you@example.com', 'him@example.com') msgtext = 'Headers: headers\n\nbodybodybody\n-- \nsig\n' self.mailer.send(fromaddr, toaddrs, msgtext) self.assertEquals(self.smtp.fromaddr, fromaddr) self.assertEquals(self.smtp.toaddrs, toaddrs) self.assertEquals(self.smtp.msgtext, msgtext) self.assert_(self.smtp.quitted) self.assert_(self.smtp.closed) def test_send_auth(self): fromaddr = 'me@example.com' toaddrs = ('you@example.com', 'him@example.com') msgtext = 'Headers: headers\n\nbodybodybody\n-- \nsig\n' self.mailer.username = 'foo' self.mailer.password = 'evil' self.mailer.hostname = 'spamrelay' self.mailer.port = 31337 self.mailer.send(fromaddr, toaddrs, msgtext) self.assertEquals(self.smtp.username, 'foo') self.assertEquals(self.smtp.password, 'evil') self.assertEquals(self.smtp.hostname, 'spamrelay') self.assertEquals(self.smtp.port, '31337') self.assertEquals(self.smtp.fromaddr, fromaddr) self.assertEquals(self.smtp.toaddrs, toaddrs) self.assertEquals(self.smtp.msgtext, msgtext) self.assert_(self.smtp.quitted) self.assert_(self.smtp.closed) def test_send_auth_unicode(self): fromaddr = 'me@example.com' toaddrs = ('you@example.com', 'him@example.com') msgtext = 'Headers: headers\n\nbodybodybody\n-- \nsig\n' self.mailer.username = u'f\u00f8\u00f8' # double o slash self.mailer.password = u'\u00e9vil' # e acute self.mailer.hostname = 'spamrelay' self.mailer.port = 31337 self.mailer.send(fromaddr, toaddrs, msgtext) self.assertEquals(self.smtp.username, 'f\xc3\xb8\xc3\xb8') self.assertEquals(self.smtp.password, '\xc3\xa9vil') def test_send_auth_nonascii(self): fromaddr = 'me@example.com' toaddrs = ('you@example.com', 'him@example.com') msgtext = 'Headers: headers\n\nbodybodybody\n-- \nsig\n' self.mailer.username = 'f\xc3\xb8\xc3\xb8' # double o slash self.mailer.password = '\xc3\xa9vil' # e acute self.mailer.hostname = 'spamrelay' self.mailer.port = 31337 self.mailer.send(fromaddr, toaddrs, msgtext) self.assertEquals(self.smtp.username, 'f\xc3\xb8\xc3\xb8') self.assertEquals(self.smtp.password, '\xc3\xa9vil') def test_send_failQuit(self): self.mailer.smtp.fail_on_quit = True try: fromaddr = 'me@example.com' toaddrs = ('you@example.com', 'him@example.com') msgtext = 'Headers: headers\n\nbodybodybody\n-- \nsig\n' self.mailer.send(fromaddr, toaddrs, msgtext) self.assertEquals(self.smtp.fromaddr, fromaddr) self.assertEquals(self.smtp.toaddrs, toaddrs) self.assertEquals(self.smtp.msgtext, msgtext) self.assert_(not self.smtp.quitted) self.assert_(self.smtp.closed) finally: self.mailer.smtp.fail_on_quit = False class TestSMTPMailerWithNoEHLO(TestSMTPMailer): def setUp(self, port=None): class SMTPWithNoEHLO(SMTP): does_esmtp = False def __init__(myself, h, p): myself.hostname = h myself.port = p myself.quitted = False myself.closed = False if type(p) == type(u""): raise socket.error("Int or String expected") self.smtp = myself def helo(self): return (200, 'Hello, I am your stupid MTA mock') def ehlo(self): return (502, 'I don\'t understand EHLO') if port is None: self.mailer = SMTPMailer() else: self.mailer = SMTPMailer(u'localhost', port) self.mailer.smtp = SMTPWithNoEHLO def test_send_auth(self): # This test requires ESMTP, which we're intentionally not enabling # here, so pass. pass test_send_auth_unicode = test_send_auth test_send_auth_nonascii = test_send_auth def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSMTPMailer)) suite.addTest(unittest.makeSuite(TestSMTPMailerWithNoEHLO)) return suite if __name__ == '__main__': unittest.main() zope.sendmail-3.7.5/src/zope/sendmail/tests/__init__.py0000664000175000017500000000004011757162701023314 0ustar pangolinpangolin# make this directory a package zope.sendmail-3.7.5/src/zope/sendmail/tests/test_delivery.py0000664000175000017500000002522611757162701024454 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mail Delivery Tests Simple implementation of the MailDelivery, Mailers and MailEvents. $Id: test_delivery.py 110390 2010-04-01 12:33:34Z mgedmin $ """ import smtplib from unittest import TestCase, TestSuite, makeSuite, main import doctest import transaction from zope.interface import implements from zope.interface.verify import verifyObject from zope.sendmail.interfaces import IMailer class MailerStub(object): implements(IMailer) def __init__(self, *args, **kw): self.sent_messages = [] def send(self, fromaddr, toaddrs, message): self.sent_messages.append((fromaddr, toaddrs, message)) class TestMailDataManager(TestCase): def testInterface(self): from transaction.interfaces import IDataManager from zope.sendmail.delivery import MailDataManager manager = MailDataManager(object, (1, 2)) verifyObject(IDataManager, manager) self.assertEqual(manager.callable, object) self.assertEqual(manager.args, (1, 2)) def print_success(*args): print "message successfully sent, args: %s" % (args, ) def print_abort(): print "message aborted" def doctest_successful_commit(): """Regression test for http://www.zope.org/Collectors/Zope3-dev/590 Let's do a full two-phase commit. >>> from zope.sendmail.delivery import MailDataManager >>> manager = MailDataManager(print_success, ('foo', 'bar'), ... onAbort=print_abort) >>> transaction = object() >>> manager.tpc_begin(transaction) >>> manager.commit(transaction) >>> manager.tpc_vote(transaction) >>> manager.tpc_finish(transaction) message successfully sent, args: ('foo', 'bar') """ def doctest_unsuccessful_commit(): """Regression test for http://www.zope.org/Collectors/Zope3-dev/590 Let's start a two-phase commit, then abort it. >>> from zope.sendmail.delivery import MailDataManager >>> manager = MailDataManager(print_success, onAbort=print_abort) >>> manager.tpc_begin(transaction) >>> manager.commit(transaction) >>> manager.tpc_vote(transaction) >>> manager.tpc_abort(transaction) message aborted """ class TestDirectMailDelivery(TestCase): def testInterface(self): from zope.sendmail.interfaces import IDirectMailDelivery from zope.sendmail.delivery import DirectMailDelivery mailer = MailerStub() delivery = DirectMailDelivery(mailer) verifyObject(IDirectMailDelivery, delivery) self.assertEqual(delivery.mailer, mailer) def testSend(self): from zope.sendmail.delivery import DirectMailDelivery mailer = MailerStub() delivery = DirectMailDelivery(mailer) fromaddr = 'Jim ', 'Steve ') opt_headers = ('From: Jim \n' 'To: some-zope-coders:;\n' 'Date: Mon, 19 May 2003 10:17:36 -0400\n' 'Message-Id: <20030519.1234@example.org>\n') message = ('Subject: example\n' '\n' 'This is just an example\n') msgid = delivery.send(fromaddr, toaddrs, opt_headers + message) self.assertEquals(msgid, '20030519.1234@example.org') self.assertEquals(mailer.sent_messages, []) transaction.commit() self.assertEquals(mailer.sent_messages, [(fromaddr, toaddrs, opt_headers + message)]) mailer.sent_messages = [] msgid = delivery.send(fromaddr, toaddrs, message) self.assert_('@' in msgid) self.assertEquals(mailer.sent_messages, []) transaction.commit() self.assertEquals(len(mailer.sent_messages), 1) self.assertEquals(mailer.sent_messages[0][0], fromaddr) self.assertEquals(mailer.sent_messages[0][1], toaddrs) self.assert_(mailer.sent_messages[0][2].endswith(message)) new_headers = mailer.sent_messages[0][2][:-len(message)] self.assert_(new_headers.find('Message-Id: <%s>' % msgid) != -1) mailer.sent_messages = [] msgid = delivery.send(fromaddr, toaddrs, opt_headers + message) self.assertEquals(mailer.sent_messages, []) transaction.abort() self.assertEquals(mailer.sent_messages, []) class MaildirWriterStub(object): data = '' commited_messages = [] # this list is shared among all instances aborted_messages = [] # this one too _closed = False def write(self, str): if self._closed: raise AssertionError('already closed') self.data += str def writelines(self, seq): if self._closed: raise AssertionError('already closed') self.data += ''.join(seq) def close(self): self._closed = True def commit(self): if not self._closed: raise AssertionError('for this test we want the message explicitly' ' closed before it is committed') self._commited = True self.commited_messages.append(self.data) def abort(self): if not self._closed: raise AssertionError('for this test we want the message explicitly' ' closed before it is committed') self._aborted = True self.aborted_messages.append(self.data) class MaildirStub(object): def __init__(self, path, create=False): self.path = path self.create = create self.msgs = [] self.files = [] def __iter__(self): return iter(self.files) def newMessage(self): m = MaildirWriterStub() self.msgs.append(m) return m class LoggerStub(object): def __init__(self): self.infos = [] self.errors = [] def getLogger(self, name): return self def error(self, msg, *args, **kwargs): self.errors.append((msg, args, kwargs)) def info(self, msg, *args, **kwargs): self.infos.append((msg, args, kwargs)) class BizzarreMailError(IOError): pass class BrokenMailerStub(object): implements(IMailer) def __init__(self, *args, **kw): pass def send(self, fromaddr, toaddrs, message): raise BizzarreMailError("bad things happened while sending mail") class SMTPResponseExceptionMailerStub(object): implements(IMailer) def __init__(self, code): self.code = code def send(self, fromaddr, toaddrs, message): raise smtplib.SMTPResponseException(self.code, 'Serious Error') class TestQueuedMailDelivery(TestCase): def setUp(self): import zope.sendmail.delivery as mail_delivery_module self.mail_delivery_module = mail_delivery_module self.old_Maildir = mail_delivery_module.Maildir mail_delivery_module.Maildir = MaildirStub def tearDown(self): self.mail_delivery_module.Maildir = self.old_Maildir MaildirWriterStub.commited_messages = [] MaildirWriterStub.aborted_messages = [] def testInterface(self): from zope.sendmail.interfaces import IQueuedMailDelivery from zope.sendmail.delivery import QueuedMailDelivery delivery = QueuedMailDelivery('/path/to/mailbox') verifyObject(IQueuedMailDelivery, delivery) self.assertEqual(delivery.queuePath, '/path/to/mailbox') def testSend(self): from zope.sendmail.delivery import QueuedMailDelivery delivery = QueuedMailDelivery('/path/to/mailbox') fromaddr = 'jim@example.com' toaddrs = ('guido@example.com', 'steve@examplecom') zope_headers = ('X-Zope-From: jim@example.com\n' 'X-Zope-To: guido@example.com, steve@examplecom\n') opt_headers = ('From: Jim \n' 'To: some-zope-coders:;\n' 'Date: Mon, 19 May 2003 10:17:36 -0400\n' 'Message-Id: <20030519.1234@example.org>\n') message = ('Subject: example\n' '\n' 'This is just an example\n') msgid = delivery.send(fromaddr, toaddrs, opt_headers + message) self.assertEquals(msgid, '20030519.1234@example.org') self.assertEquals(MaildirWriterStub.commited_messages, []) self.assertEquals(MaildirWriterStub.aborted_messages, []) transaction.commit() self.assertEquals(MaildirWriterStub.commited_messages, [zope_headers + opt_headers + message]) self.assertEquals(MaildirWriterStub.aborted_messages, []) MaildirWriterStub.commited_messages = [] msgid = delivery.send(fromaddr, toaddrs, message) self.assert_('@' in msgid) self.assertEquals(MaildirWriterStub.commited_messages, []) self.assertEquals(MaildirWriterStub.aborted_messages, []) transaction.commit() self.assertEquals(len(MaildirWriterStub.commited_messages), 1) self.assert_(MaildirWriterStub.commited_messages[0].endswith(message)) new_headers = MaildirWriterStub.commited_messages[0][:-len(message)] self.assert_(new_headers.find('Message-Id: <%s>' % msgid) != -1) self.assert_(new_headers.find('X-Zope-From: %s' % fromaddr) != 1) self.assert_(new_headers.find('X-Zope-To: %s' % ", ".join(toaddrs)) != 1) self.assertEquals(MaildirWriterStub.aborted_messages, []) MaildirWriterStub.commited_messages = [] msgid = delivery.send(fromaddr, toaddrs, opt_headers + message) self.assertEquals(MaildirWriterStub.commited_messages, []) self.assertEquals(MaildirWriterStub.aborted_messages, []) transaction.abort() self.assertEquals(MaildirWriterStub.commited_messages, []) self.assertEquals(len(MaildirWriterStub.aborted_messages), 1) def test_suite(): return TestSuite(( makeSuite(TestMailDataManager), makeSuite(TestDirectMailDelivery), makeSuite(TestQueuedMailDelivery), doctest.DocTestSuite(), )) if __name__ == '__main__': main() zope.sendmail-3.7.5/src/zope/sendmail/tests/mail.zcml0000664000175000017500000000116211757162701023022 0ustar pangolinpangolin zope.sendmail-3.7.5/src/zope/sendmail/tests/test_event.py0000664000175000017500000000315211757162701023744 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mailer Events Tests $Id: test_event.py 66922 2006-04-12 23:28:07Z jinty $ """ from unittest import TestCase, TestSuite, makeSuite from zope.interface.verify import verifyObject from zope.sendmail.interfaces import IMailSentEvent, IMailErrorEvent from zope.sendmail.event import MailSentEvent class TestMailEvents(TestCase): def testMailSendEvent(self): msgid = '<1234@example.com>' m = MailSentEvent(msgid) verifyObject(IMailSentEvent, m) self.assertEquals(m.messageId, msgid) def testMailErrorEvent(self): from zope.sendmail.event import MailErrorEvent msgid = '<1234@example.com>' error = '550 Relay access denied' m = MailErrorEvent(msgid, error) verifyObject(IMailErrorEvent, m) self.assertEquals(m.messageId, msgid) self.assertEquals(m.errorMessage, error) def test_suite(): return TestSuite(( makeSuite(TestMailEvents), )) if __name__ == '__main__': unittest.main() zope.sendmail-3.7.5/src/zope/sendmail/mailer.py0000664000175000017500000000562711757162701021704 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """These are classes which abstract different channels an email message could be sent out by. $Id: mailer.py 117103 2010-10-01 09:28:29Z mj $ """ __docformat__ = 'restructuredtext' import socket from smtplib import SMTP from zope.interface import implements from zope.sendmail.interfaces import ISMTPMailer have_ssl = hasattr(socket, 'ssl') class SMTPMailer(object): implements(ISMTPMailer) smtp = SMTP def __init__(self, hostname='localhost', port=25, username=None, password=None, no_tls=False, force_tls=False): self.hostname = hostname self.port = port self.username = username self.password = password self.force_tls = force_tls self.no_tls = no_tls def send(self, fromaddr, toaddrs, message): connection = self.smtp(self.hostname, str(self.port)) # send EHLO code, response = connection.ehlo() if code < 200 or code >= 300: code, response = connection.helo() if code < 200 or code >= 300: raise RuntimeError('Error sending HELO to the SMTP server ' '(code=%s, response=%s)' % (code, response)) # encryption support have_tls = connection.has_extn('starttls') if not have_tls and self.force_tls: raise RuntimeError('TLS is not available but TLS is required') if have_tls and have_ssl and not self.no_tls: connection.starttls() connection.ehlo() if connection.does_esmtp: if self.username is not None and self.password is not None: username, password = self.username, self.password if isinstance(username, unicode): username = username.encode('utf-8') if isinstance(password, unicode): password = password.encode('utf-8') connection.login(username, password) elif self.username: raise RuntimeError('Mailhost does not support ESMTP but a username ' 'is configured') connection.sendmail(fromaddr, toaddrs, message) try: connection.quit() except socket.sslerror: #something weird happened while quiting connection.close() zope.sendmail-3.7.5/src/zope/sendmail/event.py0000664000175000017500000000237211757162701021546 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Collection of possible Mail Events. $Id: event.py 66922 2006-04-12 23:28:07Z jinty $ """ __docformat__ = 'restructuredtext' from zope.interface import implements from zope.sendmail.interfaces import IMailSentEvent, IMailErrorEvent class MailSentEvent(object): __doc__ = IMailSentEvent.__doc__ implements(IMailSentEvent) def __init__(self, messageId): self.messageId = messageId class MailErrorEvent(object): __doc__ = IMailErrorEvent.__doc__ implements(IMailErrorEvent) def __init__(self, messageId, errorMessage): self.messageId = messageId self.errorMessage = errorMessage zope.sendmail-3.7.5/src/zope/sendmail/zcml.py0000664000175000017500000001340611757162701021372 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """'mail' ZCML Namespaces Schemas $Id: zcml.py 126456 2012-05-23 13:20:12Z tseaver $ """ __docformat__ = 'restructuredtext' from zope.component import queryUtility from zope.component.zcml import handler from zope.configuration.fields import Path from zope.configuration.exceptions import ConfigurationError from zope.interface import Interface from zope.schema import TextLine, BytesLine, Int, Bool from zope.sendmail.delivery import QueuedMailDelivery, DirectMailDelivery from zope.sendmail.interfaces import IMailer, IMailDelivery from zope.sendmail.mailer import SMTPMailer from zope.sendmail.queue import QueueProcessorThread try: from zope.component.security import proxify from zope.security.zcml import Permission except ImportError: SECURITY_SUPPORT = False from zope.schema import TextLine as Permission else: SECURITY_SUPPORT = True def _assertPermission(permission, interfaces, component): if not SECURITY_SUPPORT: raise ConfigurationError("security proxied components are not " "supported because zope.security is not available") return proxify(component, provides=interfaces, permission=permission) class IDeliveryDirective(Interface): """This abstract directive describes a generic mail delivery utility registration.""" name = TextLine( title=u"Name", description=u'Specifies the Delivery name of the mail utility. '\ u'The default is "Mail".', default=u"Mail", required=False) mailer = TextLine( title=u"Mailer", description=u"Defines the mailer to be used for sending mail.", required=True) permission = Permission( title=u"Permission", description=u"Defines the permission needed to use this service.", required=False) class IQueuedDeliveryDirective(IDeliveryDirective): """This directive creates and registers a global queued mail utility. It should be only called once during startup.""" queuePath = Path( title=u"Queue Path", description=u"Defines the path for the queue directory.", required=True) processorThread = Bool( title=u"Run Queue Processor Thread", description=u"Indicates whether to run queue processor in a thread " "in this process.", required=False, default=True) def queuedDelivery(_context, queuePath, mailer, permission=None, name="Mail", processorThread=True): def createQueuedDelivery(): delivery = QueuedMailDelivery(queuePath) if permission is not None: delivery = _assertPermission(permission, IMailDelivery, delivery) handler('registerUtility', delivery, IMailDelivery, name) mailerObject = queryUtility(IMailer, mailer) if mailerObject is None: raise ConfigurationError("Mailer %r is not defined" %mailer) if processorThread: thread = QueueProcessorThread() thread.setMailer(mailerObject) thread.setQueuePath(queuePath) thread.start() _context.action( discriminator = ('utility', IMailDelivery, name), callable = createQueuedDelivery, args = () ) class IDirectDeliveryDirective(IDeliveryDirective): """This directive creates and registers a global direct mail utility. It should be only called once during startup.""" def directDelivery(_context, mailer, permission=None, name="Mail"): def createDirectDelivery(): mailerObject = queryUtility(IMailer, mailer) if mailerObject is None: raise ConfigurationError("Mailer %r is not defined" %mailer) delivery = DirectMailDelivery(mailerObject) if permission is not None: delivery = _assertPermission(permission, IMailDelivery, delivery) handler('registerUtility', delivery, IMailDelivery, name) _context.action( discriminator = ('utility', IMailDelivery, name), callable = createDirectDelivery, args = () ) class IMailerDirective(Interface): """A generic directive registering a mailer for the mail utility.""" name = TextLine( title=u"Name", description=u"Name of the Mailer.", required=True) class ISMTPMailerDirective(IMailerDirective): """Registers a new SMTP mailer.""" hostname = BytesLine( title=u"Hostname", description=u"Hostname of the SMTP host.", default="localhost", required=False) port = Int( title=u"Port", description=u"Port of the SMTP server.", default=25, required=False) username = TextLine( title=u"Username", description=u"A username for SMTP AUTH.", required=False) password = TextLine( title=u"Password", description=u"A password for SMTP AUTH.", required=False) def smtpMailer(_context, name, hostname="localhost", port="25", username=None, password=None): _context.action( discriminator = ('utility', IMailer, name), callable = handler, args = ('registerUtility', SMTPMailer(hostname, port, username, password), IMailer, name) ) zope.sendmail-3.7.5/src/zope/sendmail/configure.zcml0000664000175000017500000000140711757162701022721 0ustar pangolinpangolin zope.sendmail-3.7.5/src/zope/sendmail/__init__.py0000664000175000017500000000004011757162701022152 0ustar pangolinpangolin# make this directory a package zope.sendmail-3.7.5/src/zope/sendmail/vocabulary.py0000664000175000017500000000404711757162701022575 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Mail vocabularies $Id: vocabulary.py 98165 2009-03-16 21:56:33Z nadako $ """ __docformat__ = 'restructuredtext' import zope.component from zope.interface import directlyProvides from zope.schema.interfaces import IVocabularyFactory from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm from zope.sendmail.interfaces import IMailDelivery def MailDeliveryNames(context=None): """Vocabulary with names of mail delivery utilities Let's provide a few stub utilities: >>> from zope.interface import implements >>> class StubMailDelivery(object): ... implements(IMailDelivery) >>> from zope.component import provideUtility >>> for name in 'and now for something completely different'.split(): ... provideUtility(StubMailDelivery(), name=name) Let's also provide another utility to verify that we only see mail delivery utilities: >>> provideUtility(MailDeliveryNames, name='Mail Delivery Names') Let's see what's in the vocabulary: >>> vocabulary = MailDeliveryNames(None) >>> names = [term.value for term in vocabulary] >>> names.sort() >>> print ' '.join(names) and completely different for now something """ utils = zope.component.getUtilitiesFor(IMailDelivery, context) terms = [SimpleTerm(name) for name, util in utils] return SimpleVocabulary(terms) directlyProvides(MailDeliveryNames, IVocabularyFactory) zope.sendmail-3.7.5/src/zope/__init__.py0000664000175000017500000000007011757162701020361 0ustar pangolinpangolin__import__('pkg_resources').declare_namespace(__name__) zope.sendmail-3.7.5/src/zope.sendmail.egg-info/0000775000175000017500000000000011757162772021550 5ustar pangolinpangolinzope.sendmail-3.7.5/src/zope.sendmail.egg-info/namespace_packages.txt0000664000175000017500000000000511757162772026076 0ustar pangolinpangolinzope zope.sendmail-3.7.5/src/zope.sendmail.egg-info/PKG-INFO0000664000175000017500000002032011757162772022642 0ustar pangolinpangolinMetadata-Version: 1.0 Name: zope.sendmail Version: 3.7.5 Summary: Zope sendmail Home-page: http://pypi.python.org/pypi/zope.sendmail Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ============= zope.sendmail ============= zope.sendmail is a package for email sending from Zope 3 applications. Email sending from Zope 3 applications works as follows: A Zope 3 application locates a mail delivery utility (``IMailDelivery``) and feeds a message to it. It gets back a unique message ID so it can keep track of the message by subscribing to ``IMailEvent`` events. The utility registers with the transaction system to make sure the message is only sent when the transaction commits successfully. (Among other things this avoids duplicate messages on ``ConflictErrors``.) If the delivery utility is a ``IQueuedMailDelivery``, it puts the message into a queue (a Maildir mailbox in the file system). A separate process or thread (``IMailQueueProcessor``) watches the queue and delivers messages asynchronously. Since the queue is located in the file system, it survives Zope restarts or crashes and the mail is not lost. The queue processor can implement batching to keep the server load low. If the delivery utility is a ``IDirectMailDelivery``, it delivers messages synchronously during the transaction commit. This is not a very good idea, as it makes the user wait. Note that transaction commits must not fail, but that is not a problem, because mail delivery problems dispatch an event instead of raising an exception. However, there is a problem -- sending events causes unknown code to be executed during the transaction commit phase. There should be a way to start a new transaction for event processing after this one is commited. An ``IMailQueueProcessor`` or ``IDirectMailDelivery`` actually delivers the messages by using a mailer (``IMailer``) component that encapsulates the delivery process. There currently is only one mailer: ``ISMTPMailer`` sends all messages to a relay host using SMTP. If mail delivery succeeds, an ``IMailSentEvent`` is dispatched by the mailer. If mail delivery fails, no exceptions are raised, but an `IMailErrorEvent` is dispatched by the mailer. ======= CHANGES ======= 3.7.5 (2012-05-23) ------------------ - Ensured that the 'queuedDelivery' directive has the same discriminator as the 'directDelivery' directive (they are mutually incompatible). https://bugs.launchpad.net/zope.sendmail/+bug/191143 - Avoid requeuing messages after an SMTP "recipients refused" error. https://bugs.launchpad.net/zope.sendmail/+bug/1003288 3.7.4 (2010-10-01) ------------------ - Handle unicode usernames and passwords, encoding them to UTF-8. Fix for https://bugs.launchpad.net/zope.sendmail/+bug/597143 3.7.3 (2010-09-25) ------------------ - Added not declared, but needed test dependency on `zope.component [test]`. 3.7.2 (2010-04-30) ------------------ - Removed no longer required testing dependency on zope.testing. - Maildir storage for queue can now handle unicode passed in for message or to/from addresses (change backported from repoze.sendmail). - Tests use stdlib doctest instead of zope.testing.doctest. 3.7.1 (2010-01-13) ------------------ - Backward compatibility import of zope.sendmail.queue.QueueProcessorThread in zope.sendmail.delivery. 3.7.0 (2010-01-12) ------------------ - Removed dependency on ``zope.security``: the security support is optional, and only available if the ``zope.security`` package is available. This change is similar to the optional security support introduced in ``zope.component`` 3.8.0, and in fact it uses the same helpers. - Sort by modification time the messages in zope.sendmail.maildir so earlier messages are sent before later messages during queue processing. - Added the new parameter ``processorThread`` to the queuedDelivery ZCML directive: if False, the QueueProcessorThread is not started and thus an independent process must process the queue; it defaults to True for b/c. - Provide a console script ``zope-sendmail`` which can be used to process the delivery queue in case processorThread is False. The console script can either process the messages in the queue once, or run in "daemon" mode. 3.6.1 (2009-11-16) ------------------ - Depend on ``zope.component`` >= 3.8.0, which supports the new semantic of zope.component.zcml.proxify needed by zope.sendmail.zcml. 3.6.0 (2009-09-14) ------------------ - Use simple vocabulary factory function instead of custom `UtilityTerm` and `UtilityVocabulary` classes, copied from ``zope.app.component`` in the previous release. - Depend on the ``transaction`` package instead of ``ZODB3``. - Remove zcml slugs and zpkg-related files. - Work around problem when used with Python >=2.5.1. See https://bugs.edge.launchpad.net/zope.sendmail/+bug/413335 . 3.5.1 (2009-01-26) ------------------ - Copied over the UtilityTerm and UtilityVocabulary implementation from zope.app.component to avoid a dependency. - Work around a problem when smtp quit fails, the mail was considered not delivered where just the quit failed. 3.5.0 (2008-07-05) ------------------ - final release (identical with 3.5.0b2) 3.5.0b2 (2007-12-19) -------------------- - If the SMTP server rejects a message (for example, when the sender or recipient address is malformed), that email stays in the queue forever (https://bugs.launchpad.net/zope3/+bug/157104). 3.5.0b1 (2007-11-08) -------------------- - Added README.txt - Can now talk to servers that don't implement EHLO - Fix bug that caused files with very long names to be created - Fix for https://bugs.launchpad.net/zope3/+bug/157104: move aside mail that's causing 5xx server responses. 3.5.0a2 (2007-10-23) -------------------- - Cleaned up ``does_esmtp`` in faux SMTP connection classes provided by the tests. - If the ``QueueProcessorThread`` is asked to stop while sending messages, do so after sending the current message; previously if there were many, many messages to send, the thread could stick around for quite a while. 3.5.0a1 (2007-10-23) -------------------- - ``QueueProcessorThread`` now accepts an optional parameter *interval* for defining how often to process the mail queue (default is 3 seconds) - Several ``QueueProcessorThreads`` (either in the same process, or multiple processes) can now deliver messages from a single maildir without duplicates being sent. 3.4.0 (2007-08-20) -------------------- - Bugfix: Don't keep open files around for every email message to be sent on transaction commit. People who try to send many emails in a single transaction now will not run out of file descriptors. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to ``zope.sendmail`` from Zope 3.4.0a1. Platform: UNKNOWN zope.sendmail-3.7.5/src/zope.sendmail.egg-info/not-zip-safe0000664000175000017500000000000111757162717023775 0ustar pangolinpangolin zope.sendmail-3.7.5/src/zope.sendmail.egg-info/entry_points.txt0000664000175000017500000000011511757162772025043 0ustar pangolinpangolin [console_scripts] zope-sendmail = zope.sendmail.queue:run zope.sendmail-3.7.5/src/zope.sendmail.egg-info/requires.txt0000664000175000017500000000023111757162772024144 0ustar pangolinpangolinsetuptools transaction zope.i18nmessageid zope.interface zope.schema zope.component>=3.8.0 zope.configuration [test] zope.security zope.component [test]zope.sendmail-3.7.5/src/zope.sendmail.egg-info/SOURCES.txt0000664000175000017500000000213711757162772023437 0ustar pangolinpangolinCHANGES.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.sendmail.egg-info/PKG-INFO src/zope.sendmail.egg-info/SOURCES.txt src/zope.sendmail.egg-info/dependency_links.txt src/zope.sendmail.egg-info/entry_points.txt src/zope.sendmail.egg-info/namespace_packages.txt src/zope.sendmail.egg-info/not-zip-safe src/zope.sendmail.egg-info/requires.txt src/zope.sendmail.egg-info/top_level.txt src/zope/sendmail/README.txt src/zope/sendmail/__init__.py src/zope/sendmail/configure.zcml src/zope/sendmail/delivery.py src/zope/sendmail/event.py src/zope/sendmail/interfaces.py src/zope/sendmail/maildir.py src/zope/sendmail/mailer.py src/zope/sendmail/meta.zcml src/zope/sendmail/queue.py src/zope/sendmail/vocabulary.py src/zope/sendmail/zcml.py src/zope/sendmail/tests/__init__.py src/zope/sendmail/tests/mail.zcml src/zope/sendmail/tests/test_delivery.py src/zope/sendmail/tests/test_directives.py src/zope/sendmail/tests/test_event.py src/zope/sendmail/tests/test_maildir.py src/zope/sendmail/tests/test_mailer.py src/zope/sendmail/tests/test_queue.py src/zope/sendmail/tests/test_vocabulary.pyzope.sendmail-3.7.5/src/zope.sendmail.egg-info/top_level.txt0000664000175000017500000000000511757162772024275 0ustar pangolinpangolinzope zope.sendmail-3.7.5/src/zope.sendmail.egg-info/dependency_links.txt0000664000175000017500000000000111757162772025616 0ustar pangolinpangolin zope.sendmail-3.7.5/README.txt0000664000175000017500000000374011757162701016211 0ustar pangolinpangolin============= zope.sendmail ============= zope.sendmail is a package for email sending from Zope 3 applications. Email sending from Zope 3 applications works as follows: A Zope 3 application locates a mail delivery utility (``IMailDelivery``) and feeds a message to it. It gets back a unique message ID so it can keep track of the message by subscribing to ``IMailEvent`` events. The utility registers with the transaction system to make sure the message is only sent when the transaction commits successfully. (Among other things this avoids duplicate messages on ``ConflictErrors``.) If the delivery utility is a ``IQueuedMailDelivery``, it puts the message into a queue (a Maildir mailbox in the file system). A separate process or thread (``IMailQueueProcessor``) watches the queue and delivers messages asynchronously. Since the queue is located in the file system, it survives Zope restarts or crashes and the mail is not lost. The queue processor can implement batching to keep the server load low. If the delivery utility is a ``IDirectMailDelivery``, it delivers messages synchronously during the transaction commit. This is not a very good idea, as it makes the user wait. Note that transaction commits must not fail, but that is not a problem, because mail delivery problems dispatch an event instead of raising an exception. However, there is a problem -- sending events causes unknown code to be executed during the transaction commit phase. There should be a way to start a new transaction for event processing after this one is commited. An ``IMailQueueProcessor`` or ``IDirectMailDelivery`` actually delivers the messages by using a mailer (``IMailer``) component that encapsulates the delivery process. There currently is only one mailer: ``ISMTPMailer`` sends all messages to a relay host using SMTP. If mail delivery succeeds, an ``IMailSentEvent`` is dispatched by the mailer. If mail delivery fails, no exceptions are raised, but an `IMailErrorEvent` is dispatched by the mailer. zope.sendmail-3.7.5/setup.cfg0000664000175000017500000000007311757162772016340 0ustar pangolinpangolin[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope.sendmail-3.7.5/PKG-INFO0000664000175000017500000002032011757162772015611 0ustar pangolinpangolinMetadata-Version: 1.0 Name: zope.sendmail Version: 3.7.5 Summary: Zope sendmail Home-page: http://pypi.python.org/pypi/zope.sendmail Author: Zope Corporation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: ============= zope.sendmail ============= zope.sendmail is a package for email sending from Zope 3 applications. Email sending from Zope 3 applications works as follows: A Zope 3 application locates a mail delivery utility (``IMailDelivery``) and feeds a message to it. It gets back a unique message ID so it can keep track of the message by subscribing to ``IMailEvent`` events. The utility registers with the transaction system to make sure the message is only sent when the transaction commits successfully. (Among other things this avoids duplicate messages on ``ConflictErrors``.) If the delivery utility is a ``IQueuedMailDelivery``, it puts the message into a queue (a Maildir mailbox in the file system). A separate process or thread (``IMailQueueProcessor``) watches the queue and delivers messages asynchronously. Since the queue is located in the file system, it survives Zope restarts or crashes and the mail is not lost. The queue processor can implement batching to keep the server load low. If the delivery utility is a ``IDirectMailDelivery``, it delivers messages synchronously during the transaction commit. This is not a very good idea, as it makes the user wait. Note that transaction commits must not fail, but that is not a problem, because mail delivery problems dispatch an event instead of raising an exception. However, there is a problem -- sending events causes unknown code to be executed during the transaction commit phase. There should be a way to start a new transaction for event processing after this one is commited. An ``IMailQueueProcessor`` or ``IDirectMailDelivery`` actually delivers the messages by using a mailer (``IMailer``) component that encapsulates the delivery process. There currently is only one mailer: ``ISMTPMailer`` sends all messages to a relay host using SMTP. If mail delivery succeeds, an ``IMailSentEvent`` is dispatched by the mailer. If mail delivery fails, no exceptions are raised, but an `IMailErrorEvent` is dispatched by the mailer. ======= CHANGES ======= 3.7.5 (2012-05-23) ------------------ - Ensured that the 'queuedDelivery' directive has the same discriminator as the 'directDelivery' directive (they are mutually incompatible). https://bugs.launchpad.net/zope.sendmail/+bug/191143 - Avoid requeuing messages after an SMTP "recipients refused" error. https://bugs.launchpad.net/zope.sendmail/+bug/1003288 3.7.4 (2010-10-01) ------------------ - Handle unicode usernames and passwords, encoding them to UTF-8. Fix for https://bugs.launchpad.net/zope.sendmail/+bug/597143 3.7.3 (2010-09-25) ------------------ - Added not declared, but needed test dependency on `zope.component [test]`. 3.7.2 (2010-04-30) ------------------ - Removed no longer required testing dependency on zope.testing. - Maildir storage for queue can now handle unicode passed in for message or to/from addresses (change backported from repoze.sendmail). - Tests use stdlib doctest instead of zope.testing.doctest. 3.7.1 (2010-01-13) ------------------ - Backward compatibility import of zope.sendmail.queue.QueueProcessorThread in zope.sendmail.delivery. 3.7.0 (2010-01-12) ------------------ - Removed dependency on ``zope.security``: the security support is optional, and only available if the ``zope.security`` package is available. This change is similar to the optional security support introduced in ``zope.component`` 3.8.0, and in fact it uses the same helpers. - Sort by modification time the messages in zope.sendmail.maildir so earlier messages are sent before later messages during queue processing. - Added the new parameter ``processorThread`` to the queuedDelivery ZCML directive: if False, the QueueProcessorThread is not started and thus an independent process must process the queue; it defaults to True for b/c. - Provide a console script ``zope-sendmail`` which can be used to process the delivery queue in case processorThread is False. The console script can either process the messages in the queue once, or run in "daemon" mode. 3.6.1 (2009-11-16) ------------------ - Depend on ``zope.component`` >= 3.8.0, which supports the new semantic of zope.component.zcml.proxify needed by zope.sendmail.zcml. 3.6.0 (2009-09-14) ------------------ - Use simple vocabulary factory function instead of custom `UtilityTerm` and `UtilityVocabulary` classes, copied from ``zope.app.component`` in the previous release. - Depend on the ``transaction`` package instead of ``ZODB3``. - Remove zcml slugs and zpkg-related files. - Work around problem when used with Python >=2.5.1. See https://bugs.edge.launchpad.net/zope.sendmail/+bug/413335 . 3.5.1 (2009-01-26) ------------------ - Copied over the UtilityTerm and UtilityVocabulary implementation from zope.app.component to avoid a dependency. - Work around a problem when smtp quit fails, the mail was considered not delivered where just the quit failed. 3.5.0 (2008-07-05) ------------------ - final release (identical with 3.5.0b2) 3.5.0b2 (2007-12-19) -------------------- - If the SMTP server rejects a message (for example, when the sender or recipient address is malformed), that email stays in the queue forever (https://bugs.launchpad.net/zope3/+bug/157104). 3.5.0b1 (2007-11-08) -------------------- - Added README.txt - Can now talk to servers that don't implement EHLO - Fix bug that caused files with very long names to be created - Fix for https://bugs.launchpad.net/zope3/+bug/157104: move aside mail that's causing 5xx server responses. 3.5.0a2 (2007-10-23) -------------------- - Cleaned up ``does_esmtp`` in faux SMTP connection classes provided by the tests. - If the ``QueueProcessorThread`` is asked to stop while sending messages, do so after sending the current message; previously if there were many, many messages to send, the thread could stick around for quite a while. 3.5.0a1 (2007-10-23) -------------------- - ``QueueProcessorThread`` now accepts an optional parameter *interval* for defining how often to process the mail queue (default is 3 seconds) - Several ``QueueProcessorThreads`` (either in the same process, or multiple processes) can now deliver messages from a single maildir without duplicates being sent. 3.4.0 (2007-08-20) -------------------- - Bugfix: Don't keep open files around for every email message to be sent on transaction commit. People who try to send many emails in a single transaction now will not run out of file descriptors. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to ``zope.sendmail`` from Zope 3.4.0a1. Platform: UNKNOWN zope.sendmail-3.7.5/bootstrap.py0000664000175000017500000000742211757162701017103 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 111765 2010-04-30 21:59:09Z hannosch $ """ import os, shutil, sys, tempfile, urllib2 from optparse import OptionParser tmpeggs = tempfile.mkdtemp() is_jython = sys.platform.startswith('java') # parsing arguments parser = OptionParser() parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="distribute", default=False, help="Use Disribute rather than Setuptools.") parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args += ['-c', options.config_file] if options.version is not None: VERSION = '==%s' % options.version else: VERSION = '' USE_DISTRIBUTE = options.distribute args = args + ['bootstrap'] to_reload = False try: import pkg_resources if not hasattr(pkg_resources, '_distribute'): to_reload = True raise ImportError except ImportError: ez = {} if USE_DISTRIBUTE: exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) else: exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) if to_reload: reload(pkg_resources) else: import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set if USE_DISTRIBUTE: requirement = 'distribute' else: requirement = 'setuptools' if is_jython: import subprocess assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', quote(tmpeggs), 'zc.buildout' + VERSION], env=dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ).wait() == 0 else: assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse(requirement)).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout' + VERSION) import zc.buildout.buildout zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) zope.sendmail-3.7.5/setup.py0000664000175000017500000000441111757162701016221 0ustar pangolinpangolin############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.sendmail package""" from setuptools import setup, find_packages tests_require=[ 'zope.security', 'zope.component [test]', ] setup(name='zope.sendmail', version='3.7.5', url='http://pypi.python.org/pypi/zope.sendmail', license='ZPL 2.1', description='Zope sendmail', author='Zope Corporation and Contributors', author_email='zope-dev@zope.org', long_description='\n\n'.join([ open('README.txt').read(), open('CHANGES.txt').read(), ]), packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope',], tests_require=tests_require, extras_require=dict(test=tests_require), install_requires=['setuptools', 'transaction', 'zope.i18nmessageid', 'zope.interface', 'zope.schema', # it's only needed for vocabulary, zcml and tests 'zope.component>=3.8.0', # these are only needed for zcml 'zope.configuration', ], include_package_data = True, zip_safe = False, entry_points=""" [console_scripts] zope-sendmail = zope.sendmail.queue:run """ ) zope.sendmail-3.7.5/buildout.cfg0000664000175000017500000000060111757162701017014 0ustar pangolinpangolin[buildout] develop = . parts = test coverage-test coverage-report [test] recipe = zc.recipe.testrunner eggs = zope.sendmail [test] [coverage-test] recipe = zc.recipe.testrunner eggs = zope.sendmail defaults = ['--coverage', '../../coverage'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('coverage', 'coverage/report') zope.sendmail-3.7.5/CHANGES.txt0000664000175000017500000001103411757162701016317 0ustar pangolinpangolin======= CHANGES ======= 3.7.5 (2012-05-23) ------------------ - Ensured that the 'queuedDelivery' directive has the same discriminator as the 'directDelivery' directive (they are mutually incompatible). https://bugs.launchpad.net/zope.sendmail/+bug/191143 - Avoid requeuing messages after an SMTP "recipients refused" error. https://bugs.launchpad.net/zope.sendmail/+bug/1003288 3.7.4 (2010-10-01) ------------------ - Handle unicode usernames and passwords, encoding them to UTF-8. Fix for https://bugs.launchpad.net/zope.sendmail/+bug/597143 3.7.3 (2010-09-25) ------------------ - Added not declared, but needed test dependency on `zope.component [test]`. 3.7.2 (2010-04-30) ------------------ - Removed no longer required testing dependency on zope.testing. - Maildir storage for queue can now handle unicode passed in for message or to/from addresses (change backported from repoze.sendmail). - Tests use stdlib doctest instead of zope.testing.doctest. 3.7.1 (2010-01-13) ------------------ - Backward compatibility import of zope.sendmail.queue.QueueProcessorThread in zope.sendmail.delivery. 3.7.0 (2010-01-12) ------------------ - Removed dependency on ``zope.security``: the security support is optional, and only available if the ``zope.security`` package is available. This change is similar to the optional security support introduced in ``zope.component`` 3.8.0, and in fact it uses the same helpers. - Sort by modification time the messages in zope.sendmail.maildir so earlier messages are sent before later messages during queue processing. - Added the new parameter ``processorThread`` to the queuedDelivery ZCML directive: if False, the QueueProcessorThread is not started and thus an independent process must process the queue; it defaults to True for b/c. - Provide a console script ``zope-sendmail`` which can be used to process the delivery queue in case processorThread is False. The console script can either process the messages in the queue once, or run in "daemon" mode. 3.6.1 (2009-11-16) ------------------ - Depend on ``zope.component`` >= 3.8.0, which supports the new semantic of zope.component.zcml.proxify needed by zope.sendmail.zcml. 3.6.0 (2009-09-14) ------------------ - Use simple vocabulary factory function instead of custom `UtilityTerm` and `UtilityVocabulary` classes, copied from ``zope.app.component`` in the previous release. - Depend on the ``transaction`` package instead of ``ZODB3``. - Remove zcml slugs and zpkg-related files. - Work around problem when used with Python >=2.5.1. See https://bugs.edge.launchpad.net/zope.sendmail/+bug/413335 . 3.5.1 (2009-01-26) ------------------ - Copied over the UtilityTerm and UtilityVocabulary implementation from zope.app.component to avoid a dependency. - Work around a problem when smtp quit fails, the mail was considered not delivered where just the quit failed. 3.5.0 (2008-07-05) ------------------ - final release (identical with 3.5.0b2) 3.5.0b2 (2007-12-19) -------------------- - If the SMTP server rejects a message (for example, when the sender or recipient address is malformed), that email stays in the queue forever (https://bugs.launchpad.net/zope3/+bug/157104). 3.5.0b1 (2007-11-08) -------------------- - Added README.txt - Can now talk to servers that don't implement EHLO - Fix bug that caused files with very long names to be created - Fix for https://bugs.launchpad.net/zope3/+bug/157104: move aside mail that's causing 5xx server responses. 3.5.0a2 (2007-10-23) -------------------- - Cleaned up ``does_esmtp`` in faux SMTP connection classes provided by the tests. - If the ``QueueProcessorThread`` is asked to stop while sending messages, do so after sending the current message; previously if there were many, many messages to send, the thread could stick around for quite a while. 3.5.0a1 (2007-10-23) -------------------- - ``QueueProcessorThread`` now accepts an optional parameter *interval* for defining how often to process the mail queue (default is 3 seconds) - Several ``QueueProcessorThreads`` (either in the same process, or multiple processes) can now deliver messages from a single maildir without duplicates being sent. 3.4.0 (2007-08-20) -------------------- - Bugfix: Don't keep open files around for every email message to be sent on transaction commit. People who try to send many emails in a single transaction now will not run out of file descriptors. 3.4.0a1 (2007-04-22) -------------------- Initial release as a separate project, corresponds to ``zope.sendmail`` from Zope 3.4.0a1.