--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/core/mail/message.py Tue May 25 02:43:45 2010 +0200
@@ -0,0 +1,285 @@
+import mimetypes
+import os
+import random
+import time
+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, getaddresses, formataddr
+
+from django.conf import settings
+from django.core.mail.utils import DNS_NAME
+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'
+
+
+class BadHeaderError(ValueError):
+ pass
+
+
+# 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
+
+
+def forbid_multi_line_headers(name, val, encoding):
+ """Forbids multi-line headers, to prevent header injection."""
+ encoding = encoding or settings.DEFAULT_CHARSET
+ 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 nm, addr in getaddresses((val,)):
+ nm = str(Header(nm.encode(encoding), encoding))
+ result.append(formataddr((nm, str(addr))))
+ val = ', '.join(result)
+ else:
+ val = Header(val.encode(encoding), encoding)
+ else:
+ if name.lower() == 'subject':
+ val = Header(val)
+ return name, val
+
+class SafeMIMEText(MIMEText):
+
+ def __init__(self, text, subtype, charset):
+ self.encoding = charset
+ MIMEText.__init__(self, text, subtype, charset)
+
+ def __setitem__(self, name, val):
+ name, val = forbid_multi_line_headers(name, val, self.encoding)
+ MIMEText.__setitem__(self, name, val)
+
+class SafeMIMEMultipart(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(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):
+ 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(smart_str(self.body, encoding),
+ self.content_subtype, encoding)
+ msg = self._create_message(msg)
+ msg['Subject'] = self.subject
+ msg['From'] = self.extra_headers.get('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():
+ if name.lower() == 'from': # From is already handled
+ continue
+ 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:
+ encoding = self.encoding or settings.DEFAULT_CHARSET
+ body_msg = msg
+ msg = SafeMIMEMultipart(_subtype=self.mixed_subtype, encoding=encoding)
+ 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':
+ encoding = self.encoding or settings.DEFAULT_CHARSET
+ attachment = SafeMIMEText(smart_str(content, encoding), subtype, encoding)
+ 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):
+ 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