From: Carl Hetherington Date: Wed, 7 Oct 2015 22:04:05 +0000 (+0100) Subject: Missing files. X-Git-Tag: v2.4.6~29 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=fa8bdd16e1b3742e921b928708614613b6a21036 Missing files. --- diff --git a/src/lib/emailer.cc b/src/lib/emailer.cc new file mode 100644 index 000000000..0febb56ab --- /dev/null +++ b/src/lib/emailer.cc @@ -0,0 +1,320 @@ +/* + Copyright (C) 2015 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "compose.hpp" +#include "job.h" +#include "data.h" +#include "config.h" +#include "emailer.h" +#include "exceptions.h" +#include +#include +#include +#include + +#include "i18n.h" + +using std::string; +using std::stringstream; +using std::min; +using std::list; +using std::cout; +using std::pair; +using boost::shared_ptr; + +Emailer::Emailer (string from, string to, string subject, string body) + : _from (from) + , _to (to) + , _subject (subject) + , _body (body) + , _offset (0) +{ + boost::algorithm::replace_all (_body, "\n", "\r\n"); +} + +void +Emailer::add_cc (string cc) +{ + _cc.push_back (cc); +} + +void +Emailer::add_bcc (string bcc) +{ + _bcc.push_back (bcc); +} + +void +Emailer::add_attachment (boost::filesystem::path attachment, string mime_type) +{ + _attachments.push_back (make_pair (attachment, mime_type)); +} + +static size_t +curl_data_shim (void* ptr, size_t size, size_t nmemb, void* userp) +{ + return reinterpret_cast(userp)->get_data (ptr, size, nmemb); +} + +size_t +Emailer::get_data (void* ptr, size_t size, size_t nmemb) +{ + size_t const t = min (_email.length() - _offset, size * nmemb); + memcpy (ptr, _email.substr(_offset, size * nmemb).c_str(), size * nmemb); + _offset += t; + return t; +} + +void +Emailer::send (shared_ptr job) +{ + char date_buffer[32]; + time_t now = time (0); + strftime (date_buffer, sizeof(date_buffer), "%a, %d %b %Y %H:%M:%S ", localtime (&now)); + + boost::posix_time::ptime const utc_now = boost::posix_time::second_clock::universal_time (); + boost::posix_time::ptime const local_now = boost::date_time::c_local_adjustor::utc_to_local (utc_now); + boost::posix_time::time_duration offset = local_now - utc_now; + sprintf (date_buffer + strlen(date_buffer), "%s%02d%02d", (offset.hours() >= 0 ? "+" : "-"), abs (offset.hours()), offset.minutes()); + + stringstream email; + + email << "Date: " << date_buffer << "\r\n" + << "To: " << _to << "\r\n" + << "From: " << _from << "\r\n"; + + if (!_cc.empty ()) { + email << "Cc: " << address_list (_cc); + } + + if (!_bcc.empty ()) { + email << "Bcc: " << address_list (_bcc); + } + + string const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + string boundary; + for (int i = 0; i < 32; ++i) { + boundary += chars[rand() % chars.length()]; + } + + if (!_attachments.empty ()) { + email << "MIME-Version: 1.0\r\n" + << "Content-Type: multipart/alternative; boundary=" << boundary << "\r\n"; + } + + email << "Subject: " << _subject << "\r\n" + << "User-Agent: DCP-o-matic\r\n" + << "\r\n"; + + if (!_attachments.empty ()) { + email << "--" << boundary << "\r\n" + << "Content-Type: text/plain; charset=utf-8\r\n\r\n"; + } + + email << _body; + + for (list >::const_iterator i = _attachments.begin(); i != _attachments.end(); ++i) { + email << "\r\n\r\n--" << boundary << "\r\n" + << "Content-Type: " << i->second << "; name=" << i->first.leaf() << "\r\n" + << "Content-Transfer-Encoding: Base64\r\n" + << "Content-Disposition: attachment; filename=" << i->first.leaf() << "\r\n\r\n"; + + BIO* b64 = BIO_new (BIO_f_base64()); + + BIO* bio = BIO_new (BIO_s_mem()); + bio = BIO_push (b64, bio); + + Data data (i->first); + BIO_write (bio, data.data().get(), data.size()); + (void) BIO_flush (bio); + + char* out; + long int bytes = BIO_get_mem_data (bio, &out); + email << string (out, bytes); + + BIO_free_all (b64); + } + + if (!_attachments.empty ()) { + email << "\r\n--" << boundary << "--\r\n"; + } + + _email = email.str (); + + curl_global_init (CURL_GLOBAL_DEFAULT); + + CURL* curl = curl_easy_init (); + if (!curl) { + throw NetworkError ("Could not initialise libcurl"); + } + + CURLM* mcurl = curl_multi_init (); + if (!mcurl) { + throw NetworkError ("Could not initialise libcurl"); + } + + curl_easy_setopt (curl, CURLOPT_URL, String::compose ( + "smtp://%1:%2", + Config::instance()->mail_server().c_str(), + Config::instance()->mail_port() + ).c_str()); + + if (!Config::instance()->mail_user().empty ()) { + curl_easy_setopt (curl, CURLOPT_USERNAME, Config::instance()->mail_user().c_str()); + } + if (!Config::instance()->mail_password().empty ()) { + curl_easy_setopt (curl, CURLOPT_PASSWORD, Config::instance()->mail_password().c_str()); + } + + curl_easy_setopt (curl, CURLOPT_MAIL_FROM, _from.c_str()); + + struct curl_slist* recipients = curl_slist_append (0, _to.c_str()); + BOOST_FOREACH (string i, _cc) { + recipients = curl_slist_append (recipients, i.c_str()); + } + BOOST_FOREACH (string i, _bcc) { + recipients = curl_slist_append (recipients, i.c_str()); + } + + curl_easy_setopt (curl, CURLOPT_MAIL_RCPT, recipients); + + curl_easy_setopt (curl, CURLOPT_READFUNCTION, curl_data_shim); + curl_easy_setopt (curl, CURLOPT_READDATA, this); + curl_easy_setopt (curl, CURLOPT_UPLOAD, 1L); + + curl_easy_setopt (curl, CURLOPT_USE_SSL, (long) CURLUSESSL_TRY); + curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L); + + _notes_buffer.reset (new char[65536]); + FILE* notes = fmemopen (_notes_buffer.get(), 65536, "w"); + curl_easy_setopt (curl, CURLOPT_STDERR, notes); + + curl_multi_add_handle (mcurl, curl); + + time_t start = time (0); + + int still_running = 1; + curl_multi_perform (mcurl, &still_running); + + while (still_running) { + + fd_set fdread; + fd_set fdwrite; + fd_set fdexcep; + + FD_ZERO (&fdread); + FD_ZERO (&fdwrite); + FD_ZERO (&fdexcep); + + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + long curl_timeout = -1; + curl_multi_timeout (mcurl, &curl_timeout); + if (curl_timeout >= 0) { + timeout.tv_sec = curl_timeout / 1000; + if (timeout.tv_sec > 1) { + timeout.tv_sec = 1; + } else { + timeout.tv_usec = (curl_timeout % 1000) * 1000; + } + } + + int maxfd = -1; + CURLMcode mc = curl_multi_fdset (mcurl, &fdread, &fdwrite, &fdexcep, &maxfd); + + if (mc != CURLM_OK) { + fclose (notes); + throw KDMError (String::compose ("Failed to send KDM email to %1", _to)); + } + + int rc; + if (maxfd == -1) { +#ifdef DCPOMATIC_WINDOWS + Sleep (100); + rc = 0; +#else + struct timeval wait = { 0, 100 * 1000}; + rc = select (0, 0, 0, 0, &wait); +#endif + } else { + rc = select (maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); + } + + if (rc < 0) { + throw KDMError ("Failed to send KDM email"); + } + + mc = curl_multi_perform (mcurl, &still_running); + if (mc != CURLM_OK) { + fclose (notes); + throw KDMError (String::compose ("Failed to send KDM email (%1)", curl_multi_strerror (mc))); + } + + if (job) { + job->set_progress_unknown (); + } + + if ((time(0) - start) > 10) { + fclose (notes); + throw KDMError (_("Failed to send KDM email (timed out)")); + } + } + + int messages; + do { + CURLMsg* m = curl_multi_info_read (mcurl, &messages); + if (m && m->data.result != CURLE_OK) { + fclose (notes); + throw KDMError (String::compose ("Failed to send KDM email (%1)", curl_easy_strerror (m->data.result))); + } + } while (messages > 0); + + /* XXX: we should do this stuff when an exception is thrown, but curl_multi_remove_handle + seems to hang if we try that. + */ + + curl_slist_free_all (recipients); + curl_multi_remove_handle (mcurl, curl); + curl_multi_cleanup (mcurl); + curl_easy_cleanup (curl); + curl_global_cleanup (); + + fclose (notes); +} + +string +Emailer::address_list (list addresses) +{ + string o; + BOOST_FOREACH (string i, addresses) { + o += i + ", "; + } + + return o.substr (0, o.length() - 2); +} + +string +Emailer::notes () const +{ + return string (_notes_buffer.get()); +} diff --git a/src/lib/emailer.h b/src/lib/emailer.h new file mode 100644 index 000000000..4b4d1f6f7 --- /dev/null +++ b/src/lib/emailer.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2015 Carl Hetherington + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include + +class Emailer +{ +public: + Emailer (std::string from, std::string to, std::string subject, std::string body); + + void add_cc (std::string cc); + void add_bcc (std::string bcc); + void add_attachment (boost::filesystem::path attachment, std::string); + + void send (boost::shared_ptr job); + + std::string notes () const; + + size_t get_data (void* ptr, size_t size, size_t nmemb); + +private: + static std::string address_list (std::list addresses); + + std::string _from; + std::string _to; + std::string _subject; + std::string _body; + std::list _cc; + std::list _bcc; + std::list > _attachments; + std::string _email; + size_t _offset; + boost::scoped_array _notes_buffer; +};