From eebf54c8596abd5c28b405d8860c173207bbec64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Wennerstr=C3=B6m?= Date: Sun, 5 Jul 2020 17:21:20 +0200 Subject: [PATCH] Infer filename from content-disposition or URL The Content-Disposition inferring is probably a bad idea security wise, so I am going to remove it. --- Makefile.am | 6 + src/command/cmd_funcs.c | 259 +++++++++++---------- src/tools/http_download.c | 125 +++++++++- src/tools/http_download.h | 5 + tests/unittests/test_http_download.c | 119 ++++++++++ tests/unittests/test_http_download.h | 2 + tests/unittests/tools/stub_http_download.c | 28 +++ tests/unittests/ui/stub_ui.c | 101 +++++++- tests/unittests/unittests.c | 4 + 9 files changed, 512 insertions(+), 137 deletions(-) create mode 100644 tests/unittests/test_http_download.c create mode 100644 tests/unittests/test_http_download.h create mode 100644 tests/unittests/tools/stub_http_download.c diff --git a/Makefile.am b/Makefile.am index 1228e1ad..a2cf5598 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,6 +43,8 @@ core_sources = \ src/tools/parser.h \ src/tools/http_upload.c \ src/tools/http_upload.h \ + src/tools/http_download.c \ + src/tools/http_download.h \ src/tools/bookmark_ignore.c \ src/tools/bookmark_ignore.h \ src/tools/autocomplete.c src/tools/autocomplete.h \ @@ -89,6 +91,8 @@ unittest_sources = \ src/tools/clipboard.c src/tools/clipboard.h \ src/tools/bookmark_ignore.c \ src/tools/bookmark_ignore.h \ + src/tools/http_download.c \ + src/tools/http_download.h \ src/config/accounts.h \ src/config/account.c src/config/account.h \ src/config/files.c src/config/files.h \ @@ -119,6 +123,7 @@ unittest_sources = \ tests/unittests/database/stub_database.c \ tests/unittests/config/stub_accounts.c \ tests/unittests/tools/stub_http_upload.c \ + tests/unittests/tools/stub_http_download.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 \ @@ -145,6 +150,7 @@ unittest_sources = \ tests/unittests/test_cmd_disconnect.c tests/unittests/test_cmd_disconnect.h \ tests/unittests/test_callbacks.c tests/unittests/test_callbacks.h \ tests/unittests/test_plugins_disco.c tests/unittests/test_plugins_disco.h \ + tests/unittests/test_http_download.c tests/unittests/test_http_download.h \ tests/unittests/unittests.c functionaltest_sources = \ diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 7d921ea3..5e8b5cab 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -4,6 +4,7 @@ * * Copyright (C) 2012 - 2019 James Booth * Copyright (C) 2019 Michael Vetter + * Copyright (C) 2020 William Wennerström * * This file is part of Profanity. * @@ -67,6 +68,7 @@ #include "config/scripts.h" #include "event/client_events.h" #include "tools/http_upload.h" +#include "tools/http_download.h" #include "tools/autocomplete.h" #include "tools/parser.h" #include "tools/bookmark_ignore.h" @@ -4805,6 +4807,43 @@ cmd_disco(ProfWin* window, const char* const command, gchar** args) return TRUE; } + +char *_add_omemo_stream(int *fd, FILE **fh, char **err) { + // Create temporary file for writing ciphertext. + int tmpfd; + char *tmpname = NULL; + if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) { + *err = "Unable to create temporary file for encrypted transfer."; + return NULL; + } + FILE *tmpfh = fdopen(tmpfd, "wb"); + + // The temporary ciphertext file should be removed after it has + // been closed. + remove(tmpname); + free(tmpname); + + int crypt_res; + char *fragment; + fragment = omemo_encrypt_file(*fh, tmpfh, file_size(*fd), &crypt_res); + if (crypt_res != 0) { + fclose(tmpfh); + return NULL; + } + + // Force flush as the upload will read from the same stream. + fflush(tmpfh); + rewind(tmpfh); + + fclose(*fh); // Also closes descriptor. + + // Switch original stream with temporary ciphertext stream. + *fd = tmpfd; + *fh = tmpfh; + + return fragment; +} + gboolean cmd_sendfile(ProfWin* window, const char* const command, gchar** args) { @@ -4855,59 +4894,29 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) case WIN_CHAT: { ProfChatWin *chatwin = (ProfChatWin*)window; - assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); #ifdef HAVE_OMEMO if (chatwin->is_omemo) { - - // Create temporary file for writing ciphertext. - int tmpfd; - char *tmpname = NULL; - if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) { - char *msg = "Unable to create temporary file for encrypted transfer."; - cons_show_error(msg); - win_println(window, THEME_ERROR, "-", msg); - fclose(fh); - goto out; - } - FILE *tmpfh = fdopen(tmpfd, "wb"); - - // The temporary ciphertext file should be removed after it has - // been closed. - remove(tmpname); - free(tmpname); - - int crypt_res; + char *err = NULL; alt_scheme = OMEMO_AESGCM_URL_SCHEME; - alt_fragment = omemo_encrypt_file(fh, tmpfh, file_size(fd), &crypt_res); - if (crypt_res != 0) { - char *msg = "Failed to encrypt file."; - cons_show_error(msg); - win_println(window, THEME_ERROR, "-", msg); - fclose(fh); - fclose(tmpfh); + alt_fragment = _add_omemo_stream(&fd, &fh, &err); + if (err != NULL) { + cons_show_error(err); + win_println(window, THEME_ERROR, "-", err); goto out; } - - // Force flush as the upload will read from the same stream. - fflush(tmpfh); - rewind(tmpfh); - - fclose(fh); // Also closes descriptor. - - // Switch original stream with temporary ciphertext stream. - fd = tmpfd; - fh = tmpfh; - break; } #endif - if ((chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE)) - || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) { - cons_show_error("Uploading unencrypted files disabled. See /otr sendfile or /pgp sendfile."); - win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet."); - goto out; + if (window->type == WIN_CHAT) { + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + if ((chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE)) + || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) { + cons_show_error("Uploading unencrypted files disabled. See /otr sendfile or /pgp sendfile."); + win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet."); + goto out; + } } break; } @@ -9154,11 +9163,75 @@ cmd_url_open(ProfWin* window, const char* const command, gchar** args) return TRUE; } + +void _url_save_fallback_method(ProfWin *window, const char *url, const char *directory, const char *filename) { + HTTPDownload *download = malloc(sizeof(HTTPDownload)); + download->window = window; + download->url = strdup(url); + + if (filename) { + download->filename = strdup(filename); + } else { + download->filename = NULL; + } + + if (directory) { + download->directory = strdup(directory); + } else { + download->directory = NULL; + } + + pthread_create(&(download->worker), NULL, &http_file_get, download); + http_download_add_download(download); +} + +void _url_save_external_method(const char *scheme_cmd, const char *url, const char *directory, char *filename) { + if (!filename) { + filename = http_filename_from_url(url); + } + + // Explicitly use "." as directory if no directory has been passed. + char *fp = NULL; + if (directory == NULL) { + fp = g_build_filename(".", filename, NULL); + } else { + fp = g_build_filename(directory, filename, NULL); + } + + if (!g_file_test(directory, G_FILE_TEST_EXISTS) || + !g_file_test(directory, G_FILE_TEST_IS_DIR)) { + cons_show_error("Directory '%s' does not exist or is not a directory.", directory); + return; + } + + gchar **argv = g_strsplit(scheme_cmd, " ", 0); + + guint num_args = 0; + while (argv[num_args]) { + if (0 == g_strcmp0(argv[num_args], "%u")) { + g_free(argv[num_args]); + argv[num_args] = g_strdup(url); + } else if (0 == g_strcmp0(argv[num_args], "%p")) { + g_free(argv[num_args]); + argv[num_args] = fp; + } + num_args++; + } + + if (!call_external(argv, NULL, NULL)) { + cons_show_error("Unable to save url: check the logs for more information."); + } else { + cons_show("URL '%s' has been saved to '%s'.", url, fp); + } +} + gboolean -cmd_url_save(ProfWin* window, const char* const command, gchar** args) +cmd_url_save(ProfWin *window, const char *const command, gchar **args) { - if (window->type != WIN_CHAT && window->type != WIN_MUC && window->type != WIN_PRIVATE) { - cons_show("url save not supported in this window"); + if (window->type != WIN_CHAT && + window->type != WIN_MUC && + window->type != WIN_PRIVATE) { + cons_show_error("`/url save` is not supported in this window."); return TRUE; } @@ -9167,91 +9240,37 @@ cmd_url_save(ProfWin* window, const char* const command, gchar** args) return TRUE; } - gchar* uri = args[1]; - gchar* target_path = g_strdup(args[2]); + gchar *url = args[1]; + gchar *path = g_strdup(args[2]); - GFile* file = g_file_new_for_uri(uri); - - gchar* target_dir = NULL; - gchar* base_name = NULL; - - if (target_path == NULL) { - target_dir = g_strdup("./"); - base_name = g_file_get_basename(file); - if (0 == g_strcmp0(base_name, ".")) { - g_free(base_name); - base_name = g_strdup("saved_url_content.html"); - } - target_path = g_strconcat(target_dir, base_name, NULL); - } - - if (g_file_test(target_path, G_FILE_TEST_EXISTS) && g_file_test(target_path, G_FILE_TEST_IS_DIR)) { - target_dir = g_strdup(target_path); - base_name = g_file_get_basename(file); - g_free(target_path); - target_path = g_strconcat(target_dir, "/", base_name, NULL); - } - - g_object_unref(file); - file = NULL; - - if (base_name == NULL) { - base_name = g_path_get_basename(target_path); - target_dir = g_path_get_dirname(target_path); - } - - if (!g_file_test(target_dir, G_FILE_TEST_EXISTS) || !g_file_test(target_dir, G_FILE_TEST_IS_DIR)) { - cons_show("%s does not exist or is not a directory.", target_dir); - g_free(target_path); - g_free(target_dir); - g_free(base_name); - return TRUE; - } - - gchar* scheme = g_uri_parse_scheme(uri); + gchar *scheme = g_uri_parse_scheme(url); if (scheme == NULL) { - cons_show("URL '%s' is not valid.", uri); - g_free(target_path); - g_free(target_dir); - g_free(base_name); + cons_show("URL '%s' is not valid.", url); + g_free(url); return TRUE; } - gchar* scheme_cmd = NULL; - - if (0 == g_strcmp0(scheme, "http") - || 0 == g_strcmp0(scheme, "https") - || 0 == g_strcmp0(scheme, OMEMO_AESGCM_URL_SCHEME) - ) { - scheme_cmd = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme); + gchar *directory = NULL; + gchar *filename = NULL; + if (path != NULL) { + directory = g_path_get_dirname(path); + filename = g_path_get_basename(path); } - g_free(scheme); - - gchar** argv = g_strsplit(scheme_cmd, " ", 0); - g_free(scheme_cmd); - - guint num_args = 0; - while (argv[num_args]) { - if (0 == g_strcmp0(argv[num_args], "%u")) { - g_free(argv[num_args]); - argv[num_args] = g_strdup(uri); - } else if (0 == g_strcmp0(argv[num_args], "%p")) { - g_free(argv[num_args]); - argv[num_args] = target_path; + gchar *scheme_cmd = prefs_get_string_with_option(PREF_URL_SAVE_CMD, scheme); + if (scheme_cmd == NULL) { + if (g_strcmp0(scheme, "http") == 0 + || g_strcmp0(scheme, "https") == 0 + || g_strcmp0(scheme, OMEMO_AESGCM_URL_SCHEME) == 0) { + _url_save_fallback_method(window, url, directory, filename); + } else { + cons_show_error("No download method defined for the scheme '%s'.", scheme); } - num_args++; - } - - if (!call_external(argv, NULL, NULL)) { - cons_show_error("Unable to save url: check the logs for more information."); } else { - cons_show("URL '%s' has been saved into '%s'.", uri, target_path); + _url_save_external_method(scheme_cmd, url, directory, filename); } - g_free(target_dir); - g_free(base_name); - g_strfreev(argv); + g_free(scheme_cmd); return TRUE; } diff --git a/src/tools/http_download.c b/src/tools/http_download.c index 80916385..5a0f6f18 100644 --- a/src/tools/http_download.c +++ b/src/tools/http_download.c @@ -104,13 +104,119 @@ _older_progress(void *p, double dltotal, double dlnow, double ultotal, double ul } #endif +char *http_filename_from_header(char *header) { + const char *header_tag_cd = "Content-Disposition:"; + const int header_tag_cd_len = strlen(header_tag_cd); + + if (!header) { + return NULL; // Bad header. + } + + if (strncasecmp(header, header_tag_cd, header_tag_cd_len) == 0) { + header += header_tag_cd_len; // Move to header content. + } else { + return NULL; // Not a CD header. + } + + const char *filename_key = "filename="; + const size_t filename_key_len = strlen(filename_key); + + char *value = strcasestr(header, filename_key); + if (!value) { + return NULL; // No filename key found. + } + + value += filename_key_len; // Move to key value. + + char fn[4096]; + char *pf = fn; + while(*value != '\0' && *value != ';') { + *pf++ = *value++; + } + *pf = '\0'; + + if (!strlen(fn)) { + return NULL; // Empty tag. + } + + return strdup(fn); +} + +char *http_filename_from_url(const char *url) { + const char *default_name = "index.html"; + + GFile *file = g_file_new_for_uri(url); + char *filename = g_file_get_basename(file); + g_object_unref(file); + + if (g_strcmp0(filename, ".") == 0 + || g_strcmp0(filename, G_DIR_SEPARATOR_S) == 0) { + g_free(filename); + return strdup(default_name); + } + + return filename; +} + +static size_t _header_callback(char *data, size_t size, size_t nitems, void *userdata) { + char *header = (char*)data; + + HTTPDownload *download = (HTTPDownload *)userdata; + size *= nitems; + + if (download->filename != NULL) { + return size; // No-op. + } + + download->filename = http_filename_from_header(header); + + return size; +} + +FILE *_get_filehandle(const char *directory, const char *filename) { + gchar *fp; + FILE *fh; + + // Explicitly use "." as directory if no directory has been passed. + if (directory == NULL) { + fp = g_build_filename(".", filename, NULL); + } else { + fp = g_build_filename(directory, filename, NULL); + } + + fh = fopen(fp, "wb"); + g_free(fp); + return fh; +} + +static size_t _write_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { + HTTPDownload *download = (HTTPDownload *)userdata; + size *= nmemb; + + if (download->filename == NULL) { + download->filename = http_filename_from_url(download->url); + } + + if (download->filename == NULL || download->directory == NULL) { + return 0; // Missing file name or directory, write no data. + } + + if (download->filehandle == NULL ) { + FILE *fh = _get_filehandle(download->directory, download->filename); + if (!fh) { + return 0; // Unable to open file handle. + } + download->filehandle = fh; + } + + return fwrite(buffer, size, nmemb, userdata); +} + void * http_file_get(void *userdata) { HTTPDownload *download = (HTTPDownload *)userdata; - FILE *fh = NULL; - char *err = NULL; CURL *curl; @@ -144,9 +250,12 @@ http_file_get(void *userdata) #endif curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); - fh = download->filehandle; + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)download); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)download); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&fh); curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity"); if (cert_path) { @@ -160,8 +269,8 @@ http_file_get(void *userdata) curl_easy_cleanup(curl); curl_global_cleanup(); - if (fh) { - fclose(fh); + if (download->filehandle) { + fclose(download->filehandle); } pthread_mutex_lock(&lock); @@ -188,7 +297,7 @@ http_file_get(void *userdata) msg = strdup(FALLBACK_MSG); } win_update_entry_message(download->window, download->url, msg); - win_mark_received(download->window, download->put_url); + win_mark_received(download->window, download->url); free(msg); } } @@ -197,6 +306,8 @@ http_file_get(void *userdata) pthread_mutex_unlock(&lock); free(download->url); + free(download->filename); + free(download->directory); free(download); return NULL; diff --git a/src/tools/http_download.h b/src/tools/http_download.h index 7348b77c..b0377d93 100644 --- a/src/tools/http_download.h +++ b/src/tools/http_download.h @@ -48,6 +48,8 @@ typedef struct http_download_t { char *url; + char *filename; + char *directory; FILE *filehandle; curl_off_t bytes_received; ProfWin *window; @@ -60,4 +62,7 @@ void* http_file_get(void *userdata); void http_download_cancel_processes(ProfWin *window); void http_download_add_download(HTTPDownload *download); +char *http_filename_from_url(const char *url); +char *http_filename_from_header(char *header); + #endif diff --git a/tests/unittests/test_http_download.c b/tests/unittests/test_http_download.c new file mode 100644 index 00000000..181fc78d --- /dev/null +++ b/tests/unittests/test_http_download.c @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "tools/http_download.h" + +typedef struct { + char *url; + char *filename; +} url_test_t; + +typedef struct { + char *header; + char *filename; +} header_test_t; + +void http_filename_from_url_td(void **state) { + int num_tests = 5; + url_test_t tests[] = { + (url_test_t){ + .url = "https://host.test/image.jpeg", + .filename = "image.jpeg", + }, + (url_test_t){ + .url = "https://host.test/images/", + .filename = "images", + }, + (url_test_t){ + .url = "https://host.test/", + .filename = "index.html", + }, + (url_test_t){ + .url = "https://host.test", + .filename = "index.html", + }, + (url_test_t){ + .url = "aesgcm://host.test", + .filename = "index.html", + }, + }; + + char *filename; + for(int i = 0; i < num_tests; i++) { + filename = http_filename_from_url(tests[i].url); + assert_string_equal(filename, tests[i].filename); + } +} + +void http_filename_from_header_td(void **state) { + int num_tests = 11; + header_test_t tests[] = { + (header_test_t){ + .header = "Content-Disposition: filename=image.jpeg", + .filename = "image.jpeg", + }, + (header_test_t){ + .header = "Content-Disposition:filename=image.jpeg", + .filename = "image.jpeg", + }, + (header_test_t){ + .header = "CoNteNt-DiSpoSItioN: filename=image.jpeg", + .filename = "image.jpeg", + }, + (header_test_t){ + .header = "Content-Disposition: attachment; filename=image.jpeg", + .filename = "image.jpeg", + }, + (header_test_t){ + .header = "Content-Disposition: filename=", + .filename = NULL, + }, + (header_test_t){ + .header = "Content-Disposition: filename=;", + .filename = NULL, + }, + (header_test_t){ + .header = "Content-Disposition: inline", + .filename = NULL, + }, + (header_test_t){ + .header = "Content-Disposition:", + .filename = NULL, + }, + (header_test_t){ + .header = "Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT ", + .filename = NULL, + }, + (header_test_t){ + .header = "", + .filename = NULL, + }, + (header_test_t){ + .header = NULL, + .filename = NULL, + }, + }; + + char *got_filename; + char *exp_filename; + char *header; + for(int i = 0; i < num_tests; i++) { + header = tests[i].header; + exp_filename = tests[i].filename; + + got_filename = http_filename_from_header(header); + + if (exp_filename == NULL) { + assert_null(got_filename); + } else { + assert_string_equal(got_filename, exp_filename); + } + } +} diff --git a/tests/unittests/test_http_download.h b/tests/unittests/test_http_download.h new file mode 100644 index 00000000..c8c333ee --- /dev/null +++ b/tests/unittests/test_http_download.h @@ -0,0 +1,2 @@ +void http_filename_from_url_td(void **state); +void http_filename_from_header_td(void **state); diff --git a/tests/unittests/tools/stub_http_download.c b/tests/unittests/tools/stub_http_download.c new file mode 100644 index 00000000..a07146b4 --- /dev/null +++ b/tests/unittests/tools/stub_http_download.c @@ -0,0 +1,28 @@ +#ifndef TOOLS_HTTP_DOWNLOAD_H +#define TOOLS_HTTP_DOWNLOAD_H + +#include +#include + +typedef struct prof_win_t ProfWin; + +typedef struct http_download_t { + char *url; + char *filename; + char *directory; + FILE *filehandle; + curl_off_t bytes_received; + ProfWin *window; + pthread_t worker; + int cancel; +} HTTPDownload; + + +void* http_file_get(void *userdata); + +void http_download_cancel_processes(ProfWin *window); +void http_download_add_download(HTTPDownload *download); + +char *http_filename_from_url(const char *url); + +#endif diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c index 192f39ee..f284d04b 100644 --- a/tests/unittests/ui/stub_ui.c +++ b/tests/unittests/ui/stub_ui.c @@ -236,16 +236,97 @@ ui_contact_online(char* barejid, Resource* resource, GDateTime* last_activity) check_expected(last_activity); } -void -ui_contact_typing(const char* const barejid, const char* const resource) -{ -} -void -chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_created) -{ -} -void -chatwin_receipt_received(ProfChatWin* chatwin, const char* const id) +void ui_contact_typing(const char * const barejid, const char * const resource) {} +void chatwin_incoming_msg(ProfChatWin *chatwin, ProfMessage *message, gboolean win_created) {} +void chatwin_receipt_received(ProfChatWin *chatwin, const char * const id) {} + +void privwin_incoming_msg(ProfPrivateWin *privatewin, ProfMessage *message) {} + +void ui_disconnected(void) {} +void chatwin_recipient_gone(ProfChatWin *chatwin) {} + +void chatwin_outgoing_msg(ProfChatWin *chatwin, const char *const message, char *id, prof_enc_t enc_mode, gboolean request_receipt, const char *const replace_id) {} +void chatwin_outgoing_carbon(ProfChatWin *chatwin, ProfMessage *message) {} +void privwin_outgoing_msg(ProfPrivateWin *privwin, const char * const message) {} + +void privwin_occupant_offline(ProfPrivateWin *privwin) {} +void privwin_occupant_kicked(ProfPrivateWin *privwin, const char *const actor, const char *const reason) {} +void privwin_occupant_banned(ProfPrivateWin *privwin, const char *const actor, const char *const reason) {} +void privwin_occupant_online(ProfPrivateWin *privwin) {} +void privwin_message_occupant_offline(ProfPrivateWin *privwin) {} + +void privwin_message_left_room(ProfPrivateWin *privwin) {} + +void ui_room_join(const char * const roomjid, gboolean focus) {} +void ui_switch_to_room(const char * const roomjid) {} + +void mucwin_role_change(ProfMucWin *mucwin, const char * const role, const char * const actor, + const char * const reason) {} +void mucwin_affiliation_change(ProfMucWin *mucwin, const char * const affiliation, const char * const actor, + const char * const reason) {} +void mucwin_role_and_affiliation_change(ProfMucWin *mucwin, const char * const role, + const char * const affiliation, const char * const actor, const char * const reason) {} +void mucwin_occupant_role_change(ProfMucWin *mucwin, const char * const nick, const char * const role, + const char * const actor, const char * const reason) {} +void mucwin_occupant_affiliation_change(ProfMucWin *mucwin, const char * const nick, const char * const affiliation, + const char * const actor, const char * const reason) {} +void mucwin_occupant_role_and_affiliation_change(ProfMucWin *mucwin, const char * const nick, const char * const role, + const char * const affiliation, const char * const actor, const char * const reason) {} +void mucwin_roster(ProfMucWin *mucwin, GList *occupants, const char * const presence) {} +void mucwin_history(ProfMucWin *mucwin, const ProfMessage *const message) {} +void mucwin_incoming_msg(ProfMucWin *mucwin, const ProfMessage *const message, GSList *mentions, GList *triggers, gboolean filter_reflection) {} +void mucwin_outgoing_msg(ProfMucWin *mucwin, const char *const message, const char *const id, prof_enc_t enc_mode, const char *const replace_id) {} +void mucwin_subject(ProfMucWin *mucwin, const char * const nick, const char * const subject) {} +void mucwin_requires_config(ProfMucWin *mucwin) {} +void ui_room_destroy(const char * const roomjid) {} +void mucwin_info(ProfMucWin *mucwin) {} +void mucwin_show_role_list(ProfMucWin *mucwin, muc_role_t role) {} +void mucwin_show_affiliation_list(ProfMucWin *mucwin, muc_affiliation_t affiliation) {} +void mucwin_room_info_error(ProfMucWin *mucwin, const char * const error) {} +void mucwin_room_disco_info(ProfMucWin *mucwin, GSList *identities, GSList *features) {} +void ui_room_destroyed(const char * const roomjid, const char * const reason, const char * const new_jid, + const char * const password) {} +void ui_room_kicked(const char * const roomjid, const char * const actor, const char * const reason) {} +void mucwin_occupant_kicked(ProfMucWin *mucwin, const char * const nick, const char * const actor, + const char * const reason) {} +void ui_room_banned(const char * const roomjid, const char * const actor, const char * const reason) {} +void mucwin_occupant_banned(ProfMucWin *mucwin, const char * const nick, const char * const actor, + const char * const reason) {} +void ui_leave_room(const char * const roomjid) {} +void mucwin_broadcast(ProfMucWin *mucwin, const char * const message) {} +void mucwin_occupant_offline(ProfMucWin *mucwin, const char * const nick) {} +void mucwin_occupant_online(ProfMucWin *mucwin, const char * const nick, const char * const roles, + const char * const affiliation, const char * const show, const char * const status) {} +void mucwin_occupant_nick_change(ProfMucWin *mucwin, const char * const old_nick, const char * const nick) {} +void mucwin_nick_change(ProfMucWin *mucwin, const char * const nick) {} +void mucwin_occupant_presence(ProfMucWin *mucwin, const char * const nick, const char * const show, + const char * const status) {} +void mucwin_update_occupants(ProfMucWin *mucwin) {} +void mucwin_show_occupants(ProfMucWin *mucwin) {} +void mucwin_hide_occupants(ProfMucWin *mucwin) {} +void mucwin_set_enctext(ProfMucWin *mucwin, const char *const enctext) {} +void mucwin_unset_enctext(ProfMucWin *mucwin) {} +void mucwin_set_message_char(ProfMucWin *mucwin, const char *const ch) {} +void mucwin_unset_message_char(ProfMucWin *mucwin) {} + +void win_update_entry_message(ProfWin *window, const char *const id, const char *const message) {}; +void win_mark_received(ProfWin *window, const char *const id) {}; +void win_print_http_transfer(ProfWin *window, const char *const message, char *url) {}; + +void ui_show_roster(void) {} +void ui_hide_roster(void) {} +void ui_roster_add(const char * const barejid, const char * const name) {} +void ui_roster_remove(const char * const barejid) {} +void ui_contact_already_in_group(const char * const contact, const char * const group) {} +void ui_contact_not_in_group(const char * const contact, const char * const group) {} +void ui_group_added(const char * const contact, const char * const group) {} +void ui_group_removed(const char * const contact, const char * const group) {} +void chatwin_contact_online(ProfChatWin *chatwin, Resource *resource, GDateTime *last_activity) {} +void chatwin_contact_offline(ProfChatWin *chatwin, char *resource, char *status) {} + +void ui_contact_offline(char *barejid, char *resource, char *status) {} + +void ui_handle_recipient_error(const char * const recipient, const char * const err_msg) { } diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c index 874f6194..9e9833bc 100644 --- a/tests/unittests/unittests.c +++ b/tests/unittests/unittests.c @@ -38,6 +38,7 @@ #include "test_form.h" #include "test_callbacks.h" #include "test_plugins_disco.h" +#include "test_http_download.h" int main(int argc, char* argv[]) @@ -626,6 +627,9 @@ main(int argc, char* argv[]) unit_test(does_not_add_duplicate_feature), unit_test(removes_plugin_features), unit_test(does_not_remove_feature_when_more_than_one_reference), + + unit_test(http_filename_from_url_td), + unit_test(http_filename_from_header_td), }; return run_tests(all_tests);