Django

Code

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

Revision 7864, 13.6 kB (checked in by adrian, 2 weeks ago)

Fixed #7655 -- Added two assertions to mail.py to help people debug a common problem (sending strings instead of lists/tuples for 'to' or 'bcc'). Thanks, guettli

  • 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             assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
209             self.to = list(to)
210         else:
211             self.to = []
212         if bcc:
213             assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
214             self.bcc = list(bcc)
215         else:
216             self.bcc = []
217         self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
218         self.subject = subject
219         self.body = body
220         self.attachments = attachments or []
221         self.extra_headers = headers or {}
222         self.connection = connection
223
224     def get_connection(self, fail_silently=False):
225         if not self.connection:
226             self.connection = SMTPConnection(fail_silently=fail_silently)
227         return self.connection
228
229     def message(self):
230         encoding = self.encoding or settings.DEFAULT_CHARSET
231         msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
232                            self.content_subtype, encoding)
233         if self.attachments:
234             body_msg = msg
235             msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
236             if self.body:
237                 msg.attach(body_msg)
238             for attachment in self.attachments:
239                 if isinstance(attachment, MIMEBase):
240                     msg.attach(attachment)
241                 else:
242                     msg.attach(self._create_attachment(*attachment))
243         msg['Subject'] = self.subject
244         msg['From'] = self.from_email
245         msg['To'] = ', '.join(self.to)
246         msg['Date'] = formatdate()
247         msg['Message-ID'] = make_msgid()
248         for name, value in self.extra_headers.items():
249             msg[name] = value
250         return msg
251
252     def recipients(self):
253         """
254         Returns a list of all recipients of the email (includes direct
255         addressees as well as Bcc entries).
256         """
257         return self.to + self.bcc
258
259     def send(self, fail_silently=False):
260         """Sends the email message."""
261         return self.get_connection(fail_silently).send_messages([self])
262
263     def attach(self, filename=None, content=None, mimetype=None):
264         """
265         Attaches a file with the given filename and content. The filename can
266         be omitted (useful for multipart/alternative messages) and the mimetype
267         is guessed, if not provided.
268
269         If the first parameter is a MIMEBase subclass it is inserted directly
270         into the resulting message attachments.
271         """
272         if isinstance(filename, MIMEBase):
273             assert content == mimetype == None
274             self.attachments.append(filename)
275         else:
276             assert content is not None
277             self.attachments.append((filename, content, mimetype))
278
279     def attach_file(self, path, mimetype=None):
280         """Attaches a file from the filesystem."""
281         filename = os.path.basename(path)
282         content = open(path, 'rb').read()
283         self.attach(filename, content, mimetype)
284
285     def _create_attachment(self, filename, content, mimetype=None):
286         """
287         Converts the filename, content, mimetype triple into a MIME attachment
288         object.
289         """
290         if mimetype is None:
291             mimetype, _ = mimetypes.guess_type(filename)
292             if mimetype is None:
293                 mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
294         basetype, subtype = mimetype.split('/', 1)
295         if basetype == 'text':
296             attachment = SafeMIMEText(smart_str(content,
297                 settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
298         else:
299             # Encode non-text attachments with base64.
300             attachment = MIMEBase(basetype, subtype)
301             attachment.set_payload(content)
302             Encoders.encode_base64(attachment)
303         if filename:
304             attachment.add_header('Content-Disposition', 'attachment',
305                                   filename=filename)
306         return attachment
307
308 class EmailMultiAlternatives(EmailMessage):
309     """
310     A version of EmailMessage that makes it easy to send multipart/alternative
311     messages. For example, including text and HTML versions of the text is
312     made easier.
313     """
314     multipart_subtype = 'alternative'
315
316     def attach_alternative(self, content, mimetype=None):
317         """Attach an alternative content representation."""
318         self.attach(content=content, mimetype=mimetype)
319
320 def send_mail(subject, message, from_email, recipient_list,
321               fail_silently=False, auth_user=None, auth_password=None):
322     """
323     Easy wrapper for sending a single message to a recipient list. All members
324     of the recipient list will see the other recipients in the 'To' field.
325
326     If auth_user is None, the EMAIL_HOST_USER setting is used.
327     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
328
329     Note: The API for this method is frozen. New code wanting to extend the
330     functionality should use the EmailMessage class directly.
331     """
332     connection = SMTPConnection(username=auth_user, password=auth_password,
333                                 fail_silently=fail_silently)
334     return EmailMessage(subject, message, from_email, recipient_list,
335                         connection=connection).send()
336
337 def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
338                    auth_password=None):
339     """
340     Given a datatuple of (subject, message, from_email, recipient_list), sends
341     each message to each recipient list. Returns the number of e-mails sent.
342
343     If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
344     If auth_user and auth_password are set, they're used to log in.
345     If auth_user is None, the EMAIL_HOST_USER setting is used.
346     If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
347
348     Note: The API for this method is frozen. New code wanting to extend the
349     functionality should use the EmailMessage class directly.
350     """
351     connection = SMTPConnection(username=auth_user, password=auth_password,
352                                 fail_silently=fail_silently)
353     messages = [EmailMessage(subject, message, sender, recipient)
354                 for subject, message, sender, recipient in datatuple]
355     return connection.send_messages(messages)
356
357 def mail_admins(subject, message, fail_silently=False):
358     """Sends a message to the admins, as defined by the ADMINS setting."""
359     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
360                  settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
361                  ).send(fail_silently=fail_silently)
362
363 def mail_managers(subject, message, fail_silently=False):
364     """Sends a message to the managers, as defined by the MANAGERS setting."""
365     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
366                  settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
367                  ).send(fail_silently=fail_silently)
Note: See TracBrowser for help on using the browser.