Django

Code

root/django/trunk/django/core/mail.py

Revision 7350, 13.4 kB (checked in by gwilson, 2 months ago)

Fixed some styling issues in django/core/mail.py.

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 """
2 Tools for sending email.
3 """
4
5 import mimetypes
6 import os
7 import smtplib
8 import socket
9 import time
10 import random
11 from email import Charset, Encoders
12 from email.MIMEText import MIMEText
13 from email.MIMEMultipart import MIMEMultipart
14 from email.MIMEBase import MIMEBase
15 from email.Header import Header
16 from email.Utils import formatdate, parseaddr, formataddr
17
18 from django.conf import settings
19 from django.utils.encoding import smart_str, force_unicode
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
25 # Default MIME type to use on attachments (if it is not explicitly given
26 # and cannot be guessed).
27 DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
28
29 # Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
30 # seconds, which slows down the restart of the server.
31 class CachedDnsName(object):
32     def __str__(self):
33         return self.get_fqdn()
34
35     def get_fqdn(self):
36         if not hasattr(self, '_fqdn'):
37             self._fqdn = socket.getfqdn()
38         return self._fqdn
39
40 DNS_NAME = CachedDnsName()
41
42 # Copied from Python standard library, with the following modifications:
43 # * Used cached hostname for performance.
44 # * Added try/except to support lack of getpid() in Jython (#5496).
45 def make_msgid(idstring=None):
46     """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
47
48     <20020201195627.33539.96671@nightshade.la.mastaler.com>
49
50     Optional idstring if given is a string used to strengthen the
51     uniqueness of the message id.
52     """
53     timeval = time.time()
54     utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
55     try:
56         pid = os.getpid()
57     except AttributeError:
58         # No getpid() in Jython, for example.
59         pid = 1
60     randint = random.randrange(100000)
61     if idstring is None:
62         idstring = ''
63     else:
64         idstring = '.' + idstring
65     idhost = DNS_NAME
66     msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
67     return msgid
68
69 class BadHeaderError(ValueError):
70     pass
71
72 def forbid_multi_line_headers(name, val):
73     """Forbids multi-line headers, to prevent header injection."""
74     if '\n' in val or '\r' in val:
75         raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
76     try:
77         val = force_unicode(val).encode('ascii')
78     except UnicodeEncodeError:
79         if name.lower() in ('to', 'from', 'cc'):
80             result = []
81             for item in val.split(', '):
82                 nm, addr = parseaddr(item)
83                 nm = str(Header(nm, settings.DEFAULT_CHARSET))
84                 result.append(formataddr((nm, str(addr))))
85             val = ', '.join(result)
86         else:
87             val = Header(force_unicode(val), settings.DEFAULT_CHARSET)
88     return name, val
89
90 class SafeMIMEText(MIMEText):
91     def __setitem__(self, name, val):
92         name, val = forbid_multi_line_headers(name, val)
93         MIMEText.__setitem__(self, name, val)
94
95 class SafeMIMEMultipart(MIMEMultipart):
96     def __setitem__(self, name, val):
97         name, val = forbid_multi_line_headers(name, val)
98         MIMEMultipart.__setitem__(self, name, val)
99
100 class SMTPConnection(object):
101     """
102     A wrapper that manages the SMTP network connection.
103     """
104
105     def __init__(self, host=None, port=None, username=None, password=None,
106                  use_tls=None, fail_silently=False):
107         self.host = host or settings.EMAIL_HOST
108         self.port = port or settings.EMAIL_PORT
109         self.username = username or settings.EMAIL_HOST_USER
110         self.password = password or settings.EMAIL_HOST_PASSWORD
111         self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
112         self.fail_silently = fail_silently
113         self.connection = None
114
115     def open(self):
116         """
117         Ensures we have a connection to the email server. Returns whether or
118         not a new connection was required (True or False).
119         """
120         if self.connection:
121             # Nothing to do if the connection is already open.
122             return False
123         try:
124             # If local_hostname is not specified, socket.getfqdn() gets used.
125             # For performance, we use the cached FQDN for local_hostname.
126             self.connection = smtplib.SMTP(self.host, self.port,
127                                            local_hostname=DNS_NAME.get_fqdn())
128             if self.use_tls:
129                 self.connection.ehlo()
130                 self.connection.starttls()
131                 self.connection.ehlo()
132             if self.username and self.password:
133                 self.connection.login(self.username, self.password)
134             return True
135         except:
136             if not self.fail_silently:
137                 raise
138
139     def close(self):
140         """Closes the connection to the email server."""
141         try:
142             try:
143                 self.connection.quit()
144             except socket.sslerror:
145                 # This happens when calling quit() on a TLS connection
146                 # sometimes.
147                 self.connection.close()
148             except:
149                 if self.fail_silently:
150                     return
151                 raise
152         finally:
153             self.connection = None
154
155     def send_messages(self, email_messages):
156         """
157         Sends one or more EmailMessage objects and returns the number of email
158         messages sent.
159         """
160         if not email_messages:
161             return
162         new_conn_created = self.open()
163         if not self.connection:
164             # We failed silently on open(). Trying to send would be pointless.
165             return
166         num_sent = 0
167         for message in email_messages:
168             sent = self._send(message)
169             if sent:
170                 num_sent += 1
171         if new_conn_created:
172             self.close()
173         return num_sent
174
175     def _send(self, email_message):
176         """A helper method that does the actual sending."""
177         if not email_message.to:
178             return False
179         try:
180             self.connection.sendmail(email_message.from_email,
181                     email_message.recipients(),
182                     email_message.message().as_string())
183         except:
184             if not self.fail_silently:
185                 raise
186             return False
187         return True
188
189 class EmailMessage(object):
190     """
191     A container for email information.
192     """
193     content_subtype = 'plain'
194     multipart_subtype = 'mixed'
195     encoding = None     # None => use settings default
196
197     def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
198             connection=None, attachments=None, headers=None):
199         """
200         Initialize a single email message (which can be sent to multiple
201         recipients).
202
203         All strings used to create the message can be unicode strings (or UTF-8
204         bytestrings). The SafeMIMEText class will handle any necessary encoding
205         conversions.
206         """
207         if to:
208             self.to = list(to)
209         else:
210             self.to = []
211         if bcc:
212             self.bcc = list(bcc)
213         else:
214             self.bcc = []
215         self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
216         self.subject = subject
217         self.body = body
218         self.attachments = attachments or []
219         self.extra_headers = headers or {}
220         self.connection = connection
221
222     def get_connection(self, fail_silently=False):
223         if not self.connection:
224             self.connection = SMTPConnection(fail_silently=fail_silently)
225         return self.connection
226
227     def message(self):
228         encoding = self.encoding or settings.DEFAULT_CHARSET
229         msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
230                            self.content_subtype, encoding)
231         if self.attachments:
232             body_msg = msg
233             msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
234             if self.body:
235                 msg.attach(body_msg)
236             for attachment in self.attachments:
237                 if isinstance(attachment, MIMEBase):
238                     msg.attach(attachment)
239                 else:
240                     msg.attach(self._create_attachment(*attachment))
241         msg['Subject'] = self.subject
242         msg['From'] = self.from_email
243         msg['To'] = ', '.join(self.to)
244         msg['Date'] = formatdate()
245         msg['Message-ID'] = make_msgid()
246         for name, value in self.extra_headers.items():
247             msg[name] = value
248         return msg
249
250     def recipients(self):
251         """
252         Returns a list of all recipients of the email (includes direct
253         addressees as well as Bcc entries).
254         """
255         return self.to + self.bcc
256
257     def send(self, fail_silently=False):
258         """Sends the email message."""
259         return self.get_connection(fail_silently).send_messages([self])
260
261     def attach(self, filename=None, content=None, mimetype=None):
262         """
263         Attaches a file with the given filename and content. The filename can
264         be omitted (useful for multipart/alternative messages) and the mimetype
265         is guessed, if not provided.
266
267         If the first parameter is a MIMEBase subclass it is inserted directly
268         into the resulting message attachments.
269         """
270         if isinstance(filename, MIMEBase):
271             assert content == mimetype == None
272             self.attachments.append(filename)
273         else:
274             assert content is not None
275             self.attachments.append((filename, content, mimetype))
276
277     def attach_file(self, path, mimetype=None):
278         """Attaches a file from the filesystem."""
279         filename = os.path.basename(path)
280         content = open(path, 'rb').read()
281         self.attach(filename, content, mimetype)
282
283     def _create_attachment(self, filename, content, mimetype=None):
284         """
285         Converts the filename, content, mimetype triple into a MIME attachment
286         object.
287         """
288         if mimetype is None:
289             mimetype, _ = mimetypes.guess_type(filename)
290             if mimetype is None:
291                 mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
292         basetype, subtype = mimetype.split('/', 1)
293         if basetype == 'text':
294             attachment = SafeMIMEText(smart_str(content,
295                 settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
296         else:
297             # Encode non-text attachments with base64.
298             attachment = MIMEBase(basetype, subtype)
299             attachment.set_payload(content)
300             Encoders.encode_base64(attachment)
301         if filename:
302             attachment.add_header('Content-Disposition', 'attachment',
303                                   filename=filename)
304         return attachment
305
306 class EmailMultiAlternatives(EmailMessage):
307     """
308     A version of EmailMessage that makes it easy to send multipart/alternative
309     messages. For example, including text and HTML versions of the text is
310     made easier.
311     """
312     multipart_subtype = 'alternative'
313
314     def attach_alternative(self, content, mimetype=None):
315         """Attach an alternative content representation."""
316         self.attach(content=content, mimetype=mimetype)
317
318 def send_mail(subject, message, from_email, recipient_list,
319               fail_silently=False, auth_user=None, auth_password=None):
320     """
321     Easy wrapper for sending a single message to a recipient list. All members
322     of the recipient list will see the other recipients in the 'To' field.
323
324     If auth_user is None, the EMAIL_HOST_USER setting is used.
325     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
326
327     Note: The API for this method is frozen. New code wanting to extend the
328     functionality should use the EmailMessage class directly.
329     """
330     connection = SMTPConnection(username=auth_user, password=auth_password,
331                                 fail_silently=fail_silently)
332     return EmailMessage(subject, message, from_email, recipient_list,
333                         connection=connection).send()
334
335 def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
336                    auth_password=None):
337     """
338     Given a datatuple of (subject, message, from_email, recipient_list), sends
339     each message to each recipient list. Returns the number of e-mails sent.
340
341     If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
342     If auth_user and auth_password are set, they're used to log in.
343     If auth_user is None, the EMAIL_HOST_USER setting is used.
344     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
345
346     Note: The API for this method is frozen. New code wanting to extend the
347     functionality should use the EmailMessage class directly.
348     """
349     connection = SMTPConnection(username=auth_user, password=auth_password,
350                                 fail_silently=fail_silently)
351     messages = [EmailMessage(subject, message, sender, recipient)
352                 for subject, message, sender, recipient in datatuple]
353     return connection.send_messages(messages)
354
355 def mail_admins(subject, message, fail_silently=False):
356     """Sends a message to the admins, as defined by the ADMINS setting."""
357     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
358                  settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
359                  ).send(fail_silently=fail_silently)
360
361 def mail_managers(subject, message, fail_silently=False):
362     """Sends a message to the managers, as defined by the MANAGERS setting."""
363     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
364                  settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
365                  ).send(fail_silently=fail_silently)
Note: See TracBrowser for help on using the browser.