[4suite-dev] Proposal for XSLT extension for sending email
Eric Larson
ionrock at gmail.com
Wed Oct 3 10:16:31 MDT 2007
Hi John,
Not sure if you are using WSGI apps much, buf if you want the
extension included with my XSLTemplates I am more than happy to add
it.
Sure not many people use my middleware, but an email extension element
makes a lot sense for WSGI templating middleware ;)
Eric
On 10/3/07, Uche Ogbuji <uche at ogbuji.net> wrote:
> "John L. Clark" wrote:
> > I have written an XSLT extension for sending email from within an XSLT
> > script. The patch (which includes documentation in doc strings)
> > should be attached to this email. What do you think of this idea?
> > Would it be ok if I checked this in?
> >
>
> Hi John,
>
> I don't think this extension is appropriate for 4Suite built-ins. In
> fact, I've written it before, and I left it out of built-ins on purpose
> :-) I wrote this for Akara and I've attached my version of the module.
> My version predates some of the Python e-mail API improvements so your
> is probably better, but my point is that first of all this is not a
> "core" enough need to go in 4Suite built-ins, and also that it's one
> that is potentially a security risk.
>
> I have a side project called ftgoodies where I include the dozen or so
> extensions I created for Akara and other projects but don't belong in
> the built-ins. I'd like to finally get it posted to cheeseshop and make
> that the destination for such non-core extensions by core 4Suite
> developers. I hope that's OK with you (and others), and that you don't
> mind backing out your addition from core 4Suite CVS.
>
> Thanks.
>
> --
> Uche Ogbuji http://uche.ogbuji.net
> Founding Partner, Zepheira http://zepheira.com
> Linked-in profile: http://www.linkedin.com/in/ucheogbuji
> Articles: http://uche.ogbuji.net/tech/publications/
>
>
> from Ft.Xml import EMPTY_NAMESPACE
> from Ft.Xml.Xslt import XSL_NAMESPACE, XsltElement, XsltException, Error
> from Ft.Xml.Xslt import ContentInfo, AttributeInfo
> from Ft.Xml.Xslt import ApplyTemplatesElement
> from Ft.Xml.Xslt import OutputParameters
> from Ft.Xml.XPath import Conversions
> from Ft.Xml.XPath import FT_EXT_NAMESPACE, FT_OLD_EXT_NAMESPACE
> from Ft.Server import RESERVED_NAMESPACE
>
> EXT_NAMESPACE = u'http://xmlns.4suite.org/4ss/modules/e-mail'
>
> import mailbox
> import os, sys, email, smtplib, cStringIO
>
> from email import Encoders
> from email.MIMEText import MIMEText
>
>
> class SendMailElement(XsltElement):
> """
> Send SMTP e-mail
>
> The content of this element is a template which is instantiated, and the
> output becomes the body of the message. All the output control attributes
> are supported, and are used to control the format of the resulting message
> For a regular text message, you could set method="text". If you choose to
> annoy people with HTML mail, you could use method="html" and the mail would
> be sent with a "text/html" IMT. One useful trick is that the media_type
> attribute supersedes the attribute method for purposes of determining the
> e-mail media type. So, for instance, if you want to send HTML mail, but
> the source of the HTML is actually a block of text, you would want to use
> method="text" in order to avoid escaing the tag characters in the text
> block, but you could maintain the HTML IMT by setting media_type="text/html"
> to ensure it's still sent as HTML mail.
>
> An example:
>
> <xsl:stylesheet version="1.0"
> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
> xmlns:date="http://exslt.org/dates-and-times"
> xmlns:email="http://xmlns.4suite.org/4ss/modules/e-mail"
> extension-element-prefixes="email"
> >
>
> <xsl:template match="/">
> <email:send-mail to="Jane Doe <recipient at bogus.com>"
> from="John Doe <sender at bogus.com>"
> subject="email:send-mail test from 4Suite daemon"
> method="text"
> >
> This message was sent at <xsl:value-of select="date:date-time()"/>
> </email:send-mail>
> </xsl:template>
> </xsl:stylesheet>
>
> TO-DO: Extend to support arbitrary headers and multi-part messages
> Support encodings and other l10n
> """
>
> content = ContentInfo.Template
>
> legalAttrs = {
> 'to' : AttributeInfo.StringAvt(description="The destination e-mail address. Can be a 'friendly' format such as 'John Doe <jdoe at bogus.com>'"),
> 'from' : AttributeInfo.StringAvt(description="The e-mail address from for the From: header. Can be a 'friendly' format such as 'John Doe <jdoe at bogus.com>'"),
> 'reply-to' : AttributeInfo.StringAvt(description="The e-mail address from for the Reply-To: header. Can be a 'friendly' format such as 'John Doe <jdoe at bogus.com>'"),
> 'subject' : AttributeInfo.StringAvt(description="The subject text for the e-mail address"),
> 'server' : AttributeInfo.StringAvt(default="localhost", description="The server hostname or address to use as the SMTP gateway"),
> }
>
> legalAttrs.update({
> 'method' : AttributeInfo.QNameAvt(),
> 'version' : AttributeInfo.NMTokenAvt(),
> 'encoding' : AttributeInfo.StringAvt(),
> 'omit-xml-declaration' : AttributeInfo.YesNoAvt(),
> 'standalone' : AttributeInfo.YesNoAvt(),
> 'doctype-public' : AttributeInfo.StringAvt(),
> 'doctype-system' : AttributeInfo.StringAvt(),
> 'cdata-section-elements' : AttributeInfo.QNamesAvt(),
> 'indent' : AttributeInfo.YesNoAvt(),
> 'media-type' : AttributeInfo.StringAvt(default='text/plain', description='Specifies the internet media type for the message'),
> })
>
> childArgument = ('src', 1, None, 'The output placed in the e-mail body')
>
> def __init__(self, root, namespaceUri, localName, baseUri):
> XsltElement.__init__(self, root, namespaceUri, localName, baseUri)
> self._output_parameters = OutputParameters.OutputParameters()
> return
>
> def instantiate(self, context, processor):
> context.setProcessState(self)
>
> to = self._to.evaluate(context)
> #'from' is a Python keyword, so you must access it in this way
> from_ = self.__dict__['_from'].evaluate(context)
> reply_to = self._reply_to.evaluate(context)
> subject = self._subject.evaluate(context)
> server = self._server.evaluate(context)
> self._output_parameters.avtParse(self, context)
>
> stream = cStringIO.StringIO()
> processor.addHandler(self._output_parameters, stream)
> for child in self.children:
> context = child.instantiate(context, processor)[0]
> processor.removeHandler()
>
> body = stream.getvalue()
> media_type = self._output_parameters.mediaType
>
> #if not self._media_type:
> # METHOD_TO_SUBTYPES = {(None, 'html'): 'html',
> # (None, 'xml'): 'xml',
> # (None, 'text'): 'text'}
> # msg = MIMEText(
> # body, _subtype=METHOD_TO_SUBTYPES.get(self._method, 'plain'),
> # _encoder=Encoders.encode_quopri)
> subtype = media_type.split('/')[1]
> msg = MIMEText(body, _subtype=subtype,
> _encoder=Encoders.encode_quopri)
>
> msg['Subject'] = subject
> msg['From'] = from_
> msg['To'] = to
>
> addr = to
> #print msg
> server = smtplib.SMTP(server)
> server.set_debuglevel(1)
> server.sendmail(from_, [addr], msg.as_string(0))
> server.quit()
> #Write to the log
> log = processor.extensionParams.get((RESERVED_NAMESPACE, 'error-log'))
> if log:
> log.debug("e-mail sent to '%s'"%(to))
> else:
> print "e-mail sent to '%s'"%(to)
> return (context,)
>
>
> class DummyMsg: # a file-like object for passing a string to rfc822.Message. A bit inefficient with keeping the text twice
> def __init__(self, text):
> self._lines = text.split('\015\012')
> self._lines.reverse()
> self._text = text
> return
> def read(self):
> return self._text
> def readline(self):
> try: return self._lines.pop() + '\n'
> except: return ''
>
>
> class CheckImapMailboxElement(XsltElement):
> """
> Check an IMAP mailbox and handle e-mail found there
>
> The contents are a template which is instantiated once for each message
> processed, with special variables set to provide the data from the
> message
>
> Example:
>
> <xsl:stylesheet version="1.0"
> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
> xmlns:email="http://xmlns.4suite.org/4ss/modules/e-mail"
> extension-element-prefixes="email"
> >
>
> <xsl:output method="text"/>
>
> <xsl:template match="/">
> <email:check-imap-mailbox server="localhost"
> user="moby" password="dick" unseen-only="no" headers-only="no">
> <!-- This template is instantiated for each message found -->
> Message found:
> From: <xsl:value-of select="$email:from"/>
> Subject: <xsl:value-of select="$email:subject"/>
> Date: <xsl:value-of select="$email:date"/>
> </email:check-imap-mailbox>
> </xsl:template>
> </xsl:stylesheet>
>
> TO-DO: Extend to support arbitrary incoming headers and multi-part messages
> Support encodings and other l10n
> Use email rather than rfc822 module
> """
>
> #Good Python/IMAP material at:
> # http://python.org/pydoc/lib/imap4-objects.html
> # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52299
> # http://www.cs.usyd.edu.au/~piers/python/py2html.cgi/~piers/python/save_all_attachments
> #General IMAP material at:
> # http://www.faqs.org/rfcs/rfc2060.html
> # http://www.cac.washington.edu/imap/
>
> legalAttrs = {
> 'server' : AttributeInfo.StringAvt(default="localhost", description="the address (name or IP) of the IMAP 4 server to connect to"),
> 'user' : AttributeInfo.StringAvt(description="the user to connect as"),
> 'password' : AttributeInfo.StringAvt(description="yes, this is rather dodgy for security. Suggestions welcome."),
> 'unseen-only' : AttributeInfo.YesNoAvt(default="yes", description="whether to read all messages not marked as deleted, or just those marked as unseen"),
> 'cleanup' : AttributeInfo.YesNoAvt(default="no", description="whether or not to delete read messages when done"),
> 'headers-only' : AttributeInfo.YesNoAvt(default="yes", description="whether to download the body for each message or just the headers. Note that if 'yes', messages are *not* marked as read"),
> }
>
> def instantiate(self, context, processor):
> import imaplib
> server_addr = self._server.evaluate(context)
> user = self._user.evaluate(context)
> password = self._password.evaluate(context)
> cleanup = self._cleanup.evaluate(context)
> unseen_only = self._unseen_only.evaluate(context)
> headers_only = self._headers_only.evaluate(context)
>
> log = processor.extensionParams.get((RESERVED_NAMESPACE, 'error-log'))
> if log:
> logfunc = log.debug
> else:
> logfunc = sys.stderr.write
>
> server = imaplib.IMAP4(server_addr)
> server.login(user, password)
> result, message = server.select(readonly=(not cleanup))
> if result != 'OK':
> raise Exception, message
> #typ, data = server.search(None, 'ALL')
> flags = unseen_only and '(UNSEEN UNDELETED)' or '(UNDELETED)'
> typ, data = server.search(None, flags)
> #e.g. for a mailbox with 3 messages in it: typ, data == ('OK', ['2 3 4'])
> save_vars = context.varBindings.copy()
> seen = []
> for num in data[0].split():
> try:
> #typ, data = server.fetch(num, '(BODY[HEADER.FIELDS (SUBJECT FROM)])')
> #According to RFC 2060:
> #RFC822 == BODY[]
> #RFC822.HEADER = BODY.PEEK[HEADERS]
> flags = headers_only and '(RFC822.HEADER)' or '(RFC822)'
> typ, mdata = server.fetch(num, flags)
> msg = email.message_from_string(mdata[0][1])
> subject = msg['subject']
> except KeyError:
> #typ, mdata = server.fetch(num, '(BODY[HEADER.FIELDS (FROM)])')
> typ, mdata = server.fetch(num, '(RFC822)')
> msg = email.message_from_string(mdata[0][1])
> subject = '(no subject)'
> #fromaddr = msg.getaddr('From')
> #if fromaddr[0] == "": n = fromaddr[1]
> #else: n = fromaddr[0]
>
> encoding = "ISO-8859-1"
> mail_vars = {
> (EXT_NAMESPACE, u"subject"): unicode(subject, encoding),
> (EXT_NAMESPACE, u"reply-to"): unicode(msg.get("Reply-To", ""), encoding),
> (EXT_NAMESPACE, u"from"): unicode(msg.get("From", ""), encoding),
> (EXT_NAMESPACE, u"date"): unicode(msg.get("Date", ""), encoding),
> }
> if not headers_only:
> mail_vars[(EXT_NAMESPACE, u"body")] = unicode(msg.get_payload(), encoding)
> logfunc('%-20.20s|%-20.20s' % (msg.get("From", ""), subject))
> context.varBindings.update(mail_vars)
> for child in self.children:
> context = child.instantiate(context, processor)[0]
> seen.append(num)
>
> if cleanup and seen:
> logfunc("Cleaning up")
> seen.sort() # Must delete from end first
> seen.reverse() # Otherwise 'num' is invalid
> for num in seen:
> #print "Deleting message", num
> server.store(num, 'FLAGS', '(\Deleted)')
>
> server.logout()
>
> context.varBindings = save_vars
> return (context,)
>
>
> class CheckLocalMailboxElement(XsltElement):
> """
> This extension is for checking local UNIX mailboxes. It probably needs
> a lot of work and I suggest no one uses it without examining it clearly.
> Patches welcome
> """
>
> legalAttrs = {
> 'mbox' : AttributeInfo.StringAvt(),
> 'cleanup' : AttributeInfo.YesNoAvt(),
> }
>
> def instantiate(self, context, processor):
> mb = self._mbox.evaluate(context)
> try:
> fp = open(mb, 'r')
> except IOError, e:
> print e
> return (context,)
> cleanup = self._cleanup.evaluate(context)
> mbox = mailbox.PortableUnixMailbox(fp)
> #print "Clean up", cleanup
> #print "Mail box", mb
> msg = mbox.next()
> save_vars = context.varBindings.copy()
> while msg:
> #print msg
> mail_vars = {
> (EXT_NAMESPACE, u"subject"): unicode(msg.get("Subject", "")),
> (EXT_NAMESPACE, u"reply-to"): unicode(msg.get("Reply-To", "")),
> (EXT_NAMESPACE, u"from"): unicode(msg.get("From", "")),
> (EXT_NAMESPACE, u"date"): unicode(msg.get("Date", "")),
> }
> context.varBindings.update(mail_vars)
> for child in self.children:
> context = child.instantiate(context, processor)[0]
>
> msg = mbox.next()
> fp.close()
> if cleanup:
> os.unlink(mb)
> context.varBindings = save_vars
> return (context,)
>
>
> ExtElements = {
> (EXT_NAMESPACE, 'check-local-mailbox'): CheckLocalMailboxElement,
> (EXT_NAMESPACE, 'check-imap-mailbox'): CheckImapMailboxElement,
> (EXT_NAMESPACE, 'send-mail'): SendMailElement,
> }
>
>
> _______________________________________________
> 4suite-dev mailing list
> 4suite-dev at lists.fourthought.com
> http://lists.fourthought.com/mailman/listinfo/4suite-dev
>
>
More information about the 4suite-dev
mailing list