diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/job.cc | 4 | ||||
| -rw-r--r-- | src/lib/kdm.cc | 2 | ||||
| -rw-r--r-- | src/lib/log.cc | 39 | ||||
| -rw-r--r-- | src/lib/log.h | 14 | ||||
| -rw-r--r-- | src/lib/quickmail.cc | 948 | ||||
| -rw-r--r-- | src/lib/quickmail.h | 289 | ||||
| -rw-r--r-- | src/lib/send_problem_report_job.cc | 105 | ||||
| -rw-r--r-- | src/lib/send_problem_report_job.h | 41 | ||||
| -rw-r--r-- | src/lib/util.h | 1 | ||||
| -rw-r--r-- | src/lib/wscript | 4 | ||||
| -rw-r--r-- | src/tools/dcpomatic.cc | 20 | ||||
| -rw-r--r-- | src/tools/dcpomatic_server.cc | 8 | ||||
| -rw-r--r-- | src/tools/wscript | 4 | ||||
| -rw-r--r-- | src/wx/report_problem_dialog.cc | 84 | ||||
| -rw-r--r-- | src/wx/report_problem_dialog.h | 44 | ||||
| -rw-r--r-- | src/wx/wscript | 1 |
16 files changed, 1596 insertions, 12 deletions
diff --git a/src/lib/job.cc b/src/lib/job.cc index 7be171417..3cd729515 100644 --- a/src/lib/job.cc +++ b/src/lib/job.cc @@ -109,7 +109,7 @@ Job::run_wrapper () set_error ( e.what (), - _("It is not known what caused this error. Please report the problem to the DCP-o-matic author (carl@dcpomatic.com).") + string (_("It is not known what caused this error.")) + " " + REPORT_PROBLEM ); set_progress (1); @@ -119,7 +119,7 @@ Job::run_wrapper () set_error ( _("Unknown error"), - _("It is not known what caused this error. Please report the problem to the DCP-o-matic author (carl@dcpomatic.com).") + string (_("It is not known what caused this error.")) + " " + REPORT_PROBLEM ); set_progress (1); diff --git a/src/lib/kdm.cc b/src/lib/kdm.cc index 108860594..3f88bbd9d 100644 --- a/src/lib/kdm.cc +++ b/src/lib/kdm.cc @@ -19,7 +19,6 @@ #include <list> #include <boost/shared_ptr.hpp> -#include <quickmail.h> #include <zip.h> #include <dcp/encrypted_kdm.h> #include <dcp/types.h> @@ -30,6 +29,7 @@ #include "film.h" #include "config.h" #include "safe_stringstream.h" +#include "quickmail.h" using std::list; using std::string; diff --git a/src/lib/log.cc b/src/lib/log.cc index 5e8277a23..156f8cf8b 100644 --- a/src/lib/log.cc +++ b/src/lib/log.cc @@ -123,3 +123,42 @@ FileLog::do_log (string m) fclose (f); } +string +FileLog::head_and_tail () const +{ + boost::mutex::scoped_lock lm (_mutex); + + uintmax_t head_amount = 1024; + uintmax_t tail_amount = 1024; + uintmax_t size = boost::filesystem::file_size (_file); + + if (size < (head_amount + tail_amount)) { + head_amount = size; + tail_amount = 0; + } + + FILE* f = fopen_boost (_file, "r"); + if (!f) { + return ""; + } + + string out; + + char* buffer = new char[max(head_amount, tail_amount) + 1]; + + int N = fread (buffer, 1, head_amount, f); + buffer[N] = '\0'; + out += buffer; + + fseek (f, tail_amount, SEEK_END); + + N = fread (buffer, 1, tail_amount, f); + buffer[N] = '\0'; + out += buffer; + + delete[] buffer; + + fclose (f); + + return out; +} diff --git a/src/lib/log.h b/src/lib/log.h index 94d30de4e..f20b0a148 100644 --- a/src/lib/log.h +++ b/src/lib/log.h @@ -48,12 +48,17 @@ public: void set_types (int types); + virtual std::string head_and_tail () const = 0; + +protected: + + /** mutex to protect the log */ + mutable boost::mutex _mutex; + private: virtual void do_log (std::string m) = 0; void config_changed (); - /** mutex to protect the log */ - boost::mutex _mutex; /** bit-field of log types which should be put into the log (others are ignored) */ int _types; boost::signals2::scoped_connection _config_connection; @@ -64,6 +69,8 @@ class FileLog : public Log public: FileLog (boost::filesystem::path file); + std::string head_and_tail () const; + private: void do_log (std::string m); /** filename to write to */ @@ -73,6 +80,9 @@ private: class NullLog : public Log { public: + std::string head_and_tail () const { + return ""; + } private: void do_log (std::string) {} diff --git a/src/lib/quickmail.cc b/src/lib/quickmail.cc new file mode 100644 index 000000000..30c987582 --- /dev/null +++ b/src/lib/quickmail.cc @@ -0,0 +1,948 @@ +/* + This file is part of libquickmail. + + libquickmail 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 3 of the License, or + (at your option) any later version. + + libquickmail 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 libquickmail. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "quickmail.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#ifndef _WIN32 +#include <unistd.h> +#endif +#include <curl/curl.h> + +#define NEWLINE "\r\n" +#define NEWLINELENGTH 2 + +#define MIME_LINE_WIDTH 72 +#define BODY_BUFFER_SIZE 256 + +//definitions of the differen stages of generating the message data +#define MAILPART_INITIALIZE 0 +#define MAILPART_HEADER 1 +#define MAILPART_BODY 2 +#define MAILPART_BODY_DONE 3 +#define MAILPART_ATTACHMENT 4 +#define MAILPART_END 5 +#define MAILPART_DONE 6 + +static const char* default_mime_type = "text/plain"; + +//////////////////////////////////////////////////////////////////////// + +#define DEBUG_ERROR(errmsg) +static const char* ERRMSG_MEMORY_ALLOCATION_ERROR = "Memory allocation error"; + +//////////////////////////////////////////////////////////////////////// + +char* randomize_zeros (char* data) +{ + //replace all 0s with random digits + char* p = data; + while (*p) { + if (*p == '0') + *p = '0' + rand() % 10; + p++; + } + return data; +} + +char* str_append (char** data, const char* newdata) +{ + //append a string to the end of an existing string + char* p; + int len = (*data ? strlen(*data) : 0); + if ((p = (char*)realloc(*data, len + strlen(newdata) + 1)) == NULL) { + free(p); + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return NULL; + } + *data = p; + strcpy(*data + len, newdata); + return *data; +} + +//////////////////////////////////////////////////////////////////////// + +struct email_info_struct { + int current; //must be zet to 0 + time_t timestamp; + char* from; + struct email_info_email_list_struct* to; + struct email_info_email_list_struct* cc; + struct email_info_email_list_struct* bcc; + char* subject; + char* header; + struct email_info_attachment_list_struct* bodylist; + struct email_info_attachment_list_struct* attachmentlist; + char* buf; + int buflen; + char* mime_boundary_body; + char* mime_boundary_part; + struct email_info_attachment_list_struct* current_attachment; + FILE* debuglog; + char dtable[64]; +}; + +//////////////////////////////////////////////////////////////////////// + +struct email_info_email_list_struct { + char* data; + struct email_info_email_list_struct* next; +}; + +void email_info_string_list_add (struct email_info_email_list_struct** list, const char* data) +{ + struct email_info_email_list_struct** p = list; + while (*p) + p = &(*p)->next; + if ((*p = (struct email_info_email_list_struct*)malloc(sizeof(struct email_info_email_list_struct))) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return; + } + (*p)->data = (data ? strdup(data) : NULL); + (*p)->next = NULL; +} + +void email_info_string_list_free (struct email_info_email_list_struct** list) +{ + struct email_info_email_list_struct* p = *list; + struct email_info_email_list_struct* current; + while (p) { + current = p; + p = current->next; + free(current->data); + free(current); + } + *list = NULL; +} + +char* email_info_string_list_concatenate (struct email_info_email_list_struct* list) +{ + char* result = NULL; + struct email_info_email_list_struct* listentry = list; + while (listentry) { + if (listentry->data && *listentry->data) { + if (result) + str_append(&result, "," NEWLINE "\t"); + str_append(&result, "<"); + str_append(&result, listentry->data); + str_append(&result, ">"); + } + listentry = listentry->next; + } + return result; +} + +//////////////////////////////////////////////////////////////////////// + +struct email_info_attachment_list_struct { + char* filename; + char* mimetype; + void* filedata; + void* handle; + quickmail_attachment_open_fn email_info_attachment_open; + quickmail_attachment_read_fn email_info_attachment_read; + quickmail_attachment_close_fn email_info_attachment_close; + quickmail_attachment_free_filedata_fn email_info_attachment_filedata_free; + struct email_info_attachment_list_struct* next; +}; + +struct email_info_attachment_list_struct* email_info_attachment_list_add (struct email_info_attachment_list_struct** list, const char* filename, const char* mimetype, void* filedata, quickmail_attachment_open_fn email_info_attachment_open, quickmail_attachment_read_fn email_info_attachment_read, quickmail_attachment_close_fn email_info_attachment_close, quickmail_attachment_free_filedata_fn email_info_attachment_filedata_free) +{ + struct email_info_attachment_list_struct** p = list; + while (*p) + p = &(*p)->next; + if ((*p = (struct email_info_attachment_list_struct*)malloc(sizeof(struct email_info_attachment_list_struct))) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return NULL; + } + (*p)->filename = strdup(filename ? filename : "UNNAMED"); + (*p)->mimetype = (mimetype ? strdup(mimetype) : NULL); + (*p)->filedata = filedata; + (*p)->handle = NULL; + (*p)->email_info_attachment_open = email_info_attachment_open; + (*p)->email_info_attachment_read = email_info_attachment_read; + (*p)->email_info_attachment_close = email_info_attachment_close; + (*p)->email_info_attachment_filedata_free = email_info_attachment_filedata_free; + (*p)->next = NULL; + return *p; +} + +void email_info_attachment_list_free_entry (struct email_info_attachment_list_struct* current) +{ + if (current->handle) { + if (current->email_info_attachment_close) + current->email_info_attachment_close(current->handle); + //else + // free(current->handle); + current->handle = NULL; + } + if (current->filedata) { + if (current->email_info_attachment_filedata_free) + current->email_info_attachment_filedata_free(current->filedata); + else + free(current->filedata); + } + if (current->mimetype) + free(current->mimetype); + free(current->filename); + free(current); +} + +void email_info_attachment_list_free (struct email_info_attachment_list_struct** list) +{ + struct email_info_attachment_list_struct* p = *list; + struct email_info_attachment_list_struct* current; + while (p) { + current = p; + p = current->next; + email_info_attachment_list_free_entry(current); + } + *list = NULL; +} + +int email_info_attachment_list_delete (struct email_info_attachment_list_struct** list, const char* filename) +{ + struct email_info_attachment_list_struct** p = list; + while (*p) { + if (strcmp((*p)->filename, filename) == 0) { + struct email_info_attachment_list_struct* current = *p; + *p = current->next; + email_info_attachment_list_free_entry(current); + return 0; + } + p = &(*p)->next; + } + return -1; +} + +void email_info_attachment_list_close_handles (struct email_info_attachment_list_struct* list) +{ + struct email_info_attachment_list_struct* p = list; + while (p) { + if (p->handle) { + if (p->email_info_attachment_close) + p->email_info_attachment_close(p->handle); + //else + // free(p->handle); + p->handle = NULL; + } + p = p->next; + } +} + +//dummy attachment functions + +void* email_info_attachment_open_dummy (void *) +{ + return (void *) &email_info_attachment_open_dummy; +} + +size_t email_info_attachment_read_dummy (void *, void *, size_t) +{ + return 0; +} + +struct email_info_attachment_list_struct* email_info_attachment_list_add_dummy (struct email_info_attachment_list_struct** list, const char* filename, const char* mimetype) +{ + return email_info_attachment_list_add(list, filename, mimetype, NULL, email_info_attachment_open_dummy, email_info_attachment_read_dummy, NULL, NULL); +} + +//file attachment functions + +void* email_info_attachment_open_file (void* filedata) +{ + return (void*)fopen((char*)filedata, "rb"); +} + +size_t email_info_attachment_read_file (void* handle, void* buf, size_t len) +{ + return fread(buf, 1, len, (FILE*)handle); +} + +void email_info_attachment_close_file (void* handle) +{ + if (handle) + fclose((FILE*)handle); +} + +struct email_info_attachment_list_struct* email_info_attachment_list_add_file (struct email_info_attachment_list_struct** list, const char* path, const char* mimetype) +{ + //determine base filename + const char* basename = path + strlen(path); + while (basename != path) { + basename--; + if (*basename == '/' +#ifdef _WIN32 + || *basename == '\\' || *basename == ':' +#endif + ) { + basename++; + break; + } + } + return email_info_attachment_list_add(list, basename, mimetype, (void*)strdup(path), email_info_attachment_open_file, email_info_attachment_read_file, email_info_attachment_close_file, NULL); +} + +//memory attachment functions + +struct email_info_attachment_memory_filedata_struct { + char* data; + size_t datalen; + int mustfree; +}; + +struct email_info_attachment_memory_handle_struct { + const char* data; + size_t datalen; + size_t pos; +}; + +void* email_info_attachment_open_memory (void* filedata) +{ + struct email_info_attachment_memory_filedata_struct* data; + struct email_info_attachment_memory_handle_struct* result; + data = ((struct email_info_attachment_memory_filedata_struct*)filedata); + if (!data->data) + return NULL; + if ((result = (struct email_info_attachment_memory_handle_struct*)malloc(sizeof(struct email_info_attachment_memory_handle_struct))) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return NULL; + } + result->data = data->data; + result->datalen = data->datalen; + result->pos = 0; + return result; +} + +size_t email_info_attachment_read_memory (void* handle, void* buf, size_t len) +{ + struct email_info_attachment_memory_handle_struct* h = (struct email_info_attachment_memory_handle_struct*)handle; + size_t n = (h->pos + len <= h->datalen ? len : h->datalen - h->pos); + memcpy(buf, h->data + h->pos, n); + h->pos += n; + return n; +} + +void email_info_attachment_close_memory (void* handle) +{ + if (handle) + free(handle); +} + +void email_info_attachment_filedata_free_memory (void* filedata) +{ + struct email_info_attachment_memory_filedata_struct* data = ((struct email_info_attachment_memory_filedata_struct*)filedata); + if (data) { + if (data->mustfree) + free(data->data); + free(data); + } +} + +struct email_info_attachment_list_struct* email_info_attachment_list_add_memory (struct email_info_attachment_list_struct** list, const char* filename, const char* mimetype, char* data, size_t datalen, int mustfree) +{ + struct email_info_attachment_memory_filedata_struct* filedata; + if ((filedata = (struct email_info_attachment_memory_filedata_struct*)malloc(sizeof(struct email_info_attachment_memory_filedata_struct))) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return NULL; + } + filedata->data = data; + filedata->datalen = datalen; + filedata->mustfree = mustfree; + return email_info_attachment_list_add(list, filename, mimetype, filedata, email_info_attachment_open_memory, email_info_attachment_read_memory, email_info_attachment_close_memory, email_info_attachment_filedata_free_memory); +} + +//////////////////////////////////////////////////////////////////////// + +int quickmail_initialize () +{ + return 0; +} + +quickmail quickmail_create (const char* from, const char* subject) +{ + int i; + struct email_info_struct* mailobj; + if ((mailobj = (struct email_info_struct*)malloc(sizeof(struct email_info_struct))) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return NULL; + } + mailobj->current = 0; + mailobj->timestamp = time(NULL); + mailobj->from = (from ? strdup(from) : NULL); + mailobj->to = NULL; + mailobj->cc = NULL; + mailobj->bcc = NULL; + mailobj->subject = (subject ? strdup(subject) : NULL); + mailobj->header = NULL; + mailobj->bodylist = NULL; + mailobj->attachmentlist = NULL; + mailobj->buf = NULL; + mailobj->buflen = 0; + mailobj->mime_boundary_body = NULL; + mailobj->mime_boundary_part = NULL; + mailobj->current_attachment = NULL; + mailobj->debuglog = NULL; + for (i = 0; i < 26; i++) { + mailobj->dtable[i] = (char)('A' + i); + mailobj->dtable[26 + i] = (char)('a' + i); + } + for (i = 0; i < 10; i++) { + mailobj->dtable[52 + i] = (char)('0' + i); + } + mailobj->dtable[62] = '+'; + mailobj->dtable[63] = '/'; + srand(time(NULL)); + return mailobj; +} + +void quickmail_destroy (quickmail mailobj) +{ + free(mailobj->from); + email_info_string_list_free(&mailobj->to); + email_info_string_list_free(&mailobj->cc); + email_info_string_list_free(&mailobj->bcc); + free(mailobj->subject); + free(mailobj->header); + email_info_attachment_list_free(&mailobj->bodylist); + email_info_attachment_list_free(&mailobj->attachmentlist); + free(mailobj->buf); + free(mailobj->mime_boundary_body); + free(mailobj->mime_boundary_part); + free(mailobj); +} + +void quickmail_set_from (quickmail mailobj, const char* from) +{ + free(mailobj->from); + mailobj->from = strdup(from); +} + +const char* quickmail_get_from (quickmail mailobj) +{ + return mailobj->from; +} + +void quickmail_add_to (quickmail mailobj, const char* email) +{ + email_info_string_list_add(&mailobj->to, email); +} + +void quickmail_add_cc (quickmail mailobj, const char* email) +{ + email_info_string_list_add(&mailobj->cc, email); +} + +void quickmail_add_bcc (quickmail mailobj, const char* email) +{ + email_info_string_list_add(&mailobj->bcc, email); +} + +void quickmail_set_subject (quickmail mailobj, const char* subject) +{ + free(mailobj->subject); + mailobj->subject = (subject ? strdup(subject) : NULL); +} + +const char* quickmail_get_subject (quickmail mailobj) +{ + return mailobj->subject; +} + +void quickmail_add_header (quickmail mailobj, const char* headerline) +{ + str_append(&mailobj->header, headerline); + str_append(&mailobj->header, NEWLINE); +} + +void quickmail_set_body (quickmail mailobj, const char* body) +{ + email_info_attachment_list_free(&mailobj->bodylist); + if (body) + email_info_attachment_list_add_memory(&mailobj->bodylist, default_mime_type, default_mime_type, strdup(body), strlen(body), 1); +} + +char* quickmail_get_body (quickmail mailobj) +{ + size_t n; + char* p; + char* result = NULL; + size_t resultlen = 0; + if (mailobj->bodylist && (mailobj->bodylist->handle = mailobj->bodylist->email_info_attachment_open(mailobj->bodylist->filedata)) != NULL) { + do { + if ((p = (char*)realloc(result, resultlen + BODY_BUFFER_SIZE)) == NULL) { + free(result); + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + break; + } + result = p; + if ((n = mailobj->bodylist->email_info_attachment_read(mailobj->bodylist->handle, result + resultlen, BODY_BUFFER_SIZE)) > 0) + resultlen += n; + } while (n > 0); + if (mailobj->bodylist->email_info_attachment_close) + mailobj->bodylist->email_info_attachment_close(mailobj->bodylist->handle); + //else + // free(mailobj->bodylist->handle); + mailobj->bodylist->handle = NULL; + } + return result; +} + +void quickmail_add_body_file (quickmail mailobj, const char* mimetype, const char* path) +{ + email_info_attachment_list_add(&mailobj->bodylist, (mimetype ? mimetype : default_mime_type), (mimetype ? mimetype : default_mime_type), (void*)strdup(path), email_info_attachment_open_file, email_info_attachment_read_file, email_info_attachment_close_file, NULL); +} + +void quickmail_add_body_memory (quickmail mailobj, const char* mimetype, char* data, size_t datalen, int mustfree) +{ + email_info_attachment_list_add_memory(&mailobj->bodylist, (mimetype ? mimetype : default_mime_type), (mimetype ? mimetype : default_mime_type), data, datalen, mustfree); +} + +void quickmail_add_body_custom (quickmail mailobj, const char* mimetype, char* data, quickmail_attachment_open_fn attachment_data_open, quickmail_attachment_read_fn attachment_data_read, quickmail_attachment_close_fn attachment_data_close, quickmail_attachment_free_filedata_fn attachment_data_filedata_free) +{ + email_info_attachment_list_add(&mailobj->bodylist, (mimetype ? mimetype : default_mime_type), (mimetype ? mimetype : default_mime_type), data, (attachment_data_open ? attachment_data_open : email_info_attachment_open_dummy), (attachment_data_read ? attachment_data_read : email_info_attachment_read_dummy), attachment_data_close, attachment_data_filedata_free); +} + +int quickmail_remove_body (quickmail mailobj, const char* mimetype) +{ + return email_info_attachment_list_delete(&mailobj->bodylist, mimetype); +} + +void quickmail_list_bodies (quickmail mailobj, quickmail_list_attachment_callback_fn callback, void* callbackdata) +{ + struct email_info_attachment_list_struct* p = mailobj->bodylist; + while (p) { + callback(mailobj, p->filename, p->mimetype, p->email_info_attachment_open, p->email_info_attachment_read, p->email_info_attachment_close, callbackdata); + p = p->next; + } +} + +void quickmail_add_attachment_file (quickmail mailobj, const char* path, const char* mimetype) +{ + email_info_attachment_list_add_file(&mailobj->attachmentlist, path, mimetype); +} + +void quickmail_add_attachment_memory (quickmail mailobj, const char* filename, const char* mimetype, char* data, size_t datalen, int mustfree) +{ + email_info_attachment_list_add_memory(&mailobj->attachmentlist, filename, mimetype, data, datalen, mustfree); +} + +void quickmail_add_attachment_custom (quickmail mailobj, const char* filename, const char* mimetype, char* data, quickmail_attachment_open_fn attachment_data_open, quickmail_attachment_read_fn attachment_data_read, quickmail_attachment_close_fn attachment_data_close, quickmail_attachment_free_filedata_fn attachment_data_filedata_free) +{ + email_info_attachment_list_add(&mailobj->attachmentlist, filename, mimetype, data, (attachment_data_open ? attachment_data_open : email_info_attachment_open_dummy), (attachment_data_read ? attachment_data_read : email_info_attachment_read_dummy), attachment_data_close, attachment_data_filedata_free); +} + +int quickmail_remove_attachment (quickmail mailobj, const char* filename) +{ + return email_info_attachment_list_delete(&mailobj->attachmentlist, filename); +} + +void quickmail_list_attachments (quickmail mailobj, quickmail_list_attachment_callback_fn callback, void* callbackdata) +{ + struct email_info_attachment_list_struct* p = mailobj->attachmentlist; + while (p) { + callback(mailobj, p->filename, p->mimetype, p->email_info_attachment_open, p->email_info_attachment_read, p->email_info_attachment_close, callbackdata); + p = p->next; + } +} + +void quickmail_set_debug_log (quickmail mailobj, FILE* filehandle) +{ + mailobj->debuglog = filehandle; +} + +void quickmail_fsave (quickmail mailobj, FILE* filehandle) +{ + size_t n; + char buf[80]; + while ((n = quickmail_get_data(buf, sizeof(buf), 1, mailobj)) > 0) { + for (size_t i = 0; i < n; i++) + fprintf(filehandle, "%c", buf[i]); + } +} + +size_t quickmail_get_data (void* ptr, size_t size, size_t nmemb, void* userp) +{ + struct email_info_struct* mailobj = (struct email_info_struct*)userp; + + //abort if no data is requested + if (size * nmemb == 0) + return 0; + + //initialize on first run + if (mailobj->current == MAILPART_INITIALIZE) { + free(mailobj->buf); + mailobj->buf = NULL; + mailobj->buflen = 0; + free(mailobj->mime_boundary_body); + mailobj->mime_boundary_body = NULL; + free(mailobj->mime_boundary_part); + mailobj->mime_boundary_part = NULL; + mailobj->current_attachment = mailobj->bodylist; + mailobj->current++; + } + + //process current part of mail if no partial data is pending + while (mailobj->buflen == 0) { + if (mailobj->buflen == 0 && mailobj->current == MAILPART_HEADER) { + char* s; + //generate header part + char** p = &mailobj->buf; + mailobj->buf = NULL; + str_append(p, "User-Agent: libquickmail"); + if (mailobj->timestamp != 0) { + char timestamptext[32]; + if (strftime(timestamptext, sizeof(timestamptext), "%a, %d %b %Y %H:%M:%S %z", localtime(&mailobj->timestamp))) {
+ str_append(p, "Date: "); + str_append(p, timestamptext); + str_append(p, NEWLINE); + } +#ifdef _WIN32
+ //fallback method for Windows when %z (time zone offset) fails + else if (strftime(timestamptext, sizeof(timestamptext), "%a, %d %b %Y %H:%M:%S", localtime(&mailobj->timestamp))) {
+ TIME_ZONE_INFORMATION tzinfo;
+ if (GetTimeZoneInformation(&tzinfo) != TIME_ZONE_ID_INVALID)
+ sprintf(timestamptext + strlen(timestamptext), " %c%02i%02i", (tzinfo.Bias > 0 ? '-' : '+'), (int)-tzinfo.Bias / 60, (int)-tzinfo.Bias % 60);
+ str_append(p, "Date: "); + str_append(p, timestamptext); + str_append(p, NEWLINE); + } +#endif
+ } + if (mailobj->from && *mailobj->from) { + str_append(p, "From: <"); + str_append(p, mailobj->from); + str_append(p, ">" NEWLINE); + } + if ((s = email_info_string_list_concatenate(mailobj->to)) != NULL) { + str_append(p, "To: "); + str_append(p, s); + str_append(p, NEWLINE); + free(s); + } + if ((s = email_info_string_list_concatenate(mailobj->cc)) != NULL) { + str_append(p, "Cc: "); + str_append(p, s); + str_append(p, NEWLINE); + free(s); + } + if (mailobj->subject) { + str_append(p, "Subject: "); + str_append(p, mailobj->subject); + str_append(p, NEWLINE); + } + if (mailobj->header) { + str_append(p, mailobj->header); + } + if (mailobj->attachmentlist) { + str_append(p, "MIME-Version: 1.0" NEWLINE); + } + if (mailobj->attachmentlist) { + mailobj->mime_boundary_part = randomize_zeros(strdup("=PART=SEPARATOR=_0000_0000_0000_0000_0000_0000_=")); + str_append(p, "Content-Type: multipart/mixed; boundary=\""); + str_append(p, mailobj->mime_boundary_part); + str_append(p, "\"" NEWLINE NEWLINE "This is a multipart message in MIME format." NEWLINE NEWLINE "--"); + str_append(p, mailobj->mime_boundary_part); + str_append(p, NEWLINE); + } + if (mailobj->bodylist && mailobj->bodylist->next) { + mailobj->mime_boundary_body = randomize_zeros(strdup("=BODY=SEPARATOR=_0000_0000_0000_0000_0000_0000_=")); + str_append(p, "Content-Type: multipart/alternative; boundary=\""); + str_append(p, mailobj->mime_boundary_body); + str_append(p, NEWLINE); + } + mailobj->buflen = strlen(mailobj->buf); + mailobj->current++; + } + if (mailobj->buflen == 0 && mailobj->current == MAILPART_BODY) { + if (mailobj->current_attachment) { + if (!mailobj->current_attachment->handle) { + //open file with body data + while (mailobj->current_attachment) { + if ((mailobj->current_attachment->handle = mailobj->current_attachment->email_info_attachment_open(mailobj->current_attachment->filedata)) != NULL) { + break; + } + mailobj->current_attachment = mailobj->current_attachment->next; + } + if (!mailobj->current_attachment) { + mailobj->current_attachment = mailobj->attachmentlist; + mailobj->current++; + } + //generate attachment header + if (mailobj->current_attachment && mailobj->current_attachment->handle) { + mailobj->buf = NULL; + if (mailobj->mime_boundary_body) { + mailobj->buf = str_append(&mailobj->buf, NEWLINE "--"); + mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_body); + mailobj->buf = str_append(&mailobj->buf, NEWLINE); + } + mailobj->buf = str_append(&mailobj->buf, "Content-Type: "); + mailobj->buf = str_append(&mailobj->buf, (mailobj->bodylist && mailobj->current_attachment->filename ? mailobj->current_attachment->filename : default_mime_type)); + mailobj->buf = str_append(&mailobj->buf, NEWLINE "Content-Transfer-Encoding: 8bit" NEWLINE "Content-Disposition: inline" NEWLINE NEWLINE); + mailobj->buflen = strlen(mailobj->buf); + } + } + if (mailobj->buflen == 0 && mailobj->current_attachment && mailobj->current_attachment->handle) { + //read body data + if ((mailobj->buf = (char *) malloc(BODY_BUFFER_SIZE)) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + } + if (mailobj->buf == NULL || (mailobj->buflen = mailobj->current_attachment->email_info_attachment_read(mailobj->current_attachment->handle, mailobj->buf, BODY_BUFFER_SIZE)) <= 0) { + //end of file + free(mailobj->buf); + mailobj->buflen = 0; + if (mailobj->current_attachment->email_info_attachment_close) + mailobj->current_attachment->email_info_attachment_close(mailobj->current_attachment->handle); + //else + // free(mailobj->current_attachment->handle); + mailobj->current_attachment->handle = NULL; + mailobj->current_attachment = mailobj->current_attachment->next; + } + } + } else { + mailobj->current_attachment = mailobj->attachmentlist; + mailobj->current++; + } + } + if (mailobj->buflen == 0 && mailobj->current == MAILPART_BODY_DONE) { + mailobj->buf = NULL; + if (mailobj->mime_boundary_body) { + mailobj->buf = str_append(&mailobj->buf, NEWLINE "--"); + mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_body); + mailobj->buf = str_append(&mailobj->buf, "--" NEWLINE); + mailobj->buflen = strlen(mailobj->buf); + free(mailobj->mime_boundary_body); + mailobj->mime_boundary_body = NULL; + } + mailobj->current++; + } + if (mailobj->buflen == 0 && mailobj->current == MAILPART_ATTACHMENT) { + if (mailobj->current_attachment) { + if (!mailobj->current_attachment->handle) { + //open file to attach + while (mailobj->current_attachment) { + if ((mailobj->current_attachment->handle = mailobj->current_attachment->email_info_attachment_open(mailobj->current_attachment->filedata)) != NULL) { + break; + } + mailobj->current_attachment = mailobj->current_attachment->next; + } + //generate attachment header + if (mailobj->current_attachment && mailobj->current_attachment->handle) { + mailobj->buf = NULL; + if (mailobj->mime_boundary_part) { + mailobj->buf = str_append(&mailobj->buf, NEWLINE "--"); + mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_part); + mailobj->buf = str_append(&mailobj->buf, NEWLINE); + } + mailobj->buf = str_append(&mailobj->buf, "Content-Type: "); + mailobj->buf = str_append(&mailobj->buf, (mailobj->current_attachment->mimetype ? mailobj->current_attachment->mimetype : "application/octet-stream")); + mailobj->buf = str_append(&mailobj->buf, "; Name=\""); + mailobj->buf = str_append(&mailobj->buf, mailobj->current_attachment->filename); + mailobj->buf = str_append(&mailobj->buf, "\"" NEWLINE "Content-Transfer-Encoding: base64" NEWLINE NEWLINE); + mailobj->buflen = strlen(mailobj->buf); + } + } else { + //generate next line of attachment data + size_t n = 0; + int mimelinepos = 0; + unsigned char igroup[3] = {0, 0, 0}; + unsigned char ogroup[4]; + mailobj->buflen = 0; + if ((mailobj->buf = (char*)malloc(MIME_LINE_WIDTH + NEWLINELENGTH + 1)) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + n = 0; + } else { + while (mimelinepos < MIME_LINE_WIDTH && (n = mailobj->current_attachment->email_info_attachment_read(mailobj->current_attachment->handle, igroup, 3)) > 0) { + //code data + ogroup[0] = mailobj->dtable[igroup[0] >> 2]; + ogroup[1] = mailobj->dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)]; + ogroup[2] = mailobj->dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)]; + ogroup[3] = mailobj->dtable[igroup[2] & 0x3F]; + //padd with "=" characters if less than 3 characters were read + if (n < 3) { + ogroup[3] = '='; + if (n < 2) + ogroup[2] = '='; + } + memcpy(mailobj->buf + mimelinepos, ogroup, 4); + mailobj->buflen += 4; + mimelinepos += 4; + } + if (mimelinepos > 0) { + memcpy(mailobj->buf + mimelinepos, NEWLINE, NEWLINELENGTH); + mailobj->buflen += NEWLINELENGTH; + } + } + if (n <= 0) { + //end of file + if (mailobj->current_attachment->email_info_attachment_close) + mailobj->current_attachment->email_info_attachment_close(mailobj->current_attachment->handle); + else + free(mailobj->current_attachment->handle); + mailobj->current_attachment->handle = NULL; + mailobj->current_attachment = mailobj->current_attachment->next; + } + } + } else { + mailobj->current++; + } + } + if (mailobj->buflen == 0 && mailobj->current == MAILPART_END) { + mailobj->buf = NULL; + mailobj->buflen = 0; + if (mailobj->mime_boundary_part) { + mailobj->buf = str_append(&mailobj->buf, NEWLINE "--"); + mailobj->buf = str_append(&mailobj->buf, mailobj->mime_boundary_part); + mailobj->buf = str_append(&mailobj->buf, "--" NEWLINE); + mailobj->buflen = strlen(mailobj->buf); + free(mailobj->mime_boundary_part); + mailobj->mime_boundary_part = NULL; + } + //mailobj->buf = str_append(&mailobj->buf, NEWLINE "." NEWLINE); + //mailobj->buflen = strlen(mailobj->buf); + mailobj->current++; + } + if (mailobj->buflen == 0 && mailobj->current == MAILPART_DONE) { + break; + } + } + + //flush pending data if any + if (mailobj->buflen > 0) { + int len = ((size_t) mailobj->buflen > size * nmemb ? size * nmemb : mailobj->buflen); + memcpy(ptr, mailobj->buf, len); + if (len < mailobj->buflen) { + mailobj->buf = (char *) memmove(mailobj->buf, mailobj->buf + len, mailobj->buflen - len); + mailobj->buflen -= len; + } else { + free(mailobj->buf); + mailobj->buf = NULL; + mailobj->buflen = 0; + } + return len; + } + + //if (mailobj->current != MAILPART_DONE) + // ;//this should never be reached + mailobj->current = 0; + return 0; +} + +char* add_angle_brackets (const char* data) +{ + size_t datalen = strlen(data); + char* result; + if ((result = (char*)malloc(datalen + 3)) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return NULL; + } + result[0] = '<'; + memcpy(result + 1, data, datalen); + result[datalen + 1] = '>'; + result[datalen + 2] = 0; + return result; +} + +const char* quickmail_send (quickmail mailobj, const char* smtpserver, unsigned int smtpport, const char* username, const char* password) +{ + //libcurl based sending + CURL *curl; + CURLcode result = CURLE_FAILED_INIT; + //curl_global_init(CURL_GLOBAL_ALL); + if ((curl = curl_easy_init()) != NULL) { + struct curl_slist *recipients = NULL; + struct email_info_email_list_struct* listentry; + //set destination URL + char* addr; + size_t len = strlen(smtpserver) + 14; + if ((addr = (char*)malloc(len)) == NULL) { + DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) + return ERRMSG_MEMORY_ALLOCATION_ERROR; + } + snprintf(addr, len, "smtp://%s:%u", smtpserver, smtpport); + curl_easy_setopt(curl, CURLOPT_URL, addr); + free(addr); + //try Transport Layer Security (TLS), but continue anyway if it fails + curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY); + //don't fail if the TLS/SSL a certificate could not be verified + //alternative: add the issuer certificate (or the host certificate if + //the certificate is self-signed) to the set of certificates that are + //known to libcurl using CURLOPT_CAINFO and/or CURLOPT_CAPATH + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + //set authentication credentials if provided + if (username && *username) + curl_easy_setopt(curl, CURLOPT_USERNAME, username); + if (password) + curl_easy_setopt(curl, CURLOPT_PASSWORD, password); + //set from value for envelope reverse-path + if (mailobj->from && *mailobj->from) { + addr = add_angle_brackets(mailobj->from); + curl_easy_setopt(curl, CURLOPT_MAIL_FROM, addr); + free(addr); + } + //set recipients + listentry = mailobj->to; + while (listentry) { + if (listentry->data && *listentry->data) { + addr = add_angle_brackets(listentry->data); + recipients = curl_slist_append(recipients, addr); + free(addr); + } + listentry = listentry->next; + } + listentry = mailobj->cc; + while (listentry) { + if (listentry->data && *listentry->data) { + addr = add_angle_brackets(listentry->data); + recipients = curl_slist_append(recipients, addr); + free(addr); + } + listentry = listentry->next; + } + listentry = mailobj->bcc; + while (listentry) { + if (listentry->data && *listentry->data) { + addr = add_angle_brackets(listentry->data); + recipients = curl_slist_append(recipients, addr); + free(addr); + } + listentry = listentry->next; + } + curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients); + //set callback function for getting message body + curl_easy_setopt(curl, CURLOPT_READFUNCTION, quickmail_get_data); + curl_easy_setopt(curl, CURLOPT_READDATA, mailobj); + /* Without this curl sends VRFY, which exim errors on + (at least on main.carlh.net) + */ + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + //enable debugging if requested + if (mailobj->debuglog) { + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_STDERR, mailobj->debuglog); + } + //send the message + result = curl_easy_perform(curl); + //free the list of recipients and clean up + curl_slist_free_all(recipients); + curl_easy_cleanup(curl); + } + return (result == CURLE_OK ? NULL : curl_easy_strerror(result)); +} diff --git a/src/lib/quickmail.h b/src/lib/quickmail.h new file mode 100644 index 000000000..9f1cc448d --- /dev/null +++ b/src/lib/quickmail.h @@ -0,0 +1,289 @@ +/*! \file quickmail.h + * \brief header file for libquickmail + * \author Brecht Sanders + * \date 2012-2013 + * \copyright GPL + */ +/* + This file is part of libquickmail. + + libquickmail 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 3 of the License, or + (at your option) any later version. + + libquickmail 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 libquickmail. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __INCLUDED_QUICKMAIL_H +#define __INCLUDED_QUICKMAIL_H + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/*! \brief quickmail object type */ +typedef struct email_info_struct* quickmail; + + + +/*! \brief type of pointer to function for opening attachment data + * \param filedata custom data as passed to quickmail_add_body_custom/quickmail_add_attachment_custom + * \return data structure to be used in calls to quickmail_attachment_read_fn and quickmail_attachment_close_fn functions + * \sa quickmail_add_body_custom() + * \sa quickmail_add_attachment_custom() + */ +typedef void* (*quickmail_attachment_open_fn)(void* filedata); + +/*! \brief type of pointer to function for reading attachment data + * \param handle data structure obtained via the corresponding quickmail_attachment_open_fn function + * \param buf buffer for receiving data + * \param len size in bytes of buffer for receiving data + * \return number of bytes read (zero on end of file) + * \sa quickmail_add_body_custom() + * \sa quickmail_add_attachment_custom() + */ +typedef size_t (*quickmail_attachment_read_fn)(void* handle, void* buf, size_t len); + +/*! \brief type of pointer to function for closing attachment data + * \param handle data structure obtained via the corresponding quickmail_attachment_open_fn function + * \sa quickmail_add_body_custom() + * \sa quickmail_add_attachment_custom() + */ +typedef void (*quickmail_attachment_close_fn)(void* handle); + +/*! \brief type of pointer to function for cleaning up custom data in quickmail_destroy + * \param filedata custom data as passed to quickmail_add_body_custom/quickmail_add_attachment_custom + * \sa quickmail_add_body_custom() + * \sa quickmail_add_attachment_custom() + */ +typedef void (*quickmail_attachment_free_filedata_fn)(void* filedata); + +/*! \brief type of pointer to function for cleaning up custom data in quickmail_destroy + * \param mailobj quickmail object + * \param filename attachment filename (same value as mimetype for mail body) + * \param mimetype MIME type + * \param attachment_data_open function for opening attachment data + * \param attachment_data_read function for reading attachment data + * \param attachment_data_close function for closing attachment data (optional, free() will be used if NULL) + * \param callbackdata custom data passed to quickmail_list_attachments + * \sa quickmail_list_bodies() + * \sa quickmail_list_attachments() + */ +typedef void (*quickmail_list_attachment_callback_fn)(quickmail mailobj, const char* filename, const char* mimetype, quickmail_attachment_open_fn email_info_attachment_open, quickmail_attachment_read_fn email_info_attachment_read, quickmail_attachment_close_fn email_info_attachment_close, void* callbackdata); + + + +/*! \brief get version quickmail library + * \return library version + */ +const char* quickmail_get_version (); + +/*! \brief initialize quickmail library + * \return zero on success + */ +int quickmail_initialize (); + +/*! \brief create a new quickmail object + * \param from sender e-mail address + * \param subject e-mail subject + * \return quickmail object or NULL on error + */ +quickmail quickmail_create (const char* from, const char* subject); + +/*! \brief clean up a quickmail object + * \param mailobj quickmail object + */ +void quickmail_destroy (quickmail mailobj); + +/*! \brief set the sender (from) e-mail address of a quickmail object + * \param mailobj quickmail object + * \param from sender e-mail address + */ +void quickmail_set_from (quickmail mailobj, const char* from); + +/*! \brief get the sender (from) e-mail address of a quickmail object + * \param mailobj quickmail object + * \return sender e-mail address + */ +const char* quickmail_get_from (quickmail mailobj); + +/*! \brief add a recipient (to) e-mail address to a quickmail object + * \param mailobj quickmail object + * \param e-mail recipient e-mail address + */ +void quickmail_add_to (quickmail mailobj, const char* email); + +/*! \brief add a carbon copy recipient (cc) e-mail address to a quickmail object + * \param mailobj quickmail object + * \param e-mail recipient e-mail address + */ +void quickmail_add_cc (quickmail mailobj, const char* email); + +/*! \brief add a blind carbon copy recipient (bcc) e-mail address to a quickmail object + * \param mailobj quickmail object + * \param e-mail recipient e-mail address + */ +void quickmail_add_bcc (quickmail mailobj, const char* email); + +/*! \brief set the subject of a quickmail object + * \param mailobj quickmail object + * \param subject e-mail subject + */ +void quickmail_set_subject (quickmail mailobj, const char* subject); + +/*! \brief set the subject of a quickmail object + * \param mailobj quickmail object + * \return e-mail subject + */ +const char* quickmail_get_subject (quickmail mailobj); + +/*! \brief add an e-mail header to a quickmail object + * \param mailobj quickmail object + * \param headerline header line to add + */ +void quickmail_add_header (quickmail mailobj, const char* headerline); + +/*! \brief set the body of a quickmail object + * \param mailobj quickmail object + * \param body e-mail body + */ +void quickmail_set_body (quickmail mailobj, const char* body); + +/*! \brief set the body of a quickmail object + * any existing bodies will be removed and a single plain text body will be added + * \param mailobj quickmail object + * \return e-mail body or NULL on error (caller must free result) + */ +char* quickmail_get_body (quickmail mailobj); + +/*! \brief add a body file to a quickmail object (deprecated) + * \param mailobj quickmail object + * \param mimetype MIME type (text/plain will be used if set to NULL) + * \param path path of file with body data + */ +void quickmail_add_body_file (quickmail mailobj, const char* mimetype, const char* path); + +/*! \brief add a body from memory to a quickmail object + * \param mailobj quickmail object + * \param mimetype MIME type (text/plain will be used if set to NULL) + * \param data body content + * \param datalen size of data in bytes + * \param mustfree non-zero if data must be freed by quickmail_destroy + */ +void quickmail_add_body_memory (quickmail mailobj, const char* mimetype, char* data, size_t datalen, int mustfree); + +/*! \brief add a body with custom read functions to a quickmail object + * \param mailobj quickmail object + * \param mimetype MIME type (text/plain will be used if set to NULL) + * \param data custom data passed to attachment_data_open and attachment_data_filedata_free functions + * \param attachment_data_open function for opening attachment data + * \param attachment_data_read function for reading attachment data + * \param attachment_data_close function for closing attachment data (optional, free() will be used if NULL) + * \param attachment_data_filedata_free function for cleaning up custom data in quickmail_destroy (optional, free() will be used if NULL) + */ +void quickmail_add_body_custom (quickmail mailobj, const char* mimetype, char* data, quickmail_attachment_open_fn attachment_data_open, quickmail_attachment_read_fn attachment_data_read, quickmail_attachment_close_fn attachment_data_close, quickmail_attachment_free_filedata_fn attachment_data_filedata_free); + +/*! \brief remove body from quickmail object + * \param mailobj quickmail object + * \param mimetype MIME type (text/plain will be used if set to NULL) + * \return zero on success + */ +int quickmail_remove_body (quickmail mailobj, const char* mimetype); + +/*! \brief list bodies by calling a callback function for each body + * \param mailobj quickmail object + * \param callback function to call for each attachment + * \param callbackdata custom data to pass to the callback function + * \sa quickmail_list_attachment_callback_fn + */ +void quickmail_list_bodies (quickmail mailobj, quickmail_list_attachment_callback_fn callback, void* callbackdata); + +/*! \brief add a file attachment to a quickmail object + * \param mailobj quickmail object + * \param path path of file to attach + * \param mimetype MIME type of file to attach (application/octet-stream will be used if set to NULL) + */ +void quickmail_add_attachment_file (quickmail mailobj, const char* path, const char* mimetype); + +/*! \brief add an attachment from memory to a quickmail object + * \param mailobj quickmail object + * \param filename name of file to attach (must not include full path) + * \param mimetype MIME type of file to attach (set to NULL for default binary file) + * \param data data content + * \param datalen size of data in bytes + * \param mustfree non-zero if data must be freed by quickmail_destroy + */ +void quickmail_add_attachment_memory (quickmail mailobj, const char* filename, const char* mimetype, char* data, size_t datalen, int mustfree); + +/*! \brief add an attachment with custom read functions to a quickmail object + * \param mailobj quickmail object + * \param filename name of file to attach (must not include full path) + * \param mimetype MIME type of file to attach (set to NULL for default binary file) + * \param data custom data passed to attachment_data_open and attachment_data_filedata_free functions + * \param attachment_data_open function for opening attachment data + * \param attachment_data_read function for reading attachment data + * \param attachment_data_close function for closing attachment data (optional, free() will be used if NULL) + * \param attachment_data_filedata_free function for cleaning up custom data in quickmail_destroy (optional, free() will be used if NULL) + */ +void quickmail_add_attachment_custom (quickmail mailobj, const char* filename, const char* mimetype, char* data, quickmail_attachment_open_fn attachment_data_open, quickmail_attachment_read_fn attachment_data_read, quickmail_attachment_close_fn attachment_data_close, quickmail_attachment_free_filedata_fn attachment_data_filedata_free); + +/*! \brief remove attachment from quickmail object + * \param mailobj quickmail object + * \param filename name of file to attach (must not include full path) + * \return zero on success + */ +int quickmail_remove_attachment (quickmail mailobj, const char* filename); + +/*! \brief list attachments by calling a callback function for each attachment + * \param mailobj quickmail object + * \param callback function to call for each attachment + * \param callbackdata custom data to pass to the callback function + * \sa quickmail_list_attachment_callback_fn + */ +void quickmail_list_attachments (quickmail mailobj, quickmail_list_attachment_callback_fn callback, void* callbackdata); + +/*! \brief set the debug logging destination of a quickmail object + * \param mailobj quickmail object + * \param filehandle file handle of logging destination (or NULL for no logging) + */ +void quickmail_set_debug_log (quickmail mailobj, FILE* filehandle); + +/*! \brief save the generated e-mail to a file + * \param mailobj quickmail object + * \param filehandle file handle to write the e-mail contents to + */ +void quickmail_fsave (quickmail mailobj, FILE* filehandle); + +/*! \brief read data the next data from the e-mail contents (can be used as CURLOPT_READFUNCTION callback function) + * \param buffer buffer to copy data to (bust be able to hold size * nmemb bytes) + * \param size record size + * \param nmemb number of records to copy to \p buffer + * \param mailobj quickmail object + * \return number of bytes copied to \p buffer or 0 if at end + */ +size_t quickmail_get_data (void* buffer, size_t size, size_t nmemb, void* mailobj); + +/*! \brief send the e-mail via SMTP + * \param mailobj quickmail object + * \param smtpserver IP address or hostname of SMTP server + * \param smtpport SMTP port number (normally this is 25) + * \param username username to use for authentication (or NULL if not needed) + * \param password password to use for authentication (or NULL if not needed) + * \return NULL on success or error message on error + */ +const char* quickmail_send (quickmail mailobj, const char* smtpserver, unsigned int smtpport, const char* username, const char* password); + +#ifdef __cplusplus +} +#endif + +#endif //__INCLUDED_QUICKMAIL_H diff --git a/src/lib/send_problem_report_job.cc b/src/lib/send_problem_report_job.cc new file mode 100644 index 000000000..b2eb4e25d --- /dev/null +++ b/src/lib/send_problem_report_job.cc @@ -0,0 +1,105 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + 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 "send_problem_report_job.h" +#include "compose.hpp" +#include "film.h" +#include "cross.h" +#include "film.h" +#include "log.h" +#include "quickmail.h" + +#include "i18n.h" + +using std::string; +using std::list; +using boost::shared_ptr; + +SendProblemReportJob::SendProblemReportJob ( + shared_ptr<const Film> film, + string from, + string summary + ) + : Job (film) + , _from (from) + , _summary (summary) +{ + +} + +string +SendProblemReportJob::name () const +{ + return String::compose (_("Email problem report for %1"), _film->name()); +} + +void +SendProblemReportJob::run () +{ + set_progress_unknown (); + + quickmail mail = quickmail_create (_from.c_str(), "DCP-o-matic problem report"); + + quickmail_add_to (mail, "carl@dcpomatic.com"); + + string body = _summary + "\n\n"; + + body += "log head and tail:\n"; + body += "---<8----\n"; + body += _film->log()->head_and_tail (); + body += "---<8----\n\n"; + + add_file (body, "ffprobe.log"); + add_file (body, "metadata.xml"); + + quickmail_set_body (mail, body.c_str()); + + char const* error = quickmail_send (mail, "main.carlh.net", 2525, 0, 0); + + if (error) { + set_state (FINISHED_ERROR); + set_error (error, ""); + } else { + set_state (FINISHED_OK); + } + + quickmail_destroy (mail); + + set_progress (1); +} + +void +SendProblemReportJob::add_file (string& body, boost::filesystem::path file) const +{ + FILE* f = fopen_boost (_film->file (file), "r"); + if (!f) { + return; + } + + body += file.string() + ":\n"; + body += "---<8----\n"; + uintmax_t const size = boost::filesystem::file_size (_film->file (file)); + char* buffer = new char[size + 1]; + int const N = fread (buffer, 1, size, f); + buffer[N] = '\0'; + body += buffer; + delete[] buffer; + body += "---<8----\n\n"; + fclose (f); +} diff --git a/src/lib/send_problem_report_job.h b/src/lib/send_problem_report_job.h new file mode 100644 index 000000000..d77eec544 --- /dev/null +++ b/src/lib/send_problem_report_job.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + 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 <boost/filesystem.hpp> +#include <dcp/types.h> +#include "job.h" + +class SendProblemReportJob : public Job +{ +public: + SendProblemReportJob ( + boost::shared_ptr<const Film>, + std::string from, + std::string summary + ); + + std::string name () const; + void run (); + +private: + void add_file (std::string& body, boost::filesystem::path file) const; + + std::string _from; + std::string _summary; +}; diff --git a/src/lib/util.h b/src/lib/util.h index 724e8937c..93d0587c5 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -46,6 +46,7 @@ extern "C" { #define MAX_DCP_AUDIO_CHANNELS 12 #define DCPOMATIC_HELLO "Boys, you gotta learn not to talk to nuns that way" #define HISTORY_SIZE 10 +#define REPORT_PROBLEM _("Please report this problem by using Help -> Report a problem or via email to carl@dcpomatic.com") class Job; struct AVSubtitle; diff --git a/src/lib/wscript b/src/lib/wscript index d62c22bae..0a6b79207 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -62,6 +62,7 @@ sources = """ player_video.cc playlist.cc position_image.cc + quickmail.cc ratio.cc raw_image_proxy.cc render_subtitles.cc @@ -70,6 +71,7 @@ sources = """ scp_dcp_job.cc scaler.cc send_kdm_email_job.cc + send_problem_report_job.cc server.cc server_finder.cc single_stream_audio_content.cc @@ -106,7 +108,7 @@ def build(bld): AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE SWRESAMPLE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 SNDFILE OPENJPEG POSTPROC TIFF MAGICK SSH DCP CXML GLIB LZMA XML++ - CURL ZIP QUICKMAIL PANGOMM CAIROMM XMLSEC SUB + CURL ZIP PANGOMM CAIROMM XMLSEC SUB """ if bld.env.TARGET_OSX: diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index edd04fd51..3edc24181 100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@ -46,6 +46,7 @@ #include "wx/hints_dialog.h" #include "wx/update_dialog.h" #include "wx/content_panel.h" +#include "wx/report_problem_dialog.h" #include "lib/film.h" #include "lib/config.h" #include "lib/util.h" @@ -127,6 +128,7 @@ enum { ID_tools_hints, ID_tools_encoding_servers, ID_tools_check_for_updates, + ID_help_report_a_problem, /* IDs for shortcuts (with no associated menu item) */ ID_add_file }; @@ -188,6 +190,7 @@ public: Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::tools_encoding_servers, this), ID_tools_encoding_servers); Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::tools_check_for_updates, this), ID_tools_check_for_updates); Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::help_about, this), wxID_ABOUT); + Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::help_report_a_problem, this), ID_help_report_a_problem); Bind (wxEVT_CLOSE_WINDOW, boost::bind (&Frame::close, this, _1)); @@ -501,6 +504,15 @@ private: d->Destroy (); } + void help_report_a_problem () + { + ReportProblemDialog* d = new ReportProblemDialog (this, _film); + if (d->ShowModal () == wxID_OK) { + d->report (); + } + d->Destroy (); + } + bool should_close () { if (!JobManager::instance()->work_to_do ()) { @@ -647,6 +659,7 @@ private: #else add_item (help, _("About"), wxID_ABOUT, ALWAYS); #endif + add_item (help, _("Report a problem..."), ID_help_report_a_problem, ALWAYS); m->Append (_file_menu, _("&File")); #ifndef __WXOSX__ @@ -824,9 +837,8 @@ class App : public wxApp try { throw; } catch (exception& e) { - error_dialog (0, wxString::Format (_("An exception occurred (%s). Please report this problem to the DCP-o-matic author (carl@dcpomatic.com)."), e.what ())); - } catch (...) { - error_dialog (0, _("An unknown exception occurred. Please report this problem to the DCP-o-matic author (carl@dcpomatic.com).")); + error_dialog (0, wxString::Format (_("An exception occurred (%s)."), e.what ()) + " " + REPORT_PROBLEM); } catch (...) { + error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM); } /* This will terminate the program */ @@ -835,7 +847,7 @@ class App : public wxApp void OnUnhandledException () { - error_dialog (0, _("An unknown exception occurred. Please report this problem to the DCP-o-matic author (carl@dcpomatic.com).")); + error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM); } void idle () diff --git a/src/tools/dcpomatic_server.cc b/src/tools/dcpomatic_server.cc index a82478dfd..bcd5c8df6 100644 --- a/src/tools/dcpomatic_server.cc +++ b/src/tools/dcpomatic_server.cc @@ -47,6 +47,14 @@ public: return _log; } + string head_and_tail () const { + if (_log.size () < 2048) { + return _log; + } + + return _log.substr (0, 1024) + _log.substr (_log.size() - 1025, 1024); + } + private: void do_log (string m) { diff --git a/src/tools/wscript b/src/tools/wscript index 3811bf760..c671aadb4 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -11,7 +11,7 @@ def configure(conf): def build(bld): for t in ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm', 'dcpomatic_create']: obj = bld(features = 'cxx cxxprogram') - obj.uselib = 'BOOST_THREAD BOOST_DATETIME OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS QUICKMAIL SUB' + obj.uselib = 'BOOST_THREAD BOOST_DATETIME OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC WXWIDGETS SUB' obj.includes = ['..'] obj.use = ['libdcpomatic2'] obj.source = '%s.cc' % t @@ -22,7 +22,7 @@ def build(bld): if not bld.env.DISABLE_GUI: for t in ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server']: obj = bld(features = 'cxx cxxprogram') - obj.uselib = 'BOOST_THREAD BOOST_DATETIME OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS QUICKMAIL SUB' + obj.uselib = 'BOOST_THREAD BOOST_DATETIME OPENJPEG DCP CXML AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE POSTPROC CXML WXWIDGETS SUB' if bld.env.BUILD_STATIC: obj.uselib += ' GTK' obj.includes = ['..'] diff --git a/src/wx/report_problem_dialog.cc b/src/wx/report_problem_dialog.cc new file mode 100644 index 000000000..78c092921 --- /dev/null +++ b/src/wx/report_problem_dialog.cc @@ -0,0 +1,84 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + 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 "report_problem_dialog.h" +#include "wx_util.h" +#include "lib/config.h" +#include "lib/job_manager.h" +#include "lib/send_problem_report_job.h" +#include <wx/sizer.h> + +using std::string; +using boost::shared_ptr; + +ReportProblemDialog::ReportProblemDialog (wxWindow* parent, shared_ptr<Film> film) + : wxDialog (parent, wxID_ANY, _("Report A Problem")) + , _film (film) +{ + _overall_sizer = new wxBoxSizer (wxVERTICAL); + SetSizer (_overall_sizer); + + _table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); + _table->AddGrowableCol (1, 1); + + _overall_sizer->Add (_table, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER); + + wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL); + if (buttons) { + _overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); + } + + wxString t = _("My problem is"); + int flags = wxALIGN_TOP | wxLEFT | wxRIGHT; +#ifdef __WXOSX__ + if (left) { + flags |= wxALIGN_RIGHT; + t += wxT (":"); + } +#endif + wxStaticText* m = new wxStaticText (this, wxID_ANY, t); + _table->Add (m, 1, flags, 6); + + _summary = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxSize (320, 240), wxTE_MULTILINE); + _table->Add (_summary, 1, wxEXPAND | wxALIGN_TOP); + + _send_logs = new wxCheckBox (this, wxID_ANY, _("Send logs")); + _send_logs->SetValue (true); + _table->Add (_send_logs, 1, wxEXPAND); + _table->AddSpacer (0); + + add_label_to_sizer (_table, this, _("Contact email"), true); + _email = new wxTextCtrl (this, wxID_ANY, wxT ("")); + _email->SetValue (std_to_wx (Config::instance()->kdm_from ())); + _table->Add (_email, 1, wxEXPAND); + + _overall_sizer->Layout (); + _overall_sizer->SetSizeHints (this); +} + +void +ReportProblemDialog::report () +{ + if (_email->GetValue().IsEmpty ()) { + error_dialog (this, _("Please enter an email address so that we can contact you with any queries about the problem.")); + return; + } + + JobManager::instance()->add (shared_ptr<Job> (new SendProblemReportJob (_film, wx_to_std (_email->GetValue ()), wx_to_std (_summary->GetValue ())))); +} diff --git a/src/wx/report_problem_dialog.h b/src/wx/report_problem_dialog.h new file mode 100644 index 000000000..a8da372e7 --- /dev/null +++ b/src/wx/report_problem_dialog.h @@ -0,0 +1,44 @@ +/* + Copyright (C) 2014 Carl Hetherington <cth@carlh.net> + + 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 <wx/dialog.h> +#include <boost/shared_ptr.hpp> + +class wxTextCtrl; +class wxFlexGridSizer; +class wxCheckBox; +class Film; + +class ReportProblemDialog : public wxDialog +{ +public: + ReportProblemDialog (wxWindow* parent, boost::shared_ptr<Film>); + + void report (); + +private: + boost::shared_ptr<Film> _film; + + wxSizer* _overall_sizer; + wxFlexGridSizer* _table; + wxTextCtrl* _summary; + wxCheckBox* _send_logs; + wxTextCtrl* _email; +}; + diff --git a/src/wx/wscript b/src/wx/wscript index 8801ccc75..6a6021c22 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -37,6 +37,7 @@ sources = """ preset_colour_conversion_dialog.cc properties_dialog.cc repeat_dialog.cc + report_problem_dialog.cc screen_dialog.cc server_dialog.cc servers_list_dialog.cc |
