Code

Ticket #1541: mail.3.diff

File mail.3.diff, 5.6 KB (added by nick.lane.au@…, 7 years ago)

Slight change to patch, added docs (last patch broken)

Line 
1--- django/core/mail.py Tue Jun 26 14:46:08 2007
2+++ django/core/mail.py Tue Jun 26 14:48:15 2007
3@@ -4,6 +4,10 @@
4 
5 from django.conf import settings
6 from email.MIMEText import MIMEText
7+from email.MIMEMultipart import MIMEMultipart
8+from email.MIMEBase import MIMEBase
9+from email import Encoders
10+import mimetypes
11 from email.Header import Header
12 from email.Utils import formatdate
13 from email import Charset
14@@ -13,6 +17,9 @@
15 import time
16 import random
17 
18+# Default MIME type to use for attachments if it cannot be guessed.
19+DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
20+
21 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
22 # some spam filters.
23 Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
24@@ -64,6 +71,15 @@
25             val = Header(val, settings.DEFAULT_CHARSET)
26         MIMEText.__setitem__(self, name, val)
27 
28+class SafeMIMEMultipart(MIMEMultipart):
29+    def __setitem__(self, name, val):
30+        "Forbids multi-line headers, to prevent header injection."
31+        if '\n' in val or '\r' in val:
32+            raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
33+        if name == 'Subject':
34+            val = Header(val, settings.DEFAULT_CHARSET)
35+        MIMEMultipart.__setitem__(self, name, val)
36+
37 class SMTPConnection(object):
38     """
39     A wrapper that manages the SMTP network connection.
40@@ -161,6 +177,7 @@
41         self.subject = subject
42         self.body = body
43         self.connection = connection
44+        self.attachments = []
45 
46     def get_connection(self, fail_silently=False):
47         if not self.connection:
48@@ -169,6 +186,12 @@
49 
50     def message(self):
51         msg = SafeMIMEText(self.body, 'plain', settings.DEFAULT_CHARSET)
52+        if self.attachments:
53+            body_msg = msg
54+            msg = SafeMIMEMultipart()
55+            msg.attach(body_msg)
56+            for params in self.attachments:
57+                msg.attach(self._compile_attachment(*params))
58         msg['Subject'] = self.subject
59         msg['From'] = self.from_email
60         msg['To'] = ', '.join(self.to)
61@@ -189,6 +212,39 @@
62         """Send the email message."""
63         return self.get_connection(fail_silently).send_messages([self])
64 
65+    def attach(self, filename, content, mimetype=None):
66+        """Attaches a file with the given filename and content."""
67+        self.attachments.append((filename, content, mimetype))
68+
69+    def attach_file(self, path, mimetype=None):
70+        """Attaches a file from the filesystem."""
71+        filename = os.path.basename(path)
72+        f = open(path, 'rb')
73+        content = f.read()
74+        f.close()
75+        self.attach(filename, content, mimetype)
76+
77+    def _compile_attachment(self, filename, content, mimetype):
78+        """
79+        Compiles the given attachment to a MIME object and returns the new
80+        attachment.
81+        """
82+        if mimetype is None:
83+            # Guess the mimetype based on the filename if possible.
84+            mimetype, xx = mimetypes.guess_type(filename)
85+            if mimetype is None:
86+                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
87+        basetype, subtype = mimetype.split('/', 1)
88+        if basetype == 'text':
89+            attachment = SafeMIMEText(content, subtype, settings.DEFAULT_CHARSET)
90+        else:
91+            # Encode non-text attachments with base64.
92+            attachment = MIMEBase(basetype, subtype)
93+            attachment.set_payload(content)
94+            Encoders.encode_base64(attachment)
95+        attachment.add_header('Content-Disposition', 'attachment', filename=filename)
96+        return attachment
97+
98
99--- docs/email.txt      Mon Jun 25 09:36:30 2007
100+++ docs/email.txt      Tue Jun 26 14:33:28 2007
101@@ -198,8 +198,8 @@
102 .. note::
103     Not all features of the ``EmailMessage`` class are available through the
104     ``send_mail()`` and related wrapper functions. If you wish to use advanced
105-    features, such as BCC'ed recipients or multi-part e-mail, you'll need to
106-    create ``EmailMessage`` instances directly.
107+    features, such as BCC'ed recipients, file attachments, or multi-part
108+    e-mail, you'll need to create ``EmailMessage`` instances directly.
109 
110 In general, ``EmailMessage`` is responsible for creating the e-mail message
111 itself. ``SMTPConnection`` is responsible for the network connection side of
112@@ -238,6 +238,20 @@
113       SMTP server needs to be told the full list of recipients when the message
114       is sent. If you add another way to specify recipients in your class, they
115       need to be returned from this method as well.
116+
117+    * ``attach()`` creates a new file attachment and adds it to the message. It
118+      takes three arguments: ``filename``, ``content`` and ``mimetype``.
119+      ``filename`` is the name of the file attachment as it will appear in the
120+      email. ``content`` is the data that will be contained inside the
121+      attachment. ``mimetype`` is optional, and defines the attachment's MIME
122+      content type. If you omit ``mimetype``, the MIME content type will be
123+      guessed from the filename of the attachment.
124+
125+    * ``attach_file()`` creates a new attachment using a file on the
126+      filesystem, and takes two arguments. The first is the path of the file to
127+      attach. The second argument is the MIME content type to use for the
128+      attachment, and is optional. If the MIME type is omitted then it will be
129+      guessed from the filename.
130 
131 The ``SMTPConnection`` class is initialized with the host, port, username and
132 password for the SMTP server. If you don't specify one or more of those