[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 &lt;recipient at bogus.com>"
>                        from="John Doe &lt;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:&#10;
>           From: <xsl:value-of select="$email:from"/>&#10;
>           Subject: <xsl:value-of select="$email:subject"/>&#10;
>           Date: <xsl:value-of select="$email:date"/>&#10;
>       </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