diff --git a/Makefile.am b/Makefile.am index 9dfb400c..ff08149c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,8 @@ core_sources = \ src/command/commands.h src/command/commands.c \ src/tools/parser.c \ src/tools/parser.h \ + src/tools/http_upload.c \ + src/tools/http_upload.h \ src/tools/p_sha1.h src/tools/p_sha1.c \ src/tools/autocomplete.c src/tools/autocomplete.h \ src/tools/tinyurl.c src/tools/tinyurl.h \ @@ -90,6 +92,7 @@ unittest_sources = \ tests/unittests/ui/stub_ui.c \ tests/unittests/log/stub_log.c \ tests/unittests/config/stub_accounts.c \ + tests/unittests/tools/stub_http_upload.c \ tests/unittests/helpers.c tests/unittests/helpers.h \ tests/unittests/test_form.c tests/unittests/test_form.h \ tests/unittests/test_common.c tests/unittests/test_common.h \ diff --git a/configure.ac b/configure.ac index 427c0f89..ae18e0c6 100644 --- a/configure.ac +++ b/configure.ac @@ -116,6 +116,12 @@ else AM_CONDITIONAL([BUILD_C_API], [false]) fi +# threading +ACX_PTHREAD +LIBS="$PTHREAD_LIBS $LIBS" +CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +CC="$PTHREAD_CC" + ### Check for libmesode, fall back to libstrophe PKG_CHECK_MODULES([libmesode], [libmesode], [LIBS="$libmesode_LIBS $LIBS" CFLAGS="$CFLAGS $libmesode_CFLAGS" AC_DEFINE([HAVE_LIBMESODE], [1], [libmesode])], diff --git a/src/command/command.c b/src/command/command.c index 984812f5..1ac87374 100644 --- a/src/command/command.c +++ b/src/command/command.c @@ -42,6 +42,10 @@ #include #include #include +#include + +#include +#include #include @@ -120,6 +124,7 @@ static char* _console_autocomplete(ProfWin *window, const char *const input); static char* _win_autocomplete(ProfWin *window, const char *const input); static char* _close_autocomplete(ProfWin *window, const char *const input); static char* _plugins_autocomplete(ProfWin *window, const char *const input); +static char* _sendfile_autocomplete(ProfWin *window, const char *const input); GHashTable *commands = NULL; @@ -795,6 +800,22 @@ static struct cmd_t command_defs[] = "/disco info myfriend@server.com/laptop") }, + { "/sendfile", + cmd_sendfile, parse_args_with_freetext, 1, 1, NULL, + CMD_TAGS( + CMD_TAG_CHAT, + CMD_TAG_GROUPCHAT) + CMD_SYN( + "/sendfile ") + CMD_DESC( + "Send a file using XEP-0363 HTTP file transfer.") + CMD_ARGS( + { "", "Path to the file." }) + CMD_EXAMPLES( + "/sendfile /etc/hosts", + "/sendfile ~/images/sweet_cat.jpg") + }, + { "/lastactivity", cmd_lastactivity, parse_args, 0, 1, NULL, CMD_TAGS( @@ -2043,6 +2064,7 @@ static Autocomplete console_msg_ac; static Autocomplete autoping_ac; static Autocomplete plugins_ac; static Autocomplete plugins_load_ac; +static Autocomplete sendfile_ac; /* * Initialise command autocompleter and history @@ -2597,6 +2619,8 @@ cmd_init(void) plugins_ac = autocomplete_new(); autocomplete_add(plugins_ac, "load"); + + sendfile_ac = autocomplete_new(); } void @@ -2686,6 +2710,7 @@ cmd_uninit(void) autocomplete_free(autoping_ac); autocomplete_free(plugins_ac); autocomplete_free(plugins_load_ac); + autocomplete_free(sendfile_ac); } gboolean @@ -2834,6 +2859,7 @@ cmd_reset_autocomplete(ProfWin *window) autocomplete_reset(notify_mention_ac); autocomplete_reset(notify_trigger_ac); autocomplete_reset(sub_ac); + autocomplete_reset(sendfile_ac); autocomplete_reset(who_room_ac); autocomplete_reset(who_roster_ac); @@ -3191,6 +3217,7 @@ _cmd_complete_parameters(ProfWin *window, const char *const input) g_hash_table_insert(ac_funcs, "/win", _win_autocomplete); g_hash_table_insert(ac_funcs, "/close", _close_autocomplete); g_hash_table_insert(ac_funcs, "/plugins", _plugins_autocomplete); + g_hash_table_insert(ac_funcs, "/sendfile", _sendfile_autocomplete); int len = strlen(input); char parsed[len+1]; @@ -4589,6 +4616,110 @@ _close_autocomplete(ProfWin *window, const char *const input) return NULL; } +static char* +_sendfile_autocomplete(ProfWin *window, const char *const input) +{ + static char* last_directory = NULL; + + unsigned int output_off = 0; + + char *result = NULL; + char *tmp; + + // strip command + char *inpcp = (char*)input + 9; + while (*inpcp == ' ') { + inpcp++; + } + + inpcp = strdup(inpcp); + + // strip quotes + if (*inpcp == '"') { + tmp = strchr(inpcp+1, '"'); + if (tmp) { + *tmp = '\0'; + } + tmp = strdup(inpcp+1); + free(inpcp); + inpcp = tmp; + } + + // expand ~ to $HOME + if (inpcp[0] == '~' && inpcp[1] == '/') { + if (asprintf(&tmp, "%s/%sfoo", getenv("HOME"), inpcp+2) == -1) { + return NULL; + } + output_off = strlen(getenv("HOME"))+1; + } else { + if (asprintf(&tmp, "%sfoo", inpcp) == -1) { + return NULL; + } + } + free(inpcp); + inpcp = tmp; + + char* inpcp2 = strdup(inpcp); + char* foofile = strdup(basename(inpcp2)); + char* directory = strdup(dirname(inpcp)); + free(inpcp); + free(inpcp2); + + if (!last_directory || strcmp(last_directory, directory) != 0) { + free(last_directory); + last_directory = directory; + autocomplete_reset(sendfile_ac); + + struct dirent *dir; + + DIR *d = opendir(directory); + if (d) { + while ((dir = readdir(d)) != NULL) { + if (strcmp(dir->d_name, ".") == 0) { + continue; + } else if (strcmp(dir->d_name, "..") == 0) { + continue; + } else if (*(dir->d_name) == '.' && *foofile != '.') { + // only show hidden files on explicit request + continue; + } + char * acstring; + if (output_off) { + if (asprintf(&tmp, "%s/%s", directory, dir->d_name) == -1) { + return NULL; + } + if (asprintf(&acstring, "~/%s", tmp+output_off) == -1) { + return NULL; + } + free(tmp); + } else if (strcmp(directory, "/") == 0) { + if (asprintf(&acstring, "/%s", dir->d_name) == -1) { + return NULL; + } + } else { + if (asprintf(&acstring, "%s/%s", directory, dir->d_name) == -1) { + return NULL; + } + } + autocomplete_add(sendfile_ac, acstring); + free(acstring); + } + closedir(d); + } + } else { + free(foofile); + free(directory); + } + + result = autocomplete_param_with_ac(input, "/sendfile", sendfile_ac, TRUE); + if (result) { + return result; + } + + return NULL; +} + + static char* _subject_autocomplete(ProfWin *window, const char *const input) { diff --git a/src/command/commands.c b/src/command/commands.c index 50877227..257b8b5c 100644 --- a/src/command/commands.c +++ b/src/command/commands.c @@ -32,10 +32,13 @@ * */ +#define _GNU_SOURCE 1 + #include "config.h" #include #include +#include #include #include #include @@ -80,6 +83,7 @@ #ifdef HAVE_GTK #include "tray.h" #endif +#include "tools/http_upload.h" static void _update_presence(const resource_presence_t presence, const char *const show, gchar **args); @@ -128,21 +132,21 @@ cmd_execute_default(ProfWin *window, const char *inp) { ProfChatWin *chatwin = (ProfChatWin*)window; assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); - cl_ev_send_msg(chatwin, inp); + cl_ev_send_msg(chatwin, inp, NULL); break; } case WIN_PRIVATE: { ProfPrivateWin *privatewin = (ProfPrivateWin*)window; assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK); - cl_ev_send_priv_msg(privatewin, inp); + cl_ev_send_priv_msg(privatewin, inp, NULL); break; } case WIN_MUC: { ProfMucWin *mucwin = (ProfMucWin*)window; assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); - cl_ev_send_muc_msg(mucwin, inp); + cl_ev_send_muc_msg(mucwin, inp, NULL); break; } case WIN_XML: @@ -1903,7 +1907,7 @@ cmd_msg(ProfWin *window, const char *const command, gchar **args) ui_focus_win((ProfWin*)privwin); if (msg) { - cl_ev_send_priv_msg(privwin, msg); + cl_ev_send_priv_msg(privwin, msg, NULL); } g_string_free(full_jid, TRUE); @@ -1928,7 +1932,7 @@ cmd_msg(ProfWin *window, const char *const command, gchar **args) ui_focus_win((ProfWin*)chatwin); if (msg) { - cl_ev_send_msg(chatwin, msg); + cl_ev_send_msg(chatwin, msg, NULL); } else { #ifdef HAVE_LIBOTR if (otr_is_secure(barejid)) { @@ -4224,6 +4228,57 @@ cmd_disco(ProfWin *window, const char *const command, gchar **args) return TRUE; } +gboolean +cmd_sendfile(ProfWin *window, const char *const command, gchar **args) +{ + jabber_conn_status_t conn_status = jabber_get_connection_status(); + char *filename = args[0]; + + // expand ~ to $HOME + if (filename[0] == '~' && filename[1] == '/') { + if (asprintf(&filename, "%s/%s", getenv("HOME"), filename+2) == -1) { + return TRUE; + } + } else { + filename = strdup(filename); + } + + if (conn_status != JABBER_CONNECTED) { + cons_show("You are not currently connected."); + free(filename); + return TRUE; + } + + if (window->type != WIN_CHAT && window->type != WIN_PRIVATE && window->type != WIN_MUC) { + cons_show_error("Unsupported window for file transmission."); + free(filename); + return TRUE; + } + + if (access(filename, R_OK) != 0) { + cons_show_error("Uploading '%s' failed: File not found!", filename); + free(filename); + return TRUE; + } + + if (!is_regular_file(filename)) { + cons_show_error("Uploading '%s' failed: Not a file!", filename); + free(filename); + return TRUE; + } + + HTTPUpload *upload = malloc(sizeof(HTTPUpload)); + upload->window = window; + + upload->filename = filename; + upload->filesize = file_size(filename); + upload->mime_type = file_mime_type(filename); + + iq_http_upload_request(upload); + + return TRUE; +} + gboolean cmd_lastactivity(ProfWin *window, const char *const command, gchar **args) { @@ -4375,21 +4430,21 @@ cmd_tiny(ProfWin *window, const char *const command, gchar **args) { ProfChatWin *chatwin = (ProfChatWin*)window; assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); - cl_ev_send_msg(chatwin, tiny); + cl_ev_send_msg(chatwin, tiny, NULL); break; } case WIN_PRIVATE: { ProfPrivateWin *privatewin = (ProfPrivateWin*)window; assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK); - cl_ev_send_priv_msg(privatewin, tiny); + cl_ev_send_priv_msg(privatewin, tiny, NULL); break; } case WIN_MUC: { ProfMucWin *mucwin = (ProfMucWin*)window; assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); - cl_ev_send_muc_msg(mucwin, tiny); + cl_ev_send_muc_msg(mucwin, tiny, NULL); break; } default: diff --git a/src/command/commands.h b/src/command/commands.h index cfa9d966..da958bea 100644 --- a/src/command/commands.h +++ b/src/command/commands.h @@ -85,6 +85,7 @@ gboolean cmd_connect(ProfWin *window, const char *const command, gchar **args); gboolean cmd_tls(ProfWin *window, const char *const command, gchar **args); gboolean cmd_decline(ProfWin *window, const char *const command, gchar **args); gboolean cmd_disco(ProfWin *window, const char *const command, gchar **args); +gboolean cmd_sendfile(ProfWin *window, const char *const command, gchar **args); gboolean cmd_lastactivity(ProfWin *window, const char *const command, gchar **args); gboolean cmd_disconnect(ProfWin *window, const char *const command, gchar **args); gboolean cmd_dnd(ProfWin *window, const char *const command, gchar **args); diff --git a/src/event/client_events.c b/src/event/client_events.c index 20a1a861..1c715f03 100644 --- a/src/event/client_events.c +++ b/src/event/client_events.c @@ -106,7 +106,7 @@ cl_ev_presence_send(const resource_presence_t presence_type, const char *const m } void -cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg) +cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oob_url) { chat_state_active(chatwin->state); char *plugin_msg = plugins_pre_chat_message_send(chatwin->barejid, msg); @@ -122,7 +122,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg) } else { gboolean handled = otr_on_message_send(chatwin, plugin_msg); if (!handled) { - char *id = message_send_chat(chatwin->barejid, plugin_msg); + char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url); chat_log_msg_out(chatwin->barejid, plugin_msg); chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN); free(id); @@ -140,7 +140,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg) #ifndef HAVE_LIBGPGME gboolean handled = otr_on_message_send(chatwin, plugin_msg); if (!handled) { - char *id = message_send_chat(chatwin->barejid, plugin_msg); + char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url); chat_log_msg_out(chatwin->barejid, plugin_msg); chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN); free(id); @@ -161,7 +161,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg) chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PGP); free(id); } else { - char *id = message_send_chat(chatwin->barejid, plugin_msg); + char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url); chat_log_msg_out(chatwin->barejid, plugin_msg); chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN); free(id); @@ -176,7 +176,7 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg) // OTR unsupported, PGP unsupported #ifndef HAVE_LIBOTR #ifndef HAVE_LIBGPGME - char *id = message_send_chat(chatwin->barejid, plugin_msg); + char *id = message_send_chat(chatwin->barejid, plugin_msg, oob_url); chat_log_msg_out(chatwin->barejid, plugin_msg); chatwin_outgoing_msg(chatwin, plugin_msg, id, PROF_MSG_PLAIN); free(id); @@ -189,18 +189,18 @@ cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg) } void -cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg) +cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const oob_url) { char *plugin_msg = plugins_pre_room_message_send(mucwin->roomjid, msg); - message_send_groupchat(mucwin->roomjid, plugin_msg); + message_send_groupchat(mucwin->roomjid, plugin_msg, oob_url); plugins_post_room_message_send(mucwin->roomjid, plugin_msg); free(plugin_msg); } void -cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg) +cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg, const char *const oob_url) { if (privwin->occupant_offline) { privwin_message_occupant_offline(privwin); @@ -209,7 +209,7 @@ cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg) } else { char *plugin_msg = plugins_pre_priv_message_send(privwin->fulljid, msg); - message_send_private(privwin->fulljid, plugin_msg); + message_send_private(privwin->fulljid, plugin_msg, oob_url); privwin_outgoing_msg(privwin, plugin_msg); plugins_post_priv_message_send(privwin->fulljid, plugin_msg); diff --git a/src/event/client_events.h b/src/event/client_events.h index 7b7ec13a..3231ade9 100644 --- a/src/event/client_events.h +++ b/src/event/client_events.h @@ -42,8 +42,8 @@ void cl_ev_disconnect(void); void cl_ev_presence_send(const resource_presence_t presence_type, const char *const msg, const int idle_secs); -void cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg); -void cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg); -void cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg); +void cl_ev_send_msg(ProfChatWin *chatwin, const char *const msg, const char *const oob_url); +void cl_ev_send_muc_msg(ProfMucWin *mucwin, const char *const msg, const char *const oob_url); +void cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char *const msg, const char *const oob_url); #endif diff --git a/src/profanity.c b/src/profanity.c index 1140594f..9e27b6e8 100644 --- a/src/profanity.c +++ b/src/profanity.c @@ -323,6 +323,11 @@ _init(char *log_level) signal(SIGINT, SIG_IGN); signal(SIGTSTP, SIG_IGN); signal(SIGWINCH, ui_sigwinch_handler); + if (pthread_mutex_init(&lock, NULL) != 0) { + log_error("Mutex init failed"); + exit(1); + } + pthread_mutex_lock(&lock); _create_directories(); log_level_t prof_log_level = log_level_from_string(log_level); prefs_load(); diff --git a/src/profanity.h b/src/profanity.h index 7e128dc8..28a71467 100644 --- a/src/profanity.h +++ b/src/profanity.h @@ -35,6 +35,8 @@ #ifndef PROFANITY_H #define PROFANITY_H +#include + #include "resource.h" #include "xmpp/xmpp.h" @@ -46,4 +48,6 @@ gboolean prof_process_input(char *inp); void prof_set_quit(void); +pthread_mutex_t lock; + #endif diff --git a/src/tools/http_upload.c b/src/tools/http_upload.c new file mode 100644 index 00000000..8a1b544d --- /dev/null +++ b/src/tools/http_upload.c @@ -0,0 +1,337 @@ +/* + * http_upload.c + * + * Copyright (C) 2012 - 2016 James Booth + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see . + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "profanity.h" +#include "ui/ui.h" +#include "ui/window.h" +#include "tools/http_upload.h" +#include "event/client_events.h" +#include "config/preferences.h" + +#define FALLBACK_MIMETYPE "application/octet-stream" +#define FALLBACK_MSG "" +#define FILE_HEADER_BYTES 512 + +struct curl_data_t { + char *buffer; + size_t size; +}; + + +static int +_xferinfo(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +{ + HTTPUpload *upload = (HTTPUpload *)userdata; + + pthread_mutex_lock(&lock); + + if (upload->cancel) { + pthread_mutex_unlock(&lock); + return 1; + } + + if (upload->bytes_sent == ulnow) { + pthread_mutex_unlock(&lock); + return 0; + } else { + upload->bytes_sent = ulnow; + } + + unsigned int ulperc = 0; + if (ultotal != 0) { + ulperc = (100 * ulnow) / ultotal; + } + + char *msg; + if (asprintf(&msg, "Uploading '%s': %d%%", upload->filename, ulperc) == -1) { + msg = FALLBACK_MSG; + } + win_update_entry_message(upload->window, upload->put_url, msg); + free(msg); + + pthread_mutex_unlock(&lock); + + return 0; +} + +#if LIBCURL_VERSION_NUM < 0x072000 +static int +_older_progress(void *p, double dltotal, double dlnow, double ultotal, double ulnow) +{ + return _xferinfo(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow); +} +#endif + +static size_t +_data_callback(void *ptr, size_t size, size_t nmemb, void *data) +{ + size_t realsize = size * nmemb; + struct curl_data_t *mem = (struct curl_data_t *) data; + mem->buffer = realloc(mem->buffer, mem->size + realsize + 1); + + if (mem->buffer) + { + memcpy( &( mem->buffer[ mem->size ] ), ptr, realsize ); + mem->size += realsize; + mem->buffer[ mem->size ] = 0; + } + + return realsize; +} + +void * +http_file_put(void *userdata) +{ + HTTPUpload *upload = (HTTPUpload *)userdata; + + FILE *fd = NULL; + + char *err = NULL; + char *content_type_header; + + CURL *curl; + CURLcode res; + + upload->cancel = 0; + upload->bytes_sent = 0; + + pthread_mutex_lock(&lock); + char* msg; + if (asprintf(&msg, "Uploading '%s': 0%%", upload->filename) == -1) { + msg = FALLBACK_MSG; + } + win_print_with_receipt(upload->window, '!', 0, NULL, 0, THEME_TEXT_ME, NULL, msg, upload->put_url); + free(msg); + + char *cert_path = prefs_get_string(PREF_TLS_CERTPATH); + pthread_mutex_unlock(&lock); + + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_URL, upload->put_url); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + + struct curl_slist *headers = NULL; + if (asprintf(&content_type_header, "Content-Type: %s", upload->mime_type) == -1) { + msg = FALLBACK_MSG; + } + headers = curl_slist_append(headers, content_type_header); + headers = curl_slist_append(headers, "Expect:"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + #if LIBCURL_VERSION_NUM >= 0x072000 + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, _xferinfo); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, upload); + #else + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, _older_progress); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, upload); + #endif + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + + struct curl_data_t output; + output.buffer = NULL; + output.size = 0; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _data_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&output); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity"); + + if (!(fd = fopen(upload->filename, "rb"))) { + if (asprintf(&err, "failed to open '%s'", upload->filename) == -1) { + err = NULL; + } + goto end; + } + + if (cert_path) { + curl_easy_setopt(curl, CURLOPT_CAPATH, cert_path); + } + + curl_easy_setopt(curl, CURLOPT_READDATA, fd); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)(upload->filesize)); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + + if ((res = curl_easy_perform(curl)) != CURLE_OK) { + err = strdup(curl_easy_strerror(res)); + } else { + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (output.buffer) { + output.buffer[output.size++] = '\0'; + } + + // XEP-0363 specifies 201 but prosody returns 200 + if (http_code != 200 && http_code != 201) { + if (asprintf(&err, "Server returned %lu", http_code) == -1) { + err = NULL; + } + } + + #if 0 + printf("HTTP Status: %lu\n", http_code); + printf("%s\n", output.buffer); + printf("%lu bytes retrieved\n", (long)output.size); + #endif + } + +end: + curl_easy_cleanup(curl); + curl_global_cleanup(); + curl_slist_free_all(headers); + if (fd) { + fclose(fd); + } + free(content_type_header); + free(output.buffer); + + pthread_mutex_lock(&lock); + prefs_free_string(cert_path); + + if (err) { + char *msg; + if (upload->cancel) { + if (asprintf(&msg, "Uploading '%s' failed: Upload was canceled", upload->filename) == -1) { + msg = FALLBACK_MSG; + } + } else { + if (asprintf(&msg, "Uploading '%s' failed: %s", upload->filename, err) == -1) { + msg = FALLBACK_MSG; + } + win_update_entry_message(upload->window, upload->put_url, msg); + } + cons_show_error(msg); + free(msg); + free(err); + } else { + if (!upload->cancel) { + if (asprintf(&msg, "Uploading '%s': 100%%", upload->filename) == -1) { + msg = FALLBACK_MSG; + } + win_update_entry_message(upload->window, upload->put_url, msg); + //win_update_entry_theme(upload->window, upload->put_url, THEME_THEM); + win_mark_received(upload->window, upload->put_url); + free(msg); + + switch (upload->window->type) { + case WIN_CHAT: + { + ProfChatWin *chatwin = (ProfChatWin*)(upload->window); + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + cl_ev_send_msg(chatwin, upload->get_url, upload->get_url); + break; + } + case WIN_PRIVATE: + { + ProfPrivateWin *privatewin = (ProfPrivateWin*)(upload->window); + assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK); + cl_ev_send_priv_msg(privatewin, upload->get_url, upload->get_url); + break; + } + case WIN_MUC: + { + ProfMucWin *mucwin = (ProfMucWin*)(upload->window); + assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + cl_ev_send_muc_msg(mucwin, upload->get_url, upload->get_url); + break; + } + default: + break; + } + } + } + + upload_processes = g_slist_remove(upload_processes, upload); + pthread_mutex_unlock(&lock); + + free(upload->filename); + free(upload->mime_type); + free(upload->get_url); + free(upload->put_url); + free(upload); + + return NULL; +} + +char* +file_mime_type(const char* const file_name) +{ + char *out_mime_type; + char file_header[FILE_HEADER_BYTES]; + FILE *fd; + if (!(fd = fopen(file_name, "rb"))) { + return strdup(FALLBACK_MIMETYPE); + } + size_t file_header_size = fread(file_header, 1, FILE_HEADER_BYTES, fd); + fclose(fd); + + char *content_type = g_content_type_guess(file_name, (unsigned char*)file_header, file_header_size, NULL); + if (content_type != NULL) { + char *mime_type = g_content_type_get_mime_type(content_type); + out_mime_type = strdup(mime_type); + g_free(mime_type); + } + else { + return strdup(FALLBACK_MIMETYPE); + } + g_free(content_type); + return out_mime_type; +} + +off_t file_size(const char* const filename) +{ + struct stat st; + stat(filename, &st); + return st.st_size; +} + +int is_regular_file(const char *filename) +{ + struct stat st; + stat(filename, &st); + return S_ISREG(st.st_mode); +} diff --git a/src/tools/http_upload.h b/src/tools/http_upload.h new file mode 100644 index 00000000..7bb539cb --- /dev/null +++ b/src/tools/http_upload.h @@ -0,0 +1,63 @@ +/* + * http_upload.h + * + * Copyright (C) 2012 - 2016 James Booth + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see . + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#ifndef TOOLS_HTTP_UPLOAD_H +#define TOOLS_HTTP_UPLOAD_H + +#include + +// forward -> ui/win_types.h +typedef struct prof_win_t ProfWin; + +typedef struct http_upload_t { + char *filename; + off_t filesize; + curl_off_t bytes_sent; + char *mime_type; + char *get_url; + char *put_url; + ProfWin *window; + pthread_t worker; + int cancel; +} HTTPUpload; + +GSList *upload_processes; + +void* http_file_put(void *userdata); + +char* file_mime_type(const char* const file_name); +off_t file_size(const char* const file_name); +int is_regular_file(const char *filename); + +#endif diff --git a/src/ui/buffer.c b/src/ui/buffer.c index 29eddd89..a86e85ae 100644 --- a/src/ui/buffer.c +++ b/src/ui/buffer.c @@ -125,6 +125,21 @@ buffer_yield_entry(ProfBuff buffer, int entry) return node->data; } +ProfBuffEntry* +buffer_yield_entry_by_id(ProfBuff buffer, const char *const id) +{ + GSList *entries = buffer->entries; + while (entries) { + ProfBuffEntry *entry = entries->data; + if (entry->receipt && g_strcmp0(entry->receipt->id, id) == 0) { + return entry; + } + entries = g_slist_next(entries); + } + + return NULL; +} + static void _free_entry(ProfBuffEntry *entry) { diff --git a/src/ui/buffer.h b/src/ui/buffer.h index ce9763ed..50ce0634 100644 --- a/src/ui/buffer.h +++ b/src/ui/buffer.h @@ -64,6 +64,7 @@ void buffer_push(ProfBuff buffer, const char show_char, int pad_indent, GDateTim const char *const from, const char *const message, DeliveryReceipt *receipt); int buffer_size(ProfBuff buffer); ProfBuffEntry* buffer_yield_entry(ProfBuff buffer, int entry); +ProfBuffEntry* buffer_yield_entry_by_id(ProfBuff buffer, const char *const id); gboolean buffer_mark_received(ProfBuff buffer, const char *const id); #endif diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c index fc1c204d..c94c0a8c 100644 --- a/src/ui/inputwin.c +++ b/src/ui/inputwin.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -145,7 +146,9 @@ inp_readline(void) FD_ZERO(&fds); FD_SET(fileno(rl_instream), &fds); errno = 0; + pthread_mutex_unlock(&lock); r = select(FD_SETSIZE, &fds, NULL, NULL, &p_rl_timeout); + pthread_mutex_lock(&lock); if (r < 0) { if (errno != EINTR) { char *err_msg = strerror(errno); diff --git a/src/ui/window.c b/src/ui/window.c index 1d95d4cb..3a96ca08 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1054,6 +1054,27 @@ win_mark_received(ProfWin *window, const char *const id) } } +void +win_update_entry_message(ProfWin *window, const char *const id, const char *const message) +{ + ProfBuffEntry *entry = buffer_yield_entry_by_id(window->layout->buffer, id); + if (entry) { + free(entry->message); + entry->message = strdup(message); + win_redraw(window); + } +} + +void +win_update_entry_theme(ProfWin *window, const char *const id, theme_item_t theme_item) +{ + ProfBuffEntry *entry = buffer_yield_entry_by_id(window->layout->buffer, id); + if (entry) { + entry->theme_item = theme_item; + win_redraw(window); + } +} + void win_println(ProfWin *window, int pad, const char *const message) { diff --git a/src/ui/window.h b/src/ui/window.h index 4923f4ec..f1f740c5 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -70,6 +70,8 @@ int win_occpuants_cols(void); void win_sub_print(WINDOW *win, char *msg, gboolean newline, gboolean wrap, int indent); void win_sub_newline_lazy(WINDOW *win); void win_mark_received(ProfWin *window, const char *const id); +void win_update_entry_message(ProfWin *window, const char *const id, const char *const message); +void win_update_entry_theme(ProfWin *window, const char *const id, theme_item_t theme_item); gboolean win_has_active_subwin(ProfWin *window); diff --git a/src/window_list.c b/src/window_list.c index 7fcc0172..6b9f2411 100644 --- a/src/window_list.c +++ b/src/window_list.c @@ -508,6 +508,17 @@ wins_close_by_num(int i) ProfWin *window = wins_get_by_num(i); if (window) { + // cancel upload proccesses of this window + GSList *upload_process = upload_processes; + while (upload_process) { + HTTPUpload *upload = upload_process->data; + if (upload->window == window) { + upload->cancel = 1; + break; + } + upload_process = g_slist_next(upload_process); + } + switch (window->type) { case WIN_CHAT: { diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index a2566945..ef1a45b5 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -64,6 +64,7 @@ #include "roster_list.h" #include "xmpp/xmpp.h" #include "plugins/plugins.h" +#include "tools/http_upload.h" typedef struct p_room_info_data_t { char *room; @@ -88,6 +89,7 @@ static void _ping_get_handler(xmpp_stanza_t *const stanza); static int _version_result_id_handler(xmpp_stanza_t *const stanza, void *const userdata); static int _disco_info_response_id_handler(xmpp_stanza_t *const stanza, void *const userdata); static int _disco_info_response_id_handler_onconnect(xmpp_stanza_t *const stanza, void *const userdata); +static int _http_upload_response_id_handler(xmpp_stanza_t *const stanza, void *const upload_ctx); static int _last_activity_response_id_handler(xmpp_stanza_t *const stanza, void *const userdata); static int _room_info_response_id_handler(xmpp_stanza_t *const stanza, void *const userdata); static int _destroy_room_result_id_handler(xmpp_stanza_t *const stanza, void *const userdata); @@ -293,6 +295,42 @@ iq_disable_carbons(void) xmpp_stanza_release(iq); } +void +iq_http_upload_request(HTTPUpload *upload) +{ + GSList *disco_items = jabber_get_disco_items(); + DiscoInfo *disco_info; + if (disco_items && (g_slist_length(disco_items) > 0)) { + while (disco_items) { + disco_info = disco_items->data; + if (g_hash_table_lookup_extended(disco_info->features, STANZA_NS_HTTP_UPLOAD, NULL, NULL)) { + break; + } + disco_items = g_slist_next(disco_items); + if (!disco_items) { + cons_show_error("XEP-0363 HTTP File Upload is not supported by the server"); + return; + } + } + } else { + cons_show_error("No disco items"); + return; + } + + xmpp_ctx_t * const ctx = connection_get_ctx(); + char *id = create_unique_id("http_upload_request"); + + xmpp_stanza_t *iq = stanza_create_http_upload_request(ctx, id, disco_info->item, upload); + + id_handler_add(id, _http_upload_response_id_handler, upload); + + free(id); + + send_iq_stanza(iq); + xmpp_stanza_release(iq); + return; +} + void iq_disco_info_request(gchar *jid) { @@ -1904,6 +1942,60 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t *const stanza, void *con return 0; } +static int +_http_upload_response_id_handler(xmpp_stanza_t *const stanza, void *const userdata) +{ + HTTPUpload *upload = (HTTPUpload *)userdata; + const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + const char *type = xmpp_stanza_get_type(stanza); + + if (from) { + log_info("Received http_upload response from: %s", from); + } else { + log_info("Received http_upload response"); + } + + // handle error responses + if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) { + char *error_message = stanza_get_error_message(stanza); + if (from) { + cons_show_error("Uploading '%s' failed for %s: %s", upload->filename, from, error_message); + } else { + cons_show_error("Uploading '%s' failed: %s", upload->filename, error_message); + } + free(error_message); + return 0; + } + + xmpp_stanza_t *slot = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SLOT); + + if (slot && g_strcmp0(xmpp_stanza_get_ns(slot), STANZA_NS_HTTP_UPLOAD) == 0) { + xmpp_stanza_t *put = xmpp_stanza_get_child_by_name(slot, STANZA_NAME_PUT); + xmpp_stanza_t *get = xmpp_stanza_get_child_by_name(slot, STANZA_NAME_GET); + + if (put && get) { + char *put_url = xmpp_stanza_get_text(put); + char *get_url = xmpp_stanza_get_text(get); + + upload->put_url = strdup(put_url); + upload->get_url = strdup(get_url); + + xmpp_conn_t *conn = connection_get_conn(); + xmpp_ctx_t *ctx = xmpp_conn_get_context(conn); + if (put_url) xmpp_free(ctx, put_url); + if (get_url) xmpp_free(ctx, get_url); + + pthread_create(&(upload->worker), NULL, &http_file_put, upload); + upload_processes = g_slist_append(upload_processes, upload); + } else { + log_error("Invalid XML in HTTP Upload slot"); + return 1; + } + } + + return 0; +} + static void _disco_items_result_handler(xmpp_stanza_t *const stanza) { diff --git a/src/xmpp/message.c b/src/xmpp/message.c index bdcceb5e..0d06408b 100644 --- a/src/xmpp/message.c +++ b/src/xmpp/message.c @@ -163,7 +163,7 @@ _session_state(const char *const barejid) } char* -message_send_chat(const char *const barejid, const char *const msg) +message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url) { xmpp_ctx_t * const ctx = connection_get_ctx(); @@ -178,6 +178,10 @@ message_send_chat(const char *const barejid, const char *const msg) stanza_attach_state(ctx, message, state); } + if (oob_url) { + stanza_attach_x_oob_url(ctx, message, oob_url); + } + if (prefs_get_boolean(PREF_RECEIPTS_REQUEST)) { stanza_attach_receipt_request(ctx, message); } @@ -274,25 +278,33 @@ message_send_chat_otr(const char *const barejid, const char *const msg) } void -message_send_private(const char *const fulljid, const char *const msg) +message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *id = create_unique_id("prv"); xmpp_stanza_t *message = stanza_create_message(ctx, id, fulljid, STANZA_TYPE_CHAT, msg); free(id); + if (oob_url) { + stanza_attach_x_oob_url(ctx, message, oob_url); + } + _send_message_stanza(message); xmpp_stanza_release(message); } void -message_send_groupchat(const char *const roomjid, const char *const msg) +message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url) { xmpp_ctx_t * const ctx = connection_get_ctx(); char *id = create_unique_id("muc"); xmpp_stanza_t *message = stanza_create_message(ctx, id, roomjid, STANZA_TYPE_GROUPCHAT, msg); free(id); + if (oob_url) { + stanza_attach_x_oob_url(ctx, message, oob_url); + } + _send_message_stanza(message); xmpp_stanza_release(message); } diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c index 618c455f..69f0b174 100644 --- a/src/xmpp/stanza.c +++ b/src/xmpp/stanza.c @@ -32,10 +32,15 @@ * */ +#define _GNU_SOURCE 1 + #include "config.h" #include #include +#include +#include +#include #include @@ -206,6 +211,61 @@ stanza_create_bookmarks_pubsub_add(xmpp_ctx_t *ctx, const char *const jid, } #endif +xmpp_stanza_t* +stanza_create_http_upload_request(xmpp_ctx_t *ctx, const char *const id, + const char *const jid, HTTPUpload *upload) +{ + int i; + + xmpp_stanza_t *iq = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(iq, STANZA_NAME_IQ); + xmpp_stanza_set_type(iq, STANZA_TYPE_GET); + xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, jid); + xmpp_stanza_set_id(iq, id); + + xmpp_stanza_t *request = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(request, STANZA_NAME_REQUEST); + xmpp_stanza_set_ns(request, STANZA_NS_HTTP_UPLOAD); + + xmpp_stanza_t *filename = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(filename, STANZA_NAME_FILENAME); + xmpp_stanza_t *filename_txt = xmpp_stanza_new(ctx); + char* filename_cpy = strdup(upload->filename); + // strip spaces from filename (servers don't spaces) + for (i=0; ifilesize)) != -1) { + xmpp_stanza_set_text(size_txt, filesize); + free(filesize); + } + xmpp_stanza_add_child(size, size_txt); + xmpp_stanza_add_child(request, size); + + xmpp_stanza_t *content_type = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(content_type, STANZA_NAME_CONTENT_TYPE); + xmpp_stanza_t *content_type_txt = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(content_type_txt, upload->mime_type); + xmpp_stanza_add_child(content_type, content_type_txt); + xmpp_stanza_add_child(request, content_type); + + xmpp_stanza_add_child(iq, request); + xmpp_stanza_release(request); + + return iq; +} + xmpp_stanza_t* stanza_enable_carbons(xmpp_ctx_t *ctx) { @@ -353,6 +413,30 @@ stanza_attach_receipt_request(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza) return stanza; } +xmpp_stanza_t* +stanza_attach_x_oob_url(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const url) +{ + xmpp_stanza_t *x_oob = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(x_oob, STANZA_NAME_X); + xmpp_stanza_set_ns(x_oob, STANZA_NS_X_OOB); + + xmpp_stanza_t *surl = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(surl, STANZA_NAME_URL); + + xmpp_stanza_t *surl_txt = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(surl_txt, url); + xmpp_stanza_add_child(surl, surl_txt); + xmpp_stanza_release(surl_txt); + + xmpp_stanza_add_child(x_oob, surl); + xmpp_stanza_release(surl); + + xmpp_stanza_add_child(stanza, x_oob); + xmpp_stanza_release(x_oob); + + return stanza; +} + xmpp_stanza_t* stanza_create_message(xmpp_ctx_t *ctx, char *id, const char *const recipient, const char *const type, const char *const message) diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h index b4a51580..83f69405 100644 --- a/src/xmpp/stanza.h +++ b/src/xmpp/stanza.h @@ -61,6 +61,7 @@ #define STANZA_NAME_STATUS "status" #define STANZA_NAME_IQ "iq" #define STANZA_NAME_QUERY "query" +#define STANZA_NAME_REQUEST "request" #define STANZA_NAME_DELAY "delay" #define STANZA_NAME_ERROR "error" #define STANZA_NAME_PING "ping" @@ -87,6 +88,13 @@ #define STANZA_NAME_ACTOR "actor" #define STANZA_NAME_ENABLE "enable" #define STANZA_NAME_DISABLE "disable" +#define STANZA_NAME_FILENAME "filename" +#define STANZA_NAME_SIZE "size" +#define STANZA_NAME_CONTENT_TYPE "content-type" +#define STANZA_NAME_SLOT "slot" +#define STANZA_NAME_PUT "put" +#define STANZA_NAME_GET "get" +#define STANZA_NAME_URL "url" // error conditions #define STANZA_NAME_BAD_REQUEST "bad-request" @@ -171,6 +179,8 @@ #define STANZA_NS_RECEIPTS "urn:xmpp:receipts" #define STANZA_NS_SIGNED "jabber:x:signed" #define STANZA_NS_ENCRYPTED "jabber:x:encrypted" +#define STANZA_NS_HTTP_UPLOAD "urn:xmpp:http:upload" +#define STANZA_NS_X_OOB "jabber:x:oob" #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo" @@ -195,6 +205,8 @@ typedef enum { xmpp_stanza_t* stanza_create_bookmarks_storage_request(xmpp_ctx_t *ctx); +xmpp_stanza_t* stanza_create_http_upload_request(xmpp_ctx_t *ctx, const char *const id, const char *const jid, HTTPUpload *upload); + xmpp_stanza_t* stanza_enable_carbons(xmpp_ctx_t *ctx); xmpp_stanza_t* stanza_disable_carbons(xmpp_ctx_t *ctx); @@ -207,6 +219,7 @@ xmpp_stanza_t* stanza_attach_carbons_private(xmpp_ctx_t *ctx, xmpp_stanza_t *sta xmpp_stanza_t* stanza_attach_hints_no_copy(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza); xmpp_stanza_t* stanza_attach_hints_no_store(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza); xmpp_stanza_t* stanza_attach_receipt_request(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza); +xmpp_stanza_t* stanza_attach_x_oob_url(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const url); xmpp_stanza_t* stanza_create_message(xmpp_ctx_t *ctx, char *id, const char *const recipient, const char *const type, const char *const message); diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h index 28cb755b..b9d84074 100644 --- a/src/xmpp/xmpp.h +++ b/src/xmpp/xmpp.h @@ -49,6 +49,7 @@ #include "contact.h" #include "jid.h" #include "tools/autocomplete.h" +#include "tools/http_upload.h" #define JABBER_PRIORITY_MIN -128 #define JABBER_PRIORITY_MAX 127 @@ -169,11 +170,11 @@ gboolean jabber_conn_is_secured(void); gboolean jabber_send_stanza(const char *const stanza); // message functions -char* message_send_chat(const char *const barejid, const char *const msg); +char* message_send_chat(const char *const barejid, const char *const msg, const char *const oob_url); char* message_send_chat_otr(const char *const barejid, const char *const msg); char* message_send_chat_pgp(const char *const barejid, const char *const msg); -void message_send_private(const char *const fulljid, const char *const msg); -void message_send_groupchat(const char *const roomjid, const char *const msg); +void message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url); +void message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url); void message_send_groupchat_subject(const char *const roomjid, const char *const subject); void message_send_inactive(const char *const jid); @@ -225,6 +226,7 @@ void iq_room_kick_occupant(const char *const room, const char *const nick, const void iq_room_role_set(const char *const room, const char *const nick, char *role, const char *const reason); void iq_room_role_list(const char * const room, char *role); void iq_autoping_check(void); +void iq_http_upload_request(HTTPUpload *upload); // caps functions Capabilities* caps_lookup(const char *const jid); diff --git a/tests/unittests/tools/stub_http_upload.c b/tests/unittests/tools/stub_http_upload.c new file mode 100644 index 00000000..cb7688cf --- /dev/null +++ b/tests/unittests/tools/stub_http_upload.c @@ -0,0 +1,63 @@ +/* + * http_upload.h + * + * Copyright (C) 2012 - 2016 James Booth + * + * This file is part of Profanity. + * + * Profanity 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. + * + * Profanity 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 Profanity. If not, see . + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#ifndef TOOLS_HTTP_UPLOAD_H +#define TOOLS_HTTP_UPLOAD_H + +#include + +// forward -> ui/win_types.h +typedef struct prof_win_t ProfWin; + +typedef struct http_upload_t { + char *filename; + off_t filesize; + curl_off_t bytes_sent; + char *mime_type; + char *get_url; + char *put_url; + ProfWin *window; + pthread_t worker; + int cancel; +} HTTPUpload; + +//GSList *upload_processes; + +void* http_file_put(void *userdata) {} + +char* file_mime_type(const char* const file_name) {} +off_t file_size(const char* const file_name) {} +int is_regular_file(const char *filename) {} + +#endif diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c index 3ba7f2b3..6437de86 100644 --- a/tests/unittests/xmpp/stub_xmpp.c +++ b/tests/unittests/xmpp/stub_xmpp.c @@ -71,7 +71,7 @@ jabber_send_stanza(const char *const stanza) } // message functions -char* message_send_chat(const char * const barejid, const char * const msg) +char* message_send_chat(const char * const barejid, const char * const msg, const char *const oob_url) { check_expected(barejid); check_expected(msg); @@ -90,8 +90,8 @@ char* message_send_chat_pgp(const char * const barejid, const char * const msg) return NULL; } -void message_send_private(const char * const fulljid, const char * const msg) {} -void message_send_groupchat(const char * const roomjid, const char * const msg) {} +void message_send_private(const char * const fulljid, const char * const msg, const char *const oob_url) {} +void message_send_groupchat(const char * const roomjid, const char * const msg, const char *const oob_url) {} void message_send_groupchat_subject(const char * const roomjid, const char * const subject) {} void message_send_inactive(const char * const barejid) {} @@ -158,6 +158,7 @@ void iq_room_list_request(gchar *conferencejid) void iq_disco_info_request(gchar *jid) {} void iq_disco_items_request(gchar *jid) {} void iq_set_autoping(int seconds) {} +void iq_http_upload_request(HTTPUpload *upload) {} void iq_confirm_instant_room(const char * const room_jid) {} void iq_destroy_room(const char * const room_jid) {} void iq_request_room_config_form(const char * const room_jid) {}