web/lib/django/core/mail/message.py
changeset 29 cc9b7e14412b
--- /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