diff --git a/salmon/bounce.py b/salmon/bounce.py index 48e00ca..df5e2a8 100644 --- a/salmon/bounce.py +++ b/salmon/bounce.py @@ -98,6 +98,7 @@ u'77': u'Message integrity failure', } + def match_bounce_headers(msg): """ Goes through the headers in a potential bounce message recursively @@ -236,7 +237,6 @@ def __init__(self, headers, score): self.notification = self.content_parts.get('notification', None) - def is_hard(self): """ Tells you if this was a hard bounce, which is determined by the message diff --git a/salmon/commands.py b/salmon/commands.py index 99e1099..bd56c6d 100644 --- a/salmon/commands.py +++ b/salmon/commands.py @@ -72,9 +72,10 @@ def log_command(parser): each message it receives and also stores it to the run/queue so that you can make sure it was received in testing. """ - def command(port, host, pid, chdir, chroot=None, uid=False, gid=False, umask=False, force=False, debug=False, daemon=True): - loader = lambda: utils.make_fake_settings(host, port) - utils.start_server(pid, force, chroot, chdir, uid, gid, umask, loader, debug, daemon) + def command(port, host, pid, chdir, chroot=None, uid=False, gid=False, umask=False, + force=False, debug=False, daemon=True): + utils.start_server(pid, force, chroot, chdir, uid, gid, umask, + lambda: utils.make_fake_settings(host, port), debug, daemon) parser.set_defaults(func=command) @@ -92,8 +93,10 @@ def command(port, host, pid, chdir, chroot=None, uid=False, gid=False, umask=Fal uid_group.add_argument("--gid", type=int, default=argparse.SUPPRESS, help="run with this group id") daemon_group = parser.add_mutually_exclusive_group() - daemon_group.add_argument("--no-daemon", default=argparse.SUPPRESS, dest="daemon", action="store_false", help="start server in foreground") - daemon_group.add_argument("--daemon", default=argparse.SUPPRESS, dest="daemon", action="store_true", help="start server as daemon (default)") + daemon_group.add_argument("--no-daemon", default=argparse.SUPPRESS, dest="daemon", action="store_false", + help="start server in foreground") + daemon_group.add_argument("--daemon", default=argparse.SUPPRESS, dest="daemon", action="store_true", + help="start server as daemon (default)") def send_command(parser): @@ -101,12 +104,14 @@ def send_command(parser): Sends an email to someone as a test message. See the sendmail command for a sendmail replacement. """ - def command(port, host, username=None, password=None, ssl=None, starttls=None, lmtp=None, sender=None, to=None, subject=None, body=None, attach=None): + def command(port, host, username=None, password=None, ssl=None, starttls=None, lmtp=None, + sender=None, to=None, subject=None, body=None, attach=None): message = mail.MailResponse(From=sender, To=to, Subject=subject, Body=body) if attach: message.attach(attach) - relay = server.Relay(host, port=port, username=username, password=password, ssl=ssl, starttls=starttls, lmtp=lmtp, debug=False) + relay = server.Relay(host, port=port, username=username, password=password, ssl=ssl, + starttls=starttls, lmtp=lmtp, debug=False) relay.deliver(message) parser.set_defaults(func=command) @@ -153,8 +158,8 @@ def start_command(parser): Runs a salmon server out of the current directory """ def command(pid, force, chdir, boot, chroot=False, uid=False, gid=False, umask=False, debug=False, daemon=True): - loader = lambda: utils.import_settings(True, boot_module=boot) - utils.start_server(pid, force, chroot, chdir, uid, gid, umask, loader, debug, daemon) + utils.start_server(pid, force, chroot, chdir, uid, gid, umask, + lambda: utils.import_settings(True, boot_module=boot), debug, daemon) parser.set_defaults(func=command) @@ -171,8 +176,10 @@ def command(pid, force, chdir, boot, chroot=False, uid=False, gid=False, umask=F uid_group.add_argument("--gid", type=int, default=argparse.SUPPRESS, help="run with this group id") daemon_group = parser.add_mutually_exclusive_group() - daemon_group.add_argument("--no-daemon", default=argparse.SUPPRESS, dest="daemon", action="store_false", help="start server in foreground") - daemon_group.add_argument("--daemon", default=argparse.SUPPRESS, dest="daemon", action="store_true", help="start server as daemon (default)") + daemon_group.add_argument("--no-daemon", default=argparse.SUPPRESS, dest="daemon", action="store_false", + help="start server in foreground") + daemon_group.add_argument("--daemon", default=argparse.SUPPRESS, dest="daemon", action="store_true", + help="start server as daemon (default)") def stop_command(parser): @@ -212,8 +219,10 @@ def command(pid, kill=False, all=False): parser.set_defaults(func=command) parser.add_argument("--pid", default=DEFAULT_PID_FILE, help="path to pid file") - parser.add_argument("-f", "--force", dest="kill", default=DEFAULT_PID_FILE, action="store_true", help="force stop server") - parser.add_argument("--all", default=argparse.SUPPRESS, help="stops all servers with .pid files in the specified directory") + parser.add_argument("-f", "--force", dest="kill", default=DEFAULT_PID_FILE, action="store_true", + help="force stop server") + parser.add_argument("--all", default=argparse.SUPPRESS, + help="stops all servers with .pid files in the specified directory") def status_command(parser): @@ -261,10 +270,13 @@ def command(name, pop=False, get=False, keys=False, remove=False, count=False, c parser.set_defaults(func=command) command_group = parser.add_mutually_exclusive_group(required=True) - command_group.add_argument("--pop", action="store_true", default=argparse.SUPPRESS, help="pop a message from queue") + command_group.add_argument("--pop", action="store_true", default=argparse.SUPPRESS, + help="pop a message from queue") command_group.add_argument("--get", metavar="KEY", default=argparse.SUPPRESS, help="get key from queue") - command_group.add_argument("--remove", metavar="KEY", default=argparse.SUPPRESS, help="remove chosen key from queue") - command_group.add_argument("--count", action="store_true", default=argparse.SUPPRESS, help="count messages in queue") + command_group.add_argument("--remove", metavar="KEY", default=argparse.SUPPRESS, + help="remove chosen key from queue") + command_group.add_argument("--count", action="store_true", default=argparse.SUPPRESS, + help="count messages in queue") command_group.add_argument("--clear", action="store_true", default=argparse.SUPPRESS, help="clear queue") command_group.add_argument("--keys", action="store_true", default=argparse.SUPPRESS, help="print queue keys") @@ -309,7 +321,8 @@ def command(modules, path=os.getcwd(), test=""): parser.set_defaults(func=command) parser.add_argument("--path", default=argparse.SUPPRESS, help="search path for modules") - parser.add_argument("modules", metavar="module", nargs="*", default=["config.testing"], help="config modules to process") + parser.add_argument("modules", metavar="module", nargs="*", default=["config.testing"], + help="config modules to process") parser.add_argument("--test", metavar="EMAIL", default=argparse.SUPPRESS, help="test address") @@ -332,7 +345,8 @@ def command(project, force=False): parser.set_defaults(func=command) parser.add_argument("project", help="project name") - parser.add_argument("-f", "--force", action="store_true", default=argparse.SUPPRESS, help="overwrite existing directories") + parser.add_argument("-f", "--force", action="store_true", default=argparse.SUPPRESS, + help="overwrite existing directories") def cleanse_command(parser): @@ -400,7 +414,8 @@ def command(input, host, port, lmtp=None, debug=False): # Bring it all together -_parser = argparse.ArgumentParser(description="Python mail server", epilog=copyright_notice, formatter_class=argparse.RawDescriptionHelpFormatter) +_parser = argparse.ArgumentParser(description="Python mail server", epilog=copyright_notice, + formatter_class=argparse.RawDescriptionHelpFormatter) _parser.add_argument("-v", "--version", action="version", version=version_info) _subparsers = _parser.add_subparsers(metavar="") @@ -408,6 +423,6 @@ def command(input, host, port, lmtp=None, debug=False): for cmd, help_txt in COMMANDS: function = globals()["{0}_command".format(cmd)] - cmd_parser = _subparsers.add_parser(cmd, description=function.__doc__, - help=help_txt, formatter_class=argparse.ArgumentDefaultsHelpFormatter) + cmd_parser = _subparsers.add_parser(cmd, description=function.__doc__, help=help_txt, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) function(cmd_parser) diff --git a/salmon/confirm.py b/salmon/confirm.py index 9d702fd..8a28048 100644 --- a/salmon/confirm.py +++ b/salmon/confirm.py @@ -15,9 +15,11 @@ """ from __future__ import print_function, unicode_literals +from email.utils import parseaddr import uuid + from salmon import queue, view -from email.utils import parseaddr + class ConfirmationStorage(object): """ @@ -81,6 +83,7 @@ def store(self, target, from_address, expected_secret, pending_message_id): self.confirmations[self.key(target, from_address)] = (expected_secret, pending_message_id) + class ConfirmationEngine(object): """ The confirmation engine is what does the work of sending a confirmation, @@ -120,7 +123,6 @@ def delete_pending(self, pending_id): """ self.pending.remove(pending_id) - def cancel(self, target, from_address, expect_secret): """ Used to cancel a pending confirmation. diff --git a/salmon/data/prototype/tests/handlers/open_relay_tests.py b/salmon/data/prototype/tests/handlers/open_relay_tests.py index 76a2501..58143dd 100644 --- a/salmon/data/prototype/tests/handlers/open_relay_tests.py +++ b/salmon/data/prototype/tests/handlers/open_relay_tests.py @@ -1,7 +1,6 @@ -from nose.tools import * -from salmon.testing import * -import os -from salmon import server +from nose.tools import * # noqa +from salmon.testing import relay, RouterConversation, queue + relay = relay(port=8823) client = RouterConversation("somedude@localhost", "requests_tests") @@ -25,5 +24,6 @@ def test_drops_open_relay_messages(): """ client.begin() client.say("tester@badplace.notinterwebs", "Relay should not happen") - assert queue().count() == 0, "You are configured currently to accept everything. You should change config/settings.py router_defaults so host is your actual host name that will receive mail." - + assert queue().count() == 0, "You are configured currently to accept everything. " \ + "You should change config/settings.py router_defaults " \ + "so host is your actual host name that will receive mail." diff --git a/salmon/encoding.py b/salmon/encoding.py index 42038c2..bfdd260 100644 --- a/salmon/encoding.py +++ b/salmon/encoding.py @@ -72,12 +72,10 @@ from email import encoders from email.charset import Charset from email.message import Message -from email.mime.base import MIMEBase from email.utils import parseaddr import email import re import string -import sys import warnings import chardet @@ -94,10 +92,13 @@ ENCODING_END_REGEX = re.compile(r"\?=", REGEX_OPTS) INDENT_REGEX = re.compile(r"\n\s+") -VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc', 'Bcc'] +def VALUE_IS_EMAIL_ADDRESS(v): + return "@" in v + + class EncodingError(Exception): """Thrown when there is an encoding error.""" pass @@ -206,7 +207,7 @@ def __delitem__(self, key): del self.mime_part[key] def __nonzero__(self): - return self.body != None or len(self.mime_part) > 0 or len(self.parts) > 0 + return self.body is not None or len(self.mime_part) > 0 or len(self.parts) > 0 def items(self): return [(normalize_header(key), header_from_mime_encoding(header)) for key, header in self.mime_part.items()] @@ -246,7 +247,6 @@ def body(self, value): ctype, params = self.content_encoding['Content-Type'] self.mime_part.set_payload(value, params.get("charset", None)) - def attach_file(self, filename, data, ctype, disposition): """ A file attachment is a raw attachment with a disposition that @@ -262,7 +262,6 @@ def attach_file(self, filename, data, ctype, disposition): {'filename': filename}) self.parts.append(part) - def attach_text(self, data, ctype): """ This attaches a simpler text encoded part, which doesn't have a @@ -302,7 +301,7 @@ def add_text(self, content, charset=None): # this is text, so encode it in canonical form if charset is not None: warnings.warn("You are adding text that is neither ASCII nor UTF-8. Please reconsider your choice.", - UnicodeWarning) + UnicodeWarning) charset = charset or 'ascii' try: @@ -337,8 +336,9 @@ def extract_payload(self, mail): def __repr__(self): return "" % (self.mimetype, self['Content-Type'], - self['Content-Disposition'], - self.is_multipart()) + self['Content-Disposition'], + self.is_multipart()) + def from_message(message, parent=None): """ @@ -372,7 +372,8 @@ def to_message(mail): ctype = 'text/plain' else: if mail.parts: - assert ctype.startswith("multipart") or ctype.startswith("message"), "Content type should be multipart or message, not %r" % ctype + assert ctype.startswith("multipart") or ctype.startswith("message"), \ + "Content type should be multipart or message, not %r" % ctype # adjust the content type according to what it should be now mail.content_encoding['Content-Type'] = (ctype, params) @@ -456,7 +457,8 @@ def parse_parameter_header(message, header): params_dict = dict(params) for key in CONTENT_ENCODING_REMOVED_PARAMS: - if key in params_dict: del params_dict[key] + if key in params_dict: + del params_dict[key] return value, params_dict else: @@ -495,7 +497,8 @@ def properly_encode_header(value, encoder, not_email): def header_to_mime_encoding(value, not_email=False): - if not value: return "" + if not value: + return "" encoder = Charset(DEFAULT_ENCODING) if isinstance(value, list): diff --git a/salmon/handlers/forward.py b/salmon/handlers/forward.py index d1f9b14..3cc7b35 100644 --- a/salmon/handlers/forward.py +++ b/salmon/handlers/forward.py @@ -12,9 +12,11 @@ of allowed hosts your machine answers to and only forwards those. """ +import logging + from salmon.routing import route, stateless from salmon.utils import settings -import logging + @route("(to)@(host)", to=".+", host=".+") @stateless @@ -22,4 +24,3 @@ def START(message, to=None, host=None): """Forwards every mail it gets to the relay. BE CAREFUL WITH THIS.""" logging.debug("MESSAGE to %s@%s forwarded to the relay host.", to, host) settings.relay.deliver(message) - diff --git a/salmon/handlers/log.py b/salmon/handlers/log.py index 4ec10be..5959e02 100644 --- a/salmon/handlers/log.py +++ b/salmon/handlers/log.py @@ -4,13 +4,13 @@ receives and dumps it to the logging.debug stream. """ -from salmon.routing import route, stateless import logging +from salmon.routing import route, stateless + + @route("(to)@(host)", to=".+", host=".+") @stateless def START(message, to=None, host=None): """This is stateless and handles every email no matter what, logging what it receives.""" logging.debug("MESSAGE to %s@%s:\n%s", to, host, str(message)) - - diff --git a/salmon/handlers/queue.py b/salmon/handlers/queue.py index dc5bfe7..c92d9b8 100644 --- a/salmon/handlers/queue.py +++ b/salmon/handlers/queue.py @@ -5,9 +5,11 @@ the salmon queue command. """ +import logging + from salmon.routing import route_like, stateless, nolocking from salmon import queue, handlers -import logging + @route_like(handlers.log.START) @stateless @@ -17,6 +19,6 @@ def START(message, to=None, host=None): @stateless and routes however handlers.log.START routes (everything). Has @nolocking, but that's alright since it's just writing to a Maildir. """ + logging.debug("MESSAGE to %s@%s added to queue.", to, host) q = queue.Queue('run/queue') q.push(message) - diff --git a/salmon/mail.py b/salmon/mail.py index ca68ab3..5e729e1 100644 --- a/salmon/mail.py +++ b/salmon/mail.py @@ -22,7 +22,7 @@ # You can change this to 'Delivered-To' on servers that support it like Postfix -ROUTABLE_TO_HEADER='to' +ROUTABLE_TO_HEADER = 'to' def _decode_header_randomness(addr): @@ -155,15 +155,12 @@ def is_bounce(self, threshold=0.3): if not self.bounce: self.bounce = bounce.detect(self) - if self.bounce.score > threshold: - return True - else: - return False + return self.bounce.score > threshold @property def original(self): warnings.warn("MailRequest.original is deprecated, use MailRequest.Data instead", - category=DeprecationWarning, stacklevel=2) + category=DeprecationWarning, stacklevel=2) return self.Data @@ -224,10 +221,13 @@ def attach(self, filename=None, content_type=None, data=None, disposition=None): assert content_type, "No content type given, and couldn't guess from the filename: %r" % filename - self.attachments.append({'filename': filename, - 'content_type': content_type, - 'data': data, - 'disposition': disposition,}) + self.attachments.append({ + 'filename': filename, + 'content_type': content_type, + 'data': data, + 'disposition': disposition, + }) + def attach_part(self, part): """ Attaches a raw MailBase part from a MailRequest (or anywhere) @@ -259,7 +259,6 @@ def clear(self): del self.base.parts[:] self.multipart = False - def update(self, message): """ Used to easily set a bunch of headers from another dict like object. diff --git a/salmon/queue.py b/salmon/queue.py index 8c04c11..6d1d236 100644 --- a/salmon/queue.py +++ b/salmon/queue.py @@ -20,6 +20,7 @@ # email we put in a queue HASHED_HOSTNAME = hashlib.md5(socket.gethostname().encode("utf-8")).hexdigest() + class SafeMaildir(mailbox.Maildir): def _create_tmp(self): now = time.time() @@ -86,7 +87,7 @@ def __init__(self, queue_dir, safe=False, pop_limit=0, oversize_dir=None): if oversize_dir: if not os.path.exists(oversize_dir): - osmb = mailbox.Maildir(oversize_dir) + mailbox.Maildir(oversize_dir) self.oversize_dir = os.path.join(oversize_dir, "new") @@ -110,16 +111,16 @@ def pop(self): It returns a (key, message) tuple for that item. """ for key in self.mbox.iterkeys(): - over, over_name = self.oversize(key) + over, over_name = self.oversize(key) if over: if self.oversize_dir: logging.info("Message key %s over size limit %d, moving to %s.", - key, self.pop_limit, self.oversize_dir) + key, self.pop_limit, self.oversize_dir) os.rename(over_name, os.path.join(self.oversize_dir, key)) else: logging.info("Message key %s over size limit %d, DELETING (set oversize_dir).", - key, self.pop_limit) + key, self.pop_limit) os.unlink(over_name) else: try: @@ -150,7 +151,6 @@ def get(self, key): logging.exception("Failed to decode message: %s; msg_data: %r", exc, msg_data) return None - def remove(self, key): """Removes the queue, but not returned.""" self.mbox.remove(key) diff --git a/salmon/routing.py b/salmon/routing.py index 44f0879..09e681b 100644 --- a/salmon/routing.py +++ b/salmon/routing.py @@ -54,7 +54,6 @@ import re import logging import sys -import email.utils import shelve import threading @@ -66,7 +65,10 @@ ROUTE_FIRST_STATE = 'START' LOG = logging.getLogger("routing") -DEFAULT_STATE_KEY = lambda mod, msg: mod + + +def DEFAULT_STATE_KEY(mod, msg): + return mod class StateStorage(object): @@ -178,7 +180,6 @@ def clear(self): self.states.close() - class RoutingBase(object): """ The self is a globally accessible class that is actually more like a @@ -274,7 +275,6 @@ def get_state(self, module_name, message): key = self.state_key(module_name, message) return self.STATE_STORE.get(key, message.From) - def in_state(self, func, message): """ Determines if this function is in the state for the to/from in the @@ -350,7 +350,8 @@ def deliver(self, message): transition to ERROR state and call your function right away. It will then stay in the ERROR state unless you return a different one. """ - if self.RELOAD: self.reload() + if self.RELOAD: + self.reload() called_count = 0 @@ -368,7 +369,6 @@ def deliver(self, message): if called_count == 0: self._enqueue_undeliverable(message) - def call_safely(self, func, message, kwargs): """ Used by self to call a function and log exceptions rather than @@ -379,7 +379,7 @@ def call_safely(self, func, message, kwargs): try: func(message, **kwargs) LOG.debug("Message to %s was handled by %s.%s", - message.To, func.__module__, func.__name__) + message.To, func.__module__, func.__name__) except SMTPError: raise except Exception: @@ -393,7 +393,6 @@ def call_safely(self, func, message, kwargs): else: raise - def clear_states(self): """Clears out the states for unit testing.""" with self.lock: @@ -405,7 +404,6 @@ def clear_routes(self): self.REGISTERED.clear() del self.ORDER[:] - def load(self, handlers): """ Loads the listed handlers making them available for processing. @@ -444,8 +442,10 @@ def reload(self): else: raise + Router = RoutingBase() + class route(object): """ The @route decorator is attached to state handlers to configure them in the @@ -496,7 +496,7 @@ def __call__(self, func): if salmon_setting(func, 'stateless'): @wraps(func) def routing_wrapper(message, *args, **kw): - next_state = func(message, *args, **kw) + func(message, *args, **kw) else: @wraps(func) def routing_wrapper(message, *args, **kw): @@ -536,6 +536,7 @@ def salmon_setting(func, key): def has_salmon_settings(func): return "_salmon_settings" in func.__dict__ + def assert_salmon_settings(func): """Used to make sure that the func has been setup by a routing decorator.""" assert has_salmon_settings(func), "Function %s has not be setup with a @route first." % func.__name__ @@ -579,6 +580,7 @@ def stateless(func): return func + def nolocking(func): """ Normally salmon.routing.Router has a lock around each call to all handlers @@ -596,6 +598,7 @@ def nolocking(func): func._salmon_settings['nolocking'] = True return func + def state_key_generator(func): """ Used to indicate that a function in your handlers should be used diff --git a/salmon/server.py b/salmon/server.py index 6d04ffe..edfb9ff 100644 --- a/salmon/server.py +++ b/salmon/server.py @@ -8,7 +8,6 @@ import logging import smtpd import smtplib -import socket import threading import time import traceback @@ -143,12 +142,10 @@ def resolve_relay_host(self, To): logging.debug("Delivering to MX record %r for target %r", mx_host, target_host) return mx_host - def __repr__(self): """Used in logging and debugging to indicate where this relay goes.""" return "" % (self.hostname, self.port) - def reply(self, original, From, Subject, Body): """Calls self.send but with the from and to of the original message reversed.""" self.send(original.From, From=From, Subject=Subject, Body=Body) @@ -212,15 +209,14 @@ def start(self): fires off threads and waits until they are done. """ logging.info("SMTPReceiver started on %s:%d.", self.host, self.port) - self.poller = threading.Thread(target=asyncore.loop, - kwargs={'timeout':0.1, 'use_poll':True}) + self.poller = threading.Thread(target=asyncore.loop, kwargs={'timeout': 0.1, 'use_poll': True}) self.poller.start() def handle_accept(self): pair = self.accept() if pair is not None: conn, addr = pair - channel = SMTPChannel(self, conn, addr) + SMTPChannel(self, conn, addr) def process_message(self, Peer, From, To, Data): """ @@ -235,7 +231,7 @@ def process_message(self, Peer, From, To, Data): return str(err) except Exception: logging.exception("Exception while processing message from Peer: %r, From: %r, to To %r.", - Peer, From, To) + Peer, From, To) undeliverable_message(Data, "Error in message %r:%r:%r, look in logs." % (Peer, From, To)) def close(self): @@ -274,8 +270,7 @@ def start(self): fires off threads and waits until they are done. """ logging.info("LMTPReceiver started on %s.", self.socket) - self.poller = threading.Thread(target=asyncore.loop, - kwargs={'timeout':0.1, 'use_poll':True}) + self.poller = threading.Thread(target=asyncore.loop, kwargs={'timeout': 0.1, 'use_poll': True}) self.poller.start() def process_message(self, Peer, From, To, Data): @@ -292,7 +287,7 @@ def process_message(self, Peer, From, To, Data): return str(err) except Exception: logging.exception("Exception while processing message from Peer: %r, From: %r, to To %r.", - Peer, From, To) + Peer, From, To) undeliverable_message(Data, "Error in message %r:%r:%r, look in logs." % (Peer, From, To)) def close(self): diff --git a/salmon/testing.py b/salmon/testing.py index c288cd7..d03f947 100644 --- a/salmon/testing.py +++ b/salmon/testing.py @@ -23,11 +23,10 @@ import re -import six +from nose.tools import assert_equal from salmon import server, routing, mail from salmon.queue import Queue -from nose.tools import assert_equal TEST_QUEUE = "run/queue" diff --git a/salmon/utils.py b/salmon/utils.py index 8e5058d..13f4efa 100644 --- a/salmon/utils.py +++ b/salmon/utils.py @@ -125,7 +125,8 @@ def start_server(pid, force, chroot, chdir, uid, gid, umask, settings_loader, de if uid and gid: drop_priv(uid, gid) elif uid or gid: - logging.warning("You probably meant to give a uid and gid, but you gave: uid=%r, gid=%r. Will not change to any user.", uid, gid) + logging.warning("You probably meant to give a uid and gid, but you gave: uid=%r, gid=%r. " + "Will not change to any user.", uid, gid) settings.receiver.start() diff --git a/salmon/view.py b/salmon/view.py index bc3e923..d268fa8 100644 --- a/salmon/view.py +++ b/salmon/view.py @@ -8,11 +8,10 @@ from __future__ import print_function, unicode_literals from salmon import mail -import email -import warnings LOADER = None + def load(template): """ Uses the registered loader to load the template you ask for. @@ -90,4 +89,3 @@ def attach(msg, variables, template, filename=None, content_type=None, msg.attach(filename=filename, data=data, content_type=content_type, disposition=disposition) - diff --git a/tests/config/settings.py b/tests/config/settings.py index 9ba7bf6..32cf58b 100644 --- a/tests/config/settings.py +++ b/tests/config/settings.py @@ -1,6 +1,4 @@ # This file contains python variables that configure Salmon for email processing. -import logging - relay_config = {'host': 'localhost', 'port': 8825} receiver_config = {'host': 'localhost', 'port': 8823} @@ -11,10 +9,9 @@ template_config = {'dir': 'salmon_tests', 'module': '.'} -BLOG_BASE="app/data/posts" +BLOG_BASE = "app/data/posts" # this is for when you run the config.queue boot queue_config = {'queue': 'run/deferred', 'sleep': 10} queue_handlers = [] - diff --git a/tests/config/testing.py b/tests/config/testing.py index c66098f..3d074b1 100644 --- a/tests/config/testing.py +++ b/tests/config/testing.py @@ -14,7 +14,7 @@ # the relay host to actually send the final message to (set debug=1 to see what # the relay is saying to the log server). -settings.relay = Relay(host=settings.relay_config['host'], +settings.relay = Relay(host=settings.relay_config['host'], port=settings.relay_config['port'], debug=0) @@ -22,8 +22,8 @@ Router.defaults(**settings.router_defaults) Router.load(settings.handlers + settings.queue_handlers) -Router.RELOAD=False -Router.LOG_EXCEPTIONS=False +Router.RELOAD = False +Router.LOG_EXCEPTIONS = False view.LOADER = jinja2.Environment(loader=jinja2.PackageLoader('salmon_tests', 'templates')) @@ -31,4 +31,3 @@ # spell checking for you, but you need to tell pyenchant where to find itself if 'PYENCHANT_LIBRARY_PATH' not in os.environ: os.environ['PYENCHANT_LIBRARY_PATH'] = '/opt/local/lib/libenchant.dylib' - diff --git a/tests/salmon_tests/encoding_tests.py b/tests/salmon_tests/encoding_tests.py index 989609e..0673f93 100644 --- a/tests/salmon_tests/encoding_tests.py +++ b/tests/salmon_tests/encoding_tests.py @@ -5,8 +5,7 @@ import os from mock import Mock, patch -from nose.plugins.skip import SkipTest -from nose.tools import assert_equal, assert_false, assert_true, raises, with_setup +from nose.tools import assert_equal, raises, with_setup import chardet from salmon import encoding @@ -20,12 +19,12 @@ '=?iso-2022-jp?B?Zmlicm91c19mYXZvcmF0ZUB5YWhvby5jby5qcA==?=', '=?windows-1252?Q?Global_Leadership_in_HandCare_-_Consumer,\n\t_Professional_and_Industrial_Products_OTC_:_FLKI?=', - '=?windows-1252?q?Global_Leadership_in_Handcare_-_Consumer, _Auto,\n\t_Professional_&_Industrial_Products_-_OTC_:_FLKI?=', + '=?windows-1252?q?Global_Leadership_in_Handcare_-_Consumer, _Auto,\n\t_Professional_&_Industrial_Products_-_OTC_:_FLKI?=', # noqa 'I am just normal.', '=?koi8-r?B?WW91ciBtYW6ScyBzdGFtaW5hIHdpbGwgY29tZSBiYWNrIHRvIHlvdSBs?=\n\t=?koi8-r?B?aWtlIGEgYm9vbWVyYW5nLg==?=', '=?koi8-r?B?WW91IGNhbiBiZSBvbiB0b3AgaW4gYmVkcm9vbSBhZ2FpbiCWIGp1c3Qg?=\n\t=?koi8-r?B?YXNrIHVzIGZvciBhZHZpY2Uu?=', '"=?koi8-r?B?5MXMz9DSz8na18/E09TXzw==?=" ', - '=?utf-8?b?IumrlOiCsuWckuWNgOermSDihpIg6ZW35bqa6Yar6Zmi56uZIOKGkiDmlofljJbk?=\n =?utf-8?b?uInot6/nq5kiIDx2Z3hkcmp5Y2lAZG5zLmh0Lm5ldC50dz4=?=', + '=?utf-8?b?IumrlOiCsuWckuWNgOermSDihpIg6ZW35bqa6Yar6Zmi56uZIOKGkiDmlofljJbk?=\n =?utf-8?b?uInot6/nq5kiIDx2Z3hkcmp5Y2lAZG5zLmh0Lm5ldC50dz4=?=', # noqa: E501 '=?iso-8859-1?B?SOlhdnkgTel05WwgVW7uY/hk?=\n\t=?iso-8859-1?Q?=E9?=', ] @@ -364,7 +363,7 @@ def test_apply_charset_to_header_with_bad_encoding_char(): def test_odd_roundtrip_bug(): decoded_addrs = [ - u'"\u0414\u0435\u043b\u043e\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u043e" ', + u'"\u0414\u0435\u043b\u043e\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u043e" ', # noqa: E501 u'"\u8003\u53d6\u5206\u4eab" ', u'"Exquisite Replica"\n\t', ] @@ -373,6 +372,7 @@ def test_odd_roundtrip_bug(): encoded = encoding.header_to_mime_encoding(decoded) assert '<' in encoded and '"' in encoded, "Address wasn't encoded correctly:\n%s" % encoded + def test_multiple_headers(): msg = encoding.MailBase() msg["To"] = "somedude@localhost" diff --git a/tests/salmon_tests/handlers/bounce_filtered_mod.py b/tests/salmon_tests/handlers/bounce_filtered_mod.py index 04b2e5c..320f42e 100644 --- a/tests/salmon_tests/handlers/bounce_filtered_mod.py +++ b/tests/salmon_tests/handlers/bounce_filtered_mod.py @@ -2,32 +2,34 @@ from salmon.bounce import bounce_to -SOFT_RAN=False -HARD_RAN=False +SOFT_RAN = False +HARD_RAN = False + @route(".+") def SOFT_BOUNCED(message): global SOFT_RAN - SOFT_RAN=True - # remember to transition back to START or the mailer daemon + SOFT_RAN = True + # remember to transition back to START or the mailer daemon # at that host will be put in a bad state return START + @route(".+") def HARD_BOUNCED(message): global HARD_RAN - HARD_RAN=True - # remember to transition back to START or the mailer daemon + HARD_RAN = True + # remember to transition back to START or the mailer daemon # at that host will be put in a bad state return START + @route("(anything)@(host)", anything=".+", host=".+") @bounce_to(soft=SOFT_BOUNCED, hard=HARD_BOUNCED) def START(message, **kw): return END + @route_like(START) def END(message, *kw): pass - - diff --git a/tests/salmon_tests/handlers/simple_fsm_mod.py b/tests/salmon_tests/handlers/simple_fsm_mod.py index 2783b0c..fa76070 100644 --- a/tests/salmon_tests/handlers/simple_fsm_mod.py +++ b/tests/salmon_tests/handlers/simple_fsm_mod.py @@ -1,6 +1,6 @@ from __future__ import print_function -from salmon.routing import * +from salmon.routing import route, route_like, stateless, nolocking, state_key_generator, Router @state_key_generator diff --git a/tests/salmon_tests/message_tests.py b/tests/salmon_tests/message_tests.py index 15db99a..ca80113 100644 --- a/tests/salmon_tests/message_tests.py +++ b/tests/salmon_tests/message_tests.py @@ -41,7 +41,8 @@ def test_mail_request(): msg.base.append_header("To", "nobody@example.com") assert_equal(msg["To"], "somedude@localhost") assert_equal(msg.keys(), ["To", "From", "To"]) - assert_equal(msg.items(), [("To", "somedude@localhost"), ("From", "somebody@localhost"), ("To", "nobody@example.com")]) + assert_equal(msg.items(), [("To", "somedude@localhost"), ("From", "somebody@localhost"), + ("To", "nobody@example.com")]) # validate that upper and lower case work for headers assert("FroM" in msg) @@ -261,11 +262,13 @@ def test_decode_header_randomness(): assert_equal(mail._decode_header_randomness(None), set()) # with strings - assert_equal(mail._decode_header_randomness(["z@localhost", '"Z A" ']), set(["z@localhost", "z@localhost"])) + assert_equal(mail._decode_header_randomness(["z@localhost", '"Z A" ']), + set(["z@localhost", "z@localhost"])) assert_equal(mail._decode_header_randomness("z@localhost"), set(["z@localhost"])) # with bytes - assert_equal(mail._decode_header_randomness([b"z@localhost", b'"Z A" ']), set(["z@localhost", "z@localhost"])) + assert_equal(mail._decode_header_randomness([b"z@localhost", b'"Z A" ']), + set(["z@localhost", "z@localhost"])) assert_equal(mail._decode_header_randomness(b"z@localhost"), set(["z@localhost"])) # with literal nonsense diff --git a/tests/salmon_tests/routing_tests.py b/tests/salmon_tests/routing_tests.py index 52579f8..6ca7b18 100644 --- a/tests/salmon_tests/routing_tests.py +++ b/tests/salmon_tests/routing_tests.py @@ -26,7 +26,7 @@ def test_ShelveStorage(): store = ShelveStorage("run/states.db") store.set(test_ShelveStorage.__module__, "tester@localhost", "TESTED") - assert_equal( store.get(test_MemoryStorage.__module__, "tester@localhost"), "TESTED") + assert_equal(store.get(test_MemoryStorage.__module__, "tester@localhost"), "TESTED") assert_equal(store.get(test_MemoryStorage.__module__, "tester2@localhost"), "START") @@ -37,7 +37,7 @@ def test_ShelveStorage(): @with_setup(setup_salmon_dirs, teardown_salmon_dirs) def test_RoutingBase(): # check that Router is in a pristine state - assert_equal( len(Router.ORDER), 0) + assert_equal(len(Router.ORDER), 0) assert_equal(len(Router.REGISTERED), 0) setup_router(['salmon_tests.handlers.simple_fsm_mod']) diff --git a/tests/salmon_tests/server_tests.py b/tests/salmon_tests/server_tests.py index a849f62..bb006a9 100644 --- a/tests/salmon_tests/server_tests.py +++ b/tests/salmon_tests/server_tests.py @@ -20,6 +20,7 @@ ) from .setup_env import setup_salmon_dirs, teardown_salmon_dirs + SMTP_MESSAGE_DEFS = { 2: {"ok": u"250 Ok\r\n".encode()}, 3: {"ok": u"250 OK\r\n".encode()}, @@ -58,7 +59,8 @@ def test_SMTPChannel(push_mock): push_mock.reset_mock() channel.smtp_RCPT("TO: them@example.com") - assert_equal(push_mock.call_args[0][1:], (u"451 Will not accept multiple recipients in one transaction\r\n".encode(),)) + assert_equal(push_mock.call_args[0][1:], + (u"451 Will not accept multiple recipients in one transaction\r\n".encode(),)) def test_SMTPReceiver_process_message(): diff --git a/tests/salmon_tests/utils_tests.py b/tests/salmon_tests/utils_tests.py index 69e2c5b..b5c1c1a 100644 --- a/tests/salmon_tests/utils_tests.py +++ b/tests/salmon_tests/utils_tests.py @@ -1,5 +1,3 @@ -import os - from mock import patch from nose.tools import assert_equal, with_setup diff --git a/tests/salmon_tests/view_tests.py b/tests/salmon_tests/view_tests.py index 7709928..fb0dad7 100644 --- a/tests/salmon_tests/view_tests.py +++ b/tests/salmon_tests/view_tests.py @@ -23,8 +23,8 @@ def test_most_basic_form(): def test_respond_cadillac_version(): person = 'Tester' - msg = view.respond(locals(), Body='template.txt', Html='template.html', - From='test@localhost', To='receiver@localhost', Subject='Test body from "%(person)s".') + msg = view.respond(locals(), Body='template.txt', Html='template.html', From='test@localhost', + To='receiver@localhost', Subject='Test body from "%(person)s".') assert msg.Body assert msg.Html @@ -36,7 +36,8 @@ def test_respond_cadillac_version(): def test_respond_plain_text(): person = 'Tester' - msg = view.respond(locals(), Body='template.txt', From='test@localhost', To='receiver@localhost', Subject='Test body from "%(person)s".') + msg = view.respond(locals(), Body='template.txt', From='test@localhost', To='receiver@localhost', + Subject='Test body from "%(person)s".') assert msg.Body assert not msg.Html @@ -48,7 +49,8 @@ def test_respond_plain_text(): def test_respond_html_only(): person = 'Tester' - msg = view.respond(locals(), Html='template.html', From='test@localhost', To='receiver@localhost', Subject='Test body from "%(person)s".') + msg = view.respond(locals(), Html='template.html', From='test@localhost', To='receiver@localhost', + Subject='Test body from "%(person)s".') assert not msg.Body assert msg.Html @@ -59,9 +61,11 @@ def test_respond_html_only(): def test_respond_attach(): person = "hello" - mail = view.respond(locals(), Body="template.txt", From="test@localhost", To="receiver@localhost", Subject='Test body from someone.') + mail = view.respond(locals(), Body="template.txt", From="test@localhost", To="receiver@localhost", + Subject='Test body from someone.') - view.attach(mail, locals(), 'template.html', content_type="text/html", filename="template.html", disposition='attachment') + view.attach(mail, locals(), 'template.html', content_type="text/html", filename="template.html", + disposition='attachment') assert_equal(len(mail.attachments), 1) @@ -81,7 +85,8 @@ def test_respond_attach(): def test_unicode(): person = u'H\xe9avy M\xe9t\xe5l Un\xeec\xf8d\xe9' - mail = view.respond(locals(), Html="unicode.html", From="test@localhost", To="receiver@localhost", Subject='Test body from someone.') + mail = view.respond(locals(), Html="unicode.html", From="test@localhost", To="receiver@localhost", + Subject='Test body from someone.') assert str(mail)