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..0057c3e5 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])], @@ -282,10 +288,12 @@ AC_CHECK_HEADERS([ncurses.h], [], []) AM_CFLAGS="-Wall -Wno-deprecated-declarations" AS_IF([test "x$PACKAGE_STATUS" = xdevelopment], [AM_CFLAGS="$AM_CFLAGS -Wunused -Werror"]) +AS_IF([test "x$PLATFORM" = xosx], + [AM_CFLAGS="$AM_CFLAGS -Qunused-arguments"]) AM_LDFLAGS="$AM_LDFLAGS -export-dynamic" AM_CPPFLAGS="$AM_CPPFLAGS $glib_CFLAGS $curl_CFLAGS $libnotify_CFLAGS $PYTHON_CPPFLAGS ${GTK_CFLAGS}" AM_CPPFLAGS="$AM_CPPFLAGS -DTHEMES_PATH=\"\\\"$THEMES_PATH\\\"\" -DICONS_PATH=\"\\\"$ICONS_PATH\\\"\"" -LIBS="$glib_LIBS $curl_LIBS $libnotify_LIBS $PYTHON_LIBS $PYTHON_LDFLAGS ${GTK_LIBS} $LIBS" +LIBS="$glib_LIBS $curl_LIBS $libnotify_LIBS $PYTHON_LIBS $PYTHON_LDFLAGS ${GTK_LIBS} -lgio-2.0 $LIBS" AC_SUBST(AM_LDFLAGS) AC_SUBST(AM_CFLAGS) diff --git a/src/command/command.c b/src/command/command.c index f109bd7b..906f2708 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; @@ -864,6 +869,24 @@ static struct cmd_t command_defs[] = "/disco info myfriend@server.com/laptop") }, + { "/sendfile", + parse_args_with_freetext, 1, 1, NULL, + CMD_NOSUBFUNCS + CMD_MAINFUNC(cmd_sendfile) + 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", parse_args, 0, 1, NULL, CMD_NOSUBFUNCS @@ -2255,6 +2278,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 @@ -2809,6 +2833,8 @@ cmd_init(void) plugins_ac = autocomplete_new(); autocomplete_add(plugins_ac, "load"); + + sendfile_ac = autocomplete_new(); } void @@ -2898,6 +2924,7 @@ cmd_uninit(void) autocomplete_free(autoping_ac); autocomplete_free(plugins_ac); autocomplete_free(plugins_load_ac); + autocomplete_free(sendfile_ac); } gboolean @@ -3046,6 +3073,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); @@ -3418,6 +3446,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]; @@ -4816,6 +4845,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 f74fc96a..7e2825e8 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: @@ -2013,7 +2017,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); @@ -2038,7 +2042,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)) { @@ -4334,6 +4338,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) { @@ -4485,21 +4540,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 27e753a0..68d3947f 100644 --- a/src/command/commands.h +++ b/src/command/commands.h @@ -104,6 +104,7 @@ gboolean cmd_tls_cert(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/otr/otr.c b/src/otr/otr.c index c119e896..b1cf1e16 100644 --- a/src/otr/otr.c +++ b/src/otr/otr.c @@ -45,6 +45,7 @@ #include "window_list.h" #include "contact.h" #include "ui/ui.h" +#include "xmpp/xmpp.h" #include "config/preferences.h" #include "chat_session.h" 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..2169a382 --- /dev/null +++ b/src/tools/http_upload.c @@ -0,0 +1,338 @@ +/* + * 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_CONTENTTYPE_HEADER "Content-Type: 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 = strdup(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 = strdup(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) { + content_type_header = strdup(FALLBACK_CONTENTTYPE_HEADER); + } + 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 = strdup(FALLBACK_MSG); + } + } else { + if (asprintf(&msg, "Uploading '%s' failed: %s", upload->filename, err) == -1) { + msg = strdup(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 = strdup(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..546eea7f --- /dev/null +++ b/src/tools/http_upload.h @@ -0,0 +1,66 @@ +/* + * 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 + +#ifdef PLATFORM_CYGWIN +#define SOCKET int +#endif + +#include +#include +#include "ui/win_types.h" + +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/notifier.c b/src/ui/notifier.c index 80ede2dc..1a6c0132 100644 --- a/src/ui/notifier.c +++ b/src/ui/notifier.c @@ -50,6 +50,7 @@ #include "ui/ui.h" #include "window_list.h" #include "config/preferences.h" +#include "xmpp/xmpp.h" static GTimer *remind_timer; diff --git a/src/ui/ui.h b/src/ui/ui.h index 01ae3953..36a70618 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -40,6 +40,7 @@ #include "command/commands.h" #include "ui/win_types.h" #include "muc.h" +#include "config/tlscerts.h" #ifdef HAVE_LIBOTR #include "otr/otr.h" #endif diff --git a/src/ui/win_types.h b/src/ui/win_types.h index e241a852..f05f237e 100644 --- a/src/ui/win_types.h +++ b/src/ui/win_types.h @@ -45,9 +45,9 @@ #include #endif -#include "xmpp/xmpp.h" #include "ui/buffer.h" #include "chat_state.h" +#include "tools/autocomplete.h" #define LAYOUT_SPLIT_MEMCHECK 12345671 #define PROFCHATWIN_MEMCHECK 22374522 @@ -57,6 +57,48 @@ #define PROFXMLWIN_MEMCHECK 87333463 #define PROFPLUGINWIN_MEMCHECK 43434777 +typedef enum { + FIELD_HIDDEN, + FIELD_TEXT_SINGLE, + FIELD_TEXT_PRIVATE, + FIELD_TEXT_MULTI, + FIELD_BOOLEAN, + FIELD_LIST_SINGLE, + FIELD_LIST_MULTI, + FIELD_JID_SINGLE, + FIELD_JID_MULTI, + FIELD_FIXED, + FIELD_UNKNOWN +} form_field_type_t; + +typedef struct form_option_t { + char *label; + char *value; +} FormOption; + +typedef struct form_field_t { + char *label; + char *type; + form_field_type_t type_t; + char *var; + char *description; + gboolean required; + GSList *values; + GSList *options; + Autocomplete value_ac; +} FormField; + +typedef struct data_form_t { + char *type; + char *title; + char *instructions; + GSList *fields; + GHashTable *var_to_tag; + GHashTable *tag_to_var; + Autocomplete tag_ac; + gboolean modified; +} DataForm; + typedef enum { LAYOUT_SIMPLE, LAYOUT_SPLIT 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/connection.c b/src/xmpp/connection.c index e760a0b7..710e71b2 100644 --- a/src/xmpp/connection.c +++ b/src/xmpp/connection.c @@ -75,6 +75,7 @@ static struct _jabber_conn_t { } jabber_conn; static GHashTable *available_resources; +static GSList *disco_items; // for auto reconnect static struct { @@ -113,6 +114,18 @@ void _connection_free_saved_account(void); void _connection_free_saved_details(void); void _connection_free_session_data(void); +static void +_info_destroy(DiscoInfo *info) +{ + if (info) { + free(info->item); + if (info->features) { + g_hash_table_remove_all(info->features); + } + free(info); + } +} + void jabber_init(void) { @@ -125,6 +138,7 @@ jabber_init(void) presence_sub_requests_init(); caps_init(); available_resources = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)resource_destroy); + disco_items = NULL; xmpp_initialize(); } @@ -323,6 +337,18 @@ jabber_set_connection_status(jabber_conn_status_t status) jabber_conn.conn_status = status; } +GSList* +jabber_get_disco_items(void) +{ + return (disco_items); +} + +void +jabber_set_disco_items(GSList *_disco_items) +{ + disco_items = _disco_items; +} + xmpp_conn_t* connection_get_conn(void) { @@ -420,6 +446,8 @@ _connection_free_saved_details(void) void _connection_free_session_data(void) { + g_slist_free_full(disco_items, (GDestroyNotify)_info_destroy); + disco_items = NULL; g_hash_table_remove_all(available_resources); chat_sessions_clear(); presence_clear_sub_requests(); @@ -651,6 +679,14 @@ _connection_handler(xmpp_conn_t *const conn, const xmpp_conn_event_t status, con roster_request(); bookmark_request(); + // items discovery + DiscoInfo *info = malloc(sizeof(struct disco_info_t)); + info->item = strdup(jabber_conn.domain); + info->features = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); + disco_items = g_slist_append(disco_items, info); + iq_disco_info_request_onconnect(info->item); + iq_disco_items_request_onconnect(jabber_conn.domain); + if (prefs_get_boolean(PREF_CARBONS)){ iq_enable_carbons(); } diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index 1242c6cf..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; @@ -87,6 +88,8 @@ 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); @@ -292,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) { @@ -307,6 +346,21 @@ iq_disco_info_request(gchar *jid) xmpp_stanza_release(iq); } +void +iq_disco_info_request_onconnect(gchar *jid) +{ + xmpp_ctx_t * const ctx = connection_get_ctx(); + char *id = create_unique_id("disco_info_onconnect"); + xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, id, jid, NULL); + + id_handler_add(id, _disco_info_response_id_handler_onconnect, NULL); + + free(id); + + send_iq_stanza(iq); + xmpp_stanza_release(iq); +} + void iq_last_activity_request(gchar *jid) { @@ -428,6 +482,15 @@ iq_disco_items_request(gchar *jid) xmpp_stanza_release(iq); } +void +iq_disco_items_request_onconnect(gchar *jid) +{ + xmpp_ctx_t * const ctx = connection_get_ctx(); + xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, "discoitemsreq_onconnect", jid); + send_iq_stanza(iq); + xmpp_stanza_release(iq); +} + void iq_send_software_version(const char *const fulljid) { @@ -1817,6 +1880,122 @@ _disco_info_response_id_handler(xmpp_stanza_t *const stanza, void *const userdat return 0; } +static int +_disco_info_response_id_handler_onconnect(xmpp_stanza_t *const stanza, void *const 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 disco#info response from: %s", from); + } else { + log_info("Received disco#info response"); + } + + // handle error responses + if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) { + char *error_message = stanza_get_error_message(stanza); + if (from) { + log_error("Service discovery failed for %s: %s", from, error_message); + } else { + log_error("Service discovery failed: %s", error_message); + } + free(error_message); + return 0; + } + + xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); + + if (query) { + xmpp_stanza_t *child = xmpp_stanza_get_children(query); + + 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_strcmp0(disco_info->item, from) == 0) { + break; + } + disco_items = g_slist_next(disco_items); + if (!disco_items) { + log_error("No matching disco item found for %s", from); + return 1; + } + } + } else { + return 1; + } + + while (child) { + const char *stanza_name = xmpp_stanza_get_name(child); + if (g_strcmp0(stanza_name, STANZA_NAME_FEATURE) == 0) { + const char *var = xmpp_stanza_get_attribute(child, STANZA_ATTR_VAR); + if (var) { + g_hash_table_add(disco_info->features, strdup(var)); + } + } + child = xmpp_stanza_get_next(child); + } + } + + 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) { @@ -1825,7 +2004,7 @@ _disco_items_result_handler(xmpp_stanza_t *const stanza) const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); GSList *items = NULL; - if ((g_strcmp0(id, "confreq") == 0) || (g_strcmp0(id, "discoitemsreq") == 0)) { + if ((g_strcmp0(id, "confreq") == 0) || (g_strcmp0(id, "discoitemsreq") == 0) || (g_strcmp0(id, "discoitemsreq_onconnect") == 0)) { log_debug("Response to query: %s", id); xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY); @@ -1857,6 +2036,19 @@ _disco_items_result_handler(xmpp_stanza_t *const stanza) cons_show_room_list(items, from); } else if (g_strcmp0(id, "discoitemsreq") == 0) { cons_show_disco_items(items, from); + } else if (g_strcmp0(id, "discoitemsreq_onconnect") == 0) { + GSList *res_items = items; + if (res_items && (g_slist_length(res_items) > 0)) { + while (res_items) { + DiscoItem *item = res_items->data; + DiscoInfo *info = malloc(sizeof(struct disco_info_t)); + info->item = strdup(item->jid); + info->features = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); + jabber_set_disco_items(g_slist_append(jabber_get_disco_items(), info)); + iq_disco_info_request_onconnect(info->item); + res_items = g_slist_next(res_items); + } + } } g_slist_free_full(items, (GDestroyNotify)_item_destroy); 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 9dcc1798..ea8a6e1d 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 @@ -95,47 +96,10 @@ typedef struct disco_identity_t { char *category; } DiscoIdentity; -typedef enum { - FIELD_HIDDEN, - FIELD_TEXT_SINGLE, - FIELD_TEXT_PRIVATE, - FIELD_TEXT_MULTI, - FIELD_BOOLEAN, - FIELD_LIST_SINGLE, - FIELD_LIST_MULTI, - FIELD_JID_SINGLE, - FIELD_JID_MULTI, - FIELD_FIXED, - FIELD_UNKNOWN -} form_field_type_t; - -typedef struct form_option_t { - char *label; - char *value; -} FormOption; - -typedef struct form_field_t { - char *label; - char *type; - form_field_type_t type_t; - char *var; - char *description; - gboolean required; - GSList *values; - GSList *options; - Autocomplete value_ac; -} FormField; - -typedef struct data_form_t { - char *type; - char *title; - char *instructions; - GSList *fields; - GHashTable *var_to_tag; - GHashTable *tag_to_var; - Autocomplete tag_ac; - gboolean modified; -} DataForm; +typedef struct disco_info_t { + char *item; + GHashTable *features; +} DiscoInfo; // connection functions void jabber_init(void); @@ -150,6 +114,8 @@ const char* jabber_get_fulljid(void); const char* jabber_get_domain(void); jabber_conn_status_t jabber_get_connection_status(void); void jabber_set_connection_status(jabber_conn_status_t status); +GSList* jabber_get_disco_items(void); +void jabber_set_disco_items(GSList *disco_items); char* jabber_get_presence_message(void); char* jabber_get_account_name(void); GList* jabber_get_available_resources(void); @@ -162,11 +128,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); @@ -194,7 +160,9 @@ void iq_disable_carbons(void); void iq_send_software_version(const char *const fulljid); void iq_room_list_request(gchar *conferencejid); void iq_disco_info_request(gchar *jid); +void iq_disco_info_request_onconnect(gchar *jid); void iq_disco_items_request(gchar *jid); +void iq_disco_items_request_onconnect(gchar *jid); void iq_last_activity_request(gchar *jid); void iq_set_autoping(int seconds); void iq_confirm_instant_room(const char *const room_jid); @@ -216,6 +184,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/functionaltests/test_muc.c b/tests/functionaltests/test_muc.c index 002b052d..4ee0e698 100644 --- a/tests/functionaltests/test_muc.c +++ b/tests/functionaltests/test_muc.c @@ -95,8 +95,8 @@ shows_role_and_affiliation_on_join(void **state) { prof_connect(); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" @@ -115,8 +115,8 @@ shows_subject_on_join(void **state) { prof_connect(); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" @@ -143,8 +143,8 @@ shows_history_message(void **state) { prof_connect(); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" @@ -172,8 +172,8 @@ shows_occupant_join(void **state) { prof_connect(); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" @@ -201,8 +201,8 @@ shows_message(void **state) { prof_connect(); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" @@ -228,8 +228,8 @@ shows_all_messages_in_console_when_window_not_focussed(void **state) { prof_connect(); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" @@ -269,8 +269,8 @@ shows_first_message_in_console_when_window_not_focussed(void **state) prof_input("/console muc first"); assert_true(prof_output_exact("Console MUC messages set: first")); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" @@ -315,8 +315,8 @@ shows_no_message_in_console_when_window_not_focussed(void **state) prof_input("/console muc none"); assert_true(prof_output_exact("Console MUC messages set: none")); - stbbr_for_id("prof_join_2", - "" + stbbr_for_id("prof_join_3", + "" "" "" "" diff --git a/tests/functionaltests/test_ping.c b/tests/functionaltests/test_ping.c index a3f3458c..8a065f15 100644 --- a/tests/functionaltests/test_ping.c +++ b/tests/functionaltests/test_ping.c @@ -14,18 +14,18 @@ void ping_multiple(void **state) { - stbbr_for_id("prof_ping_2", - "" - ); stbbr_for_id("prof_ping_3", "" ); + stbbr_for_id("prof_ping_4", + "" + ); prof_connect(); prof_input("/ping"); assert_true(stbbr_received( - "" + "" "" "" )); @@ -33,7 +33,7 @@ ping_multiple(void **state) prof_input("/ping"); assert_true(stbbr_received( - "" + "" "" "" )); diff --git a/tests/functionaltests/test_presence.c b/tests/functionaltests/test_presence.c index 5b51113c..1a933134 100644 --- a/tests/functionaltests/test_presence.c +++ b/tests/functionaltests/test_presence.c @@ -19,7 +19,7 @@ presence_online(void **state) prof_input("/online"); assert_true(stbbr_received( - "" + "" "" "" )); @@ -35,7 +35,7 @@ presence_online_with_message(void **state) prof_input("/online \"Hi there\""); assert_true(stbbr_received( - "" + "" "Hi there" "" "" @@ -52,7 +52,7 @@ presence_away(void **state) prof_input("/away"); assert_true(stbbr_received( - "" + "" "away" "" "" @@ -69,7 +69,7 @@ presence_away_with_message(void **state) prof_input("/away \"I'm not here for a bit\""); assert_true(stbbr_received( - "" + "" "away" "I'm not here for a bit" "" @@ -87,7 +87,7 @@ presence_xa(void **state) prof_input("/xa"); assert_true(stbbr_received( - "" + "" "xa" "" "" @@ -104,7 +104,7 @@ presence_xa_with_message(void **state) prof_input("/xa \"Gone to the shops\""); assert_true(stbbr_received( - "" + "" "xa" "Gone to the shops" "" @@ -122,7 +122,7 @@ presence_dnd(void **state) prof_input("/dnd"); assert_true(stbbr_received( - "" + "" "dnd" "" "" @@ -139,7 +139,7 @@ presence_dnd_with_message(void **state) prof_input("/dnd \"Working\""); assert_true(stbbr_received( - "" + "" "dnd" "Working" "" @@ -157,7 +157,7 @@ presence_chat(void **state) prof_input("/chat"); assert_true(stbbr_received( - "" + "" "chat" "" "" @@ -174,7 +174,7 @@ presence_chat_with_message(void **state) prof_input("/chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "chat" "Free to talk" "" @@ -192,7 +192,7 @@ presence_set_priority(void **state) prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "25" "" "" @@ -208,7 +208,7 @@ presence_includes_priority(void **state) prof_input("/priority 25"); assert_true(stbbr_received( - "" + "" "25" "" "" @@ -217,7 +217,7 @@ presence_includes_priority(void **state) prof_input("/chat \"Free to talk\""); assert_true(stbbr_received( - "" + "" "25" "chat" "Free to talk" diff --git a/tests/unittests/test_cmd_pgp.c b/tests/unittests/test_cmd_pgp.c index b1d0ab52..68d48b0c 100644 --- a/tests/unittests/test_cmd_pgp.c +++ b/tests/unittests/test_cmd_pgp.c @@ -9,6 +9,7 @@ #include "config.h" #include "command/commands.h" +#include "xmpp/xmpp.h" #include "ui/stub_ui.h" 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) {}