forked from indico/newdle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c656796
commit 741c478
Showing
12 changed files
with
989 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
newdle/vendor/* linguist-vendored |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# TODO: Move this whole package into a standalone pypi package, since it's | ||
# useful in general for anyoen who wants to send emails (without using django) | ||
|
||
# The code in here is taken almost verbatim from `django.core.mail`, | ||
# which is licensed under the three-clause BSD license and is originally | ||
# available on the following URL: | ||
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/__init__.py | ||
# Credits of the original code go to the Django Software Foundation | ||
# and their contributors. | ||
|
||
""" | ||
Tools for sending email. | ||
""" | ||
from flask import current_app | ||
|
||
from .backends.base import BaseEmailBackend | ||
from .mail_utils import DNS_NAME, CachedDnsName | ||
from .module_loading_utils import import_string | ||
|
||
|
||
__all__ = ['get_connection'] | ||
|
||
|
||
def get_connection(backend=None, fail_silently=False, **kwds) -> BaseEmailBackend: | ||
"""Load an email backend and return an instance of it. | ||
If backend is None (default), use ``EMAIL_BACKEND`` from config. | ||
Both fail_silently and other keyword arguments are used in the | ||
constructor of the backend. | ||
""" | ||
klass = import_string(backend or current_app.config['EMAIL_BACKEND']) | ||
return klass(fail_silently=fail_silently, **kwds) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# The code in here is taken almost verbatim from `django.core.mail.backends.base`, | ||
# which is licensed under the three-clause BSD license and is originally | ||
# available on the following URL: | ||
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/base.py | ||
# Credits of the original code go to the Django Software Foundation | ||
# and their contributors. | ||
|
||
|
||
"""Base email backend class.""" | ||
|
||
|
||
class BaseEmailBackend: | ||
""" | ||
Base class for email backend implementations. | ||
Subclasses must at least overwrite send_messages(). | ||
open() and close() can be called indirectly by using a backend object as a | ||
context manager: | ||
with backend as connection: | ||
# do something with connection | ||
pass | ||
""" | ||
|
||
def __init__(self, fail_silently=False, **kwargs): | ||
self.fail_silently = fail_silently | ||
|
||
def open(self): | ||
""" | ||
Open a network connection. | ||
This method can be overwritten by backend implementations to | ||
open a network connection. | ||
It's up to the backend implementation to track the status of | ||
a network connection if it's needed by the backend. | ||
This method can be called by applications to force a single | ||
network connection to be used when sending mails. See the | ||
send_messages() method of the SMTP backend for a reference | ||
implementation. | ||
The default implementation does nothing. | ||
""" | ||
pass | ||
|
||
def close(self): | ||
"""Close a network connection.""" | ||
pass | ||
|
||
def __enter__(self): | ||
try: | ||
self.open() | ||
except Exception: | ||
self.close() | ||
raise | ||
return self | ||
|
||
def __exit__(self, exc_type, exc_value, traceback): | ||
self.close() | ||
|
||
def send_messages(self, email_messages): | ||
""" | ||
Send one or more EmailMessage objects and return the number of email | ||
messages sent. | ||
""" | ||
raise NotImplementedError( | ||
'subclasses of BaseEmailBackend must override send_messages() method' | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# The code in here is taken almost verbatim from `django.core.mail.backends.console`, | ||
# which is licensed under the three-clause BSD license and is originally | ||
# available on the following URL: | ||
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/console.py | ||
# Credits of the original code go to the Django Software Foundation | ||
# and their contributors. | ||
|
||
""" | ||
Email backend that writes messages to console instead of sending them. | ||
""" | ||
import sys | ||
import threading | ||
|
||
from .base import BaseEmailBackend | ||
|
||
|
||
class EmailBackend(BaseEmailBackend): | ||
def __init__(self, *args, **kwargs): | ||
self.stream = kwargs.pop('stream', sys.stdout) | ||
self._lock = threading.RLock() | ||
super().__init__(*args, **kwargs) | ||
|
||
def write_message(self, message): | ||
msg = message.message() | ||
msg_data = msg.as_bytes() | ||
charset = ( | ||
msg.get_charset().get_output_charset() if msg.get_charset() else 'utf-8' | ||
) | ||
msg_data = msg_data.decode(charset) | ||
self.stream.write('%s\n' % msg_data) | ||
self.stream.write('-' * 79) | ||
self.stream.write('\n') | ||
|
||
def send_messages(self, email_messages): | ||
"""Write all messages to the stream in a thread-safe way.""" | ||
if not email_messages: | ||
return | ||
msg_count = 0 | ||
with self._lock: | ||
try: | ||
stream_created = self.open() | ||
for message in email_messages: | ||
self.write_message(message) | ||
self.stream.flush() # flush after each message | ||
msg_count += 1 | ||
if stream_created: | ||
self.close() | ||
except Exception: | ||
if not self.fail_silently: | ||
raise | ||
return msg_count |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# The code in here is taken almost verbatim from `django.core.mail.backends.locmem`, | ||
# which is licensed under the three-clause BSD license and is originally | ||
# available on the following URL: | ||
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/locmem.py | ||
# Credits of the original code go to the Django Software Foundation | ||
# and their contributors. | ||
|
||
""" | ||
Backend for test environment. | ||
""" | ||
|
||
from newdle.vendor import django_mail | ||
|
||
from .base import BaseEmailBackend | ||
|
||
|
||
class EmailBackend(BaseEmailBackend): | ||
""" | ||
An email backend for use during test sessions. | ||
The test connection stores email messages in a dummy outbox, | ||
rather than sending them out on the wire. | ||
The dummy outbox is accessible through the outbox instance attribute. | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
if not hasattr(django_mail, 'outbox'): | ||
django_mail.outbox = [] | ||
|
||
def send_messages(self, messages): | ||
"""Redirect messages to the dummy outbox""" | ||
msg_count = 0 | ||
for message in messages: # .message() triggers header validation | ||
message.message() | ||
django_mail.outbox.append(message) | ||
msg_count += 1 | ||
return msg_count |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
# The code in here is taken almost verbatim from `django.core.mail.backends.smtp`, | ||
# which is licensed under the three-clause BSD license and is originally | ||
# available on the following URL: | ||
# https://github.com/django/django/blob/stable/2.2.x/django/core/mail/backends/smtp.py | ||
# Credits of the original code go to the Django Software Foundation | ||
# and their contributors. | ||
|
||
"""SMTP email backend class.""" | ||
import smtplib | ||
import socket | ||
import ssl | ||
import threading | ||
|
||
from flask import current_app | ||
|
||
from ..mail_utils import DEFAULT_CHARSET, DNS_NAME | ||
from ..message import sanitize_address | ||
from .base import BaseEmailBackend | ||
|
||
|
||
class EmailBackend(BaseEmailBackend): | ||
""" | ||
A wrapper that manages the SMTP network connection. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
host=None, | ||
port=None, | ||
username=None, | ||
password=None, | ||
use_tls=None, | ||
fail_silently=False, | ||
use_ssl=None, | ||
timeout=None, | ||
ssl_keyfile=None, | ||
ssl_certfile=None, | ||
**kwargs, | ||
): | ||
super().__init__(fail_silently=fail_silently) | ||
self.host = host or current_app.config['EMAIL_HOST'] | ||
self.port = port or current_app.config['EMAIL_PORT'] | ||
self.username = ( | ||
current_app.config['EMAIL_HOST_USER'] if username is None else username | ||
) | ||
self.password = ( | ||
current_app.config['EMAIL_HOST_PASSWORD'] if password is None else password | ||
) | ||
self.use_tls = ( | ||
current_app.config['EMAIL_USE_TLS'] if use_tls is None else use_tls | ||
) | ||
self.use_ssl = ( | ||
current_app.config['EMAIL_USE_SSL'] if use_ssl is None else use_ssl | ||
) | ||
self.timeout = ( | ||
current_app.config['EMAIL_TIMEOUT'] if timeout is None else timeout | ||
) | ||
self.ssl_keyfile = ssl_keyfile | ||
self.ssl_certfile = ssl_certfile | ||
if self.use_ssl and self.use_tls: | ||
raise ValueError( | ||
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set " | ||
"one of those settings to True." | ||
) | ||
self.connection = None | ||
self._lock = threading.RLock() | ||
|
||
@property | ||
def connection_class(self): | ||
return smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP | ||
|
||
def open(self): | ||
""" | ||
Ensure an open connection to the email server. Return whether or not a | ||
new connection was required (True or False) or None if an exception | ||
passed silently. | ||
""" | ||
if self.connection: | ||
# Nothing to do if the connection is already open. | ||
return False | ||
|
||
# If local_hostname is not specified, socket.getfqdn() gets used. | ||
# For performance, we use the cached FQDN for local_hostname. | ||
connection_params = {'local_hostname': DNS_NAME.get_fqdn()} | ||
if self.timeout is not None: | ||
connection_params['timeout'] = self.timeout | ||
if self.use_ssl: | ||
connection_params.update( | ||
{'keyfile': self.ssl_keyfile, 'certfile': self.ssl_certfile} | ||
) | ||
try: | ||
self.connection = self.connection_class( | ||
self.host, self.port, **connection_params | ||
) | ||
|
||
# TLS/SSL are mutually exclusive, so only attempt TLS over | ||
# non-secure connections. | ||
if not self.use_ssl and self.use_tls: | ||
self.connection.starttls( | ||
keyfile=self.ssl_keyfile, certfile=self.ssl_certfile | ||
) | ||
if self.username and self.password: | ||
self.connection.login(self.username, self.password) | ||
return True | ||
except (smtplib.SMTPException, socket.error): | ||
if not self.fail_silently: | ||
raise | ||
|
||
def close(self): | ||
"""Close the connection to the email server.""" | ||
if self.connection is None: | ||
return | ||
try: | ||
try: | ||
self.connection.quit() | ||
except (ssl.SSLError, smtplib.SMTPServerDisconnected): | ||
# This happens when calling quit() on a TLS connection | ||
# sometimes, or when the connection was already disconnected | ||
# by the server. | ||
self.connection.close() | ||
except smtplib.SMTPException: | ||
if self.fail_silently: | ||
return | ||
raise | ||
finally: | ||
self.connection = None | ||
|
||
def send_messages(self, email_messages): | ||
""" | ||
Send one or more EmailMessage objects and return the number of email | ||
messages sent. | ||
""" | ||
if not email_messages: | ||
return 0 | ||
with self._lock: | ||
new_conn_created = self.open() | ||
if not self.connection or new_conn_created is None: | ||
# We failed silently on open(). | ||
# Trying to send would be pointless. | ||
return 0 | ||
num_sent = 0 | ||
for message in email_messages: | ||
sent = self._send(message) | ||
if sent: | ||
num_sent += 1 | ||
if new_conn_created: | ||
self.close() | ||
return num_sent | ||
|
||
def _send(self, email_message): | ||
"""A helper method that does the actual sending.""" | ||
if not email_message.recipients(): | ||
return False | ||
encoding = email_message.encoding or DEFAULT_CHARSET | ||
from_email = sanitize_address(email_message.from_email, encoding) | ||
recipients = [ | ||
sanitize_address(addr, encoding) for addr in email_message.recipients() | ||
] | ||
message = email_message.message() | ||
try: | ||
self.connection.sendmail( | ||
from_email, recipients, message.as_bytes(linesep='\r\n') | ||
) | ||
except smtplib.SMTPException: | ||
if not self.fail_silently: | ||
raise | ||
return False | ||
return True |
Oops, something went wrong.