diff options
Diffstat (limited to 'gentoobrowse-api/service/notifications')
11 files changed, 517 insertions, 0 deletions
diff --git a/gentoobrowse-api/service/notifications/Jamfile.jam b/gentoobrowse-api/service/notifications/Jamfile.jam new file mode 100644 index 0000000..a168649 --- /dev/null +++ b/gentoobrowse-api/service/notifications/Jamfile.jam @@ -0,0 +1,44 @@ +import type : register ; +import generators : register-standard ; + +type.register XSLT : xslt ; + +generators.register-standard xslt.h : XSLT : H ; + +h base-xslt : xslt/base.xslt : <slicer>yes ; +h signup-xslt : xslt/signup.xslt : <slicer>yes ; +h news-xslt : xslt/news.xslt : <slicer>yes ; + +lib exslt ; +lib xslt : : : : <include>/usr/include/libxslt ; +lib xml2 : : : : <include>/usr/include/libxml2 ; +lib esmtp ; +lib slicer-xml ; +lib boost_system ; + +lib gentoobrowse-service-notifications : + [ glob *.ice *.cpp ] + : + <slicer>yes + <library>xslt + <library>xml2 + <library>esmtp + <library>../../..//libxmlpp + <library>boost_system + <library>slicer-xml + <library>../../domain//gentoobrowse-domain + <implicit-dependency>../../domain//gentoobrowse-domain + <dependency>base-xslt + <dependency>signup-xslt + <dependency>news-xslt + : : + <include>. + ; + +actions xslt.h +{ + ( cd $(2:D) ; xxd -i $(2:B)$(2:S) ) > $(1) +} + +IMPORT $(__name__) : xslt.h : : xslt.h ; + diff --git a/gentoobrowse-api/service/notifications/mailserverimpl.cpp b/gentoobrowse-api/service/notifications/mailserverimpl.cpp new file mode 100644 index 0000000..c3dfdfa --- /dev/null +++ b/gentoobrowse-api/service/notifications/mailserverimpl.cpp @@ -0,0 +1,64 @@ +#include "mailserverimpl.h" +#include <libesmtp.h> +#include <Ice/ObjectAdapter.h> +#include <Ice/Communicator.h> + +namespace Gentoo { + namespace Service { + typedef std::vector<std::string> Parts; + typedef std::pair<Parts::iterator, Parts::const_iterator> PartsProgress; + + void + MailServer::sendEmail(const Gentoo::EmailPtr & msg, const Ice::Current & c) + { + auto props = c.adapter->getCommunicator()->getProperties(); + smtp_session_t session = smtp_create_session(); + smtp_message_t message = smtp_add_message(session); + auto server = props->getPropertyWithDefault("GentooBrowseAPI.MailServer", "localhost"); + smtp_set_server(session, server.c_str()); + smtp_set_header(message, "To", msg->to.name.c_str(), msg->to.address.c_str()); + smtp_set_header(message, "From", msg->from.name.c_str(), msg->from.address.c_str()); + smtp_set_header(message, "Subject", msg->subject.c_str()); + Parts parts; + parts.emplace_back("Content-Type: multipart/alternative; boundary=\"<<divider>>\"\r\n"); + parts.emplace_back("MIME-Version: 1.0\r\n"); + parts.emplace_back("Content-Transfer-Encoding: binary\r\n"); + smtp_add_recipient(message, msg->to.address.c_str()); + for (const auto & p : msg->body) { + parts.emplace_back("\r\n--<<divider>>\r\nContent-Type: " + p->mimetype + "; utf-8\r\n\r\n"); + parts.emplace_back(p->payload); + } + parts.emplace_back("\r\n--<<divider>>--\r\n"); + PartsProgress pp { parts.begin(), parts.end() }; + smtp_set_messagecb(message, writeBody, &pp); + if (!smtp_start_session(session)) { + char buf[BUFSIZ]; + auto b = smtp_strerror(smtp_errno(), buf, sizeof(buf)); + assert(b); + SendEmailFailed e(b); + smtp_destroy_session(session); + throw e; + } + smtp_destroy_session(session); + } + + const char * + MailServer::writeBody(void **, int * len, void * arg) + { + auto parts = static_cast<PartsProgress *>(arg); + if (len == NULL || parts->first == parts->second) { + return NULL; + } + const auto & p = *parts->first++; + *len = p.length(); + return p.data(); + } + } + + void + SendEmailFailed::ice_print(std::ostream & buf) const + { + buf << "Failed to send email: " << message; + } +} + diff --git a/gentoobrowse-api/service/notifications/mailserverimpl.h b/gentoobrowse-api/service/notifications/mailserverimpl.h new file mode 100644 index 0000000..23b17c2 --- /dev/null +++ b/gentoobrowse-api/service/notifications/mailserverimpl.h @@ -0,0 +1,20 @@ +#ifndef MAILSERVERIMPL_H +#define MAILSERVERIMPL_H + +#include <notifications.h> +#include <visibility.h> + +namespace Gentoo { + namespace Service { + class DLL_PUBLIC MailServer : public Gentoo::MailServer { + public: + void sendEmail(const Gentoo::EmailPtr & msg, const Ice::Current &) override; + + private: + static const char * writeBody(void ** buf, int * len, void * arg); + }; + } +} + +#endif + diff --git a/gentoobrowse-api/service/notifications/notifications.ice b/gentoobrowse-api/service/notifications/notifications.ice new file mode 100644 index 0000000..a60fa28 --- /dev/null +++ b/gentoobrowse-api/service/notifications/notifications.ice @@ -0,0 +1,49 @@ +#ifndef GENTOO_NOTIFICATIONS +#define GENTOO_NOTIFICATIONS + +#include <user-models.ice> +#include <portage-models.ice> + +module Gentoo { + class MimePart { + string mimetype; + string payload; + }; + + sequence<MimePart> MimeParts; + + struct Address { + string name; + string address; + }; + + class Email { + Address to; + Address from; + string subject; + MimeParts body; + }; + + ["cpp:ice_print"] + exception SendEmailFailed { + string message; + }; + + struct NewsContent { + Categories categories; + Packages packages; + Ebuilds ebuilds; + }; + + interface Notifications { + Email getSignup(NewUser user); + Email getNews(User user, NewsContent nc); + }; + + interface MailServer { + idempotent void sendEmail(Email msg) throws SendEmailFailed; + }; +}; + +#endif + diff --git a/gentoobrowse-api/service/notifications/notificationsimpl.cpp b/gentoobrowse-api/service/notifications/notificationsimpl.cpp new file mode 100644 index 0000000..d7dbee4 --- /dev/null +++ b/gentoobrowse-api/service/notifications/notificationsimpl.cpp @@ -0,0 +1,59 @@ +#include "notificationsimpl.h" +#include <libxml/parser.h> +#include <base-xslt.h> +#include <news-xslt.h> +#include <signup-xslt.h> +#include <string.h> +#include <slicer/slicer.h> +#include "xsltStreamSerializer.h" + +namespace Gentoo { + namespace Service { + xmlDocPtr xsltDocLoaderFunc (const xmlChar * URI, xmlDictPtr, int, void *, xsltLoadType) + { +#define MATCH(name) \ + if (xmlStrcmp(URI, BAD_CAST #name ".xslt") == 0) { \ + return xmlParseMemory((char *)name ## _xslt, name ## _xslt_len); \ + } + MATCH(base) + MATCH(news) + MATCH(signup) + return NULL; +#undef MATCH + } + + Notifications::Notifications() + { + xsltSetLoaderFunc(&xsltDocLoaderFunc); + news = xsltSSPtr(xsltParseStylesheetFile(BAD_CAST "news.xslt"), xsltFreeStylesheet); + signup = xsltSSPtr(xsltParseStylesheetFile(BAD_CAST "signup.xslt"), xsltFreeStylesheet); + xsltSetLoaderFunc(NULL); + } + + Gentoo::EmailPtr Notifications::getSignup(const Gentoo::NewUserPtr & u, const Ice::Current &) + { + auto e = basicMail("Welcome", u); + Slicer::SerializeAny<XsltStreamSerializer>(u, e, signup.get()); + return e; + } + + Gentoo::EmailPtr Notifications::getNews(const Gentoo::UserPtr & u, const Gentoo::NewsContent & c, const Ice::Current &) + { + auto e = basicMail("Latest updates", u); + Slicer::SerializeAny<XsltStreamSerializer>(c, e, news.get()); + return e; + } + + Gentoo::EmailPtr Notifications::basicMail(const std::string & subject, Gentoo::UserPtr u) + { + Gentoo::EmailPtr e = new Gentoo::Email(); + e->subject = "Gentoo Browse: " + subject; + e->from.name = "Gentoo Browse"; + e->from.address = "noreply@gentoobrowse.randomdan.homeip.net"; + e->to.name = u->userrealname; + e->to.address = u->useremail; + return e; + } + } +} + diff --git a/gentoobrowse-api/service/notifications/notificationsimpl.h b/gentoobrowse-api/service/notifications/notificationsimpl.h new file mode 100644 index 0000000..1d3b1fc --- /dev/null +++ b/gentoobrowse-api/service/notifications/notificationsimpl.h @@ -0,0 +1,29 @@ +#ifndef NOTIFICATIONSIMPL_H +#define NOTIFICATIONSIMPL_H + +#include <notifications.h> +#include <visibility.h> +#include <memory> +#include <libxslt/documents.h> + +namespace Gentoo { + namespace Service { + class DLL_PUBLIC Notifications : public Gentoo::Notifications { + public: + Notifications(); + + Gentoo::EmailPtr getSignup(const Gentoo::NewUserPtr &, const Ice::Current &) override; + Gentoo::EmailPtr getNews(const Gentoo::UserPtr &, const Gentoo::NewsContent &, const Ice::Current &) override; + + private: + Gentoo::EmailPtr basicMail(const std::string &, Gentoo::UserPtr u); + + typedef std::shared_ptr<xsltStylesheet> xsltSSPtr; + xsltSSPtr news; + xsltSSPtr signup; + }; + } +} + +#endif + diff --git a/gentoobrowse-api/service/notifications/xslt/base.xslt b/gentoobrowse-api/service/notifications/xslt/base.xslt new file mode 100644 index 0000000..9db7752 --- /dev/null +++ b/gentoobrowse-api/service/notifications/xslt/base.xslt @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:project2="http://project2.randomdan.homeip.net" exclude-result-prefixes="project2" > + <xsl:output encoding="utf-8" method="html" media-type="text/html" indent="yes" /> + + <xsl:template match="/*"> + <xsl:text disable-output-escaping="yes"><!DOCTYPE html> </xsl:text> + <html lang="en"> + <head> + <meta charset="utf-8" /> + <link href="http://gentoobrowse.randomdan.homeip.net/css/bootstrap.min.css" rel="stylesheet" media="screen" /> + <link href="http://gentoobrowse.randomdan.homeip.net/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen" /> + <link href="http://gentoobrowse.randomdan.homeip.net/css/general.css" rel="stylesheet" media="screen" /> + <title><xsl:call-template name="title" /></title> + </head> + <body data-spy="scroll" data-target=".bs-docs-sidebar"> + <!-- nav --> + <nav class="navbar navbar-inverse navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <a class="brand" href="http://gentoobrowse.randomdan.homeip.net/">Gentoo Browse</a> + </div> + </div> + </nav> + + <!-- page content --> + <header class="jumbotron subhead"> + <div class="container"> + <h1> + <xsl:call-template name="title" /> + </h1> + </div> + </header> + <div class="container"> + <article class="row"> + <xsl:call-template name="content" /> + <section class="signoff"> + </section> + </article> + </div> + + <!-- footer --> + <footer class="footer"> + <div class="container"> + <p>Gentoo Browse is not an official Gentoo website. The name "Gentoo" and the "g" logo are trademarks of the Gentoo Foundation, Inc.</p> + </div> + </footer> + + <script src="http://code.jquery.com/jquery-latest.js"></script> + <script src="http://gentoobrowse.randomdan.homeip.net/js/bootstrap.min.js"></script> + <script src="http://gentoobrowse.randomdan.homeip.net/js/js.js"></script> + </body> + </html> + </xsl:template> + + <xsl:template name="head"> + </xsl:template> + + <xsl:template name="header"> + </xsl:template> +</xsl:stylesheet> diff --git a/gentoobrowse-api/service/notifications/xslt/news.xslt b/gentoobrowse-api/service/notifications/xslt/news.xslt new file mode 100644 index 0000000..6b2ea29 --- /dev/null +++ b/gentoobrowse-api/service/notifications/xslt/news.xslt @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://exslt.org/strings" xmlns:project2="http://project2.randomdan.homeip.net" version="1.0" exclude-result-prefixes="project2 fn"> + <xsl:import href="base.xslt"/> + <xsl:output encoding="utf-8" method="html" media-type="text/html" indent="yes"/> + + <xsl:template name="title">Latest news</xsl:template> + + <xsl:template name="content"> + <xsl:variable name="packages" select="packages/package[packageid = ../../ebuilds/ebuild/packageid]"/> + <xsl:variable name="categories" select="categories/category[categoryid = $packages/categoryid]"/> + <section> + <h2 class="page-header">New versions of tracked packages</h2> + <div class="span3 bs-docs-sidebar"> + <ul class="nav nav-list bs-docs-sidenav"> + <xsl:for-each select="$categories"> + <xsl:sort select="name" data-type="text" order="ascending"/> + <li> + <a> + <xsl:attribute name="href">http://gentoobrowse.randomdan.homeip.net/packages/<xsl:value-of select="name" /></xsl:attribute> + <i class="icon-chevron-right"></i> + <xsl:value-of select="name" /> + </a> + </li> + </xsl:for-each> + </ul> + </div> + <div class="span9"> + <xsl:for-each select="$categories"> + <xsl:sort select="name" data-type="text" order="ascending"/> + <xsl:variable name="category" select="." /> + <xsl:for-each select="$packages[categoryid = current()/categoryid]"> + <xsl:sort select="name" data-type="text" order="ascending"/> + <xsl:variable name="package" select="." /> + <p> + <a> + <xsl:attribute name="href">http://gentoobrowse.randomdan.homeip.net/packages/<xsl:value-of select="$category/name" /></xsl:attribute> + <xsl:value-of select="$category/name" /> + </a> + / + <a> + <xsl:attribute name="href">http://gentoobrowse.randomdan.homeip.net/packages/<xsl:value-of select="$category/name" />/<xsl:value-of select="name" /></xsl:attribute> + <xsl:value-of select="name" /> + </a> + : + <xsl:value-of select="description" /> + </p> + <xsl:for-each select="../../ebuilds/ebuild[packageid = current()/packageid]"> + <p> + v<xsl:value-of select="version" /> + </p> + </xsl:for-each> + </xsl:for-each> + </xsl:for-each> + </div> + </section> + </xsl:template> +</xsl:stylesheet> diff --git a/gentoobrowse-api/service/notifications/xslt/signup.xslt b/gentoobrowse-api/service/notifications/xslt/signup.xslt new file mode 100644 index 0000000..e579835 --- /dev/null +++ b/gentoobrowse-api/service/notifications/xslt/signup.xslt @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:import href="base.xslt"/> + + <xsl:template name="title">Welcome to Gentoo Browse</xsl:template> + + <xsl:template name="content"> + <section> + <h2 class="page-header">Confirmation</h2> + <p> + Click <a> + <xsl:attribute name="href"> + <xsl:text>http://gentoobrowse.randomdan.homeip.net/user/verify/</xsl:text> + <xsl:value-of select="verifyguid" /> + </xsl:attribute> + <xsl:text>http://gentoobrowse.randomdan.homeip.net/user/verify/</xsl:text> + <xsl:value-of select="verifyguid" /> + </a> to confirm your account. + </p> + <p>Or alternatively, copy and paste this code into the confirmation page: <xsl:value-of select="verifyguid" /></p> + </section> + </xsl:template> +</xsl:stylesheet> + diff --git a/gentoobrowse-api/service/notifications/xsltStreamSerializer.cpp b/gentoobrowse-api/service/notifications/xsltStreamSerializer.cpp new file mode 100644 index 0000000..5a71b35 --- /dev/null +++ b/gentoobrowse-api/service/notifications/xsltStreamSerializer.cpp @@ -0,0 +1,82 @@ +#include "xsltStreamSerializer.h" +#include <libxslt/xsltInternals.h> +#include <libxml/HTMLtree.h> +#include <factory.impl.h> +#include <processPipes.h> +#include <sys/wait.h> + +namespace Gentoo { + static int xmlstrmclosecallback(void * context) + { + ((std::ostream*)context)->flush(); + return 0; + } + + static int xmlstrmwritecallback(void * context, const char * buffer, int len) + { + ((std::ostream*)context)->write(buffer, len); + return len; + } + + XsltStreamSerializer::XsltStreamSerializer(Gentoo::EmailPtr e, xsltStylesheet * ss) : + Slicer::XmlDocumentSerializer(doc), + mail(e), + doc(nullptr), + stylesheet(ss) + { + } + + XsltStreamSerializer::~XsltStreamSerializer() + { + delete doc; + } + + void + XsltStreamSerializer::Serialize(Slicer::ModelPartForRootPtr mp) + { + Slicer::XmlDocumentSerializer::Serialize(mp); + auto result = std::shared_ptr<xmlDoc>(xsltApplyStylesheet(stylesheet, doc->cobj(), nullptr), xmlFreeDoc); + if (!result) { + throw xmlpp::exception("Failed to apply XSL transform"); + } + appendText(result.get()); + appendHtml(result.get()); + } + + void XsltStreamSerializer::appendHtml(xmlDoc * result) const + { + std::stringstream strm; + xmlOutputBufferPtr buf = xmlOutputBufferCreateIO(xmlstrmwritecallback, xmlstrmclosecallback, &strm, NULL); + htmlDocContentDumpFormatOutput(buf, result, "utf-8", 0); + xmlOutputBufferClose(buf); + mail->body.push_back(new Gentoo::MimePart("text/html", strm.str())); + } + + void XsltStreamSerializer::appendText(xmlDoc * result) const + { + std::stringstream strm; + std::vector<std::string> callLynx; + callLynx.push_back("/usr/bin/lynx"); + callLynx.push_back("-dump"); + callLynx.push_back("-stdin"); + std::string widthArg = "-width=105"; + callLynx.push_back(widthArg); + AdHoc::System::ProcessPipes fds(callLynx, true, true, false); + FILE * lynxIn = fdopen(fds.fdIn(), "w"); + // Fixed encoding as we want the result to go back into a ustring + htmlNodeDumpFileFormat(lynxIn, result, xmlDocGetRootElement(result), "utf-8", 0); + fclose(lynxIn); + char buf[1024]; + int r; + while ((r = read(fds.fdOut(), buf, sizeof(buf))) > 0) { + strm.write(buf, r); + } + int status; + waitpid(fds.pid(), &status, 0); + if (status != 0) { + throw std::runtime_error("Lynx failed"); + } + mail->body.push_back(new Gentoo::MimePart("text/plain", strm.str())); + } +} + diff --git a/gentoobrowse-api/service/notifications/xsltStreamSerializer.h b/gentoobrowse-api/service/notifications/xsltStreamSerializer.h new file mode 100644 index 0000000..492b954 --- /dev/null +++ b/gentoobrowse-api/service/notifications/xsltStreamSerializer.h @@ -0,0 +1,28 @@ +#ifndef ICESPIDER_CORE_XSLTSTREAMSERIALIZER_H +#define ICESPIDER_CORE_XSLTSTREAMSERIALIZER_H + +#include <slicer/xml/serializer.h> +#include <visibility.h> +#include <libxslt/transform.h> +#include <notifications.h> + +namespace Gentoo { + class DLL_PUBLIC XsltStreamSerializer : public Slicer::XmlDocumentSerializer { + public: + XsltStreamSerializer(Gentoo::EmailPtr, xsltStylesheet *); + ~XsltStreamSerializer(); + + void Serialize(Slicer::ModelPartForRootPtr mp) override; + + protected: + void appendHtml(xmlDoc *) const; + void appendText(xmlDoc *) const; + + Gentoo::EmailPtr mail; + xmlpp::Document * doc; + xsltStylesheet * stylesheet; + }; +} + +#endif + |