Permalink
Browse files

attachment support for web.sendmail (tx gregglind)

  • Loading branch information...
1 parent 0a7c9de commit b7d17b18b96b4ce40d8df1308ea6a5dac315dd88 @anandology anandology committed Mar 8, 2010
Showing with 154 additions and 99 deletions.
  1. +12 −20 web/debugerror.py
  2. +142 −79 web/utils.py
View
@@ -13,7 +13,7 @@
import sys, urlparse, pprint, traceback
from net import websafe
from template import Template
-from utils import sendmail
+from utils import sendmail, safestr
import webapi as web
import os, os.path
@@ -323,26 +323,18 @@ def emailerrors_internal():
tb_txt = ''.join(traceback.format_exception(*tb))
path = web.ctx.path
request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath
- text = ("""\
-------here----
-Content-Type: text/plain
-Content-Disposition: inline
-
-%(request)s
-
-%(tb_txt)s
-
-------here----
-Content-Type: text/html; name="bug.html"
-Content-Disposition: attachment; filename="bug.html"
-
-""" % locals()) + str(djangoerror())
+
+ message = "\n%s\n\n%s\n\n" % (request, tb_txt)
+
sendmail(
- "your buggy site <%s>" % from_address,
- "the bugfixer <%s>" % to_address,
- "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
- text,
- headers={'Content-Type': 'multipart/mixed; boundary="----here----"'})
+ "your buggy site <%s>" % from_address,
+ "the bugfixer <%s>" % to_address,
+ "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
+ message,
+ attachments=[
+ dict(filename="bug.html", payload=safestr(djangoerror()))
+ ],
+ )
return error
return emailerrors_internal
View
@@ -1252,99 +1252,162 @@ def sendmail(from_address, to_address, subject, message, headers=None, **kw):
for from `from_address_` to `to_address` with `subject`.
Additional email headers can be specified with the dictionary
`headers.
+
+ Optionally cc, bcc and attachments can be specified as keyword arguments.
+ Attachments must be a list and each attachment can be either a file object
+ or a dictionary with filename, payload and optionally content_type keys.
If `web.config.smtp_server` is set, it will send the message
to that SMTP server. Otherwise it will look for
`/usr/sbin/sendmail`, the typical location for the sendmail-style
binary. To use sendmail from a different path, set `web.config.sendmail_path`.
"""
- try:
- import webapi
- except ImportError:
- webapi = Storage(config=Storage())
+ mail = _EmailMessage(from_address, to_address, subject, message, headers, **kw)
- if headers is None: headers = {}
+ for a in attachments or []:
+ if isinstance(a, dict):
+ mail.attach(a['filename'], a['payload'], a.get('content_type'))
+ elif hasattr(a, 'read'): # file
+ filename = getattr(a, "name", "")
+ content_type = getattr(a, 'content_type', None)
+ mail.attah(filename, a.read(), content_type)
+ else:
+ raise ValueError, "Invalid attachment: %s" % repr(a)
+
+ mail.send()
+
+class _EmailMessage:
+ def __init__(self, from_address, to_address, subject, message, headers=None, **kw):
+ def listify(x):
+ if not isinstance(x, list):
+ return [safestr(x)]
+ else:
+ return [safestr(a) for a in x]
- cc = kw.get('cc', [])
- bcc = kw.get('bcc', [])
+ subject = safestr(subject)
+ message = safestr(message)
+
+ from_address = safestr(from_address)
+ to_address = listify(to_address)
+ cc = listify(kw.get('cc', []))
+ bcc = listify(kw.get('bcc', []))
+ recipients = to_address + cc + bcc
+
+ import email.Utils
+ self.from_address = email.Utils.parseaddr(from_address)[1]
+ self.recipients = [email.Utils.parseaddr(r)[1] for r in recipients]
- def listify(x):
- if not isinstance(x, list):
- return [safestr(x)]
- else:
- return [safestr(a) for a in x]
-
- from_address = safestr(from_address)
-
- to_address = listify(to_address)
- cc = listify(cc)
- bcc = listify(bcc)
-
- recipients = to_address + cc + bcc
+ self.headers = dictadd({
+ 'From': from_address,
+ 'To': ", ".join(to_address),
+ 'Subject': subject
+ }, headers or {})
+
+ if cc:
+ self.headers['Cc'] = ", ".join(cc)
+
+ self.message = self.new_message()
+ self.message.add_header("Content-Transfer-Encoding", "7bit")
+ self.message.add_header("Content-Disposition", "inline")
+ self.message.add_header("MIME-Version", "1.0")
+ self.message.set_payload(message, 'utf-8')
+ self.multipart = False
+
+ def new_message(self):
+ try:
+ from email.message import Message
+ except:
+ from email import Message
+
+ return Message()
+
+ def attach(self, filename, payload, content_type=None):
+ if not self.multipart:
+ msg = self.new_message()
+ msg.add_header("Content-Type", "multipart/mixed")
+ msg.attach(self.message)
+ self.message = msg
+ self.multipart = True
+
+ import mimetypes
+ try:
+ from email import encoders
+ except:
+ from email import Encoders as encoders
+
+ content_type = content_type or mimetypes.guess_type(filename)[0] or "applcation/octet-stream"
+
+ msg = self.new_message()
+ msg.set_payload(payload)
+ msg.add_header('Content-Type', content_type)
+ msg.add_header('Content-Disposition', 'attachment', filename=filename)
+
+ if not content_type.startswith("text/"):
+ encoders.encode_base64(msg)
+
+ self.message.attach(msg)
- headers = dictadd({
- 'MIME-Version': '1.0',
- 'Content-Type': 'text/plain; charset=UTF-8',
- 'Content-Disposition': 'inline',
- 'From': from_address,
- 'To': ", ".join(to_address),
- 'Subject': subject
- }, headers)
-
- if cc:
- headers['Cc'] = ", ".join(cc)
+ def send(self):
+ try:
+ import webapi
+ except ImportError:
+ webapi = Storage(config=Storage())
+
+ for k, v in self.headers.iteritems():
+ self.message.add_header(k, v)
+
+ message_text = self.message.as_string()
- import email.Utils
- from_address = email.Utils.parseaddr(from_address)[1]
- recipients = [email.Utils.parseaddr(r)[1] for r in recipients]
- message = ('\n'.join([safestr('%s: %s' % x) for x in headers.iteritems()])
- + "\n\n" + safestr(message))
-
- if webapi.config.get('smtp_server'):
- server = webapi.config.get('smtp_server')
- port = webapi.config.get('smtp_port', 0)
- username = webapi.config.get('smtp_username')
- password = webapi.config.get('smtp_password')
- debug_level = webapi.config.get('smtp_debuglevel', None)
- starttls = webapi.config.get('smtp_starttls', False)
-
- import smtplib
- smtpserver = smtplib.SMTP(server, port)
-
- if debug_level:
- smtpserver.set_debuglevel(debug_level)
-
- if starttls:
- smtpserver.ehlo()
- smtpserver.starttls()
- smtpserver.ehlo()
-
- if username and password:
- smtpserver.login(username, password)
-
- smtpserver.sendmail(from_address, recipients, message)
- smtpserver.quit()
- else:
- sendmail = webapi.config.get('sendmail_path', '/usr/sbin/sendmail')
+ if webapi.config.get('smtp_server'):
+ server = webapi.config.get('smtp_server')
+ port = webapi.config.get('smtp_port', 0)
+ username = webapi.config.get('smtp_username')
+ password = webapi.config.get('smtp_password')
+ debug_level = webapi.config.get('smtp_debuglevel', None)
+ starttls = webapi.config.get('smtp_starttls', False)
+
+ import smtplib
+ smtpserver = smtplib.SMTP(server, port)
+
+ if debug_level:
+ smtpserver.set_debuglevel(debug_level)
+
+ if starttls:
+ smtpserver.ehlo()
+ smtpserver.starttls()
+ smtpserver.ehlo()
+
+ if username and password:
+ smtpserver.login(username, password)
+
+ smtpserver.sendmail(self.from_address, self.recipients, message_text)
+ smtpserver.quit()
+ else:
+ sendmail = webapi.config.get('sendmail_path', '/usr/sbin/sendmail')
- assert not from_address.startswith('-'), 'security'
- for r in recipients:
- assert not r.startswith('-'), 'security'
+ assert not from_address.startswith('-'), 'security'
+ for r in recipients:
+ assert not r.startswith('-'), 'security'
- cmd = [sendmail, '-f', from_address] + recipients
+ cmd = [sendmail, '-f', self.from_address] + self.recipients
- if subprocess:
- p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
- p.stdin.write(message)
- p.stdin.close()
- p.wait()
- else:
- import os
- i, o = os.popen2(cmd)
- i.write(message)
- i.close()
- o.close()
- del i, o
+ if subprocess:
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
+ p.stdin.write(message_text)
+ p.stdin.close()
+ p.wait()
+ else:
+ i, o = os.popen2(cmd)
+ i.write(message)
+ i.close()
+ o.close()
+ del i, o
+
+ def __repr__(self):
+ return "<EmailMessage>"
+
+ def __str__(self):
+ return self.message.as_string()
if __name__ == "__main__":
import doctest

2 comments on commit b7d17b1

Please sign in to comment.