--- a/web/lib/django/core/mail.py Wed May 19 17:43:59 2010 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,426 +0,0 @@
-"""
-Tools for sending email.
-"""
-
-import mimetypes
-import os
-import smtplib
-import socket
-import time
-import random
-from email import Charset, Encoders
-from email.MIMEText import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from email.Header import Header
-from email.Utils import formatdate, parseaddr, formataddr
-
-from django.conf import settings
-from django.utils.encoding import smart_str, force_unicode
-
-# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
-# some spam filters.
-Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
-
-# Default MIME type to use on attachments (if it is not explicitly given
-# and cannot be guessed).
-DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
-
-# 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(object):
- def __str__(self):
- return self.get_fqdn()
-
- def get_fqdn(self):
- if not hasattr(self, '_fqdn'):
- self._fqdn = socket.getfqdn()
- return self._fqdn
-
-DNS_NAME = CachedDnsName()
-
-# Copied from Python standard library, with the following modifications:
-# * Used cached hostname for performance.
-# * Added try/except to support lack of getpid() in Jython (#5496).
-def make_msgid(idstring=None):
- """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
-
- <20020201195627.33539.96671@nightshade.la.mastaler.com>
-
- Optional idstring if given is a string used to strengthen the
- uniqueness of the message id.
- """
- timeval = time.time()
- utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
- try:
- pid = os.getpid()
- except AttributeError:
- # No getpid() in Jython, for example.
- pid = 1
- randint = random.randrange(100000)
- if idstring is None:
- idstring = ''
- else:
- idstring = '.' + idstring
- idhost = DNS_NAME
- msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
- return msgid
-
-class BadHeaderError(ValueError):
- pass
-
-def forbid_multi_line_headers(name, val):
- """Forbids multi-line headers, to prevent header injection."""
- val = force_unicode(val)
- 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 = val.encode('ascii')
- except UnicodeEncodeError:
- if name.lower() in ('to', 'from', 'cc'):
- result = []
- for item in val.split(', '):
- nm, addr = parseaddr(item)
- nm = str(Header(nm, settings.DEFAULT_CHARSET))
- result.append(formataddr((nm, str(addr))))
- val = ', '.join(result)
- else:
- val = Header(val, settings.DEFAULT_CHARSET)
- else:
- if name.lower() == 'subject':
- val = Header(val)
- return name, val
-
-class SafeMIMEText(MIMEText):
- def __setitem__(self, name, val):
- name, val = forbid_multi_line_headers(name, val)
- MIMEText.__setitem__(self, name, val)
-
-class SafeMIMEMultipart(MIMEMultipart):
- def __setitem__(self, name, val):
- name, val = forbid_multi_line_headers(name, val)
- MIMEMultipart.__setitem__(self, name, val)
-
-class SMTPConnection(object):
- """
- 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):
- self.host = host or settings.EMAIL_HOST
- self.port = port or settings.EMAIL_PORT
- self.username = username or settings.EMAIL_HOST_USER
- self.password = password or settings.EMAIL_HOST_PASSWORD
- self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
- self.fail_silently = fail_silently
- self.connection = None
-
- def open(self):
- """
- Ensures we have a connection to the email server. Returns whether or
- not a new connection was required (True or False).
- """
- if self.connection:
- # Nothing to do if the connection is already open.
- return False
- try:
- # If local_hostname is not specified, socket.getfqdn() gets used.
- # For performance, we use the cached FQDN for local_hostname.
- self.connection = smtplib.SMTP(self.host, self.port,
- local_hostname=DNS_NAME.get_fqdn())
- if self.use_tls:
- self.connection.ehlo()
- self.connection.starttls()
- self.connection.ehlo()
- if self.username and self.password:
- self.connection.login(self.username, self.password)
- return True
- except:
- if not self.fail_silently:
- raise
-
- def close(self):
- """Closes the connection to the email server."""
- try:
- try:
- self.connection.quit()
- except socket.sslerror:
- # This happens when calling quit() on a TLS connection
- # sometimes.
- self.connection.close()
- except:
- if self.fail_silently:
- return
- raise
- finally:
- self.connection = None
-
- def send_messages(self, email_messages):
- """
- Sends one or more EmailMessage objects and returns the number of email
- messages sent.
- """
- if not email_messages:
- return
- new_conn_created = self.open()
- if not self.connection:
- # We failed silently on open(). Trying to send would be pointless.
- return
- 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
- try:
- self.connection.sendmail(email_message.from_email,
- email_message.recipients(),
- email_message.message().as_string())
- except:
- if not self.fail_silently:
- raise
- return False
- return True
-
-class EmailMessage(object):
- """
- 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):
- """
- Initialize a single email message (which can be sent to multiple
- recipients).
-
- All strings used to create the message can be unicode strings (or UTF-8
- bytestrings). The SafeMIMEText class will handle any necessary encoding
- conversions.
- """
- if to:
- assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
- self.to = list(to)
- else:
- self.to = []
- if bcc:
- assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
- self.bcc = list(bcc)
- else:
- self.bcc = []
- self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
- self.subject = subject
- self.body = body
- self.attachments = attachments or []
- self.extra_headers = headers or {}
- self.connection = connection
-
- def get_connection(self, fail_silently=False):
- if not self.connection:
- self.connection = SMTPConnection(fail_silently=fail_silently)
- return self.connection
-
- def message(self):
- encoding = self.encoding or settings.DEFAULT_CHARSET
- msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
- self.content_subtype, encoding)
- msg = self._create_message(msg)
- msg['Subject'] = self.subject
- msg['From'] = self.extra_headers.pop('From', self.from_email)
- msg['To'] = ', '.join(self.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:
- msg['Date'] = formatdate()
- if 'message-id' not in header_names:
- msg['Message-ID'] = make_msgid()
- for name, value in self.extra_headers.items():
- msg[name] = value
- return msg
-
- def recipients(self):
- """
- Returns a list of all recipients of the email (includes direct
- addressees as well as Bcc entries).
- """
- return self.to + self.bcc
-
- def send(self, fail_silently=False):
- """Sends 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):
- """
- Attaches 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 it is inserted directly
- into the resulting message attachments.
- """
- if isinstance(filename, MIMEBase):
- assert content == mimetype == None
- self.attachments.append(filename)
- else:
- assert content is not None
- self.attachments.append((filename, content, mimetype))
-
- def attach_file(self, path, mimetype=None):
- """Attaches a file from the filesystem."""
- filename = os.path.basename(path)
- content = open(path, 'rb').read()
- self.attach(filename, content, mimetype)
-
- def _create_message(self, msg):
- return self._create_attachments(msg)
-
- def _create_attachments(self, msg):
- if self.attachments:
- body_msg = msg
- msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
- if self.body:
- 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):
- """
- Converts the content, mimetype pair into a MIME attachment object.
- """
- basetype, subtype = mimetype.split('/', 1)
- if basetype == 'text':
- attachment = SafeMIMEText(smart_str(content,
- settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
- 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):
- """
- Converts the filename, content, mimetype triple into a MIME attachment
- object.
- """
- if mimetype is None:
- mimetype, _ = mimetypes.guess_type(filename)
- if mimetype is None:
- mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
- attachment = self._create_mime_attachment(content, mimetype)
- if filename:
- attachment.add_header('Content-Disposition', 'attachment',
- filename=filename)
- return attachment
-
-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):
- """
- Initialize a single email message (which can be sent to multiple
- recipients).
-
- All strings used to create the message can be unicode strings (or UTF-8
- bytestrings). The SafeMIMEText class will handle any necessary encoding
- conversions.
- """
- super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
- self.alternatives=alternatives or []
-
- def attach_alternative(self, content, mimetype):
- """Attach an alternative content representation."""
- assert content is not None
- assert mimetype is not None
- self.alternatives.append((content, mimetype))
-
- def _create_message(self, msg):
- return self._create_attachments(self._create_alternatives(msg))
-
- def _create_alternatives(self, msg):
- if self.alternatives:
- body_msg = msg
- msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
- if self.body:
- msg.attach(body_msg)
- for alternative in self.alternatives:
- msg.attach(self._create_mime_attachment(*alternative))
- return msg
-
-def send_mail(subject, message, from_email, recipient_list,
- fail_silently=False, auth_user=None, auth_password=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 auth_user is None, the EMAIL_HOST_USER setting is used.
- If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
-
- Note: The API for this method is frozen. New code wanting to extend the
- functionality should use the EmailMessage class directly.
- """
- connection = SMTPConnection(username=auth_user, password=auth_password,
- fail_silently=fail_silently)
- return EmailMessage(subject, message, from_email, recipient_list,
- connection=connection).send()
-
-def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
- auth_password=None):
- """
- Given a datatuple of (subject, message, from_email, recipient_list), sends
- each message to each recipient list. Returns the number of e-mails sent.
-
- If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
- If auth_user and auth_password are set, they're used to log in.
- If auth_user is None, the EMAIL_HOST_USER setting is used.
- If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
-
- Note: The API for this method is frozen. New code wanting to extend the
- functionality should use the EmailMessage class directly.
- """
- connection = SMTPConnection(username=auth_user, password=auth_password,
- fail_silently=fail_silently)
- messages = [EmailMessage(subject, message, sender, recipient)
- for subject, message, sender, recipient in datatuple]
- return connection.send_messages(messages)
-
-def mail_admins(subject, message, fail_silently=False):
- """Sends a message to the admins, as defined by the ADMINS setting."""
- if not settings.ADMINS:
- return
- EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
- settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
- ).send(fail_silently=fail_silently)
-
-def mail_managers(subject, message, fail_silently=False):
- """Sends a message to the managers, as defined by the MANAGERS setting."""
- if not settings.MANAGERS:
- return
- EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
- settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
- ).send(fail_silently=fail_silently)