diff options
-rw-r--r-- | ChangeLog.adoc | 3 | ||||
-rw-r--r-- | doc/en/weechat_dev.en.adoc | 1 | ||||
-rw-r--r-- | doc/fr/weechat_dev.fr.adoc | 1 | ||||
-rw-r--r-- | doc/ja/weechat_dev.ja.adoc | 1 | ||||
-rw-r--r-- | doc/sr/weechat_dev.sr.adoc | 1 | ||||
-rw-r--r-- | po/srcfiles.cmake | 2 | ||||
-rw-r--r-- | src/core/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/core/hook/wee-hook-process.c | 4 | ||||
-rw-r--r-- | src/core/hook/wee-hook-url.c | 419 | ||||
-rw-r--r-- | src/core/hook/wee-hook-url.h | 59 | ||||
-rw-r--r-- | src/core/wee-command.c | 25 | ||||
-rw-r--r-- | src/core/wee-hook.c | 126 | ||||
-rw-r--r-- | src/core/wee-hook.h | 2 | ||||
-rw-r--r-- | src/core/wee-url.c | 127 | ||||
-rw-r--r-- | src/core/wee-url.h | 4 | ||||
-rw-r--r-- | src/plugins/plugin.c | 1 | ||||
-rw-r--r-- | src/plugins/weechat-plugin.h | 19 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 4 |
18 files changed, 740 insertions, 60 deletions
diff --git a/ChangeLog.adoc b/ChangeLog.adoc index 59781f647..75a8ae888 100644 --- a/ChangeLog.adoc +++ b/ChangeLog.adoc @@ -27,8 +27,7 @@ New features:: * core: add options weechat.buffer.* to save buffer properties set by user, add option `setauto` in command `/buffer` (issue #352) * core: add parameters and key bindings to move to edges of current area with commands `/cursor go` and `/cursor move` (issue #1282) * core: add variables "_chat_focused_line_bol" and "_chat_focused_line_eol" in focus data (issue #1955) - * core: add algorithms `sha512-224`, `sha512-256`, `sha3-*`, blake2b-*` and `blake2s-*` in option sec.crypt.hash_algo (issue #2008) - * api: add algorithms `sha512-224`, `sha512-256`, blake2b-*` and `blake2s-*` in hash functions (issue #2008) + * api: add function hook_url, add option `url` in command `/debug` (issue #1723) * api: add support of path to variable and hashtable comparison in function hdata_compare (issue #1066) * api: add infos "nick_color_ignore_case" and "nick_color_name_ignore_case" (issue #194) * api: add info "buffer" (issue #1962) diff --git a/doc/en/weechat_dev.en.adoc b/doc/en/weechat_dev.en.adoc index 85c822102..c47b96969 100644 --- a/doc/en/weechat_dev.en.adoc +++ b/doc/en/weechat_dev.en.adoc @@ -167,6 +167,7 @@ WeeChat "core" is located in following directories: | wee-hook-process.c | Hook "process". | wee-hook-signal.c | Hook "signal". | wee-hook-timer.c | Hook "timer". +| wee-hook-url.c | Hook "url". | gui/ | Functions for buffers, windows, ... (used by all interfaces). | gui-bar-item.c | Bar items. | gui-bar-window.c | Bar windows. diff --git a/doc/fr/weechat_dev.fr.adoc b/doc/fr/weechat_dev.fr.adoc index b72f98f00..9d5d76fbb 100644 --- a/doc/fr/weechat_dev.fr.adoc +++ b/doc/fr/weechat_dev.fr.adoc @@ -169,6 +169,7 @@ Le cœur de WeeChat est situé dans les répertoires suivants : | wee-hook-process.c | Hook "process". | wee-hook-signal.c | Hook "signal". | wee-hook-timer.c | Hook "timer". +| wee-hook-url.c | Hook "url". | gui/ | Fonctions pour les tampons, fenêtres, ... (utilisées par toutes les interfaces). | gui-bar-item.c | Objets de barre. | gui-bar-window.c | Fenêtres de barre. diff --git a/doc/ja/weechat_dev.ja.adoc b/doc/ja/weechat_dev.ja.adoc index 5e0268dc7..c530ac684 100644 --- a/doc/ja/weechat_dev.ja.adoc +++ b/doc/ja/weechat_dev.ja.adoc @@ -180,6 +180,7 @@ WeeChat "core" は以下のディレクトリに配置されています: | wee-hook-process.c | "process" フック | wee-hook-signal.c | "signal" フック | wee-hook-timer.c | "timer" フック +| wee-hook-url.c | "url" フック | gui/ | バッファ、ウィンドウなどの関数 (全てのインターフェースで利用) | gui-bar-item.c | バー要素 | gui-bar-window.c | バーウィンドウ diff --git a/doc/sr/weechat_dev.sr.adoc b/doc/sr/weechat_dev.sr.adoc index 6f6c8d661..5f342747e 100644 --- a/doc/sr/weechat_dev.sr.adoc +++ b/doc/sr/weechat_dev.sr.adoc @@ -169,6 +169,7 @@ WeeChat „језгро” се налази у следећим директо | wee-hook-process.c | Кука "process". | wee-hook-signal.c | Кука "signal". | wee-hook-timer.c | Кука "timer". +| wee-hook-url.c | Кука "url". | gui/ | Функције за бафере, прозоре, ... (користе их сви интерфејси). | gui-bar-item.c | Ставке трака. | gui-bar-window.c | Прозори трака. diff --git a/po/srcfiles.cmake b/po/srcfiles.cmake index 2f8a688ba..5eed74044 100644 --- a/po/srcfiles.cmake +++ b/po/srcfiles.cmake @@ -35,6 +35,8 @@ SET(WEECHAT_SOURCES ./src/core/hook/wee-hook-signal.h ./src/core/hook/wee-hook-timer.c ./src/core/hook/wee-hook-timer.h +./src/core/hook/wee-hook-url.c +./src/core/hook/wee-hook-url.h ./src/core/wee-arraylist.c ./src/core/wee-arraylist.h ./src/core/wee-backtrace.c diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0e8432748..0769999a4 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -72,6 +72,7 @@ set(LIB_CORE_SRC hook/wee-hook-process.c hook/wee-hook-process.h hook/wee-hook-signal.c hook/wee-hook-signal.h hook/wee-hook-timer.c hook/wee-hook-timer.h + hook/wee-hook-url.c hook/wee-hook-url.h ) # Check for flock support diff --git a/src/core/hook/wee-hook-process.c b/src/core/hook/wee-hook-process.c index 4de66b131..ff676dffc 100644 --- a/src/core/hook/wee-hook-process.c +++ b/src/core/hook/wee-hook-process.c @@ -278,7 +278,9 @@ hook_process_child (struct t_hook *hook_process) { ptr_url++; } - rc = weeurl_download (ptr_url, HOOK_PROCESS(hook_process, options)); + rc = weeurl_download (ptr_url, + HOOK_PROCESS(hook_process, options), + NULL); /* output */ } else if (strncmp (HOOK_PROCESS(hook_process, command), "func:", 5) == 0) { diff --git a/src/core/hook/wee-hook-url.c b/src/core/hook/wee-hook-url.c new file mode 100644 index 000000000..6f00da4f2 --- /dev/null +++ b/src/core/hook/wee-hook-url.c @@ -0,0 +1,419 @@ +/* + * wee-hook-url.c - WeeChat URL hook + * + * Copyright (C) 2023 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat 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. + * + * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/wait.h> +#include <poll.h> +#include <fcntl.h> +#include <errno.h> +#include <pthread.h> + +#include "../weechat.h" +#include "../wee-hashtable.h" +#include "../wee-hook.h" +#include "../wee-infolist.h" +#include "../wee-log.h" +#include "../wee-string.h" +#include "../wee-url.h" +#include "../../gui/gui-chat.h" +#include "../../plugins/plugin.h" + + +/* + * Returns description of hook. + * + * Note: result must be freed after use. + */ + +char * +hook_url_get_description (struct t_hook *hook) +{ + char str_desc[1024]; + + snprintf (str_desc, sizeof (str_desc), + "URL: \"%s\", thread id: %d", + HOOK_URL(hook, url), + 0); + + return strdup (str_desc); +} + +/* + * Displays keys and values of a hashtable. + */ + +void +hook_url_hashtable_map_cb (void *data, struct t_hashtable *hashtable, + const void *key, const void *value) +{ + /* make C compiler happy */ + (void) data; + (void) hashtable; + + gui_chat_printf (NULL, " %s: \"%s\"", + (const char *)key, + (const char *)value); +} + +/* + * Runs callback of url hook. + */ + +void +hook_url_run_callback (struct t_hook *hook) +{ + if (url_debug) + { + gui_chat_printf (NULL, "Running hook_url callback for URL \"%s\":", + HOOK_URL(hook, url)); + gui_chat_printf (NULL, " options:"); + hashtable_map (HOOK_URL(hook, options), &hook_url_hashtable_map_cb, NULL); + gui_chat_printf (NULL, " output:"); + hashtable_map (HOOK_URL(hook, output), &hook_url_hashtable_map_cb, NULL); + } + + (void) (HOOK_URL(hook, callback)) + (hook->callback_pointer, + hook->callback_data, + HOOK_URL(hook, url), + HOOK_URL(hook, options), + HOOK_URL(hook, output)); +} + +/* + * Thread cleanup function: mark thread as not running any more. + */ + +void +hook_url_thread_cleanup (void *hook_pointer) +{ + struct t_hook *hook; + + hook = (struct t_hook *)hook_pointer; + + HOOK_URL(hook, thread_running) = 0; +} + +/* + * URL transfer (in a separate thread). + */ + +void * +hook_url_transfer_thread (void *hook_pointer) +{ + struct t_hook *hook; + + hook = (struct t_hook *)hook_pointer; + + pthread_cleanup_push (&hook_url_thread_cleanup, hook); + + (void) weeurl_download (HOOK_URL(hook, url), + HOOK_URL(hook, options), + HOOK_URL(hook, output)); + + pthread_cleanup_pop (1); + + return NULL; +} + +/* + * Checks if thread is still alive. + */ + +int +hook_url_timer_cb (const void *pointer, void *data, int remaining_calls) +{ + struct t_hook *hook; + const char *ptr_error; + + /* make C compiler happy */ + (void) data; + (void) remaining_calls; + + hook = (struct t_hook *)pointer; + + if (hook->deleted) + return WEECHAT_RC_OK; + + if (!HOOK_URL(hook, thread_running)) + { + hook_url_run_callback (hook); + ptr_error = hashtable_get (HOOK_URL(hook, output), "error"); + if (ptr_error && ptr_error[0]) + { + gui_chat_printf ( + NULL, + _("%sURL transfer error: %s (URL: \"%s\")"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + ptr_error, + HOOK_URL(hook, url)); + } + unhook (hook); + return WEECHAT_RC_OK; + } + + if (remaining_calls == 0) + { + hook_url_run_callback (hook); + if (weechat_debug_core >= 1) + { + gui_chat_printf ( + NULL, + _("End of URL transfer '%s', timeout reached (%.1fs)"), + HOOK_URL(hook, url), + ((float)HOOK_URL(hook, timeout)) / 1000); + } + pthread_cancel (HOOK_URL(hook, thread_id)); + usleep (1000); + unhook (hook); + } + + return WEECHAT_RC_OK; +} + +/* + * Starts transfer for an URL hook. + */ + +void +hook_url_transfer (struct t_hook *hook) +{ + int rc, timeout, max_calls; + long interval; + + HOOK_URL(hook, thread_running) = 1; + + /* create thread */ + rc = pthread_create (&(HOOK_URL(hook, thread_id)), NULL, + &hook_url_transfer_thread, hook); + if (rc != 0) + { + gui_chat_printf (NULL, + _("%sError running thread in hook_url: %s (URL: \"%s\")"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + strerror (rc), + HOOK_URL(hook, url)); + unhook (hook); + return; + } + + /* main thread */ + timeout = HOOK_URL(hook, timeout); + interval = 100; + max_calls = 0; + if (timeout > 0) + { + if (timeout <= 100) + { + interval = timeout; + max_calls = 1; + } + else + { + interval = 100; + max_calls = timeout / 100; + if (timeout % 100 == 0) + max_calls++; + } + } + HOOK_URL(hook, hook_timer) = hook_timer (hook->plugin, + interval, 0, max_calls, + &hook_url_timer_cb, + hook, + NULL); +} + +/* + * Hooks a URL. + * + * Returns pointer to new hook, NULL if error. + */ + +struct t_hook * +hook_url (struct t_weechat_plugin *plugin, + const char *url, + struct t_hashtable *options, + int timeout, + t_hook_callback_url *callback, + const void *callback_pointer, + void *callback_data) +{ + struct t_hook *new_hook; + struct t_hook_url *new_hook_url; + + new_hook = NULL; + new_hook_url = NULL; + + if (!url || !url[0] || !callback) + goto error; + + new_hook = malloc (sizeof (*new_hook)); + if (!new_hook) + goto error; + + new_hook_url = malloc (sizeof (*new_hook_url)); + if (!new_hook_url) + goto error; + + hook_init_data (new_hook, plugin, HOOK_TYPE_URL, HOOK_PRIORITY_DEFAULT, + callback_pointer, callback_data); + + new_hook->hook_data = new_hook_url; + new_hook_url->callback = callback; + new_hook_url->url = strdup (url); + new_hook_url->options = (options) ? hashtable_dup (options) : NULL; + new_hook_url->timeout = timeout; + new_hook_url->thread_id = 0; + new_hook_url->thread_running = 0; + new_hook_url->hook_timer = NULL; + new_hook_url->output = hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + hook_add_to_list (new_hook); + + if (weechat_debug_core >= 1) + { + gui_chat_printf (NULL, + "debug: hook_url: url=\"%s\", " + "options=\"%s\", timeout=%d", + new_hook_url->url, + hashtable_get_string (new_hook_url->options, + "keys_values"), + new_hook_url->timeout); + } + + hook_url_transfer (new_hook); + + return new_hook; + +error: + if (new_hook) + free (new_hook); + if (new_hook_url) + free (new_hook_url); + return NULL; +} + +/* + * Frees data in a url hook. + */ + +void +hook_url_free_data (struct t_hook *hook) +{ + if (!hook || !hook->hook_data) + return; + + if (HOOK_URL(hook, url)) + { + free (HOOK_URL(hook, url)); + HOOK_URL(hook, url) = NULL; + } + if (HOOK_URL(hook, options)) + { + hashtable_free (HOOK_URL(hook, options)); + HOOK_URL(hook, options) = NULL; + } + if (HOOK_URL(hook, hook_timer)) + { + unhook (HOOK_URL(hook, hook_timer)); + HOOK_URL(hook, hook_timer) = NULL; + } + if (HOOK_URL(hook, thread_running)) + { + pthread_cancel (HOOK_URL(hook, thread_id)); + HOOK_URL(hook, thread_running) = 0; + } + if (HOOK_URL(hook, output)) + { + hashtable_free (HOOK_URL(hook, output)); + HOOK_URL(hook, output) = NULL; + } + + free (hook->hook_data); + hook->hook_data = NULL; +} + +/* + * Adds url hook data in the infolist item. + * + * Returns: + * 1: OK + * 0: error + */ + +int +hook_url_add_to_infolist (struct t_infolist_item *item, + struct t_hook *hook) +{ + if (!item || !hook || !hook->hook_data) + return 0; + + if (!infolist_new_var_pointer (item, "callback", HOOK_URL(hook, callback))) + return 0; + if (!infolist_new_var_string (item, "url", HOOK_URL(hook, url))) + return 0; + if (!infolist_new_var_string (item, "options", hashtable_get_string (HOOK_URL(hook, options), "keys_values"))) + return 0; + if (!infolist_new_var_integer (item, "timeout", (int)(HOOK_URL(hook, timeout)))) + return 0; + if (!infolist_new_var_integer (item, "thread_running", (int)(HOOK_URL(hook, thread_running)))) + return 0; + if (!infolist_new_var_pointer (item, "hook_timer", HOOK_URL(hook, hook_timer))) + return 0; + if (!infolist_new_var_string (item, "output", hashtable_get_string (HOOK_URL(hook, output), "keys_values"))) + return 0; + + return 1; +} + +/* + * Prints url hook data in WeeChat log file (usually for crash dump). + */ + +void +hook_url_print_log (struct t_hook *hook) +{ + if (!hook || !hook->hook_data) + return; + + log_printf (" url data:"); + log_printf (" callback. . . . . . . : 0x%lx", HOOK_URL(hook, callback)); + log_printf (" url . . . . . . . . . : '%s'", HOOK_URL(hook, url)); + log_printf (" options . . . . . . . : 0x%lx (hashtable: '%s')", + HOOK_URL(hook, options), + hashtable_get_string (HOOK_URL(hook, options), + "keys_values")); + log_printf (" timeout . . . . . . . : %ld", HOOK_URL(hook, timeout)); + log_printf (" thread_running. . . . : %d", (int)HOOK_URL(hook, thread_running)); + log_printf (" hook_timer. . . . . . : 0x%lx", HOOK_URL(hook, hook_timer)); + log_printf (" output. . . . . . . . : 0x%lx (hashtable: '%s')", + HOOK_URL(hook, output), + hashtable_get_string (HOOK_URL(hook, output), + "keys_values")); +} diff --git a/src/core/hook/wee-hook-url.h b/src/core/hook/wee-hook-url.h new file mode 100644 index 000000000..1b306659b --- /dev/null +++ b/src/core/hook/wee-hook-url.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 Sébastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat 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. + * + * WeeChat 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 WeeChat. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WEECHAT_HOOK_URL_H +#define WEECHAT_HOOK_URL_H + +struct t_weechat_plugin; +struct t_infolist_item; +struct t_hashtable; + +#define HOOK_URL(hook, var) (((struct t_hook_url *)hook->hook_data)->var) + +typedef int (t_hook_callback_url)(const void *pointer, void *data, + const char *url, + struct t_hashtable *options, + struct t_hashtable *output); + +struct t_hook_url +{ + t_hook_callback_url *callback; /* URL callback */ + char *url; /* URL */ + struct t_hashtable *options; /* URL options (see doc) */ + long timeout; /* timeout (ms) (0 = no timeout) */ + pthread_t thread_id; /* thread id */ + int thread_running; /* 1 if thread is running */ + struct t_hook *hook_timer; /* timer to check if thread has ended*/ + struct t_hashtable *output; /* URL transfer output data */ +}; + +extern char *hook_url_get_description (struct t_hook *hook); +extern struct t_hook *hook_url (struct t_weechat_plugin *plugin, + const char *url, + struct t_hashtable *options, + int timeout, + t_hook_callback_url *callback, + const void *callback_pointer, + void *callback_data); +extern void hook_url_free_data (struct t_hook *hook); +extern int hook_url_add_to_infolist (struct t_infolist_item *item, + struct t_hook *hook); +extern void hook_url_print_log (struct t_hook *hook); + +#endif /* WEECHAT_HOOK_URL_H */ diff --git a/src/core/wee-command.c b/src/core/wee-command.c index 6269b945c..64fb1ea08 100644 --- a/src/core/wee-command.c +++ b/src/core/wee-command.c @@ -63,6 +63,7 @@ #include "wee-string.h" #include "wee-sys.h" #include "wee-upgrade.h" +#include "wee-url.h" #include "wee-utf8.h" #include "wee-util.h" #include "wee-version.h" @@ -2223,6 +2224,15 @@ COMMAND_CALLBACK(debug) return WEECHAT_RC_OK; } + if (string_strcmp (argv[1], "url") == 0) + { + url_debug ^= 1; + gui_chat_printf (NULL, + _("Debug hook_url: %s"), + (url_debug) ? _("enabled") : _("disabled")); + return WEECHAT_RC_OK; + } + if (string_strcmp (argv[1], "windows") == 0) { debug_windows_tree (); @@ -7056,14 +7066,17 @@ COMMAND_CALLBACK(upgrade) } /* - * it is forbidden to upgrade while there are some background process - * (hook type "process" or "connect") + * it is forbidden to upgrade while there are some background process or + * thread (hook types: process, connect, url) */ - if (weechat_hooks[HOOK_TYPE_PROCESS] || weechat_hooks[HOOK_TYPE_CONNECT]) + if (weechat_hooks[HOOK_TYPE_PROCESS] + || weechat_hooks[HOOK_TYPE_CONNECT] + || weechat_hooks[HOOK_TYPE_URL]) { gui_chat_printf (NULL, _("%sCan't upgrade: there is one or more background " - "process (hook type 'process' or 'connect')"), + "process/thread running (hook type: process, " + "connect or url)"), gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]); return WEECHAT_RC_OK; } @@ -8189,7 +8202,7 @@ command_init () " || set <plugin> <level>" " || dump|hooks [<plugin>]" " || buffer|certs|color|dirs|infolists|libs|memory|tags|" - "term|windows" + "term|url|windows" " || callbacks <duration>[<unit>]" " || mouse|cursor [verbose]" " || hdata [free]" @@ -8227,6 +8240,7 @@ command_init () " mouse: toggle debug for mouse\n" " tags: display tags for lines\n" " term: display infos about terminal\n" + " url: toggle debug for calls to hook_url (display output hashtable)\n" " windows: display windows tree\n" " time: measure time to execute a command or to send text to " "the current buffer\n" @@ -8256,6 +8270,7 @@ command_init () " || mouse verbose" " || tags" " || term" + " || url" " || windows" " || time %(commands:/)" " || unicode", diff --git a/src/core/wee-hook.c b/src/core/wee-hook.c index b6bf36d96..758052a72 100644 --- a/src/core/wee-hook.c +++ b/src/core/wee-hook.c @@ -46,7 +46,7 @@ char *hook_type_string[HOOK_NUM_TYPES] = { "command", "command_run", "timer", "fd", "process", "connect", "line", "print", "signal", "hsignal", "config", "completion", "modifier", - "info", "info_hashtable", "infolist", "hdata", "focus" }; + "info", "info_hashtable", "infolist", "hdata", "focus", "url" }; struct t_hook *weechat_hooks[HOOK_NUM_TYPES]; /* list of hooks */ struct t_hook *last_weechat_hook[HOOK_NUM_TYPES]; /* last hook */ int hooks_count[HOOK_NUM_TYPES]; /* number of hooks */ @@ -59,50 +59,98 @@ int hook_socketpair_ok = 0; /* 1 if socketpair() is OK */ /* hook callbacks */ t_callback_hook *hook_callback_add[HOOK_NUM_TYPES] = { NULL, NULL, NULL, &hook_fd_add_cb, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; t_callback_hook *hook_callback_remove[HOOK_NUM_TYPES] = { NULL, NULL, NULL, &hook_fd_remove_cb, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; t_callback_hook *hook_callback_free_data[HOOK_NUM_TYPES] = -{ &hook_command_free_data, &hook_command_run_free_data, - &hook_timer_free_data, &hook_fd_free_data, - &hook_process_free_data, &hook_connect_free_data, - &hook_line_free_data, &hook_print_free_data, - &hook_signal_free_data, &hook_hsignal_free_data, - &hook_config_free_data, &hook_completion_free_data, - &hook_modifier_free_data, &hook_info_free_data, - &hook_info_hashtable_free_data, &hook_infolist_free_data, - &hook_hdata_free_data, &hook_focus_free_data }; +{ + &hook_command_free_data, + &hook_command_run_free_data, + &hook_timer_free_data, + &hook_fd_free_data, + &hook_process_free_data, + &hook_connect_free_data, + &hook_line_free_data, + &hook_print_free_data, + &hook_signal_free_data, + &hook_hsignal_free_data, + &hook_config_free_data, + &hook_completion_free_data, + &hook_modifier_free_data, + &hook_info_free_data, + &hook_info_hashtable_free_data, + &hook_infolist_free_data, + &hook_hdata_free_data, + &hook_focus_free_data, + &hook_url_free_data, +}; t_callback_hook_get_desc *hook_callback_get_desc[HOOK_NUM_TYPES] = -{ &hook_command_get_description, &hook_command_run_get_description, - &hook_timer_get_description, &hook_fd_get_description, - &hook_process_get_description, &hook_connect_get_description, - &hook_line_get_description, &hook_print_get_description, - &hook_signal_get_description, &hook_hsignal_get_description, - &hook_config_get_description, &hook_completion_get_description, - &hook_modifier_get_description, &hook_info_get_description, - &hook_info_hashtable_get_description, &hook_infolist_get_description, - &hook_hdata_get_description, &hook_focus_get_description }; +{ + &hook_command_get_description, + &hook_command_run_get_description, + &hook_timer_get_description, + &hook_fd_get_description, + &hook_process_get_description, + &hook_connect_get_description, + &hook_line_get_description, + &hook_print_get_description, + &hook_signal_get_description, + &hook_hsignal_get_description, + &hook_config_get_description, + &hook_completion_get_description, + &hook_modifier_get_description, + &hook_info_get_description, + &hook_info_hashtable_get_description, + &hook_infolist_get_description, + &hook_hdata_get_description, + &hook_focus_get_description, + &hook_url_get_description, +}; t_callback_hook_infolist *hook_callback_add_to_infolist[HOOK_NUM_TYPES] = -{ &hook_command_add_to_infolist, &hook_command_run_add_to_infolist, - &hook_timer_add_to_infolist, &hook_fd_add_to_infolist, - &hook_process_add_to_infolist, &hook_connect_add_to_infolist, - &hook_line_add_to_infolist, &hook_print_add_to_infolist, - &hook_signal_add_to_infolist, &hook_hsignal_add_to_infolist, - &hook_config_add_to_infolist, &hook_completion_add_to_infolist, - &hook_modifier_add_to_infolist, &hook_info_add_to_infolist, - &hook_info_hashtable_add_to_infolist, &hook_infolist_add_to_infolist, - &hook_hdata_add_to_infolist, &hook_focus_add_to_infolist }; +{ + &hook_command_add_to_infolist, + &hook_command_run_add_to_infolist, + &hook_timer_add_to_infolist, + &hook_fd_add_to_infolist, + &hook_process_add_to_infolist, + &hook_connect_add_to_infolist, + &hook_line_add_to_infolist, + &hook_print_add_to_infolist, + &hook_signal_add_to_infolist, + &hook_hsignal_add_to_infolist, + &hook_config_add_to_infolist, + &hook_completion_add_to_infolist, + &hook_modifier_add_to_infolist, + &hook_info_add_to_infolist, + &hook_info_hashtable_add_to_infolist, + &hook_infolist_add_to_infolist, + &hook_hdata_add_to_infolist, + &hook_focus_add_to_infolist, + &hook_url_add_to_infolist, +}; t_callback_hook *hook_callback_print_log[HOOK_NUM_TYPES] = -{ &hook_command_print_log, &hook_command_run_print_log, - &hook_timer_print_log, &hook_fd_print_log, - &hook_process_print_log, &hook_connect_print_log, - &hook_line_print_log, &hook_print_print_log, - &hook_signal_print_log, &hook_hsignal_print_log, - &hook_config_print_log, &hook_completion_print_log, - &hook_modifier_print_log, &hook_info_print_log, - &hook_info_hashtable_print_log, &hook_infolist_print_log, - &hook_hdata_print_log, &hook_focus_print_log }; +{ + &hook_command_print_log, + &hook_command_run_print_log, + &hook_timer_print_log, + &hook_fd_print_log, + &hook_process_print_log, + &hook_connect_print_log, + &hook_line_print_log, + &hook_print_print_log, + &hook_signal_print_log, + &hook_hsignal_print_log, + &hook_config_print_log, + &hook_completion_print_log, + &hook_modifier_print_log, + &hook_info_print_log, + &hook_info_hashtable_print_log, + &hook_infolist_print_log, + &hook_hdata_print_log, + &hook_focus_print_log, + &hook_url_print_log, +}; /* diff --git a/src/core/wee-hook.h b/src/core/wee-hook.h index 04b17c921..bebb0419e 100644 --- a/src/core/wee-hook.h +++ b/src/core/wee-hook.h @@ -40,6 +40,7 @@ struct t_hook; #include "hook/wee-hook-process.h" #include "hook/wee-hook-signal.h" #include "hook/wee-hook-timer.h" +#include "hook/wee-hook-url.h" struct t_gui_bar; struct t_gui_buffer; @@ -73,6 +74,7 @@ enum t_hook_type HOOK_TYPE_INFOLIST, /* get some info as infolist */ HOOK_TYPE_HDATA, /* get hdata pointer */ HOOK_TYPE_FOCUS, /* focus event (mouse/key) */ + HOOK_TYPE_URL, /* URL transfer */ /* number of hook types */ HOOK_NUM_TYPES, }; diff --git a/src/core/wee-url.c b/src/core/wee-url.c index 2c6be1f4f..7ec89b94f 100644 --- a/src/core/wee-url.c +++ b/src/core/wee-url.c @@ -44,6 +44,7 @@ { #__name, CURLOPT_##__name, URL_TYPE_##__type, __constants } +int url_debug = 0; char *url_type_string[] = { "string", "long", "long long", "mask", "list" }; /* @@ -1013,8 +1014,6 @@ struct t_url_option url_options[] = { NULL, 0, 0, NULL }, }; -char url_error[CURL_ERROR_SIZE + 1]; - /* * Searches for a constant in array of constants. @@ -1116,7 +1115,7 @@ weeurl_search_option (const char *name) */ size_t -weeurl_read (void *buffer, size_t size, size_t nmemb, void *stream) +weeurl_read_stream (void *buffer, size_t size, size_t nmemb, void *stream) { return (stream) ? fread (buffer, size, nmemb, stream) : 0; } @@ -1126,12 +1125,27 @@ weeurl_read (void *buffer, size_t size, size_t nmemb, void *stream) */ size_t -weeurl_write (void *buffer, size_t size, size_t nmemb, void *stream) +weeurl_write_stream (void *buffer, size_t size, size_t nmemb, void *stream) { return (stream) ? fwrite (buffer, size, nmemb, stream) : 0; } /* + * Adds data to a dynamic string (callback called to catch stdout). + */ + +size_t +weeurl_write_string (void *buffer, size_t size, size_t nmemb, void *string) +{ + if (!string) + return 0; + + string_dyn_concat ((char **)string, buffer, size * nmemb); + + return size * nmemb; +} + +/* * Sets option in CURL easy handle (callback called for each option in hashtable * "options"). */ @@ -1303,6 +1317,17 @@ weeurl_set_proxy (CURL *curl, struct t_proxy *proxy) /* * Downloads URL using options. * + * If output is not NULL, it must be a hashtable with keys and values of type + * "string". The following keys may be added in the hashtable, + * depending on the success or error of the URL transfer: + * + * key | description + * --------------|-------------------------------------------------------- + * response_code | HTTP response code (as string) + * headers | HTTP headers in response + * output | stdout (set only if "file_out" was not set in options) + * error | error message (set only in case of error) + * * Returns: * 0: OK * 1: invalid URL @@ -1312,20 +1337,28 @@ weeurl_set_proxy (CURL *curl, struct t_proxy *proxy) */ int -weeurl_download (const char *url, struct t_hashtable *options) +weeurl_download (const char *url, struct t_hashtable *options, + struct t_hashtable *output) { CURL *curl; struct t_url_file url_file[2]; char *url_file_option[2] = { "file_in", "file_out" }; char *url_file_mode[2] = { "rb", "wb" }; + char url_error[CURL_ERROR_SIZE + 1], **string_headers, **string_output; + char str_response_code[32]; CURLoption url_file_opt_func[2] = { CURLOPT_READFUNCTION, CURLOPT_WRITEFUNCTION }; CURLoption url_file_opt_data[2] = { CURLOPT_READDATA, CURLOPT_WRITEDATA }; - void *url_file_opt_cb[2] = { &weeurl_read, &weeurl_write }; + void *url_file_opt_cb[2] = { &weeurl_read_stream, &weeurl_write_stream }; struct t_proxy *ptr_proxy; - int rc, curl_rc, i; + int rc, curl_rc, i, output_to_file; + long response_code; rc = 0; + string_headers = NULL; + string_output = NULL; + url_error[0] = '\0'; + for (i = 0; i < 2; i++) { url_file[i].filename = NULL; @@ -1334,6 +1367,7 @@ weeurl_download (const char *url, struct t_hashtable *options) if (!url || !url[0]) { + snprintf (url_error, sizeof (url_error), "%s", _("invalid URL")); rc = 1; goto end; } @@ -1341,6 +1375,7 @@ weeurl_download (const char *url, struct t_hashtable *options) curl = curl_easy_init (); if (!curl) { + snprintf (url_error, sizeof (url_error), "%s", _("not enough memory")); rc = 3; goto end; } @@ -1358,7 +1393,19 @@ weeurl_download (const char *url, struct t_hashtable *options) weeurl_set_proxy (curl, ptr_proxy); } + /* set callback to retrieve HTTP headers */ + if (output) + { + string_headers = string_dyn_alloc (1024); + if (string_headers) + { + curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, &weeurl_write_string); + curl_easy_setopt (curl, CURLOPT_HEADERDATA, string_headers); + } + } + /* set file in/out from options in hashtable */ + output_to_file = 0; if (options) { for (i = 0; i < 2; i++) @@ -1369,15 +1416,33 @@ weeurl_download (const char *url, struct t_hashtable *options) url_file[i].stream = fopen (url_file[i].filename, url_file_mode[i]); if (!url_file[i].stream) { + snprintf (url_error, sizeof (url_error), + (i == 0) ? + _("file \"%s\" not found") : + _("can not write file \"%s\""), + url_file[i].filename); rc = 4; goto end; } curl_easy_setopt (curl, url_file_opt_func[i], url_file_opt_cb[i]); curl_easy_setopt (curl, url_file_opt_data[i], url_file[i].stream); + if (i == 1) + output_to_file = 1; } } } + /* redirect stdout if no filename was given (via key "file_out") */ + if (output && !output_to_file) + { + string_output = string_dyn_alloc (1024); + if (string_output) + { + curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &weeurl_write_string); + curl_easy_setopt (curl, CURLOPT_WRITEDATA, string_output); + } + } + /* set other options in hashtable */ hashtable_map (options, &weeurl_option_map_cb, curl); @@ -1386,11 +1451,36 @@ weeurl_download (const char *url, struct t_hashtable *options) /* perform action! */ curl_rc = curl_easy_perform (curl); - if (curl_rc != CURLE_OK) + if (curl_rc == CURLE_OK) + { + if (output) + { + curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &response_code); + snprintf (str_response_code, sizeof (str_response_code), + "%ld", response_code); + hashtable_set (output, "response_code", str_response_code); + } + } + else { - fprintf (stderr, - _("curl error %d (%s) (URL: \"%s\")\n"), - curl_rc, url_error, url); + if (output) + { + if (!url_error[0]) + { + snprintf (url_error, sizeof (url_error), + "%s", _("transfer error")); + } + } + else + { + /* + * URL transfer done in a forked process: display error on stderr, + * which will be sent to the hook_process callback + */ + fprintf (stderr, + _("curl error %d (%s) (URL: \"%s\")\n"), + curl_rc, url_error, url); + } rc = 2; } @@ -1403,6 +1493,21 @@ end: if (url_file[i].stream) fclose (url_file[i].stream); } + if (output) + { + if (string_headers) + { + hashtable_set (output, "headers", *string_headers); + string_dyn_free (string_headers, 1); + } + if (string_output) + { + hashtable_set (output, "output", *string_output); + string_dyn_free (string_output, 1); + } + if (url_error[0]) + hashtable_set (output, "error", url_error); + } return rc; } diff --git a/src/core/wee-url.h b/src/core/wee-url.h index a70e29842..cfa8f6f00 100644 --- a/src/core/wee-url.h +++ b/src/core/wee-url.h @@ -54,10 +54,12 @@ struct t_url_file FILE *stream; /* file stream */ }; +extern int url_debug; extern char *url_type_string[]; extern struct t_url_option url_options[]; -extern int weeurl_download (const char *url, struct t_hashtable *options); +extern int weeurl_download (const char *url, struct t_hashtable *options, + struct t_hashtable *output); extern int weeurl_option_add_to_infolist (struct t_infolist *infolist, struct t_url_option *option); diff --git a/src/plugins/plugin.c b/src/plugins/plugin.c index f2e290bc3..91836e3e2 100644 --- a/src/plugins/plugin.c +++ b/src/plugins/plugin.c @@ -798,6 +798,7 @@ plugin_load (const char *filename, int init_plugin, int argc, char **argv) new_plugin->hook_fd = &hook_fd; new_plugin->hook_process = &hook_process; new_plugin->hook_process_hashtable = &hook_process_hashtable; + new_plugin->hook_url = &hook_url; new_plugin->hook_connect = &hook_connect; new_plugin->hook_line = &hook_line; new_plugin->hook_print = &hook_print; diff --git a/src/plugins/weechat-plugin.h b/src/plugins/weechat-plugin.h index 3e425ff6e..ae27b9bdd 100644 --- a/src/plugins/weechat-plugin.h +++ b/src/plugins/weechat-plugin.h @@ -68,7 +68,7 @@ struct timeval; * please change the date with current one; for a second change at same * date, increment the 01, otherwise please keep 01. */ -#define WEECHAT_PLUGIN_API_VERSION "20230706-01" +#define WEECHAT_PLUGIN_API_VERSION "20230908-01" /* macros for defining plugin infos */ #define WEECHAT_PLUGIN_NAME(__name) \ @@ -748,6 +748,17 @@ struct t_weechat_plugin const char *err), const void *callback_pointer, void *callback_data); + struct t_hook *(*hook_url) (struct t_weechat_plugin *plugin, + const char *url, + struct t_hashtable *options, + int timeout, + int (*callback)(const void *pointer, + void *data, + const char *url, + struct t_hashtable *options, + struct t_hashtable *output), + const void *callback_pointer, + void *callback_data); struct t_hook *(*hook_connect) (struct t_weechat_plugin *plugin, const char *proxy, const char *address, @@ -1810,6 +1821,12 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin); __callback, \ __callback_pointer, \ __callback_data) +#define weechat_hook_url(__command, __options, __timeout, \ + __callback, __callback_pointer, \ + __callback_data) \ + (weechat_plugin->hook_url)(weechat_plugin, __command, __options, \ + __timeout, __callback, \ + __callback_pointer, __callback_data) #define weechat_hook_connect(__proxy, __address, __port, __ipv6, \ __retry, __gnutls_sess, __gnutls_cb, \ __gnutls_dhkey_size, __gnutls_priorities, \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 84701e431..bab962a58 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -139,6 +139,10 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") endif() endif() +if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Haiku") + list(APPEND EXTRA_LIBS "pthread") +endif() + # binary to run tests set(WEECHAT_TESTS_SRC tests.cpp tests.h |