diff options
-rw-r--r-- | butl/buildfile | 1 | ||||
-rw-r--r-- | butl/sendmail | 92 | ||||
-rw-r--r-- | butl/sendmail.cxx | 40 | ||||
-rw-r--r-- | butl/sendmail.ixx | 65 | ||||
-rw-r--r-- | tests/buildfile | 3 | ||||
-rw-r--r-- | tests/sendmail/buildfile | 7 | ||||
-rw-r--r-- | tests/sendmail/driver.cxx | 42 | ||||
-rw-r--r-- | tests/sendmail/testscript | 10 |
8 files changed, 259 insertions, 1 deletions
diff --git a/butl/buildfile b/butl/buildfile index 6cef7f2..7d33535 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -23,6 +23,7 @@ lib{butl}: \ {hxx ixx cxx}{ process } \ {hxx }{ process-details } \ { txx cxx}{ process-run } \ + {hxx ixx cxx}{ sendmail } \ {hxx cxx}{ sha256 } \ {hxx }{ small-vector } \ {hxx txx }{ string-table } \ diff --git a/butl/sendmail b/butl/sendmail new file mode 100644 index 0000000..5a380e7 --- /dev/null +++ b/butl/sendmail @@ -0,0 +1,92 @@ +// file : butl/sendmail -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_SENDMAIL +#define BUTL_SENDMAIL + +#include <string> + +#include <butl/export> + +#include <butl/process> +#include <butl/fdstream> +#include <butl/small-vector> + +namespace butl +{ + // Send email using the sendmail program. + // + // Write the body of the email to out. Note that you must explicitly close + // it before calling wait(). Throw process_error and io_error (both derive + // from system_error) in case of errors. + // + // Typical usage: + // + // try + // { + // sendmail sm (2, // Diagnostics to stderr. + // "", // Default From: address. + // "Test subject", + // {"test@example.com"}); + // + // sm.out << "Test body" << endl; + // + // sm.close (); + // + // if (!sm.wait ()) + // ... // sendmail returned non-zero status. + // } + // catch (const std::system_error& e) + // { + // cerr << "sendmail error: " << e << endl; + // } + // + class LIBBUTL_EXPORT sendmail: public process + { + public: + ofdstream out; + + // Notes: + // + // - If from is empty then the process user's address is used. + // + // - The to/cc/bcc addressed should already be quoted if required. + // + using recipients_type = small_vector<std::string, 1>; + + template <typename E, typename... O> + sendmail (E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to, + const recipients_type& cc = recipients_type (), + const recipients_type& bcc = recipients_type (), + O&&... options); + + // Version with the command line callback (see process_run() for + // details). + // + template <typename C, typename E, typename... O> + sendmail (const C&, + E&& err, + const std::string& from, + const std::string& subject, + const recipients_type& to, + const recipients_type& cc = recipients_type (), + const recipients_type& bcc = recipients_type (), + O&&... options); + + private: + void + headers (const std::string& from, + const std::string& subj, + const recipients_type& to, + const recipients_type& cc, + const recipients_type& bcc); + }; +} + +#include <butl/sendmail.ixx> + +#endif // BUTL_SENDMAIL diff --git a/butl/sendmail.cxx b/butl/sendmail.cxx new file mode 100644 index 0000000..103bab5 --- /dev/null +++ b/butl/sendmail.cxx @@ -0,0 +1,40 @@ +// file : butl/sendmail.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <butl/sendmail> + +using namespace std; + +namespace butl +{ + void sendmail:: + headers (const std::string& from, + const std::string& subj, + const recipients_type& to, + const recipients_type& cc, + const recipients_type& bcc) + { + if (!from.empty ()) + out << "From: " << from << endl; + + auto rcp =[this] (const char* h, const recipients_type& rs) + { + if (!rs.empty ()) + { + bool f (true); + out << h << ": "; + for (const string& r: rs) + out << (f ? (f = false, "") : ", ") << r; + out << endl; + } + }; + + rcp ("To", to); + rcp ("Cc", cc); + rcp ("Bcc", bcc); + + out << "Subject: " << subj << endl + << endl; // Header/body separator. + } +} diff --git a/butl/sendmail.ixx b/butl/sendmail.ixx new file mode 100644 index 0000000..3f6597d --- /dev/null +++ b/butl/sendmail.ixx @@ -0,0 +1,65 @@ +// file : butl/sendmail.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <utility> // move(), forward() + +namespace butl +{ + template <typename E, typename... O> + inline sendmail:: + sendmail (E&& err, + const std::string& from, + const std::string& subj, + const recipients_type& to, + const recipients_type& cc, + const recipients_type& bcc, + O&&... options) + : sendmail ([] (const char* [], std::size_t) {}, + std::forward<E> (err), + from, + subj, + to, + cc, + bcc, + std::forward<O> (options)...) + { + } + + template <typename C, typename E, typename... O> + inline sendmail:: + sendmail (const C& cmdc, + E&& err, + const std::string& from, + const std::string& subj, + const recipients_type& to, + const recipients_type& cc, + const recipients_type& bcc, + O&&... options) + { + { + fdpipe pipe (fdopen_pipe ()); // Text mode seems appropriate. + + process& p (*this); + p = process_start (cmdc, + pipe.in, + 2, // No output expected so redirect to stderr. + std::forward<E> (err), + dir_path (), + "sendmail", + "-i", // Don't treat '.' as the end of input. + "-t", // Read recipients from headers. + std::forward<O> (options)...); + + out.open (std::move (pipe.out)); + } // Close pipe.in. + + // Write headers. + // + // Note that if this throws, then the ofdstream will be closed first + // (which should signal to the process we are done). Then the process's + // destructor will wait for its termination ignoring any errors. + // + headers (from, subj, to, cc, bcc); + } +} diff --git a/tests/buildfile b/tests/buildfile index 10e73ec..efc54b8 100644 --- a/tests/buildfile +++ b/tests/buildfile @@ -2,4 +2,5 @@ # copyright : Copyright (c) 2014-2017 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -./: */ +./: {*/ -sendmail/} + diff --git a/tests/sendmail/buildfile b/tests/sendmail/buildfile new file mode 100644 index 0000000..ddc8bca --- /dev/null +++ b/tests/sendmail/buildfile @@ -0,0 +1,7 @@ +# file : tests/sendmail/buildfile +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +exe{driver}: cxx{driver} ../../butl/lib{butl} test{testscript} + +include ../../butl/ diff --git a/tests/sendmail/driver.cxx b/tests/sendmail/driver.cxx new file mode 100644 index 0000000..9e1af96 --- /dev/null +++ b/tests/sendmail/driver.cxx @@ -0,0 +1,42 @@ +// file : tests/sendmail/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <iostream> +#include <system_error> + +#include <butl/path> +#include <butl/utility> // operator<<(ostream, exception) +#include <butl/sendmail> + +using namespace std; +using namespace butl; + +// Usage: argv[0] <to> +// +int +main (int argc, const char* argv[]) +try +{ + assert (argc == 2); + + sendmail sm ([] (const char* c[], std::size_t n) + { + process::print (cerr, c, n); cerr << endl; + }, + 2, + "", + "tests/sendmail/driver", + {argv[1]}); + + sm.out << cin.rdbuf (); + sm.out.close (); + + if (!sm.wait ()) + return 1; // Assume diagnostics has been issued. +} +catch (const system_error& e) +{ + cerr << argv[0] << ": " << e << endl; + return 1; +} diff --git a/tests/sendmail/testscript b/tests/sendmail/testscript new file mode 100644 index 0000000..49776a3 --- /dev/null +++ b/tests/sendmail/testscript @@ -0,0 +1,10 @@ +# file : tests/sendmail/testscript +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +$* admin@build2.org <<EOI 2>>EOE +Test email to admin@build2.org. +Sent from libbutl/tests/sendmail/testscript. +EOI +sendmail -i -t +EOE |