PageRenderTime 26ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/site/newsite/site-geraldo/django/core/mail.py

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