Ajoutez des fichiers projet.
This commit is contained in:
121
venv/Lib/site-packages/django/core/mail/__init__.py
Normal file
121
venv/Lib/site-packages/django/core/mail/__init__.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Tools for sending email.
|
||||
"""
|
||||
from django.conf import settings
|
||||
# Imported for backwards compatibility and for the sake
|
||||
# of a cleaner namespace. These symbols used to be in
|
||||
# django/core/mail.py before the introduction of email
|
||||
# backends and the subsequent reorganization (See #10355)
|
||||
from django.core.mail.message import (
|
||||
DEFAULT_ATTACHMENT_MIME_TYPE, BadHeaderError, EmailMessage,
|
||||
EmailMultiAlternatives, SafeMIMEMultipart, SafeMIMEText,
|
||||
forbid_multi_line_headers, make_msgid,
|
||||
)
|
||||
from django.core.mail.utils import DNS_NAME, CachedDnsName
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
__all__ = [
|
||||
'CachedDnsName', 'DNS_NAME', 'EmailMessage', 'EmailMultiAlternatives',
|
||||
'SafeMIMEText', 'SafeMIMEMultipart', 'DEFAULT_ATTACHMENT_MIME_TYPE',
|
||||
'make_msgid', 'BadHeaderError', 'forbid_multi_line_headers',
|
||||
'get_connection', 'send_mail', 'send_mass_mail', 'mail_admins',
|
||||
'mail_managers',
|
||||
]
|
||||
|
||||
|
||||
def get_connection(backend=None, fail_silently=False, **kwds):
|
||||
"""Load an email backend and return an instance of it.
|
||||
|
||||
If backend is None (default), use settings.EMAIL_BACKEND.
|
||||
|
||||
Both fail_silently and other keyword arguments are used in the
|
||||
constructor of the backend.
|
||||
"""
|
||||
klass = import_string(backend or settings.EMAIL_BACKEND)
|
||||
return klass(fail_silently=fail_silently, **kwds)
|
||||
|
||||
|
||||
def send_mail(subject, message, from_email, recipient_list,
|
||||
fail_silently=False, auth_user=None, auth_password=None,
|
||||
connection=None, html_message=None):
|
||||
"""
|
||||
Easy wrapper for sending a single message to a recipient list. All members
|
||||
of the recipient list will see the other recipients in the 'To' field.
|
||||
|
||||
If from_email is None, use the DEFAULT_FROM_EMAIL setting.
|
||||
If auth_user is None, use the EMAIL_HOST_USER setting.
|
||||
If auth_password is None, use the EMAIL_HOST_PASSWORD setting.
|
||||
|
||||
Note: The API for this method is frozen. New code wanting to extend the
|
||||
functionality should use the EmailMessage class directly.
|
||||
"""
|
||||
connection = connection or get_connection(
|
||||
username=auth_user,
|
||||
password=auth_password,
|
||||
fail_silently=fail_silently,
|
||||
)
|
||||
mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, connection=connection)
|
||||
if html_message:
|
||||
mail.attach_alternative(html_message, 'text/html')
|
||||
|
||||
return mail.send()
|
||||
|
||||
|
||||
def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
|
||||
auth_password=None, connection=None):
|
||||
"""
|
||||
Given a datatuple of (subject, message, from_email, recipient_list), send
|
||||
each message to each recipient list. Return the number of emails sent.
|
||||
|
||||
If from_email is None, use the DEFAULT_FROM_EMAIL setting.
|
||||
If auth_user and auth_password are set, use them to log in.
|
||||
If auth_user is None, use the EMAIL_HOST_USER setting.
|
||||
If auth_password is None, use the EMAIL_HOST_PASSWORD setting.
|
||||
|
||||
Note: The API for this method is frozen. New code wanting to extend the
|
||||
functionality should use the EmailMessage class directly.
|
||||
"""
|
||||
connection = connection or get_connection(
|
||||
username=auth_user,
|
||||
password=auth_password,
|
||||
fail_silently=fail_silently,
|
||||
)
|
||||
messages = [
|
||||
EmailMessage(subject, message, sender, recipient, connection=connection)
|
||||
for subject, message, sender, recipient in datatuple
|
||||
]
|
||||
return connection.send_messages(messages)
|
||||
|
||||
|
||||
def mail_admins(subject, message, fail_silently=False, connection=None,
|
||||
html_message=None):
|
||||
"""Send a message to the admins, as defined by the ADMINS setting."""
|
||||
if not settings.ADMINS:
|
||||
return
|
||||
if not all(isinstance(a, (list, tuple)) and len(a) == 2 for a in settings.ADMINS):
|
||||
raise ValueError('The ADMINS setting must be a list of 2-tuples.')
|
||||
mail = EmailMultiAlternatives(
|
||||
'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message,
|
||||
settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS],
|
||||
connection=connection,
|
||||
)
|
||||
if html_message:
|
||||
mail.attach_alternative(html_message, 'text/html')
|
||||
mail.send(fail_silently=fail_silently)
|
||||
|
||||
|
||||
def mail_managers(subject, message, fail_silently=False, connection=None,
|
||||
html_message=None):
|
||||
"""Send a message to the managers, as defined by the MANAGERS setting."""
|
||||
if not settings.MANAGERS:
|
||||
return
|
||||
if not all(isinstance(a, (list, tuple)) and len(a) == 2 for a in settings.MANAGERS):
|
||||
raise ValueError('The MANAGERS setting must be a list of 2-tuples.')
|
||||
mail = EmailMultiAlternatives(
|
||||
'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message,
|
||||
settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS],
|
||||
connection=connection,
|
||||
)
|
||||
if html_message:
|
||||
mail.attach_alternative(html_message, 'text/html')
|
||||
mail.send(fail_silently=fail_silently)
|
@@ -0,0 +1 @@
|
||||
# Mail backends shipped with Django.
|
59
venv/Lib/site-packages/django/core/mail/backends/base.py
Normal file
59
venv/Lib/site-packages/django/core/mail/backends/base.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""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')
|
42
venv/Lib/site-packages/django/core/mail/backends/console.py
Normal file
42
venv/Lib/site-packages/django/core/mail/backends/console.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Email backend that writes messages to console instead of sending them.
|
||||
"""
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from django.core.mail.backends.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
|
10
venv/Lib/site-packages/django/core/mail/backends/dummy.py
Normal file
10
venv/Lib/site-packages/django/core/mail/backends/dummy.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Dummy email backend that does nothing.
|
||||
"""
|
||||
|
||||
from django.core.mail.backends.base import BaseEmailBackend
|
||||
|
||||
|
||||
class EmailBackend(BaseEmailBackend):
|
||||
def send_messages(self, email_messages):
|
||||
return len(list(email_messages))
|
@@ -0,0 +1,64 @@
|
||||
"""Email backend that writes messages to a file."""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.mail.backends.console import (
|
||||
EmailBackend as ConsoleEmailBackend,
|
||||
)
|
||||
|
||||
|
||||
class EmailBackend(ConsoleEmailBackend):
|
||||
def __init__(self, *args, file_path=None, **kwargs):
|
||||
self._fname = None
|
||||
if file_path is not None:
|
||||
self.file_path = file_path
|
||||
else:
|
||||
self.file_path = getattr(settings, 'EMAIL_FILE_PATH', None)
|
||||
self.file_path = os.path.abspath(self.file_path)
|
||||
try:
|
||||
os.makedirs(self.file_path, exist_ok=True)
|
||||
except FileExistsError:
|
||||
raise ImproperlyConfigured(
|
||||
'Path for saving email messages exists, but is not a directory: %s' % self.file_path
|
||||
)
|
||||
except OSError as err:
|
||||
raise ImproperlyConfigured(
|
||||
'Could not create directory for saving email messages: %s (%s)' % (self.file_path, err)
|
||||
)
|
||||
# Make sure that self.file_path is writable.
|
||||
if not os.access(self.file_path, os.W_OK):
|
||||
raise ImproperlyConfigured('Could not write to directory: %s' % self.file_path)
|
||||
# Finally, call super().
|
||||
# Since we're using the console-based backend as a base,
|
||||
# force the stream to be None, so we don't default to stdout
|
||||
kwargs['stream'] = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def write_message(self, message):
|
||||
self.stream.write(message.message().as_bytes() + b'\n')
|
||||
self.stream.write(b'-' * 79)
|
||||
self.stream.write(b'\n')
|
||||
|
||||
def _get_filename(self):
|
||||
"""Return a unique file name."""
|
||||
if self._fname is None:
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
fname = "%s-%s.log" % (timestamp, abs(id(self)))
|
||||
self._fname = os.path.join(self.file_path, fname)
|
||||
return self._fname
|
||||
|
||||
def open(self):
|
||||
if self.stream is None:
|
||||
self.stream = open(self._get_filename(), 'ab')
|
||||
return True
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
if self.stream is not None:
|
||||
self.stream.close()
|
||||
finally:
|
||||
self.stream = None
|
30
venv/Lib/site-packages/django/core/mail/backends/locmem.py
Normal file
30
venv/Lib/site-packages/django/core/mail/backends/locmem.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Backend for test environment.
|
||||
"""
|
||||
|
||||
from django.core import mail
|
||||
from django.core.mail.backends.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(mail, 'outbox'):
|
||||
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()
|
||||
mail.outbox.append(message)
|
||||
msg_count += 1
|
||||
return msg_count
|
130
venv/Lib/site-packages/django/core/mail/backends/smtp.py
Normal file
130
venv/Lib/site-packages/django/core/mail/backends/smtp.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""SMTP email backend class."""
|
||||
import smtplib
|
||||
import ssl
|
||||
import threading
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail.backends.base import BaseEmailBackend
|
||||
from django.core.mail.message import sanitize_address
|
||||
from django.core.mail.utils import DNS_NAME
|
||||
|
||||
|
||||
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 settings.EMAIL_HOST
|
||||
self.port = port or settings.EMAIL_PORT
|
||||
self.username = settings.EMAIL_HOST_USER if username is None else username
|
||||
self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
|
||||
self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
|
||||
self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
|
||||
self.timeout = settings.EMAIL_TIMEOUT if timeout is None else timeout
|
||||
self.ssl_keyfile = settings.EMAIL_SSL_KEYFILE if ssl_keyfile is None else ssl_keyfile
|
||||
self.ssl_certfile = settings.EMAIL_SSL_CERTFILE if ssl_certfile is None else 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 OSError:
|
||||
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 settings.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
|
451
venv/Lib/site-packages/django/core/mail/message.py
Normal file
451
venv/Lib/site-packages/django/core/mail/message.py
Normal file
@@ -0,0 +1,451 @@
|
||||
import mimetypes
|
||||
from email import (
|
||||
charset as Charset, encoders as Encoders, generator, message_from_string,
|
||||
)
|
||||
from email.errors import HeaderParseError
|
||||
from email.header import Header
|
||||
from email.headerregistry import Address, parser
|
||||
from email.message import Message
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.message import MIMEMessage
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formataddr, formatdate, getaddresses, make_msgid
|
||||
from io import BytesIO, StringIO
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail.utils import DNS_NAME
|
||||
from django.utils.encoding import force_str, punycode
|
||||
|
||||
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
|
||||
# some spam filters.
|
||||
utf8_charset = Charset.Charset('utf-8')
|
||||
utf8_charset.body_encoding = None # Python defaults to BASE64
|
||||
utf8_charset_qp = Charset.Charset('utf-8')
|
||||
utf8_charset_qp.body_encoding = Charset.QP
|
||||
|
||||
# Default MIME type to use on attachments (if it is not explicitly given
|
||||
# and cannot be guessed).
|
||||
DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
|
||||
|
||||
RFC5322_EMAIL_LINE_LENGTH_LIMIT = 998
|
||||
|
||||
|
||||
class BadHeaderError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
# Header names that contain structured address data (RFC #5322)
|
||||
ADDRESS_HEADERS = {
|
||||
'from',
|
||||
'sender',
|
||||
'reply-to',
|
||||
'to',
|
||||
'cc',
|
||||
'bcc',
|
||||
'resent-from',
|
||||
'resent-sender',
|
||||
'resent-to',
|
||||
'resent-cc',
|
||||
'resent-bcc',
|
||||
}
|
||||
|
||||
|
||||
def forbid_multi_line_headers(name, val, encoding):
|
||||
"""Forbid multi-line headers to prevent header injection."""
|
||||
encoding = encoding or settings.DEFAULT_CHARSET
|
||||
val = str(val) # val may be lazy
|
||||
if '\n' in val or '\r' in val:
|
||||
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
|
||||
try:
|
||||
val.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
if name.lower() in ADDRESS_HEADERS:
|
||||
val = ', '.join(sanitize_address(addr, encoding) for addr in getaddresses((val,)))
|
||||
else:
|
||||
val = Header(val, encoding).encode()
|
||||
else:
|
||||
if name.lower() == 'subject':
|
||||
val = Header(val).encode()
|
||||
return name, val
|
||||
|
||||
|
||||
def sanitize_address(addr, encoding):
|
||||
"""
|
||||
Format a pair of (name, address) or an email address string.
|
||||
"""
|
||||
address = None
|
||||
if not isinstance(addr, tuple):
|
||||
addr = force_str(addr)
|
||||
try:
|
||||
token, rest = parser.get_mailbox(addr)
|
||||
except (HeaderParseError, ValueError, IndexError):
|
||||
raise ValueError('Invalid address "%s"' % addr)
|
||||
else:
|
||||
if rest:
|
||||
# The entire email address must be parsed.
|
||||
raise ValueError(
|
||||
'Invalid address; only %s could be parsed from "%s"'
|
||||
% (token, addr)
|
||||
)
|
||||
nm = token.display_name or ''
|
||||
localpart = token.local_part
|
||||
domain = token.domain or ''
|
||||
else:
|
||||
nm, address = addr
|
||||
localpart, domain = address.rsplit('@', 1)
|
||||
|
||||
address_parts = nm + localpart + domain
|
||||
if '\n' in address_parts or '\r' in address_parts:
|
||||
raise ValueError('Invalid address; address parts cannot contain newlines.')
|
||||
|
||||
# Avoid UTF-8 encode, if it's possible.
|
||||
try:
|
||||
nm.encode('ascii')
|
||||
nm = Header(nm).encode()
|
||||
except UnicodeEncodeError:
|
||||
nm = Header(nm, encoding).encode()
|
||||
try:
|
||||
localpart.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
localpart = Header(localpart, encoding).encode()
|
||||
domain = punycode(domain)
|
||||
|
||||
parsed_address = Address(username=localpart, domain=domain)
|
||||
return formataddr((nm, parsed_address.addr_spec))
|
||||
|
||||
|
||||
class MIMEMixin:
|
||||
def as_string(self, unixfrom=False, linesep='\n'):
|
||||
"""Return the entire formatted message as a string.
|
||||
Optional `unixfrom' when True, means include the Unix From_ envelope
|
||||
header.
|
||||
|
||||
This overrides the default as_string() implementation to not mangle
|
||||
lines that begin with 'From '. See bug #13433 for details.
|
||||
"""
|
||||
fp = StringIO()
|
||||
g = generator.Generator(fp, mangle_from_=False)
|
||||
g.flatten(self, unixfrom=unixfrom, linesep=linesep)
|
||||
return fp.getvalue()
|
||||
|
||||
def as_bytes(self, unixfrom=False, linesep='\n'):
|
||||
"""Return the entire formatted message as bytes.
|
||||
Optional `unixfrom' when True, means include the Unix From_ envelope
|
||||
header.
|
||||
|
||||
This overrides the default as_bytes() implementation to not mangle
|
||||
lines that begin with 'From '. See bug #13433 for details.
|
||||
"""
|
||||
fp = BytesIO()
|
||||
g = generator.BytesGenerator(fp, mangle_from_=False)
|
||||
g.flatten(self, unixfrom=unixfrom, linesep=linesep)
|
||||
return fp.getvalue()
|
||||
|
||||
|
||||
class SafeMIMEMessage(MIMEMixin, MIMEMessage):
|
||||
|
||||
def __setitem__(self, name, val):
|
||||
# message/rfc822 attachments must be ASCII
|
||||
name, val = forbid_multi_line_headers(name, val, 'ascii')
|
||||
MIMEMessage.__setitem__(self, name, val)
|
||||
|
||||
|
||||
class SafeMIMEText(MIMEMixin, MIMEText):
|
||||
|
||||
def __init__(self, _text, _subtype='plain', _charset=None):
|
||||
self.encoding = _charset
|
||||
MIMEText.__init__(self, _text, _subtype=_subtype, _charset=_charset)
|
||||
|
||||
def __setitem__(self, name, val):
|
||||
name, val = forbid_multi_line_headers(name, val, self.encoding)
|
||||
MIMEText.__setitem__(self, name, val)
|
||||
|
||||
def set_payload(self, payload, charset=None):
|
||||
if charset == 'utf-8' and not isinstance(charset, Charset.Charset):
|
||||
has_long_lines = any(
|
||||
len(line.encode()) > RFC5322_EMAIL_LINE_LENGTH_LIMIT
|
||||
for line in payload.splitlines()
|
||||
)
|
||||
# Quoted-Printable encoding has the side effect of shortening long
|
||||
# lines, if any (#22561).
|
||||
charset = utf8_charset_qp if has_long_lines else utf8_charset
|
||||
MIMEText.set_payload(self, payload, charset=charset)
|
||||
|
||||
|
||||
class SafeMIMEMultipart(MIMEMixin, MIMEMultipart):
|
||||
|
||||
def __init__(self, _subtype='mixed', boundary=None, _subparts=None, encoding=None, **_params):
|
||||
self.encoding = encoding
|
||||
MIMEMultipart.__init__(self, _subtype, boundary, _subparts, **_params)
|
||||
|
||||
def __setitem__(self, name, val):
|
||||
name, val = forbid_multi_line_headers(name, val, self.encoding)
|
||||
MIMEMultipart.__setitem__(self, name, val)
|
||||
|
||||
|
||||
class EmailMessage:
|
||||
"""A container for email information."""
|
||||
content_subtype = 'plain'
|
||||
mixed_subtype = 'mixed'
|
||||
encoding = None # None => use settings default
|
||||
|
||||
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
|
||||
connection=None, attachments=None, headers=None, cc=None,
|
||||
reply_to=None):
|
||||
"""
|
||||
Initialize a single email message (which can be sent to multiple
|
||||
recipients).
|
||||
"""
|
||||
if to:
|
||||
if isinstance(to, str):
|
||||
raise TypeError('"to" argument must be a list or tuple')
|
||||
self.to = list(to)
|
||||
else:
|
||||
self.to = []
|
||||
if cc:
|
||||
if isinstance(cc, str):
|
||||
raise TypeError('"cc" argument must be a list or tuple')
|
||||
self.cc = list(cc)
|
||||
else:
|
||||
self.cc = []
|
||||
if bcc:
|
||||
if isinstance(bcc, str):
|
||||
raise TypeError('"bcc" argument must be a list or tuple')
|
||||
self.bcc = list(bcc)
|
||||
else:
|
||||
self.bcc = []
|
||||
if reply_to:
|
||||
if isinstance(reply_to, str):
|
||||
raise TypeError('"reply_to" argument must be a list or tuple')
|
||||
self.reply_to = list(reply_to)
|
||||
else:
|
||||
self.reply_to = []
|
||||
self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
|
||||
self.subject = subject
|
||||
self.body = body or ''
|
||||
self.attachments = []
|
||||
if attachments:
|
||||
for attachment in attachments:
|
||||
if isinstance(attachment, MIMEBase):
|
||||
self.attach(attachment)
|
||||
else:
|
||||
self.attach(*attachment)
|
||||
self.extra_headers = headers or {}
|
||||
self.connection = connection
|
||||
|
||||
def get_connection(self, fail_silently=False):
|
||||
from django.core.mail import get_connection
|
||||
if not self.connection:
|
||||
self.connection = get_connection(fail_silently=fail_silently)
|
||||
return self.connection
|
||||
|
||||
def message(self):
|
||||
encoding = self.encoding or settings.DEFAULT_CHARSET
|
||||
msg = SafeMIMEText(self.body, self.content_subtype, encoding)
|
||||
msg = self._create_message(msg)
|
||||
msg['Subject'] = self.subject
|
||||
msg['From'] = self.extra_headers.get('From', self.from_email)
|
||||
self._set_list_header_if_not_empty(msg, 'To', self.to)
|
||||
self._set_list_header_if_not_empty(msg, 'Cc', self.cc)
|
||||
self._set_list_header_if_not_empty(msg, 'Reply-To', self.reply_to)
|
||||
|
||||
# Email header names are case-insensitive (RFC 2045), so we have to
|
||||
# accommodate that when doing comparisons.
|
||||
header_names = [key.lower() for key in self.extra_headers]
|
||||
if 'date' not in header_names:
|
||||
# formatdate() uses stdlib methods to format the date, which use
|
||||
# the stdlib/OS concept of a timezone, however, Django sets the
|
||||
# TZ environment variable based on the TIME_ZONE setting which
|
||||
# will get picked up by formatdate().
|
||||
msg['Date'] = formatdate(localtime=settings.EMAIL_USE_LOCALTIME)
|
||||
if 'message-id' not in header_names:
|
||||
# Use cached DNS_NAME for performance
|
||||
msg['Message-ID'] = make_msgid(domain=DNS_NAME)
|
||||
for name, value in self.extra_headers.items():
|
||||
if name.lower() != 'from': # From is already handled
|
||||
msg[name] = value
|
||||
return msg
|
||||
|
||||
def recipients(self):
|
||||
"""
|
||||
Return a list of all recipients of the email (includes direct
|
||||
addressees as well as Cc and Bcc entries).
|
||||
"""
|
||||
return [email for email in (self.to + self.cc + self.bcc) if email]
|
||||
|
||||
def send(self, fail_silently=False):
|
||||
"""Send the email message."""
|
||||
if not self.recipients():
|
||||
# Don't bother creating the network connection if there's nobody to
|
||||
# send to.
|
||||
return 0
|
||||
return self.get_connection(fail_silently).send_messages([self])
|
||||
|
||||
def attach(self, filename=None, content=None, mimetype=None):
|
||||
"""
|
||||
Attach a file with the given filename and content. The filename can
|
||||
be omitted and the mimetype is guessed, if not provided.
|
||||
|
||||
If the first parameter is a MIMEBase subclass, insert it directly
|
||||
into the resulting message attachments.
|
||||
|
||||
For a text/* mimetype (guessed or specified), when a bytes object is
|
||||
specified as content, decode it as UTF-8. If that fails, set the
|
||||
mimetype to DEFAULT_ATTACHMENT_MIME_TYPE and don't decode the content.
|
||||
"""
|
||||
if isinstance(filename, MIMEBase):
|
||||
if content is not None or mimetype is not None:
|
||||
raise ValueError(
|
||||
'content and mimetype must not be given when a MIMEBase '
|
||||
'instance is provided.'
|
||||
)
|
||||
self.attachments.append(filename)
|
||||
elif content is None:
|
||||
raise ValueError('content must be provided.')
|
||||
else:
|
||||
mimetype = mimetype or mimetypes.guess_type(filename)[0] or DEFAULT_ATTACHMENT_MIME_TYPE
|
||||
basetype, subtype = mimetype.split('/', 1)
|
||||
|
||||
if basetype == 'text':
|
||||
if isinstance(content, bytes):
|
||||
try:
|
||||
content = content.decode()
|
||||
except UnicodeDecodeError:
|
||||
# If mimetype suggests the file is text but it's
|
||||
# actually binary, read() raises a UnicodeDecodeError.
|
||||
mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
|
||||
|
||||
self.attachments.append((filename, content, mimetype))
|
||||
|
||||
def attach_file(self, path, mimetype=None):
|
||||
"""
|
||||
Attach a file from the filesystem.
|
||||
|
||||
Set the mimetype to DEFAULT_ATTACHMENT_MIME_TYPE if it isn't specified
|
||||
and cannot be guessed.
|
||||
|
||||
For a text/* mimetype (guessed or specified), decode the file's content
|
||||
as UTF-8. If that fails, set the mimetype to
|
||||
DEFAULT_ATTACHMENT_MIME_TYPE and don't decode the content.
|
||||
"""
|
||||
path = Path(path)
|
||||
with path.open('rb') as file:
|
||||
content = file.read()
|
||||
self.attach(path.name, content, mimetype)
|
||||
|
||||
def _create_message(self, msg):
|
||||
return self._create_attachments(msg)
|
||||
|
||||
def _create_attachments(self, msg):
|
||||
if self.attachments:
|
||||
encoding = self.encoding or settings.DEFAULT_CHARSET
|
||||
body_msg = msg
|
||||
msg = SafeMIMEMultipart(_subtype=self.mixed_subtype, encoding=encoding)
|
||||
if self.body or body_msg.is_multipart():
|
||||
msg.attach(body_msg)
|
||||
for attachment in self.attachments:
|
||||
if isinstance(attachment, MIMEBase):
|
||||
msg.attach(attachment)
|
||||
else:
|
||||
msg.attach(self._create_attachment(*attachment))
|
||||
return msg
|
||||
|
||||
def _create_mime_attachment(self, content, mimetype):
|
||||
"""
|
||||
Convert the content, mimetype pair into a MIME attachment object.
|
||||
|
||||
If the mimetype is message/rfc822, content may be an
|
||||
email.Message or EmailMessage object, as well as a str.
|
||||
"""
|
||||
basetype, subtype = mimetype.split('/', 1)
|
||||
if basetype == 'text':
|
||||
encoding = self.encoding or settings.DEFAULT_CHARSET
|
||||
attachment = SafeMIMEText(content, subtype, encoding)
|
||||
elif basetype == 'message' and subtype == 'rfc822':
|
||||
# Bug #18967: per RFC2046 s5.2.1, message/rfc822 attachments
|
||||
# must not be base64 encoded.
|
||||
if isinstance(content, EmailMessage):
|
||||
# convert content into an email.Message first
|
||||
content = content.message()
|
||||
elif not isinstance(content, Message):
|
||||
# For compatibility with existing code, parse the message
|
||||
# into an email.Message object if it is not one already.
|
||||
content = message_from_string(force_str(content))
|
||||
|
||||
attachment = SafeMIMEMessage(content, subtype)
|
||||
else:
|
||||
# Encode non-text attachments with base64.
|
||||
attachment = MIMEBase(basetype, subtype)
|
||||
attachment.set_payload(content)
|
||||
Encoders.encode_base64(attachment)
|
||||
return attachment
|
||||
|
||||
def _create_attachment(self, filename, content, mimetype=None):
|
||||
"""
|
||||
Convert the filename, content, mimetype triple into a MIME attachment
|
||||
object.
|
||||
"""
|
||||
attachment = self._create_mime_attachment(content, mimetype)
|
||||
if filename:
|
||||
try:
|
||||
filename.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
filename = ('utf-8', '', filename)
|
||||
attachment.add_header('Content-Disposition', 'attachment', filename=filename)
|
||||
return attachment
|
||||
|
||||
def _set_list_header_if_not_empty(self, msg, header, values):
|
||||
"""
|
||||
Set msg's header, either from self.extra_headers, if present, or from
|
||||
the values argument.
|
||||
"""
|
||||
if values:
|
||||
try:
|
||||
value = self.extra_headers[header]
|
||||
except KeyError:
|
||||
value = ', '.join(str(v) for v in values)
|
||||
msg[header] = value
|
||||
|
||||
|
||||
class EmailMultiAlternatives(EmailMessage):
|
||||
"""
|
||||
A version of EmailMessage that makes it easy to send multipart/alternative
|
||||
messages. For example, including text and HTML versions of the text is
|
||||
made easier.
|
||||
"""
|
||||
alternative_subtype = 'alternative'
|
||||
|
||||
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
|
||||
connection=None, attachments=None, headers=None, alternatives=None,
|
||||
cc=None, reply_to=None):
|
||||
"""
|
||||
Initialize a single email message (which can be sent to multiple
|
||||
recipients).
|
||||
"""
|
||||
super().__init__(
|
||||
subject, body, from_email, to, bcc, connection, attachments,
|
||||
headers, cc, reply_to,
|
||||
)
|
||||
self.alternatives = alternatives or []
|
||||
|
||||
def attach_alternative(self, content, mimetype):
|
||||
"""Attach an alternative content representation."""
|
||||
if content is None or mimetype is None:
|
||||
raise ValueError('Both content and mimetype must be provided.')
|
||||
self.alternatives.append((content, mimetype))
|
||||
|
||||
def _create_message(self, msg):
|
||||
return self._create_attachments(self._create_alternatives(msg))
|
||||
|
||||
def _create_alternatives(self, msg):
|
||||
encoding = self.encoding or settings.DEFAULT_CHARSET
|
||||
if self.alternatives:
|
||||
body_msg = msg
|
||||
msg = SafeMIMEMultipart(_subtype=self.alternative_subtype, encoding=encoding)
|
||||
if self.body:
|
||||
msg.attach(body_msg)
|
||||
for alternative in self.alternatives:
|
||||
msg.attach(self._create_mime_attachment(*alternative))
|
||||
return msg
|
22
venv/Lib/site-packages/django/core/mail/utils.py
Normal file
22
venv/Lib/site-packages/django/core/mail/utils.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Email message and email sending related helper functions.
|
||||
"""
|
||||
|
||||
import socket
|
||||
|
||||
from django.utils.encoding import punycode
|
||||
|
||||
|
||||
# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
|
||||
# seconds, which slows down the restart of the server.
|
||||
class CachedDnsName:
|
||||
def __str__(self):
|
||||
return self.get_fqdn()
|
||||
|
||||
def get_fqdn(self):
|
||||
if not hasattr(self, '_fqdn'):
|
||||
self._fqdn = punycode(socket.getfqdn())
|
||||
return self._fqdn
|
||||
|
||||
|
||||
DNS_NAME = CachedDnsName()
|
Reference in New Issue
Block a user