summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-01-21 14:33:32 (GMT)
committerStefan Radomski <radomski@tk.informatik.tu-darmstadt.de>2014-01-21 14:33:32 (GMT)
commit948fbc12c1b4e48a24b18a84878bd560cbaf91a7 (patch)
tree23976fb5eedfedaeadd8fed8da60aa1159f3d286
parent08eff21dfbdbf7d20e1b96ff2f00882ec0a87b08 (diff)
downloaduscxml-948fbc12c1b4e48a24b18a84878bd560cbaf91a7.zip
uscxml-948fbc12c1b4e48a24b18a84878bd560cbaf91a7.tar.gz
uscxml-948fbc12c1b4e48a24b18a84878bd560cbaf91a7.tar.bz2
SMTP Invoker and "invoke.success.<INV_ID>" events when an invoker was started
-rw-r--r--src/uscxml/Factory.cpp5
-rw-r--r--src/uscxml/Interpreter.cpp6
-rw-r--r--src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp23
-rw-r--r--src/uscxml/plugins/element/file/FileElement.cpp45
-rw-r--r--src/uscxml/plugins/element/file/FileElement.h2
-rw-r--r--src/uscxml/plugins/invoker/CMakeLists.txt20
-rw-r--r--src/uscxml/plugins/invoker/smtp/SMTPInvoker.cpp360
-rw-r--r--src/uscxml/plugins/invoker/smtp/SMTPInvoker.h78
-rw-r--r--test/samples/uscxml/test-smtp.scxml50
9 files changed, 561 insertions, 28 deletions
diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp
index c32c6a8..08a1307 100644
--- a/src/uscxml/Factory.cpp
+++ b/src/uscxml/Factory.cpp
@@ -39,6 +39,7 @@
# include "uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.h"
# include "uscxml/plugins/invoker/system/SystemInvoker.h"
# include "uscxml/plugins/invoker/xhtml/XHTMLInvoker.h"
+# include "uscxml/plugins/invoker/smtp/SMTPInvoker.h"
#ifdef PROTOBUF_FOUND
@@ -278,6 +279,10 @@ Factory::Factory() {
registerInvoker(invoker);
}
{
+ SMTPInvoker* invoker = new SMTPInvoker();
+ registerInvoker(invoker);
+ }
+ {
USCXMLInvoker* invoker = new USCXMLInvoker();
registerInvoker(invoker);
}
diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp
index db24dc6..3567833 100644
--- a/src/uscxml/Interpreter.cpp
+++ b/src/uscxml/Interpreter.cpp
@@ -1132,6 +1132,12 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) {
LOG(INFO) << "Added invoker " << invokeReq.type << " at " << invokeReq.invokeid;
try {
invoker.invoke(invokeReq);
+
+ // this is out of draft but so useful to know when an invoker started
+ Event invSuccess;
+ invSuccess.name = "invoke.success." + invokeReq.invokeid;
+ receive(invSuccess);
+
} catch(boost::bad_lexical_cast e) {
LOG(ERROR) << "Exception caught while sending invoke request to invoker " << invokeReq.invokeid << ": " << e.what();
} catch(...) {
diff --git a/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp b/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp
index 9a4b528..de94088 100644
--- a/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp
+++ b/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp
@@ -627,20 +627,19 @@ std::string SWIDataModel::evalAsString(const std::string& expr) {
PlQuery query(compound.name(), termv);
std::stringstream ss;
- const char* separator = "";
+ std::string solSeparator = "";
while (query.next_solution()) {
+ ss << solSeparator;
std::map<std::string, PlTerm> vars = resolveAtoms(compound, orig);
- if (vars.size() == 1) {
- ss << separator << (char *)vars.begin()->second;
- separator = "\n";
- } else {
- std::map<std::string, PlTerm>::const_iterator varIter = vars.begin();
- while(varIter != vars.end()) {
- ss << separator << (char *)varIter->second;
- separator = ", ";
- varIter++;
- }
+ std::map<std::string, PlTerm>::const_iterator varIter = vars.begin();
+
+ std::string varSeparator = "";
+ while(varIter != vars.end()) {
+ ss << varSeparator << (char *)varIter->second;
+ varSeparator = ", ";
+ varIter++;
}
+ solSeparator = "\n";
}
return ss.str();
}
@@ -684,6 +683,8 @@ std::map<std::string, PlTerm> SWIDataModel::resolveAtoms(PlTerm& term, PlTerm& o
atoms.insert(result.begin(), result.end());
}
break;
+ default:
+ LOG(ERROR) << "Resolving variable of unknown type in query solution";
}
return atoms;
} RETHROW_PLEX_AS_EVENT
diff --git a/src/uscxml/plugins/element/file/FileElement.cpp b/src/uscxml/plugins/element/file/FileElement.cpp
index 3a8574d..d88a598 100644
--- a/src/uscxml/plugins/element/file/FileElement.cpp
+++ b/src/uscxml/plugins/element/file/FileElement.cpp
@@ -134,8 +134,9 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
if (_sandBoxed)
_actualUrl.toAbsolute(URL::getResourceDir());
- _filename = _actualUrl.path();
-
+ _filepath = _actualUrl.path();
+
+
std::string writeMode;
switch (_operation) {
case APPEND:
@@ -145,39 +146,43 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
writeMode = "w+";
FILE *fp;
- fp = fopen(_filename.c_str(), writeMode.c_str());
+ fp = fopen(_filepath.c_str(), writeMode.c_str());
if (fp == NULL) {
- LOG(ERROR) << "Error opening '" << _filename << "' for writing: " << strerror(errno);
+ LOG(ERROR) << "Error opening '" << _filepath << "' for writing: " << strerror(errno);
}
if (content && contentSize > 0) {
size_t written = fwrite(content, 1, contentSize, fp);
if (written != contentSize) {
- LOG(ERROR) << "Error writing to '" << _filename << "': " << strerror(errno);
+ LOG(ERROR) << "Error writing to '" << _filepath << "': " << strerror(errno);
return;
}
} else if (contentStr.length() > 0) {
size_t written = fwrite(contentStr.c_str(), contentStr.length(), 1, fp);
if (written < 1) {
- LOG(ERROR) << "Error writing to '" << _filename << "': " << strerror(errno);
+ LOG(ERROR) << "Error writing to '" << _filepath << "': " << strerror(errno);
}
} else {
- LOG(WARNING) << "Nothing to write to '" << _filename;
+ LOG(WARNING) << "Nothing to write to '" << _filepath;
}
fclose(fp);
break;
}
case READ: {
struct stat fileStat;
- int err = stat(_filename.c_str(), &fileStat);
+ int err = stat(_filepath.c_str(), &fileStat);
if (err < 0) {
- LOG(ERROR) << "Cannot stat file '" << _filename << "': " << strerror(errno);
+ LOG(ERROR) << "Cannot stat file '" << _filepath << "': " << strerror(errno);
return;
}
Event event;
event.name = callback;
- event.data.compound["file"].compound["name"] = Data(_filename, Data::VERBATIM);
+
+ std::string filename = _actualUrl.pathComponents()[_actualUrl.pathComponents().size() - 1];
+
+ event.data.compound["file"].compound["name"] = Data(filename, Data::VERBATIM);
+ event.data.compound["file"].compound["path"] = Data(_filepath, Data::VERBATIM);
event.data.compound["file"].compound["mtime"] = toStr(fileStat.st_mtime);
event.data.compound["file"].compound["ctime"] = toStr(fileStat.st_ctime);
event.data.compound["file"].compound["atime"] = toStr(fileStat.st_atime);
@@ -185,7 +190,7 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
FILE *fp;
- fp = fopen(_filename.c_str(), "r");
+ fp = fopen(_filepath.c_str(), "r");
fseek (fp, 0, SEEK_END);
size_t filesize = ftell(fp);
@@ -195,14 +200,22 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
size_t read = fread(fileContents, 1, filesize, fp);
fclose(fp);
if (read != filesize) {
- LOG(ERROR) << "Error reading from '" << _filename << "': " << strerror(errno);
+ LOG(ERROR) << "Error reading from '" << _filepath << "': " << strerror(errno);
return;
}
switch (_type) {
- case BINARY:
- event.data.compound["content"] = Data(fileContents, fileStat.st_size, "application/octet-stream", true);
+ case BINARY: {
+ std::string mimetype = "application/octet-stream";
+ if (HAS_ATTR(node, "mimetype")) {
+ mimetype = ATTR(node, "mimetype");
+ } else if(HAS_ATTR(node, "mimetypeexpr")) {
+ mimetype = _interpreter->getDataModel().evalAsString(ATTR(node, "mimetypeexpr"));
+ }
+
+ event.data.compound["content"] = Data(fileContents, fileStat.st_size, mimetype, true);
break;
+ }
case TEXT:
event.data.compound["content"] = Data(fileContents, Data::VERBATIM);
free(fileContents);
@@ -211,7 +224,7 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
Data json = Data::fromJSON(fileContents);
free(fileContents);
if (!json) {
- LOG(ERROR) << "Cannot parse contents of " << _filename << " as JSON";
+ LOG(ERROR) << "Cannot parse contents of " << _filepath << " as JSON";
return;
}
event.data.compound["content"] = json;
@@ -220,7 +233,7 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) {
case XML: {
NameSpacingParser parser = NameSpacingParser::fromXML(fileContents);
if (parser.errorsReported()) {
- LOG(ERROR) << "Cannot parse contents of " << _filename << " as XML";
+ LOG(ERROR) << "Cannot parse contents of " << _filepath << " as XML";
return;
}
event.dom = parser.getDocument().getDocumentElement();
diff --git a/src/uscxml/plugins/element/file/FileElement.h b/src/uscxml/plugins/element/file/FileElement.h
index 987ae11..462ccc8 100644
--- a/src/uscxml/plugins/element/file/FileElement.h
+++ b/src/uscxml/plugins/element/file/FileElement.h
@@ -70,7 +70,7 @@ protected:
bool _sandBoxed;
std::string _givenUrl;
URL _actualUrl;
- std::string _filename;
+ std::string _filepath;
Operation _operation;
Type _type;
};
diff --git a/src/uscxml/plugins/invoker/CMakeLists.txt b/src/uscxml/plugins/invoker/CMakeLists.txt
index c6ec719..e731d74 100644
--- a/src/uscxml/plugins/invoker/CMakeLists.txt
+++ b/src/uscxml/plugins/invoker/CMakeLists.txt
@@ -117,6 +117,26 @@ if (EXPECT_FOUND AND TCL_FOUND)
endif()
+# SMTP invoker via curl
+
+set(USCXML_INVOKERS "smtp ${USCXML_INVOKERS}")
+file(GLOB_RECURSE SMTP_INVOKER
+ smtp/*.cpp
+ smtp/*.h
+)
+if (BUILD_AS_PLUGINS)
+ source_group("" FILES SMTP_INVOKER)
+ add_library(
+ invoker_smtp SHARED
+ ${SMTP_INVOKER}
+ "../Plugins.cpp")
+ target_link_libraries(invoker_smtp uscxml)
+ set_target_properties(invoker_smtp PROPERTIES FOLDER "Plugin Invoker")
+else()
+ list (APPEND USCXML_FILES ${SMTP_INVOKER})
+endif()
+
+
# SQLite3 SQL Invoker
if (SQLITE3_FOUND)
diff --git a/src/uscxml/plugins/invoker/smtp/SMTPInvoker.cpp b/src/uscxml/plugins/invoker/smtp/SMTPInvoker.cpp
new file mode 100644
index 0000000..420ab52
--- /dev/null
+++ b/src/uscxml/plugins/invoker/smtp/SMTPInvoker.cpp
@@ -0,0 +1,360 @@
+/**
+ * @file
+ * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de)
+ * @copyright Simplified BSD
+ *
+ * @cond
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the FreeBSD license as published by the FreeBSD
+ * project.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of the FreeBSD license along with this
+ * program. If not, see <http://www.opensource.org/licenses/bsd-license>.
+ * @endcond
+ */
+
+#include "SMTPInvoker.h"
+#include <glog/logging.h>
+
+#ifdef BUILD_AS_PLUGINS
+#include <Pluma/Connector.hpp>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include "uscxml/UUID.h"
+
+namespace uscxml {
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_CONNECTOR
+bool pluginConnect(pluma::Host& host) {
+ host.add( new SMTPInvokerProvider() );
+ return true;
+}
+#endif
+
+SMTPInvoker::SMTPInvoker() {
+}
+
+SMTPInvoker::~SMTPInvoker() {
+};
+
+boost::shared_ptr<InvokerImpl> SMTPInvoker::create(InterpreterImpl* interpreter) {
+ boost::shared_ptr<SMTPInvoker> invoker = boost::shared_ptr<SMTPInvoker>(new SMTPInvoker());
+ return invoker;
+}
+
+Data SMTPInvoker::getDataModelVariables() {
+ Data data;
+ return data;
+}
+
+size_t SMTPInvoker::readCurlData(void *ptr, size_t size, size_t nmemb, void *userdata) {
+ if (!userdata)
+ return 0;
+
+ SMTPContext* ctx = (SMTPContext*)userdata;
+
+ size_t toWrite = std::min(ctx->content.length() - ctx->readPtr, size * nmemb);
+ if (toWrite > 0) {
+ memcpy (ptr, ctx->content.c_str() + ctx->readPtr, toWrite);
+ ctx->readPtr += toWrite;
+ }
+
+ return toWrite;
+}
+
+std::list<std::string> SMTPInvoker::getAtoms(std::list<Data> list) {
+ std::list<std::string> atoms;
+
+ std::list<Data>::const_iterator iter = list.begin();
+ while(iter != list.end()) {
+ const Data& data = *iter;
+ if (data.atom.size() > 0) {
+ atoms.push_back(data.atom);
+ } else if (data.array.size() > 0) {
+ std::list<Data>::const_iterator arrIter = data.array.begin();
+ while(arrIter != data.array.end()) {
+ if (arrIter->atom.size() > 0) {
+ atoms.push_back(arrIter->atom);
+ arrIter++;
+ }
+ }
+ }
+ iter++;
+ }
+ return atoms;
+}
+
+void SMTPInvoker::getAttachments(std::list<Data> list, std::list<Data>& attachments) {
+ // accumulate attachments with filename, mimetype and data
+ std::list<Data>::const_iterator iter = list.begin();
+ while(iter != list.end()) {
+ const Data& data = *iter;
+ if (data.hasKey("data")) {
+ // compound structure with all information
+ Data att = data;
+
+ if (!att.hasKey("mimetype")) {
+ if (att["data"].binary && att["data"].binary->mimeType.size() > 0) {
+ att.compound["mimetype"] = Data(att["data"].binary->mimeType, Data::VERBATIM);
+ } else {
+ att.compound["mimetype"] = Data("text/plain", Data::VERBATIM);
+ }
+ }
+
+ if (!att.hasKey("filename")) {
+ std::stringstream filenameSS;
+ filenameSS << "attachment" << attachments.size() + 1;
+ if (boost::starts_with(att.compound["mimetype"].atom, "text")) {
+ filenameSS << ".txt";
+ } else {
+ filenameSS << ".bin";
+ }
+ att.compound["filename"] = Data(filenameSS.str(), Data::VERBATIM);
+ }
+
+ attachments.push_back(att);
+
+ } else if (data.binary) {
+ // a single binary blob
+ Data att;
+
+ att.compound["data"].binary = data.binary;
+
+ if (data.binary->mimeType.size() > 0) {
+ att.compound["mimetype"] = Data(attachments.back()["data"].binary->mimeType, Data::VERBATIM);
+ } else {
+ att.compound["mimetype"] = Data("application/octet-stream", Data::VERBATIM);
+ }
+
+ std::stringstream filenameSS;
+ filenameSS << "attachment" << attachments.size() + 1;
+ if (boost::starts_with(att.compound["mimetype"].atom, "text")) {
+ filenameSS << ".txt";
+ } else {
+ filenameSS << ".bin";
+ }
+ att.compound["filename"] = Data(filenameSS.str(), Data::VERBATIM);
+
+ attachments.push_back(att);
+
+ } else if (data.compound.size() > 0) {
+ // data is some compound, descent to find attachment structures or binaries
+ std::map<std::string, Data>::const_iterator compIter = data.compound.begin();
+ while(compIter != data.compound.end()) {
+ std::list<Data> tmp;
+ tmp.push_back(compIter->second);
+ getAttachments(tmp, attachments);
+ compIter++;
+ }
+ } else if (data.array.size() > 0) {
+ // descent into array
+ getAttachments(data.array, attachments);
+ }
+ iter++;
+ }
+}
+
+void SMTPInvoker::send(const SendRequest& req) {
+ if (iequals(req.name, "mail.send")) {
+
+ struct curl_slist* recipients = NULL;
+ CURLcode curlError;
+ std::string multipartSep;
+
+ bool verbose;
+ std::string from;
+ std::string subject;
+ std::string contentType;
+ std::list<Data> headerParams;
+ std::list<Data> toParams;
+ std::list<Data> ccParams;
+ std::list<Data> bccParams;
+ std::list<Data> attachmentParams;
+
+ Event::getParam(req.params, "verbose", verbose);
+ Event::getParam(req.params, "Content-Type", contentType);
+ Event::getParam(req.params, "attachment", attachmentParams);
+ Event::getParam(req.params, "from", from);
+ Event::getParam(req.params, "subject", subject);
+ Event::getParam(req.params, "header", headerParams);
+ Event::getParam(req.params, "to", toParams);
+ Event::getParam(req.params, "cc", ccParams);
+ Event::getParam(req.params, "bcc", bccParams);
+
+ if (contentType.size() == 0)
+ contentType = "text/plain; charset=\"UTF-8\"";
+
+ SMTPContext* ctx = new SMTPContext();
+ std::stringstream contentSS;
+
+ std::list<std::string>::const_iterator recIter;
+ std::list<std::string> to = getAtoms(toParams);
+ std::list<std::string> cc = getAtoms(ccParams);
+ std::list<std::string> bcc = getAtoms(bccParams);
+ std::list<std::string> headers = getAtoms(headerParams);
+ std::list<Data> attachments; getAttachments(attachmentParams, attachments);
+
+ if (to.size() == 0)
+ return;
+
+ recIter = to.begin();
+ recIter++; // skip first as we need it in CURLOPT_MAIL_RCPT
+ while(recIter != to.end()) {
+ contentSS << "TO: " << *recIter << std::endl;
+ recIter++;
+ }
+ recIter = cc.begin();
+ while(recIter != cc.end()) {
+ contentSS << "CC: " << *recIter << std::endl;
+ recIter++;
+ }
+ recIter = bcc.begin();
+ while(recIter != bcc.end()) {
+ contentSS << "BCC: " << *recIter << std::endl;
+ recIter++;
+ }
+
+ recIter = headers.begin();
+ while(recIter != headers.end()) {
+ contentSS << *recIter << std::endl;
+ recIter++;
+ }
+
+ if (subject.length() > 0) {
+ boost::replace_all(subject, "\n\r", " ");
+ boost::replace_all(subject, "\r\n", " ");
+ boost::replace_all(subject, "\n", " ");
+ boost::replace_all(subject, "\r", " ");
+ contentSS << "Subject: " << subject << "\n";
+ }
+
+ // content type is different when we have attachments
+ if (attachments.size() > 0) {
+ multipartSep = UUID::getUUID();
+ boost::replace_all(multipartSep, "-", "");
+ contentSS << "Content-Type: multipart/mixed; boundary=\"" << multipartSep << "\"\n";
+ contentSS << "MIME-Version: 1.0\n";
+ contentSS << "\n";
+ contentSS << "--" << multipartSep << "\n";
+ contentSS << "Content-Type: " << contentType << "\n";
+ } else {
+ // when we have no attachment, respect user-defined or use text/plain
+ contentSS << "Content-Type: " << contentType << "\n";
+ }
+
+ contentSS << "\n";
+ contentSS << req.content;
+
+ std::list<Data>::iterator attIter = attachments.begin();
+ while(attIter != attachments.end()) {
+ // only send valid attachments
+ if(!attIter->hasKey("filename") || !attIter->hasKey("mimetype") || !attIter->hasKey("data")) {
+ LOG(ERROR) << "Not sending attachment as filename, mimetype or data is missing: " << *attIter;
+ } else {
+ contentSS << "\n\n";
+ contentSS << "--" << multipartSep << "\n";
+ contentSS << "Content-Disposition: attachment; filename=\"" << attIter->compound["filename"].atom << "\"";
+ contentSS << "\n";
+
+ contentSS << "Content-Type: " << attIter->compound["mimetype"].atom << "; ";
+ contentSS << "name=\"" << attIter->compound["filename"].atom << "\"";
+ contentSS << "\n";
+
+ if (attIter->compound["data"].binary) {
+ contentSS << "Content-Transfer-Encoding: base64";
+ contentSS << "\n\n";
+ contentSS << attIter->compound["data"].binary->base64();
+ } else {
+ contentSS << "Content-Transfer-Encoding: 7Bit";
+ contentSS << "\n\n";
+ contentSS << attIter->compound["data"].atom;
+ }
+ }
+ attIter++;
+ }
+
+ ctx->content = contentSS.str();
+ ctx->invoker = this;
+
+
+ // see http://curl.haxx.se/libcurl/c/smtp-tls.html
+ _curl = curl_easy_init();
+ if(_curl) {
+ (curlError = curl_easy_setopt(_curl, CURLOPT_USERNAME, _username.c_str())) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set username: " << curl_easy_strerror(curlError);
+ (curlError = curl_easy_setopt(_curl, CURLOPT_PASSWORD, _password.c_str())) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set password: " << curl_easy_strerror(curlError);
+ (curlError = curl_easy_setopt(_curl, CURLOPT_URL, _server.c_str())) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set server string: " << curl_easy_strerror(curlError);
+ (curlError = curl_easy_setopt(_curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot use SSL: " << curl_easy_strerror(curlError);
+
+ // this is needed, even if we have a callback function
+ recipients = curl_slist_append(recipients, to.begin()->c_str());
+ (curlError = curl_easy_setopt(_curl, CURLOPT_MAIL_RCPT, recipients)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set mail recipient: " << curl_easy_strerror(curlError);
+
+ (curlError = curl_easy_setopt(_curl, CURLOPT_READFUNCTION, SMTPInvoker::readCurlData)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot register read function: " << curl_easy_strerror(curlError);
+ (curlError = curl_easy_setopt(_curl, CURLOPT_READDATA, ctx)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot register userdata for read function: " << curl_easy_strerror(curlError);
+ (curlError = curl_easy_setopt(_curl, CURLOPT_UPLOAD, 1L)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set upload parameter: " << curl_easy_strerror(curlError);
+
+#if 1
+ (curlError = curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, 0L)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot unset verify peer with SSL: " << curl_easy_strerror(curlError);
+ (curlError = curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, 0L)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot unset verify host with SSL: " << curl_easy_strerror(curlError);
+#else
+ (curlError = curl_easy_setopt(_curl, CURLOPT_CAINFO, "/path/to/certificate.pem")) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set CA info path: " << curl_easy_strerror(curlError);
+#endif
+
+ if (from.length() > 0) {
+ (curlError = curl_easy_setopt(_curl, CURLOPT_MAIL_FROM, from.c_str())) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set from parameter: " << curl_easy_strerror(curlError);
+ }
+
+ if (verbose) {
+ (curlError = curl_easy_setopt(_curl, CURLOPT_VERBOSE, 1L)) == CURLE_OK ||
+ LOG(ERROR) << "Cannot set curl to verbose: " << curl_easy_strerror(curlError);
+ }
+
+ CURLcode res = curl_easy_perform(_curl);
+
+ /* Check for errors */
+ if(res != CURLE_OK){
+ LOG(ERROR) << "curl_easy_perform() failed: " << curl_easy_strerror(res);
+ returnErrorExecution("error.mail.send");
+ } else {
+ returnErrorExecution("success.mail.send");
+ }
+ /* Free the list of recipients */
+ if (recipients)
+ curl_slist_free_all(recipients);
+
+ /* Always cleanup */
+ curl_easy_cleanup(_curl);
+
+ }
+
+ }
+}
+
+void SMTPInvoker::cancel(const std::string sendId) {
+}
+
+void SMTPInvoker::invoke(const InvokeRequest& req) {
+ Event::getParam(req.params, "username", _username);
+ Event::getParam(req.params, "password", _password);
+ Event::getParam(req.params, "server", _server);
+}
+
+} \ No newline at end of file
diff --git a/src/uscxml/plugins/invoker/smtp/SMTPInvoker.h b/src/uscxml/plugins/invoker/smtp/SMTPInvoker.h
new file mode 100644
index 0000000..f3876bd
--- /dev/null
+++ b/src/uscxml/plugins/invoker/smtp/SMTPInvoker.h
@@ -0,0 +1,78 @@
+/**
+ * @file
+ * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de)
+ * @copyright Simplified BSD
+ *
+ * @cond
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the FreeBSD license as published by the FreeBSD
+ * project.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of the FreeBSD license along with this
+ * program. If not, see <http://www.opensource.org/licenses/bsd-license>.
+ * @endcond
+ */
+
+#ifndef SMTPINVOKER_H_W09J90F0
+#define SMTPINVOKER_H_W09J90F0
+
+#include <uscxml/Interpreter.h>
+
+#ifdef BUILD_AS_PLUGINS
+#include "uscxml/plugins/Plugins.h"
+#endif
+
+#include <curl/curl.h>
+
+namespace uscxml {
+
+class SMTPInvoker : public InvokerImpl {
+public:
+ SMTPInvoker();
+ virtual ~SMTPInvoker();
+ virtual boost::shared_ptr<InvokerImpl> create(InterpreterImpl* interpreter);
+
+ virtual std::set<std::string> getNames() {
+ std::set<std::string> names;
+ names.insert("smtp");
+ names.insert("http://uscxml.tk.informatik.tu-darmstadt.de/#smtp");
+ return names;
+ }
+
+ virtual Data getDataModelVariables();
+ virtual void send(const SendRequest& req);
+ virtual void cancel(const std::string sendId);
+ virtual void invoke(const InvokeRequest& req);
+
+protected:
+
+ class SMTPContext {
+ public:
+ SMTPContext() : readPtr(0) {}
+ std::string content;
+ size_t readPtr;
+ SMTPInvoker* invoker;
+ };
+
+ CURL* _curl;
+ std::string _username;
+ std::string _password;
+ std::string _server;
+
+ std::list<std::string> getAtoms(std::list<Data> list);
+ void getAttachments(std::list<Data> list, std::list<Data>& attachments);
+ static size_t readCurlData(void *ptr, size_t size, size_t nmemb, void *userdata);
+};
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_INHERIT_PROVIDER(SMTPInvoker, InvokerImpl);
+#endif
+
+}
+
+
+#endif /* end of include guard: SMTPINVOKER_H_W09J90F0 */
diff --git a/test/samples/uscxml/test-smtp.scxml b/test/samples/uscxml/test-smtp.scxml
new file mode 100644
index 0000000..7c3ad22
--- /dev/null
+++ b/test/samples/uscxml/test-smtp.scxml
@@ -0,0 +1,50 @@
+<scxml datamodel="ecmascript">
+ <script>
+//<![CDATA[
+ someBinaryData1 = new ArrayBuffer(256);
+ var view = new Uint8Array(someBinaryData1);
+ for (var i = 0; i < view.length; i++) {
+ view[i] = i;
+ }
+//]]>
+ </script>
+
+ <state id="main">
+
+ <invoke type="smtp" id="smtp">
+ <param name="username" expr="'username'" />
+ <param name="password" expr="'password'" />
+ <param name="server" expr="'smtp://example.com:587'" />
+ </invoke>
+
+ <transition event="invoke.success.smtp">
+ <file operation="read"
+ sandbox="off"
+ url="file:///Users/sradomski/Documents/TK/Presentations/umundo/umundo-techtalk.pdf"
+ callback="file.load.success"
+ type="binary"
+ mimetype="application/pdf"
+ />
+ </transition>
+
+ <transition event="file.load.success">
+ <send target="#_smtp" event="mail.send" delay="500ms">
+ <param name="header" expr="'X-User-Agent: uscxml'" />
+ <param name="verbose" expr="'on'" />
+ <param name="subject" expr="'This is a test'" />
+ <param name="Content-Type" expr="'text/plain'" />
+ <param name="attachment" expr="({ filename: 'attachment.txt', mimetype: 'text/plain', data: 'Attachment'})" />
+ <param name="attachment" expr="({ filename: 'attachment3.txt', data: someBinaryData1 })" />
+ <param name="attachment" expr="someBinaryData2" />
+ <param name="attachment" expr="({ filename: _event.data.file.name, data: _event.data.content })" />
+ <param name="from" expr="'sradomski@example.com'" />
+ <param name="to" expr="'sradomski@example.com'" />
+ <param name="cc" expr="'sradomski2@example.com'" />
+ <param name="cc" expr="'sradomski3@example.com'" />
+ <param name="bcc" expr="'sradomski4@example.com'" />
+ <content>I just tried your SMTP invoker</content>
+ </send>
+ </transition>
+
+ </state>
+</scxml> \ No newline at end of file