From 706f9bca82d463f6f1bd17d5dc609807e4a1e8a9 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Sat, 2 Sep 2017 17:52:25 -0400 Subject: python: Rewrite with CFFI --- plugins/python/_hexchat.py | 364 ++++ plugins/python/generate_plugin.py | 89 + plugins/python/hexchat.py | 1 + plugins/python/meson.build | 14 +- plugins/python/python.c | 2834 -------------------------------- plugins/python/python.def | 1 - plugins/python/python.py | 497 ++++++ plugins/python/python2.vcxproj | 8 +- plugins/python/python3.vcxproj | 15 +- plugins/python/python3.vcxproj.filters | 19 +- plugins/python/xchat.py | 1 + 11 files changed, 1000 insertions(+), 2843 deletions(-) create mode 100644 plugins/python/_hexchat.py create mode 100755 plugins/python/generate_plugin.py create mode 100644 plugins/python/hexchat.py delete mode 100644 plugins/python/python.c create mode 100644 plugins/python/python.py create mode 100644 plugins/python/xchat.py (limited to 'plugins') diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py new file mode 100644 index 00000000..52b3ec14 --- /dev/null +++ b/plugins/python/_hexchat.py @@ -0,0 +1,364 @@ +from contextlib import contextmanager +import inspect +import sys +from _hexchat_embedded import ffi, lib + +__all__ = [ + 'EAT_ALL', 'EAT_HEXCHAT', 'EAT_NONE', 'EAT_PLUGIN', 'EAT_XCHAT', + 'PRI_HIGH', 'PRI_HIGHEST', 'PRI_LOW', 'PRI_LOWEST', 'PRI_NORM', + '__doc__', '__version__', 'command', 'del_pluginpref', 'emit_print', + 'find_context', 'get_context', 'get_info', + 'get_list', 'get_lists', 'get_pluginpref', 'get_prefs', 'hook_command', + 'hook_print', 'hook_print_attrs', 'hook_server', 'hook_server_attrs', + 'hook_timer', 'hook_unload', 'list_pluginpref', 'nickcmp', 'prnt', + 'set_pluginpref', 'strip', 'unhook', +] + +__doc__ = 'HexChat Scripting Interface' +__version__ = (2, 0) +__license__ = 'GPL-2.0+' + +EAT_NONE = 0 +EAT_HEXCHAT = 1 +EAT_XCHAT = EAT_HEXCHAT +EAT_PLUGIN = 2 +EAT_ALL = EAT_HEXCHAT | EAT_PLUGIN + +PRI_LOWEST = -128 +PRI_LOW = -64 +PRI_NORM = 0 +PRI_HIGH = 64 +PRI_HIGHEST = 127 + + +# We need each module to be able to reference their parent plugin +# which is a bit tricky since they all share the exact same module. +# Simply navigating up to what module called it seems to actually +# be a fairly reliable and simple method of doing so if ugly. +def __get_current_plugin(): + frame = inspect.stack()[1][0] + while '__plugin' not in frame.f_globals: + frame = frame.f_back + assert frame is not None + return frame.f_globals['__plugin'] + + +# Keeping API compat +if sys.version_info[0] is 2: + def __decode(string): + return string +else: + def __decode(string): + return string.decode() + + +# ------------ API ------------ +def prnt(string): + lib.hexchat_print(lib.ph, string.encode()) + + +def emit_print(event_name, *args, **kwargs): + time = kwargs.pop('time', 0) # For py2 compat + cargs = [] + for i in range(4): + arg = args[i].encode() if len(args) > i else b'' + cstring = ffi.new('char[]', arg) + cargs.append(cstring) + if time is 0: + return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs) + else: + attrs = lib.hexchat_event_attrs_create(lib.ph) + attrs.server_time_utc = time + ret = lib.hexchat_emit_print(lib.ph, attrs, event_name.encode(), *cargs) + lib.hexchat_event_attrs_free(lib.ph, attrs) + return ret + + +def command(command): + lib.hexchat_command(lib.ph, command.encode()) + + +def nickcmp(string1, string2): + return lib.hexchat_nickcmp(lib.ph, string1.encode(), string2.encode()) + + +def strip(text, length=-1, flags=3): + stripped = lib.hexchat_strip(lib.ph, text.encode(), length, flags) + ret = __decode(ffi.string(stripped)) + lib.hexchat_free(lib.ph, stripped) + return ret + + +def get_info(name): + ret = lib.hexchat_get_info(lib.ph, name.encode()) + if ret == ffi.NULL: + return None + if name in ('gtkwin_ptr', 'win_ptr'): + # Surely there is a less dumb way? + ptr = repr(ret).rsplit(' ', 1)[1][:-1] + return ptr + return __decode(ffi.string(ret)) + + +def get_prefs(name): + string_out = ffi.new('char**') + int_out = ffi.new('int*') + type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out) + if type is 0: + return None + elif type is 1: + return __decode(ffi.string(string_out[0])) + elif type is 2 or type is 3: # XXX: 3 should be a bool, but keeps API + return int_out[0] + else: + assert False + + +def __cstrarray_to_list(arr): + i = 0 + ret = [] + while arr[i] != ffi.NULL: + ret.append(ffi.string(arr[i])) + i += 1 + return ret + + +__FIELD_CACHE = {} + + +def __get_fields(name): + return __FIELD_CACHE.setdefault(name, + __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name))) + + +__FIELD_PROPERTY_CACHE = {} + + +def __cached_decoded_str(string): + return __FIELD_PROPERTY_CACHE.setdefault(string, __decode(string)) + + +def get_lists(): + return [__cached_decoded_str(field) for field in __get_fields(b'lists')] + + +class ListItem: + def __init__(self, name): + self._listname = name + + def __repr__(self): + return '<{} list item at {}>'.format(self._listname, id(self)) + + +def get_list(name): + # XXX: This function is extremely inefficient and could be interators and + # lazily loaded properties, but for API compat we stay slow + orig_name = name + name = name.encode() + + if name not in __get_fields(b'lists'): + raise KeyError('list not available') + + list_ = lib.hexchat_list_get(lib.ph, name) + if list_ == ffi.NULL: + return None + + ret = [] + fields = __get_fields(name) + + def string_getter(field): + string = lib.hexchat_list_str(lib.ph, list_, field) + if string != ffi.NULL: + return __decode(ffi.string(string)) + return '' + + def ptr_getter(field): + if field == b'context': + ptr = lib.hexchat_list_str(lib.ph, list_, field) + ctx = ffi.cast('hexchat_context*', ptr) + return Context(ctx) + return None + + getters = { + ord('s'): string_getter, + ord('i'): lambda field: lib.hexchat_list_int(lib.ph, list_, field), + ord('t'): lambda field: lib.hexchat_list_time(lib.ph, list_, field), + ord('p'): ptr_getter, + } + + while lib.hexchat_list_next(lib.ph, list_) is 1: + item = ListItem(orig_name) + for field in fields: + getter = getters.get(ord(field[0])) + if getter is not None: + field_name = field[1:] + setattr(item, __cached_decoded_str(field_name), getter(field_name)) + ret.append(item) + + lib.hexchat_list_free(lib.ph, list_) + return ret + + +def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None): + plugin = __get_current_plugin() + hook = plugin.add_hook(callback, userdata) + handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook, + help.encode() if help is not None else ffi.NULL, + hook.handle) + hook.hexchat_hook = handle + return id(hook) + + +def hook_print(name, callback, userdata=None, priority=PRI_NORM): + plugin = __get_current_plugin() + hook = plugin.add_hook(callback, userdata) + handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, + hook.handle) + hook.hexchat_hook = handle + return id(hook) + + +def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM): + plugin = __get_current_plugin() + hook = plugin.add_hook(callback, userdata) + handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, + hook.handle) + hook.hexchat_hook = handle + return id(hook) + + +def hook_server(name, callback, userdata=None, priority=PRI_NORM): + plugin = __get_current_plugin() + hook = plugin.add_hook(callback, userdata) + handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, + hook.handle) + hook.hexchat_hook = handle + return id(hook) + + +def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM): + plugin = __get_current_plugin() + hook = plugin.add_hook(callback, userdata) + handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, + hook.handle) + hook.hexchat_hook = handle + return id(hook) + + +def hook_timer(timeout, callback, userdata=None): + plugin = __get_current_plugin() + hook = plugin.add_hook(callback, userdata) + handle = lib.hexchat_hook_timer(lib.ph, timeout, lib._on_timer_hook, hook.handle) + hook.hexchat_hook = handle + return id(hook) + + +def hook_unload(callback, userdata=None): + plugin = __get_current_plugin() + hook = plugin.add_hook(callback, userdata, is_unload=True) + return id(hook) + + +def unhook(handle): + plugin = __get_current_plugin() + return plugin.remove_hook(handle) + + +def set_pluginpref(name, value): + if isinstance(value, str): + return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode())) + elif isinstance(value, int): + return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value)) + else: + # XXX: This should probably raise but this keeps API + return False + + +def get_pluginpref(name): + name = name.encode() + string_out = ffi.new('char[512]') + if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) is not 1: + return None + + string = ffi.string(string_out) + # This API stores everything as a string so we have to figure out what + # its actual type was supposed to be. + if len(string) > 12: # Can't be a number + return __decode(string) + + number = lib.hexchat_pluginpref_get_int(lib.ph, name) + if number == -1 and string != b'-1': + return __decode(string) + + return number + + +def del_pluginpref(name): + return bool(lib.hexchat_pluginpref_delete(lib.ph, name.encode())) + + +def list_pluginpref(): + prefs_str = ffi.new('char[4096]') + if lib.hexchat_pluginpref_list(lib.ph, prefs_str) is 1: + return __decode(prefs_str).split(',') + return [] + + +class Context: + def __init__(self, ctx): + self._ctx = ctx + + def __eq__(self, value): + if not isinstance(value, Context): + return False + return self._ctx == value._ctx + + @contextmanager + def __change_context(self): + old_ctx = lib.hexchat_get_context(lib.ph) + if not self.set(): + # XXX: Behavior change, previously used wrong context + lib.hexchat_print(lib.ph, + b'Context object refers to closed context, ignoring call') + return + yield + lib.hexchat_set_context(lib.ph, old_ctx) + + def set(self): + # XXX: API addition, C plugin silently ignored failure + return bool(lib.hexchat_set_context(lib.ph, self._ctx)) + + def prnt(self, string): + with self.__change_context(): + prnt(string) + + def emit_print(self, event_name, *args, **kwargs): + time = kwargs.pop('time', 0) # For py2 compat + with self.__change_context(): + return emit_print(event_name, *args, time=time) + + def command(self, string): + with self.__change_context(): + command(string) + + def get_info(self, name): + with self.__change_context(): + return get_info(name) + + def get_list(self, name): + with self.__change_context(): + return get_list(name) + + +def get_context(): + ctx = lib.hexchat_get_context(lib.ph) + return Context(ctx) + + +def find_context(server=None, channel=None): + server = server.encode() if server is not None else ffi.NULL + channel = channel.encode() if channel is not None else ffi.NULL + ctx = lib.hexchat_find_context(lib.ph, server, channel) + if ctx == ffi.NULL: + return None + return Context(ctx) diff --git a/plugins/python/generate_plugin.py b/plugins/python/generate_plugin.py new file mode 100755 index 00000000..5c52b37b --- /dev/null +++ b/plugins/python/generate_plugin.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +import sys +import cffi + +builder = cffi.FFI() + +# hexchat-plugin.h +with open(sys.argv[1]) as f: + output = [] + eat_until_endif = 0 + # This is very specific to hexchat-plugin.h, it is not a cpp + for line in f: + if line.startswith('#define'): + continue + elif line.endswith('HEXCHAT_PLUGIN_H\n'): + continue + elif 'time.h' in line: + output.append('typedef int... time_t;') + elif line.startswith('#if'): + eat_until_endif += 1 + elif line.startswith('#endif'): + eat_until_endif -= 1 + elif eat_until_endif and '_hexchat_context' not in line: + continue + else: + output.append(line) + builder.cdef(''.join(output)) + +builder.embedding_api(''' +extern "Python" int _on_py_command(char **, char **, void *); +extern "Python" int _on_load_command(char **, char **, void *); +extern "Python" int _on_unload_command(char **, char **, void *); +extern "Python" int _on_reload_command(char **, char **, void *); +extern "Python" int _on_say_command(char **, char **, void *); + +extern "Python" int _on_command_hook(char **, char **, void *); +extern "Python" int _on_print_hook(char **, void *); +extern "Python" int _on_print_attrs_hook(char **, hexchat_event_attrs *, void *); +extern "Python" int _on_server_hook(char **, char **, void *); +extern "Python" int _on_server_attrs_hook(char **, char **, hexchat_event_attrs *, void *); +extern "Python" int _on_timer_hook(void *); + +extern "Python" int _on_plugin_init(char **, char **, char **, char *, char *); +extern "Python" int _on_plugin_deinit(void); + +static hexchat_plugin *ph; +''') + +builder.set_source('_hexchat_embedded', ''' +/* Python's header defines these.. */ +#undef HAVE_MEMRCHR +#undef HAVE_STRINGS_H + +#include "config.h" +#include "hexchat-plugin.h" + +static hexchat_plugin *ph; +CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *); +CFFI_DLLEXPORT int _on_plugin_deinit(void); + +int hexchat_plugin_init(hexchat_plugin *plugin_handle, + char **name_out, char **description_out, + char **version_out, char *arg) +{ + if (ph != NULL) + { + puts ("Python plugin already loaded\\n"); + return 0; /* Prevent loading twice */ + } + + ph = plugin_handle; + return _on_plugin_init(name_out, description_out, version_out, arg, HEXCHATLIBDIR); +} + +int hexchat_plugin_deinit(void) +{ + int ret = _on_plugin_deinit(); + ph = NULL; + return ret; +} +''') + +# python.py +with open(sys.argv[2]) as f: + builder.embedding_init_code(f.read()) + +# python.c +builder.emit_c_code(sys.argv[3]) diff --git a/plugins/python/hexchat.py b/plugins/python/hexchat.py new file mode 100644 index 00000000..6922490b --- /dev/null +++ b/plugins/python/hexchat.py @@ -0,0 +1 @@ +from _hexchat import * diff --git a/plugins/python/meson.build b/plugins/python/meson.build index e24f0c6f..2ad5128e 100644 --- a/plugins/python/meson.build +++ b/plugins/python/meson.build @@ -5,8 +5,18 @@ else python_dep = dependency(python_opt, version: '>= 2.7') endif -shared_module('python', 'python.c', - dependencies: [libgio_dep, hexchat_plugin_dep, python_dep], +python3_source = custom_target('python-bindings', + input: ['../../src/common/hexchat-plugin.h', 'python.py'], + output: 'python.c', + command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@'] +) + +install_data(['_hexchat.py', 'hexchat.py', 'xchat.py'], + install_dir: join_paths(get_option('libdir'), 'hexchat/python') +) + +shared_module('python', python3_source, + dependencies: [hexchat_plugin_dep, python_dep], install: true, install_dir: plugindir, name_prefix: '', diff --git a/plugins/python/python.c b/plugins/python/python.c deleted file mode 100644 index 4403474d..00000000 --- a/plugins/python/python.c +++ /dev/null @@ -1,2834 +0,0 @@ -/* -* Copyright (c) 2002-2003 Gustavo Niemeyer -* -* XChat Python Plugin Interface -* -* Xchat Python Plugin Interface is free software; you can redistribute -* it and/or modify it under the terms of the GNU General Public License -* as published by the Free Software Foundation; either version 2 of the -* License, or (at your option) any later version. -* -* pybot is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this file; if not, write to the Free Software -* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA -*/ - -/* Thread support - * ============== - * - * The python interpreter has a global interpreter lock. Any thread - * executing must acquire it before working with data accessible from - * python code. Here we must also care about xchat not being - * thread-safe. We do this by using an xchat lock, which protects - * xchat instructions from being executed out of time (when this - * plugin is not "active"). - * - * When xchat calls python code: - * - Change the current_plugin for the executing plugin; - * - Release xchat lock - * - Acquire the global interpreter lock - * - Make the python call - * - Release the global interpreter lock - * - Acquire xchat lock - * - * When python code calls xchat: - * - Release the global interpreter lock - * - Acquire xchat lock - * - Restore context, if necessary - * - Make the xchat call - * - Release xchat lock - * - Acquire the global interpreter lock - * - * Inside a timer, so that individual threads have a chance to run: - * - Release xchat lock - * - Go ahead threads. Have a nice time! - * - Acquire xchat lock - * - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#ifdef WIN32 -#include -#else -#include -#include -#endif - -#include "hexchat-plugin.h" -#undef _POSIX_C_SOURCE /* Avoid warnings from /usr/include/features.h */ -#undef _XOPEN_SOURCE -#undef HAVE_MEMRCHR /* Avoid redefinition in Python.h */ -#undef HAVE_STRINGS_H -#include -#include -#include - -/* Macros to convert version macros into string literals. - * The indirect macro is a well-known preprocessor trick to force X to be evaluated before the # operator acts to make it a string literal. - * If STRINGIZE were to be directly defined as #X instead, VERSION would be "VERSION_MAJOR" instead of "1". - */ -#define STRINGIZE2(X) #X -#define STRINGIZE(X) STRINGIZE2(X) - -/* Version number macros */ -#define VERSION_MAJOR 1 -#define VERSION_MINOR 0 - -/* Version string macro e.g 1.0/3.3 */ -#define VERSION STRINGIZE(VERSION_MAJOR) "." STRINGIZE(VERSION_MINOR) "/" \ - STRINGIZE(PY_MAJOR_VERSION) "." STRINGIZE (PY_MINOR_VERSION) - -/* #define's for Python 2 */ -#if PY_MAJOR_VERSION == 2 -#undef PyLong_Check -#define PyLong_Check PyInt_Check -#define PyLong_AsLong PyInt_AsLong -#define PyLong_FromLong PyInt_FromLong - -#undef PyUnicode_Check -#undef PyUnicode_FromString -#undef PyUnicode_FromFormat -#define PyUnicode_Check PyString_Check -#define PyUnicode_AsFormat PyString_AsFormat -#define PyUnicode_FromFormat PyString_FromFormat -#define PyUnicode_FromString PyString_FromString -#define PyUnicode_AsUTF8 PyString_AsString - -#ifdef WIN32 -#undef WITH_THREAD -#endif -#endif - -/* #define for Python 3 */ -#if PY_MAJOR_VERSION == 3 -#define IS_PY3K -#endif - -#define NONE 0 -#define ALLOW_THREADS 1 -#define RESTORE_CONTEXT 2 - -#ifdef WITH_THREAD -#define ACQUIRE_XCHAT_LOCK() PyThread_acquire_lock(xchat_lock, 1) -#define RELEASE_XCHAT_LOCK() PyThread_release_lock(xchat_lock) -#define BEGIN_XCHAT_CALLS(x) \ - do { \ - PyObject *calls_plugin = NULL; \ - PyThreadState *calls_thread; \ - if ((x) & RESTORE_CONTEXT) \ - calls_plugin = Plugin_GetCurrent(); \ - calls_thread = PyEval_SaveThread(); \ - ACQUIRE_XCHAT_LOCK(); \ - if (!((x) & ALLOW_THREADS)) { \ - PyEval_RestoreThread(calls_thread); \ - calls_thread = NULL; \ - } \ - if (calls_plugin) \ - hexchat_set_context(ph, \ - Plugin_GetContext(calls_plugin)); \ - while (0) -#define END_XCHAT_CALLS() \ - RELEASE_XCHAT_LOCK(); \ - if (calls_thread) \ - PyEval_RestoreThread(calls_thread); \ - } while(0) -#else -#define ACQUIRE_XCHAT_LOCK() -#define RELEASE_XCHAT_LOCK() -#define BEGIN_XCHAT_CALLS(x) -#define END_XCHAT_CALLS() -#endif - -#ifdef WITH_THREAD - -#define BEGIN_PLUGIN(plg) \ - do { \ - hexchat_context *begin_plugin_ctx = hexchat_get_context(ph); \ - RELEASE_XCHAT_LOCK(); \ - Plugin_AcquireThread(plg); \ - Plugin_SetContext(plg, begin_plugin_ctx); \ - } while (0) -#define END_PLUGIN(plg) \ - do { \ - Plugin_ReleaseThread(plg); \ - ACQUIRE_XCHAT_LOCK(); \ - } while (0) - -#else /* !WITH_THREAD (win32) */ - -static PyThreadState *pTempThread; - -#define BEGIN_PLUGIN(plg) \ - do { \ - hexchat_context *begin_plugin_ctx = hexchat_get_context(ph); \ - RELEASE_XCHAT_LOCK(); \ - PyEval_AcquireLock(); \ - pTempThread = PyThreadState_Swap(((PluginObject *)(plg))->tstate); \ - Plugin_SetContext(plg, begin_plugin_ctx); \ - } while (0) -#define END_PLUGIN(plg) \ - do { \ - ((PluginObject *)(plg))->tstate = PyThreadState_Swap(pTempThread); \ - PyEval_ReleaseLock(); \ - ACQUIRE_XCHAT_LOCK(); \ - } while (0) - -#endif /* !WITH_THREAD */ - -#define Plugin_Swap(x) \ - PyThreadState_Swap(((PluginObject *)(x))->tstate) -#define Plugin_AcquireThread(x) \ - PyEval_AcquireThread(((PluginObject *)(x))->tstate) -#define Plugin_ReleaseThread(x) \ - Util_ReleaseThread(((PluginObject *)(x))->tstate) -#define Plugin_GetFilename(x) \ - (((PluginObject *)(x))->filename) -#define Plugin_GetName(x) \ - (((PluginObject *)(x))->name) -#define Plugin_GetVersion(x) \ - (((PluginObject *)(x))->version) -#define Plugin_GetDesc(x) \ - (((PluginObject *)(x))->description) -#define Plugin_GetHooks(x) \ - (((PluginObject *)(x))->hooks) -#define Plugin_GetContext(x) \ - (((PluginObject *)(x))->context) -#define Plugin_SetFilename(x, y) \ - ((PluginObject *)(x))->filename = (y); -#define Plugin_SetName(x, y) \ - ((PluginObject *)(x))->name = (y); -#define Plugin_SetVersion(x, y) \ - ((PluginObject *)(x))->version = (y); -#define Plugin_SetDescription(x, y) \ - ((PluginObject *)(x))->description = (y); -#define Plugin_SetHooks(x, y) \ - ((PluginObject *)(x))->hooks = (y); -#define Plugin_SetContext(x, y) \ - ((PluginObject *)(x))->context = (y); -#define Plugin_SetGui(x, y) \ - ((PluginObject *)(x))->gui = (y); - -#define HOOK_XCHAT 1 -#define HOOK_XCHAT_ATTR 2 -#define HOOK_UNLOAD 3 - -/* ===================================================================== */ -/* Object definitions */ - -typedef struct { - PyObject_HEAD - int softspace; /* We need it for print support. */ -} XChatOutObject; - -typedef struct { - PyObject_HEAD - hexchat_context *context; -} ContextObject; - -typedef struct { - PyObject_HEAD - PyObject *time; -} AttributeObject; - -typedef struct { - PyObject_HEAD - const char *listname; - PyObject *dict; -} ListItemObject; - -typedef struct { - PyObject_HEAD - char *name; - char *version; - char *filename; - char *description; - GSList *hooks; - PyThreadState *tstate; - hexchat_context *context; - void *gui; -} PluginObject; - -typedef struct { - int type; - PyObject *plugin; - PyObject *callback; - PyObject *userdata; - char *name; - void *data; /* A handle, when type == HOOK_XCHAT */ -} Hook; - - -/* ===================================================================== */ -/* Function declarations */ - -static PyObject *Util_BuildList(char *word[]); -static PyObject *Util_BuildEOLList(char *word[]); -static void Util_Autoload(void); -static char *Util_Expand(char *filename); - -static int Callback_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata); -static int Callback_Command(char *word[], char *word_eol[], void *userdata); -static int Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, void *userdata); -static int Callback_Print(char *word[], void *userdata); -static int Callback_Timer(void *userdata); -static int Callback_ThreadTimer(void *userdata); - -static PyObject *XChatOut_New(void); -static PyObject *XChatOut_write(PyObject *self, PyObject *args); -static void XChatOut_dealloc(PyObject *self); - -static PyObject *Attribute_New(hexchat_event_attrs *attrs); - -static void Context_dealloc(PyObject *self); -static PyObject *Context_set(ContextObject *self, PyObject *args); -static PyObject *Context_command(ContextObject *self, PyObject *args); -static PyObject *Context_prnt(ContextObject *self, PyObject *args); -static PyObject *Context_get_info(ContextObject *self, PyObject *args); -static PyObject *Context_get_list(ContextObject *self, PyObject *args); -static PyObject *Context_compare(ContextObject *a, ContextObject *b, int op); -static PyObject *Context_FromContext(hexchat_context *context); -static PyObject *Context_FromServerAndChannel(char *server, char *channel); - -static PyObject *Plugin_New(char *filename, PyObject *xcoobj); -static PyObject *Plugin_GetCurrent(void); -static PluginObject *Plugin_ByString(char *str); -static Hook *Plugin_AddHook(int type, PyObject *plugin, PyObject *callback, - PyObject *userdata, char *name, void *data); -static Hook *Plugin_FindHook(PyObject *plugin, char *name); -static void Plugin_RemoveHook(PyObject *plugin, Hook *hook); -static void Plugin_RemoveAllHooks(PyObject *plugin); - -static PyObject *Module_hexchat_command(PyObject *self, PyObject *args); -static PyObject *Module_xchat_prnt(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_get_context(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_find_context(PyObject *self, PyObject *args, - PyObject *kwargs); -static PyObject *Module_hexchat_get_info(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_hook_command(PyObject *self, PyObject *args, - PyObject *kwargs); -static PyObject *Module_hexchat_hook_server(PyObject *self, PyObject *args, - PyObject *kwargs); -static PyObject *Module_hexchat_hook_print(PyObject *self, PyObject *args, - PyObject *kwargs); -static PyObject *Module_hexchat_hook_timer(PyObject *self, PyObject *args, - PyObject *kwargs); -static PyObject *Module_hexchat_unhook(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_get_info(PyObject *self, PyObject *args); -static PyObject *Module_xchat_get_list(PyObject *self, PyObject *args); -static PyObject *Module_xchat_get_lists(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_nickcmp(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_strip(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_pluginpref_set(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_pluginpref_get(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args); -static PyObject *Module_hexchat_pluginpref_list(PyObject *self, PyObject *args); - -static void IInterp_Exec(char *command); -static int IInterp_Cmd(char *word[], char *word_eol[], void *userdata); - -static void Command_PyList(void); -static void Command_PyLoad(char *filename); -static void Command_PyUnload(char *name); -static void Command_PyReload(char *name); -static void Command_PyAbout(void); -static int Command_Py(char *word[], char *word_eol[], void *userdata); - -/* ===================================================================== */ -/* Static declarations and definitions */ - -static PyTypeObject Plugin_Type; -static PyTypeObject XChatOut_Type; -static PyTypeObject Context_Type; -static PyTypeObject ListItem_Type; -static PyTypeObject Attribute_Type; - -static PyThreadState *main_tstate = NULL; -static void *thread_timer = NULL; - -static hexchat_plugin *ph; -static GSList *plugin_list = NULL; - -static PyObject *interp_plugin = NULL; -static PyObject *xchatout = NULL; - -#ifdef WITH_THREAD -static PyThread_type_lock xchat_lock = NULL; -#endif - -static const char usage[] = "\ -Usage: /PY LOAD \n\ - UNLOAD \n\ - RELOAD \n\ - LIST\n\ - EXEC \n\ - CONSOLE\n\ - ABOUT\n\ -\n"; - -static const char about[] = "HexChat Python interface version " VERSION "\n"; - -/* ===================================================================== */ -/* Utility functions */ - -static PyObject * -Util_BuildList(char *word[]) -{ - PyObject *list; - int listsize = 31; - int i; - /* Find the last valid array member; there may be intermediate NULLs that - * would otherwise cause us to drop some members. */ - while (listsize > 0 && - (word[listsize] == NULL || word[listsize][0] == 0)) - listsize--; - list = PyList_New(listsize); - if (list == NULL) { - PyErr_Print(); - return NULL; - } - for (i = 1; i <= listsize; i++) { - PyObject *o; - if (word[i] == NULL) { - Py_INCREF(Py_None); - o = Py_None; - } else { - /* This handles word[i][0] == 0 automatically. */ - o = PyUnicode_FromString(word[i]); - } - PyList_SetItem(list, i - 1, o); - } - return list; -} - -static PyObject * -Util_BuildEOLList(char *word[]) -{ - PyObject *list; - int listsize = 31; - int i; - char *accum = NULL; - char *last = NULL; - - /* Find the last valid array member; there may be intermediate NULLs that - * would otherwise cause us to drop some members. */ - while (listsize > 0 && - (word[listsize] == NULL || word[listsize][0] == 0)) - listsize--; - list = PyList_New(listsize); - if (list == NULL) { - PyErr_Print(); - return NULL; - } - for (i = listsize; i > 0; i--) { - char *part = word[i]; - PyObject *uni_part; - if (accum == NULL) { - accum = g_strdup (part); - } else if (part != NULL && part[0] != 0) { - last = accum; - accum = g_strjoin(" ", part, last, NULL); - g_free (last); - last = NULL; - - if (accum == NULL) { - Py_DECREF(list); - hexchat_print(ph, "Not enough memory to alloc accum" - "for python plugin callback"); - return NULL; - } - } - uni_part = PyUnicode_FromString(accum); - PyList_SetItem(list, i - 1, uni_part); - } - - g_free (last); - g_free (accum); - - return list; -} - -static void -Util_Autoload_from (const char *dir_name) -{ - gchar *oldcwd; - const char *entry_name; - GDir *dir; - - oldcwd = g_get_current_dir (); - if (oldcwd == NULL) - return; - if (g_chdir(dir_name) != 0) - { - g_free (oldcwd); - return; - } - dir = g_dir_open (".", 0, NULL); - if (dir == NULL) - { - g_free (oldcwd); - return; - } - while ((entry_name = g_dir_read_name (dir))) - { - if (g_str_has_suffix (entry_name, ".py")) - Command_PyLoad((char*)entry_name); - } - g_dir_close (dir); - g_chdir (oldcwd); -} - -static void -Util_Autoload() -{ - const char *xdir; - char *sub_dir; - /* we need local filesystem encoding for g_chdir, g_dir_open etc */ - - xdir = hexchat_get_info(ph, "configdir"); - - /* auto-load from subdirectory addons */ - sub_dir = g_build_filename (xdir, "addons", NULL); - Util_Autoload_from(sub_dir); - g_free (sub_dir); -} - -static char * -Util_Expand(char *filename) -{ - char *expanded; - - /* Check if this is an absolute path. */ - if (g_path_is_absolute(filename)) { - if (g_file_test(filename, G_FILE_TEST_EXISTS)) - return g_strdup(filename); - else - return NULL; - } - - /* Check if it starts with ~/ and expand the home if positive. */ - if (*filename == '~' && *(filename+1) == '/') { - expanded = g_build_filename(g_get_home_dir(), - filename+2, NULL); - if (g_file_test(expanded, G_FILE_TEST_EXISTS)) - return expanded; - else { - g_free(expanded); - return NULL; - } - } - - /* Check if it's in the current directory. */ - expanded = g_build_filename(g_get_current_dir(), - filename, NULL); - if (g_file_test(expanded, G_FILE_TEST_EXISTS)) - return expanded; - g_free(expanded); - - /* Check if ~/.config/hexchat/addons/ exists. */ - expanded = g_build_filename(hexchat_get_info(ph, "configdir"), - "addons", filename, NULL); - if (g_file_test(expanded, G_FILE_TEST_EXISTS)) - return expanded; - g_free(expanded); - - return NULL; -} - -/* Similar to PyEval_ReleaseThread, but accepts NULL thread states. */ -static void -Util_ReleaseThread(PyThreadState *tstate) -{ - PyThreadState *old_tstate; - if (tstate == NULL) - Py_FatalError("PyEval_ReleaseThread: NULL thread state"); - old_tstate = PyThreadState_Swap(NULL); - if (old_tstate != tstate && old_tstate != NULL) - Py_FatalError("PyEval_ReleaseThread: wrong thread state"); - PyEval_ReleaseLock(); -} - -/* ===================================================================== */ -/* Hookable functions. These are the entry points to python code, besides - * the load function, and the hooks for interactive interpreter. */ - -static int -Callback_Server(char *word[], char *word_eol[], hexchat_event_attrs *attrs, void *userdata) -{ - Hook *hook = (Hook *) userdata; - PyObject *retobj; - PyObject *word_list, *word_eol_list; - PyObject *attributes; - int ret = HEXCHAT_EAT_NONE; - PyObject *plugin; - - plugin = hook->plugin; - BEGIN_PLUGIN(plugin); - - word_list = Util_BuildList(word); - if (word_list == NULL) { - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - word_eol_list = Util_BuildList(word_eol); - if (word_eol_list == NULL) { - Py_DECREF(word_list); - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - - attributes = Attribute_New(attrs); - - if (hook->type == HOOK_XCHAT_ATTR) - retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list, - word_eol_list, hook->userdata, attributes); - else - retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list, - word_eol_list, hook->userdata); - Py_DECREF(word_list); - Py_DECREF(word_eol_list); - Py_DECREF(attributes); - - if (retobj == Py_None) { - ret = HEXCHAT_EAT_NONE; - Py_DECREF(retobj); - } else if (retobj) { - ret = PyLong_AsLong(retobj); - Py_DECREF(retobj); - } else { - PyErr_Print(); - } - - END_PLUGIN(plugin); - - return ret; -} - -static int -Callback_Command(char *word[], char *word_eol[], void *userdata) -{ - Hook *hook = (Hook *) userdata; - PyObject *retobj; - PyObject *word_list, *word_eol_list; - int ret = HEXCHAT_EAT_NONE; - PyObject *plugin; - - plugin = hook->plugin; - BEGIN_PLUGIN(plugin); - - word_list = Util_BuildList(word); - if (word_list == NULL) { - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - word_eol_list = Util_BuildList(word_eol); - if (word_eol_list == NULL) { - Py_DECREF(word_list); - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - - retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list, - word_eol_list, hook->userdata); - Py_DECREF(word_list); - Py_DECREF(word_eol_list); - - if (retobj == Py_None) { - ret = HEXCHAT_EAT_NONE; - Py_DECREF(retobj); - } else if (retobj) { - ret = PyLong_AsLong(retobj); - Py_DECREF(retobj); - } else { - PyErr_Print(); - } - - END_PLUGIN(plugin); - - return ret; -} - -static int -Callback_Print_Attrs(char *word[], hexchat_event_attrs *attrs, void *userdata) -{ - Hook *hook = (Hook *) userdata; - PyObject *retobj; - PyObject *word_list; - PyObject *word_eol_list; - PyObject *attributes; - int ret = HEXCHAT_EAT_NONE; - PyObject *plugin; - - plugin = hook->plugin; - BEGIN_PLUGIN(plugin); - - word_list = Util_BuildList(word); - if (word_list == NULL) { - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - word_eol_list = Util_BuildEOLList(word); - if (word_eol_list == NULL) { - Py_DECREF(word_list); - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - - attributes = Attribute_New(attrs); - - retobj = PyObject_CallFunction(hook->callback, "(OOOO)", word_list, - word_eol_list, hook->userdata, attributes); - - Py_DECREF(word_list); - Py_DECREF(word_eol_list); - Py_DECREF(attributes); - - if (retobj == Py_None) { - ret = HEXCHAT_EAT_NONE; - Py_DECREF(retobj); - } else if (retobj) { - ret = PyLong_AsLong(retobj); - Py_DECREF(retobj); - } else { - PyErr_Print(); - } - - END_PLUGIN(plugin); - - return ret; -} - -static int -Callback_Print(char *word[], void *userdata) -{ - Hook *hook = (Hook *) userdata; - PyObject *retobj; - PyObject *word_list; - PyObject *word_eol_list; - int ret = HEXCHAT_EAT_NONE; - PyObject *plugin; - - plugin = hook->plugin; - BEGIN_PLUGIN(plugin); - - word_list = Util_BuildList(word); - if (word_list == NULL) { - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - word_eol_list = Util_BuildEOLList(word); - if (word_eol_list == NULL) { - Py_DECREF(word_list); - END_PLUGIN(plugin); - return HEXCHAT_EAT_NONE; - } - - retobj = PyObject_CallFunction(hook->callback, "(OOO)", word_list, - word_eol_list, hook->userdata); - - Py_DECREF(word_list); - Py_DECREF(word_eol_list); - - if (retobj == Py_None) { - ret = HEXCHAT_EAT_NONE; - Py_DECREF(retobj); - } else if (retobj) { - ret = PyLong_AsLong(retobj); - Py_DECREF(retobj); - } else { - PyErr_Print(); - } - - END_PLUGIN(plugin); - - return ret; -} - -static int -Callback_Timer(void *userdata) -{ - Hook *hook = (Hook *) userdata; - PyObject *retobj; - int ret = 0; - PyObject *plugin; - - plugin = hook->plugin; - - BEGIN_PLUGIN(hook->plugin); - - retobj = PyObject_CallFunction(hook->callback, "(O)", hook->userdata); - - if (retobj) { - ret = PyObject_IsTrue(retobj); - Py_DECREF(retobj); - } else { - PyErr_Print(); - } - - /* Returning 0 for this callback unhooks itself. */ - if (ret == 0) - Plugin_RemoveHook(plugin, hook); - - END_PLUGIN(plugin); - - return ret; -} - -#ifdef WITH_THREAD -static int -Callback_ThreadTimer(void *userdata) -{ - RELEASE_XCHAT_LOCK(); -#ifndef WIN32 - usleep(1); -#endif - ACQUIRE_XCHAT_LOCK(); - return 1; -} -#endif - -/* ===================================================================== */ -/* XChatOut object */ - -/* We keep this information global, so we can reset it when the - * deinit function is called. */ -/* XXX This should be somehow bound to the printing context. */ -static GString *xchatout_buffer = NULL; - -static PyObject * -XChatOut_New() -{ - XChatOutObject *xcoobj; - xcoobj = PyObject_New(XChatOutObject, &XChatOut_Type); - if (xcoobj != NULL) - xcoobj->softspace = 0; - return (PyObject *) xcoobj; -} - -static void -XChatOut_dealloc(PyObject *self) -{ - Py_TYPE(self)->tp_free((PyObject *)self); -} - -/* This is a little bit complex because we have to buffer data - * until a \n is received, since xchat breaks the line automatically. - * We also crop the last \n for this reason. */ -static PyObject * -XChatOut_write(PyObject *self, PyObject *args) -{ - gboolean add_space; - char *data, *pos; - - if (!PyArg_ParseTuple(args, "s:write", &data)) - return NULL; - if (!data || !*data) { - Py_RETURN_NONE; - } - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); - if (((XChatOutObject *)self)->softspace) { - add_space = TRUE; - ((XChatOutObject *)self)->softspace = 0; - } else { - add_space = FALSE; - } - - g_string_append (xchatout_buffer, data); - - /* If not end of line add space to continue buffer later */ - if (add_space && xchatout_buffer->str[xchatout_buffer->len - 1] != '\n') - { - g_string_append_c (xchatout_buffer, ' '); - } - - /* If there is an end of line print up to that */ - if ((pos = strrchr (xchatout_buffer->str, '\n'))) - { - *pos = '\0'; - hexchat_print (ph, xchatout_buffer->str); - - /* Then remove it from buffer */ - g_string_erase (xchatout_buffer, 0, pos - xchatout_buffer->str + 1); - } - - END_XCHAT_CALLS(); - Py_RETURN_NONE; -} - -#define OFF(x) offsetof(XChatOutObject, x) - -static PyMemberDef XChatOut_members[] = { - {"softspace", T_INT, OFF(softspace), 0}, - {0} -}; - -static PyMethodDef XChatOut_methods[] = { - {"write", XChatOut_write, METH_VARARGS}, - {NULL, NULL} -}; - -static PyTypeObject XChatOut_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "hexchat.XChatOut", /*tp_name*/ - sizeof(XChatOutObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - XChatOut_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - PyObject_GenericGetAttr,/*tp_getattro*/ - PyObject_GenericSetAttr,/*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - XChatOut_methods, /*tp_methods*/ - XChatOut_members, /*tp_members*/ - 0, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - 0, /*tp_init*/ - PyType_GenericAlloc, /*tp_alloc*/ - PyType_GenericNew, /*tp_new*/ - PyObject_Del, /*tp_free*/ - 0, /*tp_is_gc*/ -}; - - -/* ===================================================================== */ -/* Attribute object */ - -#undef OFF -#define OFF(x) offsetof(AttributeObject, x) - -static PyMemberDef Attribute_members[] = { - {"time", T_OBJECT, OFF(time), 0}, - {0} -}; - -static void -Attribute_dealloc(PyObject *self) -{ - Py_DECREF(((AttributeObject*)self)->time); - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyObject * -Attribute_repr(PyObject *self) -{ - return PyUnicode_FromFormat("", self); -} - -static PyTypeObject Attribute_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "hexchat.Attribute", /*tp_name*/ - sizeof(AttributeObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - Attribute_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - Attribute_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - PyObject_GenericGetAttr,/*tp_getattro*/ - PyObject_GenericSetAttr,/*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - 0, /*tp_methods*/ - Attribute_members, /*tp_members*/ - 0, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - 0, /*tp_init*/ - PyType_GenericAlloc, /*tp_alloc*/ - PyType_GenericNew, /*tp_new*/ - PyObject_Del, /*tp_free*/ - 0, /*tp_is_gc*/ -}; - -static PyObject * -Attribute_New(hexchat_event_attrs *attrs) -{ - AttributeObject *attr; - attr = PyObject_New(AttributeObject, &Attribute_Type); - if (attr != NULL) { - attr->time = PyLong_FromLong((long)attrs->server_time_utc); - } - return (PyObject *) attr; -} - - -/* ===================================================================== */ -/* Context object */ - -static void -Context_dealloc(PyObject *self) -{ - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyObject * -Context_set(ContextObject *self, PyObject *args) -{ - PyObject *plugin = Plugin_GetCurrent(); - Plugin_SetContext(plugin, self->context); - Py_RETURN_NONE; -} - -static PyObject * -Context_command(ContextObject *self, PyObject *args) -{ - char *text; - if (!PyArg_ParseTuple(args, "s:command", &text)) - return NULL; - BEGIN_XCHAT_CALLS(ALLOW_THREADS); - hexchat_set_context(ph, self->context); - hexchat_command(ph, text); - END_XCHAT_CALLS(); - Py_RETURN_NONE; -} - -static PyObject * -Context_prnt(ContextObject *self, PyObject *args) -{ - char *text; - if (!PyArg_ParseTuple(args, "s:prnt", &text)) - return NULL; - BEGIN_XCHAT_CALLS(ALLOW_THREADS); - hexchat_set_context(ph, self->context); - hexchat_print(ph, text); - END_XCHAT_CALLS(); - Py_RETURN_NONE; -} - -static PyObject * -Context_emit_print(ContextObject *self, PyObject *args, PyObject *kwargs) -{ - char *argv[6]; - char *name; - int res; - long time = 0; - hexchat_event_attrs *attrs; - char *kwlist[] = {"name", "arg1", "arg2", "arg3", - "arg4", "arg5", "arg6", - "time", NULL}; - memset(&argv, 0, sizeof(char*)*6); - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name, - &argv[0], &argv[1], &argv[2], - &argv[3], &argv[4], &argv[5], - &time)) - return NULL; - BEGIN_XCHAT_CALLS(ALLOW_THREADS); - hexchat_set_context(ph, self->context); - attrs = hexchat_event_attrs_create(ph); - attrs->server_time_utc = (time_t)time; - - res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2], - argv[3], argv[4], argv[5], NULL); - - hexchat_event_attrs_free(ph, attrs); - END_XCHAT_CALLS(); - return PyLong_FromLong(res); -} - -static PyObject * -Context_get_info(ContextObject *self, PyObject *args) -{ - const char *info; - char *name; - if (!PyArg_ParseTuple(args, "s:get_info", &name)) - return NULL; - BEGIN_XCHAT_CALLS(NONE); - hexchat_set_context(ph, self->context); - info = hexchat_get_info(ph, name); - END_XCHAT_CALLS(); - if (info == NULL) { - Py_RETURN_NONE; - } - return PyUnicode_FromString(info); -} - -static PyObject * -Context_get_list(ContextObject *self, PyObject *args) -{ - PyObject *plugin = Plugin_GetCurrent(); - hexchat_context *saved_context = Plugin_GetContext(plugin); - PyObject *ret; - Plugin_SetContext(plugin, self->context); - ret = Module_xchat_get_list((PyObject*)self, args); - Plugin_SetContext(plugin, saved_context); - return ret; -} - -/* needed to make context1 == context2 work */ -static PyObject * -Context_compare(ContextObject *a, ContextObject *b, int op) -{ - PyObject *ret; - /* check for == */ - if (op == Py_EQ) - ret = (a->context == b->context ? Py_True : Py_False); - /* check for != */ - else if (op == Py_NE) - ret = (a->context != b->context ? Py_True : Py_False); - /* only makes sense as == and != */ - else - { - PyErr_SetString(PyExc_TypeError, "contexts are either equal or not equal"); - ret = Py_None; - } - - Py_INCREF(ret); - return ret; -} - -static PyMethodDef Context_methods[] = { - {"set", (PyCFunction) Context_set, METH_NOARGS}, - {"command", (PyCFunction) Context_command, METH_VARARGS}, - {"prnt", (PyCFunction) Context_prnt, METH_VARARGS}, - {"emit_print", (PyCFunction) Context_emit_print, METH_VARARGS|METH_KEYWORDS}, - {"get_info", (PyCFunction) Context_get_info, METH_VARARGS}, - {"get_list", (PyCFunction) Context_get_list, METH_VARARGS}, - {NULL, NULL} -}; - -static PyTypeObject Context_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "hexchat.Context", /*tp_name*/ - sizeof(ContextObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - Context_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - PyObject_GenericGetAttr,/*tp_getattro*/ - PyObject_GenericSetAttr,/*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - (richcmpfunc)Context_compare, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - Context_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - 0, /*tp_init*/ - PyType_GenericAlloc, /*tp_alloc*/ - PyType_GenericNew, /*tp_new*/ - PyObject_Del, /*tp_free*/ - 0, /*tp_is_gc*/ -}; - -static PyObject * -Context_FromContext(hexchat_context *context) -{ - ContextObject *ctxobj = PyObject_New(ContextObject, &Context_Type); - if (ctxobj != NULL) - ctxobj->context = context; - return (PyObject *) ctxobj; -} - -static PyObject * -Context_FromServerAndChannel(char *server, char *channel) -{ - ContextObject *ctxobj; - hexchat_context *context; - BEGIN_XCHAT_CALLS(NONE); - context = hexchat_find_context(ph, server, channel); - END_XCHAT_CALLS(); - if (context == NULL) - return NULL; - ctxobj = PyObject_New(ContextObject, &Context_Type); - if (ctxobj == NULL) - return NULL; - ctxobj->context = context; - return (PyObject *) ctxobj; -} - - -/* ===================================================================== */ -/* ListItem object */ - -#undef OFF -#define OFF(x) offsetof(ListItemObject, x) - -static PyMemberDef ListItem_members[] = { - {"__dict__", T_OBJECT, OFF(dict), 0}, - {0} -}; - -static void -ListItem_dealloc(PyObject *self) -{ - Py_DECREF(((ListItemObject*)self)->dict); - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyObject * -ListItem_repr(PyObject *self) -{ - return PyUnicode_FromFormat("<%s list item at %p>", - ((ListItemObject*)self)->listname, self); -} - -static PyTypeObject ListItem_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "hexchat.ListItem", /*tp_name*/ - sizeof(ListItemObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - ListItem_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - ListItem_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - PyObject_GenericGetAttr,/*tp_getattro*/ - PyObject_GenericSetAttr,/*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - 0, /*tp_methods*/ - ListItem_members, /*tp_members*/ - 0, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - OFF(dict), /*tp_dictoffset*/ - 0, /*tp_init*/ - PyType_GenericAlloc, /*tp_alloc*/ - PyType_GenericNew, /*tp_new*/ - PyObject_Del, /*tp_free*/ - 0, /*tp_is_gc*/ -}; - -static PyObject * -ListItem_New(const char *listname) -{ - ListItemObject *item; - item = PyObject_New(ListItemObject, &ListItem_Type); - if (item != NULL) { - /* listname parameter must be statically allocated. */ - item->listname = listname; - item->dict = PyDict_New(); - if (item->dict == NULL) { - Py_DECREF(item); - item = NULL; - } - } - return (PyObject *) item; -} - - -/* ===================================================================== */ -/* Plugin object */ - -#define GET_MODULE_DATA(x, force) \ - o = PyObject_GetAttrString(m, "__module_" #x "__"); \ - if (o == NULL) { \ - if (force) { \ - hexchat_print(ph, "Module has no __module_" #x "__ " \ - "defined"); \ - goto error; \ - } \ - plugin->x = g_strdup(""); \ - } else {\ - if (!PyUnicode_Check(o)) { \ - hexchat_print(ph, "Variable __module_" #x "__ " \ - "must be a string"); \ - goto error; \ - } \ - plugin->x = g_strdup(PyUnicode_AsUTF8(o)); \ - if (plugin->x == NULL) { \ - hexchat_print(ph, "Not enough memory to allocate " #x); \ - goto error; \ - } \ - } - -static PyObject * -Plugin_GetCurrent() -{ - PyObject *plugin; - plugin = PySys_GetObject("__plugin__"); - if (plugin == NULL) - PyErr_SetString(PyExc_RuntimeError, "lost sys.__plugin__"); - return plugin; -} - -static hexchat_plugin * -Plugin_GetHandle(PluginObject *plugin) -{ - /* This works but the issue is that the script must be ran to get - * the name of it thus upon first use it will use the wrong handler - * work around would be to run a fake script once to get name? */ -#if 0 - /* return fake handle for pluginpref */ - if (plugin->gui != NULL) - return plugin->gui; - else -#endif - return ph; -} - -static PluginObject * -Plugin_ByString(char *str) -{ - GSList *list; - PluginObject *plugin; - char *basename; - list = plugin_list; - while (list != NULL) { - plugin = (PluginObject *) list->data; - basename = g_path_get_basename(plugin->filename); - if (basename == NULL) - break; - if (strcasecmp(plugin->name, str) == 0 || - strcasecmp(plugin->filename, str) == 0 || - strcasecmp(basename, str) == 0) { - g_free(basename); - return plugin; - } - g_free(basename); - list = list->next; - } - return NULL; -} - -static Hook * -Plugin_AddHook(int type, PyObject *plugin, PyObject *callback, - PyObject *userdata, char *name, void *data) -{ - Hook *hook = g_new(Hook, 1); - hook->type = type; - hook->plugin = plugin; - Py_INCREF(callback); - hook->callback = callback; - Py_INCREF(userdata); - hook->userdata = userdata; - hook->name = g_strdup (name); - hook->data = NULL; - Plugin_SetHooks(plugin, g_slist_append(Plugin_GetHooks(plugin), - hook)); - - return hook; -} - -static Hook * -Plugin_FindHook(PyObject *plugin, char *name) -{ - Hook *hook = NULL; - GSList *plugin_hooks = Plugin_GetHooks(plugin); - - while (plugin_hooks) - { - if (g_strcmp0 (((Hook *)plugin_hooks->data)->name, name) == 0) - { - hook = (Hook *)plugin_hooks->data; - break; - } - - plugin_hooks = g_slist_next(plugin_hooks); - } - - return hook; -} - -static void -Plugin_RemoveHook(PyObject *plugin, Hook *hook) -{ - GSList *list; - /* Is this really a hook of the running plugin? */ - list = g_slist_find(Plugin_GetHooks(plugin), hook); - if (list) { - /* Ok, unhook it. */ - if (hook->type != HOOK_UNLOAD) { - /* This is an xchat hook. Unregister it. */ - BEGIN_XCHAT_CALLS(NONE); - hexchat_unhook(ph, (hexchat_hook*)hook->data); - END_XCHAT_CALLS(); - } - Plugin_SetHooks(plugin, - g_slist_remove(Plugin_GetHooks(plugin), - hook)); - Py_DECREF(hook->callback); - Py_DECREF(hook->userdata); - g_free(hook->name); - g_free(hook); - } -} - -static void -Plugin_RemoveAllHooks(PyObject *plugin) -{ - GSList *list = Plugin_GetHooks(plugin); - while (list) { - Hook *hook = (Hook *) list->data; - if (hook->type != HOOK_UNLOAD) { - /* This is an xchat hook. Unregister it. */ - BEGIN_XCHAT_CALLS(NONE); - hexchat_unhook(ph, (hexchat_hook*)hook->data); - END_XCHAT_CALLS(); - } - Py_DECREF(hook->callback); - Py_DECREF(hook->userdata); - g_free(hook->name); - g_free(hook); - list = list->next; - } - Plugin_SetHooks(plugin, NULL); -} - -static void -Plugin_Delete(PyObject *plugin) -{ - PyThreadState *tstate = ((PluginObject*)plugin)->tstate; - GSList *list = Plugin_GetHooks(plugin); - while (list) { - Hook *hook = (Hook *) list->data; - if (hook->type == HOOK_UNLOAD) { - PyObject *retobj; - retobj = PyObject_CallFunction(hook->callback, "(O)", - hook->userdata); - if (retobj) { - Py_DECREF(retobj); - } else { - PyErr_Print(); - PyErr_Clear(); - } - } - list = list->next; - } - Plugin_RemoveAllHooks(plugin); - if (((PluginObject *)plugin)->gui != NULL) - hexchat_plugingui_remove(ph, ((PluginObject *)plugin)->gui); - Py_DECREF(plugin); - /*PyThreadState_Swap(tstate); needed? */ - Py_EndInterpreter(tstate); -} - -static PyObject * -Plugin_New(char *filename, PyObject *xcoobj) -{ - PluginObject *plugin = NULL; - PyObject *m, *o; -#ifdef IS_PY3K - wchar_t *argv[] = { L"", 0 }; -#else - char *argv[] = { "", 0 }; -#endif - - if (filename) { - char *old_filename = filename; - filename = Util_Expand(filename); - if (filename == NULL) { - hexchat_printf(ph, "File not found: %s", old_filename); - return NULL; - } - } - - /* Allocate plugin structure. */ - plugin = PyObject_New(PluginObject, &Plugin_Type); - if (plugin == NULL) { - hexchat_print(ph, "Can't create plugin object"); - goto error; - } - - Plugin_SetName(plugin, NULL); - Plugin_SetVersion(plugin, NULL); - Plugin_SetFilename(plugin, NULL); - Plugin_SetDescription(plugin, NULL); - Plugin_SetHooks(plugin, NULL); - Plugin_SetContext(plugin, hexchat_get_context(ph)); - Plugin_SetGui(plugin, NULL); - - /* Start a new interpreter environment for this plugin. */ - PyEval_AcquireThread(main_tstate); - plugin->tstate = Py_NewInterpreter(); - if (plugin->tstate == NULL) { - hexchat_print(ph, "Can't create interpreter state"); - goto error; - } - - PySys_SetArgv(1, argv); - PySys_SetObject("__plugin__", (PyObject *) plugin); - - /* Set stdout and stderr to xchatout. */ - Py_INCREF(xcoobj); - PySys_SetObject("stdout", xcoobj); - Py_INCREF(xcoobj); - PySys_SetObject("stderr", xcoobj); - - if (filename) { -#ifdef WIN32 - char *file; - if (!g_file_get_contents(filename, &file, NULL, NULL)) { - hexchat_printf(ph, "Can't open file %s: %s\n", - filename, strerror(errno)); - goto error; - } - - if (PyRun_SimpleString(file) != 0) { - hexchat_printf(ph, "Error loading module %s\n", - filename); - g_free (file); - goto error; - } - - plugin->filename = filename; - filename = NULL; - g_free (file); -#else - FILE *fp; - plugin->filename = filename; - - /* It's now owned by the plugin. */ - filename = NULL; - - /* Open the plugin file. */ - fp = fopen(plugin->filename, "r"); - if (fp == NULL) { - hexchat_printf(ph, "Can't open file %s: %s\n", - plugin->filename, strerror(errno)); - goto error; - } - - /* Run the plugin. */ - if (PyRun_SimpleFile(fp, plugin->filename) != 0) { - hexchat_printf(ph, "Error loading module %s\n", - plugin->filename); - fclose(fp); - goto error; - } - fclose(fp); -#endif - m = PyDict_GetItemString(PyImport_GetModuleDict(), - "__main__"); - if (m == NULL) { - hexchat_print(ph, "Can't get __main__ module"); - goto error; - } - GET_MODULE_DATA(name, 1); - GET_MODULE_DATA(version, 0); - GET_MODULE_DATA(description, 0); - plugin->gui = hexchat_plugingui_add(ph, plugin->filename, - plugin->name, - plugin->description, - plugin->version, NULL); - } - - PyEval_ReleaseThread(plugin->tstate); - - return (PyObject *) plugin; - -error: - g_free(filename); - - if (plugin) { - if (plugin->tstate) - Plugin_Delete((PyObject *)plugin); - else - Py_DECREF(plugin); - } - PyEval_ReleaseLock(); - - return NULL; -} - -static void -Plugin_dealloc(PluginObject *self) -{ - g_free(self->filename); - g_free(self->name); - g_free(self->version); - g_free(self->description); - Py_TYPE(self)->tp_free((PyObject *)self); -} - -static PyTypeObject Plugin_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "hexchat.Plugin", /*tp_name*/ - sizeof(PluginObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)Plugin_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - PyObject_GenericGetAttr,/*tp_getattro*/ - PyObject_GenericSetAttr,/*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - 0, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - 0, /*tp_init*/ - PyType_GenericAlloc, /*tp_alloc*/ - PyType_GenericNew, /*tp_new*/ - PyObject_Del, /*tp_free*/ - 0, /*tp_is_gc*/ -}; - - -/* ===================================================================== */ -/* XChat module */ - -static PyObject * -Module_hexchat_command(PyObject *self, PyObject *args) -{ - char *text; - if (!PyArg_ParseTuple(args, "s:command", &text)) - return NULL; - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); - hexchat_command(ph, text); - END_XCHAT_CALLS(); - Py_RETURN_NONE; -} - -static PyObject * -Module_xchat_prnt(PyObject *self, PyObject *args) -{ - char *text; - if (!PyArg_ParseTuple(args, "s:prnt", &text)) - return NULL; - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); - hexchat_print(ph, text); - END_XCHAT_CALLS(); - Py_RETURN_NONE; -} - -static PyObject * -Module_hexchat_emit_print(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *argv[6]; - char *name; - int res; - long time = 0; - hexchat_event_attrs *attrs; - char *kwlist[] = {"name", "arg1", "arg2", "arg3", - "arg4", "arg5", "arg6", - "time", NULL}; - memset(&argv, 0, sizeof(char*)*6); - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssssssl:print_event", kwlist, &name, - &argv[0], &argv[1], &argv[2], - &argv[3], &argv[4], &argv[5], - &time)) - return NULL; - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT|ALLOW_THREADS); - attrs = hexchat_event_attrs_create(ph); - attrs->server_time_utc = (time_t)time; - - res = hexchat_emit_print_attrs(ph, attrs, name, argv[0], argv[1], argv[2], - argv[3], argv[4], argv[5], NULL); - - hexchat_event_attrs_free(ph, attrs); - END_XCHAT_CALLS(); - return PyLong_FromLong(res); -} - -static PyObject * -Module_hexchat_get_info(PyObject *self, PyObject *args) -{ - const char *info; - char *name; - if (!PyArg_ParseTuple(args, "s:get_info", &name)) - return NULL; - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT); - info = hexchat_get_info(ph, name); - END_XCHAT_CALLS(); - if (info == NULL) { - Py_RETURN_NONE; - } - if (strcmp (name, "gtkwin_ptr") == 0 || strcmp (name, "win_ptr") == 0) - return PyUnicode_FromFormat("%p", info); /* format as pointer */ - else - return PyUnicode_FromString(info); -} - -static PyObject * -Module_xchat_get_prefs(PyObject *self, PyObject *args) -{ - PyObject *res; - const char *info; - int integer; - char *name; - int type; - if (!PyArg_ParseTuple(args, "s:get_prefs", &name)) - return NULL; - BEGIN_XCHAT_CALLS(NONE); - type = hexchat_get_prefs(ph, name, &info, &integer); - END_XCHAT_CALLS(); - switch (type) { - case 0: - Py_INCREF(Py_None); - res = Py_None; - break; - case 1: - res = PyUnicode_FromString((char*)info); - break; - case 2: - case 3: - res = PyLong_FromLong(integer); - break; - default: - PyErr_Format(PyExc_RuntimeError, - "unknown get_prefs type (%d), " - "please report", type); - res = NULL; - break; - } - return res; -} - -static PyObject * -Module_hexchat_get_context(PyObject *self, PyObject *args) -{ - PyObject *plugin; - PyObject *ctxobj; - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - ctxobj = Context_FromContext(Plugin_GetContext(plugin)); - if (ctxobj == NULL) { - Py_RETURN_NONE; - } - return ctxobj; -} - -static PyObject * -Module_hexchat_find_context(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *server = NULL; - char *channel = NULL; - PyObject *ctxobj; - char *kwlist[] = {"server", "channel", 0}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|zz:find_context", - kwlist, &server, &channel)) - return NULL; - ctxobj = Context_FromServerAndChannel(server, channel); - if (ctxobj == NULL) { - Py_RETURN_NONE; - } - return ctxobj; -} - -static PyObject * -Module_hexchat_pluginpref_set(PyObject *self, PyObject *args) -{ - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); - hexchat_plugin *prefph = Plugin_GetHandle(plugin); - int result; - char *var; - PyObject *value; - - if (!PyArg_ParseTuple(args, "sO:set_pluginpref", &var, &value)) - return NULL; - if (PyLong_Check(value)) { - int intvalue = PyLong_AsLong(value); - BEGIN_XCHAT_CALLS(NONE); - result = hexchat_pluginpref_set_int(prefph, var, intvalue); - END_XCHAT_CALLS(); - } - else if (PyUnicode_Check(value)) { - char *charvalue = PyUnicode_AsUTF8(value); - BEGIN_XCHAT_CALLS(NONE); - result = hexchat_pluginpref_set_str(prefph, var, charvalue); - END_XCHAT_CALLS(); - } - else - result = 0; - return PyBool_FromLong(result); -} - -static PyObject * -Module_hexchat_pluginpref_get(PyObject *self, PyObject *args) -{ - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); - hexchat_plugin *prefph = Plugin_GetHandle(plugin); - PyObject *ret; - char *var; - char retstr[512]; - int retint; - int result; - if (!PyArg_ParseTuple(args, "s:get_pluginpref", &var)) - return NULL; - - /* This will always return numbers as integers. */ - BEGIN_XCHAT_CALLS(NONE); - result = hexchat_pluginpref_get_str(prefph, var, retstr); - END_XCHAT_CALLS(); - if (result) { - if (strlen (retstr) <= 12) { - BEGIN_XCHAT_CALLS(NONE); - retint = hexchat_pluginpref_get_int(prefph, var); - END_XCHAT_CALLS(); - if ((retint == -1) && (strcmp(retstr, "-1") != 0)) - ret = PyUnicode_FromString(retstr); - else - ret = PyLong_FromLong(retint); - } else - ret = PyUnicode_FromString(retstr); - } - else - { - Py_INCREF(Py_None); - ret = Py_None; - } - return ret; -} - -static PyObject * -Module_hexchat_pluginpref_delete(PyObject *self, PyObject *args) -{ - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); - hexchat_plugin *prefph = Plugin_GetHandle(plugin); - char *var; - int result; - if (!PyArg_ParseTuple(args, "s:del_pluginpref", &var)) - return NULL; - BEGIN_XCHAT_CALLS(NONE); - result = hexchat_pluginpref_delete(prefph, var); - END_XCHAT_CALLS(); - return PyBool_FromLong(result); -} - -static PyObject * -Module_hexchat_pluginpref_list(PyObject *self, PyObject *args) -{ - PluginObject *plugin = (PluginObject*)Plugin_GetCurrent(); - hexchat_plugin *prefph = Plugin_GetHandle(plugin); - char list[4096]; - char* token; - int result; - PyObject *pylist; - pylist = PyList_New(0); - BEGIN_XCHAT_CALLS(NONE); - result = hexchat_pluginpref_list(prefph, list); - END_XCHAT_CALLS(); - if (result) { - token = strtok(list, ","); - while (token != NULL) { - PyList_Append(pylist, PyUnicode_FromString(token)); - token = strtok (NULL, ","); - } - } - return pylist; -} - -static PyObject * -Module_hexchat_hook_command(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *name; - PyObject *callback; - PyObject *userdata = Py_None; - int priority = HEXCHAT_PRI_NORM; - char *help = NULL; - PyObject *plugin; - Hook *hook; - char *kwlist[] = {"name", "callback", "userdata", - "priority", "help", 0}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oiz:hook_command", - kwlist, &name, &callback, &userdata, - &priority, &help)) - return NULL; - - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback is not callable"); - return NULL; - } - - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, name, NULL); - if (hook == NULL) - return NULL; - - BEGIN_XCHAT_CALLS(NONE); - hook->data = (void*)hexchat_hook_command(ph, name, priority, - Callback_Command, help, hook); - END_XCHAT_CALLS(); - - return PyLong_FromVoidPtr(hook); -} - -static PyObject * -Module_hexchat_hook_server(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *name; - PyObject *callback; - PyObject *userdata = Py_None; - int priority = HEXCHAT_PRI_NORM; - PyObject *plugin; - Hook *hook; - char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_server", - kwlist, &name, &callback, &userdata, - &priority)) - return NULL; - - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback is not callable"); - return NULL; - } - - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL, NULL); - if (hook == NULL) - return NULL; - - BEGIN_XCHAT_CALLS(NONE); - hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority, - Callback_Server, hook); - END_XCHAT_CALLS(); - - return PyLong_FromVoidPtr(hook); -} - -static PyObject * -Module_hexchat_hook_server_attrs(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *name; - PyObject *callback; - PyObject *userdata = Py_None; - int priority = HEXCHAT_PRI_NORM; - PyObject *plugin; - Hook *hook; - char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_server", - kwlist, &name, &callback, &userdata, - &priority)) - return NULL; - - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback is not callable"); - return NULL; - } - - hook = Plugin_AddHook(HOOK_XCHAT_ATTR, plugin, callback, userdata, NULL, NULL); - if (hook == NULL) - return NULL; - - BEGIN_XCHAT_CALLS(NONE); - hook->data = (void*)hexchat_hook_server_attrs(ph, name, priority, - Callback_Server, hook); - END_XCHAT_CALLS(); - - return PyLong_FromVoidPtr(hook); -} - -static PyObject * -Module_hexchat_hook_print(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *name; - PyObject *callback; - PyObject *userdata = Py_None; - int priority = HEXCHAT_PRI_NORM; - PyObject *plugin; - Hook *hook; - char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print", - kwlist, &name, &callback, &userdata, - &priority)) - return NULL; - - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback is not callable"); - return NULL; - } - - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, name, NULL); - if (hook == NULL) - return NULL; - - BEGIN_XCHAT_CALLS(NONE); - hook->data = (void*)hexchat_hook_print(ph, name, priority, - Callback_Print, hook); - END_XCHAT_CALLS(); - - return PyLong_FromVoidPtr(hook); -} - -static PyObject * -Module_hexchat_hook_print_attrs(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *name; - PyObject *callback; - PyObject *userdata = Py_None; - int priority = HEXCHAT_PRI_NORM; - PyObject *plugin; - Hook *hook; - char *kwlist[] = {"name", "callback", "userdata", "priority", 0}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|Oi:hook_print_attrs", - kwlist, &name, &callback, &userdata, - &priority)) - return NULL; - - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback is not callable"); - return NULL; - } - - hook = Plugin_AddHook(HOOK_XCHAT_ATTR, plugin, callback, userdata, name, NULL); - if (hook == NULL) - return NULL; - - BEGIN_XCHAT_CALLS(NONE); - hook->data = (void*)hexchat_hook_print_attrs(ph, name, priority, - Callback_Print_Attrs, hook); - END_XCHAT_CALLS(); - - return PyLong_FromVoidPtr(hook); -} - -static PyObject * -Module_hexchat_hook_timer(PyObject *self, PyObject *args, PyObject *kwargs) -{ - int timeout; - PyObject *callback; - PyObject *userdata = Py_None; - PyObject *plugin; - Hook *hook; - char *kwlist[] = {"timeout", "callback", "userdata", 0}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO|O:hook_timer", - kwlist, &timeout, &callback, - &userdata)) - return NULL; - - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback is not callable"); - return NULL; - } - - hook = Plugin_AddHook(HOOK_XCHAT, plugin, callback, userdata, NULL, NULL); - if (hook == NULL) - return NULL; - - BEGIN_XCHAT_CALLS(NONE); - hook->data = (void*)hexchat_hook_timer(ph, timeout, - Callback_Timer, hook); - END_XCHAT_CALLS(); - - return PyLong_FromVoidPtr(hook); -} - -static PyObject * -Module_hexchat_hook_unload(PyObject *self, PyObject *args, PyObject *kwargs) -{ - PyObject *callback; - PyObject *userdata = Py_None; - PyObject *plugin; - Hook *hook; - char *kwlist[] = {"callback", "userdata", 0}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O:hook_unload", - kwlist, &callback, &userdata)) - return NULL; - - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - if (!PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback is not callable"); - return NULL; - } - - hook = Plugin_AddHook(HOOK_UNLOAD, plugin, callback, userdata, NULL, NULL); - if (hook == NULL) - return NULL; - - return PyLong_FromVoidPtr(hook); -} - -static PyObject * -Module_hexchat_unhook(PyObject *self, PyObject *args) -{ - PyObject *plugin; - PyObject *obj; - Hook *hook; - if (!PyArg_ParseTuple(args, "O:unhook", &obj)) - return NULL; - plugin = Plugin_GetCurrent(); - if (plugin == NULL) - return NULL; - - if (PyUnicode_Check (obj)) - { - hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj)); - while (hook) - { - Plugin_RemoveHook(plugin, hook); - hook = Plugin_FindHook(plugin, PyUnicode_AsUTF8 (obj)); - } - } - else - { - hook = (Hook *)PyLong_AsVoidPtr(obj); - Plugin_RemoveHook(plugin, hook); - } - - Py_RETURN_NONE; -} - -static PyObject * -Module_xchat_get_list(PyObject *self, PyObject *args) -{ - hexchat_list *list; - PyObject *l; - const char *name; - const char *const *fields; - int i; - - if (!PyArg_ParseTuple(args, "s:get_list", &name)) - return NULL; - /* This function is thread safe, and returns statically - * allocated data. */ - fields = hexchat_list_fields(ph, "lists"); - for (i = 0; fields[i]; i++) { - if (strcmp(fields[i], name) == 0) { - /* Use the static allocated one. */ - name = fields[i]; - break; - } - } - if (fields[i] == NULL) { - PyErr_SetString(PyExc_KeyError, "list not available"); - return NULL; - } - l = PyList_New(0); - if (l == NULL) - return NULL; - BEGIN_XCHAT_CALLS(RESTORE_CONTEXT); - list = hexchat_list_get(ph, (char*)name); - if (list == NULL) - goto error; - fields = hexchat_list_fields(ph, (char*)name); - while (hexchat_list_next(ph, list)) { - PyObject *o = ListItem_New(name); - if (o == NULL || PyList_Append(l, o) == -1) { - Py_XDECREF(o); - goto error; - } - Py_DECREF(o); /* l is holding a reference */ - for (i = 0; fields[i]; i++) { - const char *fld = fields[i]+1; - PyObject *attr = NULL; - const char *sattr; - int iattr; - time_t tattr; - switch(fields[i][0]) { - case 's': - sattr = hexchat_list_str(ph, list, (char*)fld); - attr = PyUnicode_FromString(sattr?sattr:""); - break; - case 'i': - iattr = hexchat_list_int(ph, list, (char*)fld); - attr = PyLong_FromLong((long)iattr); - break; - case 't': - tattr = hexchat_list_time(ph, list, (char*)fld); - attr = PyLong_FromLong((long)tattr); - break; - case 'p': - sattr = hexchat_list_str(ph, list, (char*)fld); - if (strcmp(fld, "context") == 0) { - attr = Context_FromContext( - (hexchat_context*)sattr); - break; - } - default: /* ignore unknown (newly added?) types */ - continue; - } - if (attr == NULL) - goto error; - PyObject_SetAttrString(o, (char*)fld, attr); /* add reference on attr in o */ - Py_DECREF(attr); /* make o own attr */ - } - } - hexchat_list_free(ph, list); - goto exit; -error: - if (list) - hexchat_list_free(ph, list); - Py_DECREF(l); - l = NULL; - -exit: - END_XCHAT_CALLS(); - return l; -} - -static PyObject * -Module_xchat_get_lists(PyObject *self, PyObject *args) -{ - PyObject *l, *o; - const char *const *fields; - int i; - /* This function is thread safe, and returns statically - * allocated data. */ - fields = hexchat_list_fields(ph, "lists"); - l = PyList_New(0); - if (l == NULL) - return NULL; - for (i = 0; fields[i]; i++) { - o = PyUnicode_FromString(fields[i]); - if (o == NULL || PyList_Append(l, o) == -1) { - Py_DECREF(l); - Py_XDECREF(o); - return NULL; - } - Py_DECREF(o); /* l is holding a reference */ - } - return l; -} - -static PyObject * -Module_hexchat_nickcmp(PyObject *self, PyObject *args) -{ - char *s1, *s2; - if (!PyArg_ParseTuple(args, "ss:nickcmp", &s1, &s2)) - return NULL; - return PyLong_FromLong((long) hexchat_nickcmp(ph, s1, s2)); -} - -static PyObject * -Module_hexchat_strip(PyObject *self, PyObject *args) -{ - PyObject *result; - char *str, *str2; - int len = -1, flags = 1 | 2; - if (!PyArg_ParseTuple(args, "s|ii:strip", &str, &len, &flags)) - return NULL; - str2 = hexchat_strip(ph, str, len, flags); - result = PyUnicode_FromString(str2); - hexchat_free(ph, str2); - return result; -} - -static PyMethodDef Module_xchat_methods[] = { - {"command", Module_hexchat_command, - METH_VARARGS}, - {"prnt", Module_xchat_prnt, - METH_VARARGS}, - {"emit_print", (PyCFunction)Module_hexchat_emit_print, - METH_VARARGS|METH_KEYWORDS}, - {"get_info", Module_hexchat_get_info, - METH_VARARGS}, - {"get_prefs", Module_xchat_get_prefs, - METH_VARARGS}, - {"get_context", Module_hexchat_get_context, - METH_NOARGS}, - {"find_context", (PyCFunction)Module_hexchat_find_context, - METH_VARARGS|METH_KEYWORDS}, - {"set_pluginpref", Module_hexchat_pluginpref_set, - METH_VARARGS}, - {"get_pluginpref", Module_hexchat_pluginpref_get, - METH_VARARGS}, - {"del_pluginpref", Module_hexchat_pluginpref_delete, - METH_VARARGS}, - {"list_pluginpref", Module_hexchat_pluginpref_list, - METH_VARARGS}, - {"hook_command", (PyCFunction)Module_hexchat_hook_command, - METH_VARARGS|METH_KEYWORDS}, - {"hook_server", (PyCFunction)Module_hexchat_hook_server, - METH_VARARGS|METH_KEYWORDS}, - {"hook_server_attrs", (PyCFunction)Module_hexchat_hook_server_attrs, - METH_VARARGS|METH_KEYWORDS}, - {"hook_print", (PyCFunction)Module_hexchat_hook_print, - METH_VARARGS|METH_KEYWORDS}, - {"hook_print_attrs", (PyCFunction)Module_hexchat_hook_print_attrs, - METH_VARARGS|METH_KEYWORDS}, - {"hook_timer", (PyCFunction)Module_hexchat_hook_timer, - METH_VARARGS|METH_KEYWORDS}, - {"hook_unload", (PyCFunction)Module_hexchat_hook_unload, - METH_VARARGS|METH_KEYWORDS}, - {"unhook", Module_hexchat_unhook, - METH_VARARGS}, - {"get_list", Module_xchat_get_list, - METH_VARARGS}, - {"get_lists", Module_xchat_get_lists, - METH_NOARGS}, - {"nickcmp", Module_hexchat_nickcmp, - METH_VARARGS}, - {"strip", Module_hexchat_strip, - METH_VARARGS}, - {NULL, NULL} -}; - -#ifdef IS_PY3K -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "hexchat", /* m_name */ - "HexChat Scripting Interface", /* m_doc */ - -1, /* m_size */ - Module_xchat_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; - -static struct PyModuleDef xchat_moduledef = { - PyModuleDef_HEAD_INIT, - "xchat", /* m_name */ - "HexChat Scripting Interface", /* m_doc */ - -1, /* m_size */ - Module_xchat_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; -#endif - -static PyObject * -moduleinit_hexchat(void) -{ - PyObject *hm; -#ifdef IS_PY3K - hm = PyModule_Create(&moduledef); -#else - hm = Py_InitModule3("hexchat", Module_xchat_methods, "HexChat Scripting Interface"); -#endif - - PyModule_AddIntConstant(hm, "EAT_NONE", HEXCHAT_EAT_NONE); - PyModule_AddIntConstant(hm, "EAT_HEXCHAT", HEXCHAT_EAT_HEXCHAT); - PyModule_AddIntConstant(hm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT); /* for compat */ - PyModule_AddIntConstant(hm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN); - PyModule_AddIntConstant(hm, "EAT_ALL", HEXCHAT_EAT_ALL); - PyModule_AddIntConstant(hm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST); - PyModule_AddIntConstant(hm, "PRI_HIGH", HEXCHAT_PRI_HIGH); - PyModule_AddIntConstant(hm, "PRI_NORM", HEXCHAT_PRI_NORM); - PyModule_AddIntConstant(hm, "PRI_LOW", HEXCHAT_PRI_LOW); - PyModule_AddIntConstant(hm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST); - - PyObject_SetAttrString(hm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR)); - - return hm; -} - -static PyObject * -moduleinit_xchat(void) -{ - PyObject *xm; -#ifdef IS_PY3K - xm = PyModule_Create(&xchat_moduledef); -#else - xm = Py_InitModule3("xchat", Module_xchat_methods, "HexChat Scripting Interface"); -#endif - - PyModule_AddIntConstant(xm, "EAT_NONE", HEXCHAT_EAT_NONE); - PyModule_AddIntConstant(xm, "EAT_XCHAT", HEXCHAT_EAT_HEXCHAT); - PyModule_AddIntConstant(xm, "EAT_PLUGIN", HEXCHAT_EAT_PLUGIN); - PyModule_AddIntConstant(xm, "EAT_ALL", HEXCHAT_EAT_ALL); - PyModule_AddIntConstant(xm, "PRI_HIGHEST", HEXCHAT_PRI_HIGHEST); - PyModule_AddIntConstant(xm, "PRI_HIGH", HEXCHAT_PRI_HIGH); - PyModule_AddIntConstant(xm, "PRI_NORM", HEXCHAT_PRI_NORM); - PyModule_AddIntConstant(xm, "PRI_LOW", HEXCHAT_PRI_LOW); - PyModule_AddIntConstant(xm, "PRI_LOWEST", HEXCHAT_PRI_LOWEST); - - PyObject_SetAttrString(xm, "__version__", Py_BuildValue("(ii)", VERSION_MAJOR, VERSION_MINOR)); - - return xm; -} - -#ifdef IS_PY3K -PyMODINIT_FUNC -PyInit_hexchat(void) -{ - return moduleinit_hexchat(); -} -PyMODINIT_FUNC -PyInit_xchat(void) -{ - return moduleinit_xchat(); -} -#else -PyMODINIT_FUNC -inithexchat(void) -{ - moduleinit_hexchat(); -} -PyMODINIT_FUNC -initxchat(void) -{ - moduleinit_xchat(); -} -#endif - -/* ===================================================================== */ -/* Python interactive interpreter functions */ - -static void -IInterp_Exec(char *command) -{ - PyObject *m, *d, *o; - char *buffer; - int len; - - BEGIN_PLUGIN(interp_plugin); - - m = PyImport_AddModule("__main__"); - if (m == NULL) { - hexchat_print(ph, "Can't get __main__ module"); - goto fail; - } - d = PyModule_GetDict(m); - len = strlen(command); - - buffer = g_malloc(len + 2); - memcpy(buffer, command, len); - buffer[len] = '\n'; - buffer[len+1] = 0; - PyRun_SimpleString("import hexchat"); - o = PyRun_StringFlags(buffer, Py_single_input, d, d, NULL); - g_free(buffer); - if (o == NULL) { - PyErr_Print(); - goto fail; - } - Py_DECREF(o); - -fail: - END_PLUGIN(interp_plugin); - return; -} - -static int -IInterp_Cmd(char *word[], char *word_eol[], void *userdata) -{ - char *channel = (char *) hexchat_get_info(ph, "channel"); - if (channel && channel[0] == '>' && strcmp(channel, ">>python<<") == 0) { - hexchat_printf(ph, ">>> %s\n", word_eol[1]); - IInterp_Exec(word_eol[1]); - return HEXCHAT_EAT_HEXCHAT; - } - return HEXCHAT_EAT_NONE; -} - - -/* ===================================================================== */ -/* Python command handling */ - -static void -Command_PyList(void) -{ - GSList *list; - list = plugin_list; - if (list == NULL) { - hexchat_print(ph, "No python modules loaded"); - } else { - hexchat_print(ph, - "Name Version Filename Description\n" - "---- ------- -------- -----------\n"); - while (list != NULL) { - PluginObject *plg = (PluginObject *) list->data; - char *basename = g_path_get_basename(plg->filename); - hexchat_printf(ph, "%-12s %-8s %-20s %-10s\n", - plg->name, - *plg->version ? plg->version - : "", - basename, - *plg->description ? plg->description - : ""); - g_free(basename); - list = list->next; - } - hexchat_print(ph, "\n"); - } -} - -static void -Command_PyLoad(char *filename) -{ - PyObject *plugin; - RELEASE_XCHAT_LOCK(); - plugin = Plugin_New(filename, xchatout); - ACQUIRE_XCHAT_LOCK(); - if (plugin) - plugin_list = g_slist_append(plugin_list, plugin); -} - -static void -Command_PyUnload(char *name) -{ - PluginObject *plugin = Plugin_ByString(name); - if (!plugin) { - hexchat_print(ph, "Can't find a python plugin with that name"); - } else { - BEGIN_PLUGIN(plugin); - Plugin_Delete((PyObject*)plugin); - END_PLUGIN(plugin); - plugin_list = g_slist_remove(plugin_list, plugin); - } -} - -static void -Command_PyReload(char *name) -{ - PluginObject *plugin = Plugin_ByString(name); - if (!plugin) { - hexchat_print(ph, "Can't find a python plugin with that name"); - } else { - char *filename = g_strdup(plugin->filename); - Command_PyUnload(filename); - Command_PyLoad(filename); - g_free(filename); - } -} - -static void -Command_PyAbout(void) -{ - hexchat_print(ph, about); -} - -static int -Command_Py(char *word[], char *word_eol[], void *userdata) -{ - char *cmd = word[2]; - int ok = 0; - if (strcasecmp(cmd, "LIST") == 0) { - ok = 1; - Command_PyList(); - } else if (strcasecmp(cmd, "EXEC") == 0) { - if (word[3][0]) { - ok = 1; - IInterp_Exec(word_eol[3]); - } - } else if (strcasecmp(cmd, "LOAD") == 0) { - if (word[3][0]) { - ok = 1; - Command_PyLoad(word[3]); - } - } else if (strcasecmp(cmd, "UNLOAD") == 0) { - if (word[3][0]) { - ok = 1; - Command_PyUnload(word[3]); - } - } else if (strcasecmp(cmd, "RELOAD") == 0) { - if (word[3][0]) { - ok = 1; - Command_PyReload(word[3]); - } - } else if (strcasecmp(cmd, "CONSOLE") == 0) { - ok = 1; - hexchat_command(ph, "QUERY >>python<<"); - } else if (strcasecmp(cmd, "ABOUT") == 0) { - ok = 1; - Command_PyAbout(); - } - if (!ok) - hexchat_print(ph, usage); - return HEXCHAT_EAT_ALL; -} - -static int -Command_Load(char *word[], char *word_eol[], void *userdata) -{ - int len = strlen(word[2]); - if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) { - Command_PyLoad(word[2]); - return HEXCHAT_EAT_HEXCHAT; - } - return HEXCHAT_EAT_NONE; -} - -static int -Command_Reload(char *word[], char *word_eol[], void *userdata) -{ - int len = strlen(word[2]); - if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) { - Command_PyReload(word[2]); - return HEXCHAT_EAT_HEXCHAT; - } - return HEXCHAT_EAT_NONE; -} - -static int -Command_Unload(char *word[], char *word_eol[], void *userdata) -{ - int len = strlen(word[2]); - if (len > 3 && strcasecmp(".py", word[2]+len-3) == 0) { - Command_PyUnload(word[2]); - return HEXCHAT_EAT_HEXCHAT; - } - return HEXCHAT_EAT_NONE; -} - -/* ===================================================================== */ -/* Autoload function */ - -/* ===================================================================== */ -/* (De)initialization functions */ - -static int initialized = 0; -static int reinit_tried = 0; - -void -hexchat_plugin_get_info(char **name, char **desc, char **version, void **reserved) -{ - *name = "Python"; - *version = VERSION; - *desc = "Python scripting interface"; - if (reserved) - *reserved = NULL; -} - -int -hexchat_plugin_init(hexchat_plugin *plugin_handle, - char **plugin_name, - char **plugin_desc, - char **plugin_version, - char *arg) -{ -#ifdef IS_PY3K - wchar_t *argv[] = { L"", 0 }; -#else - char *argv[] = { "", 0 }; -#endif - - ph = plugin_handle; - - /* Block double initalization. */ - if (initialized != 0) { - hexchat_print(ph, "Python interface already loaded"); - /* deinit is called even when init fails, so keep track - * of a reinit failure. */ - reinit_tried++; - return 0; - } - initialized = 1; - - *plugin_name = "Python"; - *plugin_version = VERSION; - - /* FIXME You can't free this since it's used as long as the plugin's - * loaded, but if you unload it, everything belonging to the plugin is - * supposed to be freed anyway. - */ - *plugin_desc = g_strdup_printf ("Python %d scripting interface", PY_MAJOR_VERSION); - - /* Initialize python. */ -#ifdef IS_PY3K - Py_SetProgramName(L"hexchat"); - PyImport_AppendInittab("hexchat", PyInit_hexchat); - PyImport_AppendInittab("xchat", PyInit_xchat); -#else - Py_SetProgramName("hexchat"); - PyImport_AppendInittab("hexchat", inithexchat); - PyImport_AppendInittab("xchat", initxchat); -#endif - Py_Initialize(); - PySys_SetArgv(1, argv); - - xchatout_buffer = g_string_new (NULL); - xchatout = XChatOut_New(); - if (xchatout == NULL) { - hexchat_print(ph, "Can't allocate xchatout object"); - return 0; - } - -#ifdef WITH_THREAD - PyEval_InitThreads(); - xchat_lock = PyThread_allocate_lock(); - if (xchat_lock == NULL) { - hexchat_print(ph, "Can't allocate hexchat lock"); - Py_DECREF(xchatout); - xchatout = NULL; - return 0; - } -#endif - - main_tstate = PyEval_SaveThread(); - - interp_plugin = Plugin_New(NULL, xchatout); - if (interp_plugin == NULL) { - hexchat_print(ph, "Plugin_New() failed.\n"); -#ifdef WITH_THREAD - PyThread_free_lock(xchat_lock); -#endif - Py_DECREF(xchatout); - xchatout = NULL; - return 0; - } - - - hexchat_hook_command(ph, "", HEXCHAT_PRI_NORM, IInterp_Cmd, 0, 0); - hexchat_hook_command(ph, "PY", HEXCHAT_PRI_NORM, Command_Py, usage, 0); - hexchat_hook_command(ph, "LOAD", HEXCHAT_PRI_NORM, Command_Load, 0, 0); - hexchat_hook_command(ph, "UNLOAD", HEXCHAT_PRI_NORM, Command_Unload, 0, 0); - hexchat_hook_command(ph, "RELOAD", HEXCHAT_PRI_NORM, Command_Reload, 0, 0); -#ifdef WITH_THREAD - thread_timer = hexchat_hook_timer(ph, 300, Callback_ThreadTimer, NULL); -#endif - - hexchat_print(ph, "Python interface loaded\n"); - - Util_Autoload(); - return 1; -} - -int -hexchat_plugin_deinit(void) -{ - GSList *list; - - /* A reinitialization was tried. Just give up and live the - * environment as is. We are still alive. */ - if (reinit_tried) { - reinit_tried--; - return 1; - } - - list = plugin_list; - while (list != NULL) { - PyObject *plugin = (PyObject *) list->data; - BEGIN_PLUGIN(plugin); - Plugin_Delete(plugin); - END_PLUGIN(plugin); - list = list->next; - } - g_slist_free(plugin_list); - plugin_list = NULL; - - /* Reset xchatout buffer. */ - g_string_free (xchatout_buffer, TRUE); - xchatout_buffer = NULL; - - if (interp_plugin) { - Py_DECREF(interp_plugin); - interp_plugin = NULL; - } - - /* Switch back to the main thread state. */ - if (main_tstate) { - PyEval_RestoreThread(main_tstate); - PyThreadState_Swap(main_tstate); - main_tstate = NULL; - } - Py_Finalize(); - -#ifdef WITH_THREAD - if (thread_timer != NULL) { - hexchat_unhook(ph, thread_timer); - thread_timer = NULL; - } - PyThread_free_lock(xchat_lock); -#endif - - hexchat_print(ph, "Python interface unloaded\n"); - initialized = 0; - - return 1; -} - diff --git a/plugins/python/python.def b/plugins/python/python.def index 6ce04e98..e560f50f 100644 --- a/plugins/python/python.def +++ b/plugins/python/python.def @@ -1,4 +1,3 @@ EXPORTS hexchat_plugin_init hexchat_plugin_deinit -hexchat_plugin_get_info diff --git a/plugins/python/python.py b/plugins/python/python.py new file mode 100644 index 00000000..3845a79f --- /dev/null +++ b/plugins/python/python.py @@ -0,0 +1,497 @@ +from __future__ import print_function + +import os +import sys +from contextlib import contextmanager +import importlib +import signal +import site +import traceback +import weakref +from _hexchat_embedded import ffi, lib + +VERSION = b'2.0' # Sync with hexchat.__version__ +PLUGIN_NAME = ffi.new('char[]', b'Python') +PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' + % (sys.version_info[0], sys.version_info[1])) +PLUGIN_VERSION = ffi.new('char[]', VERSION) +hexchat = None +local_interp = None +hexchat_stdout = None +plugins = set() + + +@contextmanager +def redirected_stdout(): + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + yield + sys.stdout = hexchat_stdout + sys.stderr = hexchat_stdout + + +if os.environ.get('HEXCHAT_LOG_PYTHON'): + def log(*args): + with redirected_stdout(): + print(*args) +else: + def log(*args): + pass + + +class Stdout: + def __init__(self): + self.buffer = bytearray() + + def write(self, string): + string = string.encode() + idx = string.rfind(b'\n') + if idx is not -1: + self.buffer += string[:idx] + lib.hexchat_print(lib.ph, bytes(self.buffer)) + self.buffer = bytearray(string[idx + 1:]) + else: + self.buffer += string + + def isatty(self): + # FIXME: help() locks app despite this? + return False + + +class Attribute: + def __init__(self): + self.time = 0 + + def __repr__(self): + return ''.format(id(self)) + + +class Hook: + def __init__(self, plugin, callback, userdata, is_unload): + self.is_unload = is_unload + self.plugin = weakref.proxy(plugin) + self.callback = callback + self.userdata = userdata + self.hexchat_hook = None + self.handle = ffi.new_handle(weakref.proxy(self)) + + def __del__(self): + log('Removing hook', id(self)) + if self.is_unload is False: + assert self.hexchat_hook is not None + lib.hexchat_unhook(lib.ph, self.hexchat_hook) + + +if sys.version_info[0] is 2: + def compile_file(data, filename): + return compile(data, filename, 'exec', dont_inherit=True) + + def compile_line(string): + try: + return compile(string, '', 'eval', dont_inherit=True) + except SyntaxError: + # For some reason `print` is invalid for eval + # This will hide any return value though + return compile(string, '', 'exec', dont_inherit=True) +else: + def compile_file(data, filename): + return compile(data, filename, 'exec', optimize=2, dont_inherit=True) + + def compile_line(string): + return compile(string, '', 'eval', optimize=2, dont_inherit=True) + + +class Plugin: + def __init__(self): + self.ph = None + self.name = '' + self.filename = '' + self.version = '' + self.description = '' + self.hooks = set() + self.globals = { + '__plugin': weakref.proxy(self), + '__name__': '__main__', + } + + def add_hook(self, callback, userdata, is_unload=False): + hook = Hook(self, callback, userdata, is_unload=is_unload) + self.hooks.add(hook) + return hook + + def remove_hook(self, hook): + for h in self.hooks: + if id(h) == hook: + ud = hook.userdata + self.hooks.remove(h) + return ud + else: + log('Hook not found') + + def loadfile(self, filename): + try: + self.filename = filename + with open(filename) as f: + data = f.read() + compiled = compile_file(data, filename) + exec(compiled, self.globals) + + try: + self.name = self.globals['__module_name__'] + except KeyError: + lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set') + return False + + self.version = self.globals.get('__module_version__', '') + self.description = self.globals.get('__module_description__', '') + self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), + self.name.encode(), + self.description.encode(), + self.version.encode(), + ffi.NULL) + except Exception as e: + lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode()) + traceback.print_exc() + return False + return True + + def __del__(self): + log('unloading', self.filename) + for hook in self.hooks: + if hook.is_unload is True: + try: + hook.callback(hook.userdata) + except Exception as e: + log('Failed to run hook:', e) + traceback.print_exc() + del self.hooks + if self.ph is not None: + lib.hexchat_plugingui_remove(lib.ph, self.ph) + + +if sys.version_info[0] is 2: + def __decode(string): + return string +else: + def __decode(string): + return string.decode() + + +# There can be empty entries between non-empty ones so find the actual last value +def wordlist_len(words): + for i in range(31, 1, -1): + if ffi.string(words[i]): + return i + return 0 + + +def create_wordlist(words): + size = wordlist_len(words) + return [__decode(ffi.string(words[i])) for i in range(1, size + 1)] + + +# This function only exists for compat reasons with the C plugin +# It turns the word list from print hooks into a word_eol list +# This makes no sense to do... +def create_wordeollist(words): + words = reversed(words) + last = None + accum = None + ret = [] + for word in words: + if accum is None: + accum = word + elif word: + last = accum + accum = ' '.join((word, last)) + ret.insert(0, accum) + return ret + + +def to_cb_ret(value): + if value is None: + return 0 + else: + return int(value) + + +@ffi.def_extern() +def _on_command_hook(word, word_eol, userdata): + hook = ffi.from_handle(userdata) + word = create_wordlist(word) + word_eol = create_wordlist(word_eol) + return to_cb_ret(hook.callback(word, word_eol, hook.userdata)) + + +@ffi.def_extern() +def _on_print_hook(word, userdata): + hook = ffi.from_handle(userdata) + word = create_wordlist(word) + word_eol = create_wordeollist(word) + return to_cb_ret(hook.callback(word, word_eol, hook.userdata)) + + +@ffi.def_extern() +def _on_print_attrs_hook(word, attrs, userdata): + hook = ffi.from_handle(userdata) + word = create_wordlist(word) + word_eol = create_wordeollist(word) + attr = Attribute() + attr.time = attrs.server_time_utc + return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr)) + + +@ffi.def_extern() +def _on_server_hook(word, word_eol, userdata): + hook = ffi.from_handle(userdata) + word = create_wordlist(word) + word_eol = create_wordlist(word_eol) + return to_cb_ret(hook.callback(word, word_eol, hook.userdata)) + + +@ffi.def_extern() +def _on_server_attrs_hook(word, word_eol, attrs, userdata): + hook = ffi.from_handle(userdata) + word = create_wordlist(word) + word_eol = create_wordlist(word_eol) + attr = Attribute() + attr.time = attrs.server_time_utc + return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr)) + + +@ffi.def_extern() +def _on_timer_hook(userdata): + hook = ffi.from_handle(userdata) + if hook.callback(hook.userdata) is True: + return 1 + else: + hook.is_unload = True # Don't unhook + for h in hook.plugin.hooks: + if h == hook: + hook.plugin.hooks.remove(h) + break + return 0 + + +@ffi.def_extern(error=3) +def _on_say_command(word, word_eol, userdata): + channel = ffi.string(lib.hexchat_get_info(lib.ph, b'channel')) + if channel == b'>>python<<': + python = ffi.string(word_eol[1]) + lib.hexchat_print(lib.ph, b'>>> ' + python) + exec_in_interp(__decode(python)) + return 0 + + +def load_filename(filename): + filename = os.path.expanduser(filename) + if not os.path.isabs(filename): + configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir'))) + filename = os.path.join(configdir, 'addons', filename) + if filename and not any(plugin.filename == filename for plugin in plugins): + plugin = Plugin() + if plugin.loadfile(filename): + plugins.add(plugin) + return True + return False + + +def unload_name(name): + if name: + for plugin in plugins: + if name in (plugin.name, plugin.filename, + os.path.basename(plugin.filename)): + plugins.remove(plugin) + return True + return False + + +def reload_name(name): + if name: + for plugin in plugins: + if name in (plugin.name, plugin.filename, + os.path.basename(plugin.filename)): + filename = plugin.filename + plugins.remove(plugin) + return load_filename(filename) + return False + + +@contextmanager +def change_cwd(path): + old_cwd = os.getcwd() + os.chdir(path) + yield + os.chdir(old_cwd) + + +def autoload(): + configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir'))) + addondir = os.path.join(configdir, 'addons') + try: + with change_cwd(addondir): # Maintaining old behavior + for f in os.listdir(addondir): + if f.endswith('.py'): + log('Autoloading', f) + # TODO: Set cwd + load_filename(os.path.join(addondir, f)) + except FileNotFoundError as e: + log('Autoload failed', e) + + +def list_plugins(): + if not plugins: + lib.hexchat_print(lib.ph, b'No python modules loaded') + return + + lib.hexchat_print(lib.ph, b'Name Version Filename Description') + lib.hexchat_print(lib.ph, b'---- ------- -------- -----------') + for plugin in plugins: + basename = os.path.basename(plugin.filename).encode() + name = plugin.name.encode() + version = plugin.version.encode() if plugin.version else b'' + description = plugin.description.encode() if plugin.description else b'' + string = b'%-12s %-8s %-20s %-10s' %(name, version, basename, description) + lib.hexchat_print(lib.ph, string) + lib.hexchat_print(lib.ph, b'') + + +def exec_in_interp(python): + global local_interp + + if not python: + return + + if local_interp is None: + local_interp = Plugin() + local_interp.locals = {} + local_interp.globals['hexchat'] = hexchat + + code = compile_line(python) + try: + ret = eval(code, local_interp.globals, local_interp.locals) + if ret is not None: + lib.hexchat_print(lib.ph, '{}'.format(ret).encode()) + except Exception as e: + traceback.print_exc(file=hexchat_stdout) + + +@ffi.def_extern() +def _on_load_command(word, word_eol, userdata): + filename = ffi.string(word[2]) + if filename.endswith(b'.py'): + load_filename(__decode(filename)) + return 3 + return 0 + + +@ffi.def_extern() +def _on_unload_command(word, word_eol, userdata): + filename = ffi.string(word[2]) + if filename.endswith(b'.py'): + unload_name(__decode(filename)) + return 3 + return 0 + + +@ffi.def_extern() +def _on_reload_command(word, word_eol, userdata): + filename = ffi.string(word[2]) + if filename.endswith(b'.py'): + reload_name(__decode(filename)) + return 3 + return 0 + + +@ffi.def_extern(error=3) +def _on_py_command(word, word_eol, userdata): + subcmd = __decode(ffi.string(word[2])).lower() + + if subcmd == 'exec': + python = __decode(ffi.string(word_eol[3])) + exec_in_interp(python) + elif subcmd == 'load': + filename = __decode(ffi.string(word[3])) + load_filename(filename) + elif subcmd == 'unload': + name = __decode(ffi.string(word[3])) + if not unload_name(name): + lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name') + elif subcmd == 'reload': + name = __decode(ffi.string(word[3])) + if not reload_name(name): + lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name') + elif subcmd == 'console': + lib.hexchat_command(lib.ph, b'QUERY >>python<<') + elif subcmd == 'list': + list_plugins() + elif subcmd == 'about': + lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION) + else: + lib.hexchat_command(lib.ph, b'HELP PY') + + return 3 + + +@ffi.def_extern() +def _on_plugin_init(plugin_name, plugin_desc, plugin_version, arg, libdir): + global hexchat + global hexchat_stdout + + signal.signal(signal.SIGINT, signal.SIG_DFL) + + plugin_name[0] = PLUGIN_NAME + plugin_desc[0] = PLUGIN_DESC + plugin_version[0] = PLUGIN_VERSION + + try: + libdir = __decode(ffi.string(libdir)) + modpath = os.path.join(libdir, '..', 'python') + sys.path.append(os.path.abspath(modpath)) + hexchat = importlib.import_module('hexchat') + except (UnicodeDecodeError, ImportError) as e: + lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode()) + return 0 + + hexchat_stdout = Stdout() + sys.stdout = hexchat_stdout + sys.stderr = hexchat_stdout + + lib.hexchat_hook_command(lib.ph, b'', 0, lib._on_say_command, ffi.NULL, ffi.NULL) + lib.hexchat_hook_command(lib.ph, b'LOAD', 0, lib._on_load_command, ffi.NULL, ffi.NULL) + lib.hexchat_hook_command(lib.ph, b'UNLOAD', 0, lib._on_unload_command, ffi.NULL, ffi.NULL) + lib.hexchat_hook_command(lib.ph, b'RELOAD', 0, lib._on_reload_command, ffi.NULL, ffi.NULL) + lib.hexchat_hook_command(lib.ph, b'PY', 0, lib._on_py_command, b'''Usage: /PY LOAD + UNLOAD + RELOAD + LIST + EXEC + CONSOLE + ABOUT''', ffi.NULL) + + lib.hexchat_print(lib.ph, b'Python interface loaded') + autoload() + return 1 + + +@ffi.def_extern() +def _on_plugin_deinit(): + global local_interp + global hexchat + global hexchat_stdout + global plugins + + plugins = set() + local_interp = None + hexchat = None + hexchat_stdout = None + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + + for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'): + try: + del sys.modules[mod] + except KeyError: + pass + + return 1 diff --git a/plugins/python/python2.vcxproj b/plugins/python/python2.vcxproj index f914a865..0b098112 100644 --- a/plugins/python/python2.vcxproj +++ b/plugins/python/python2.vcxproj @@ -37,6 +37,9 @@ "$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies) $(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories) + + "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" + @@ -48,12 +51,15 @@ "$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies) $(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories) + + "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" + - + diff --git a/plugins/python/python3.vcxproj b/plugins/python/python3.vcxproj index 815dc8b1..5868d3b0 100644 --- a/plugins/python/python3.vcxproj +++ b/plugins/python/python3.vcxproj @@ -37,6 +37,9 @@ "$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies) $(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories) + + "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" + @@ -48,12 +51,20 @@ "$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies) $(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories) + + "$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c" + + + + + + - + - + \ No newline at end of file diff --git a/plugins/python/python3.vcxproj.filters b/plugins/python/python3.vcxproj.filters index 9165e798..5c5834bb 100644 --- a/plugins/python/python3.vcxproj.filters +++ b/plugins/python/python3.vcxproj.filters @@ -9,13 +9,26 @@ - - Source Files - + Resource Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + \ No newline at end of file diff --git a/plugins/python/xchat.py b/plugins/python/xchat.py new file mode 100644 index 00000000..6922490b --- /dev/null +++ b/plugins/python/xchat.py @@ -0,0 +1 @@ +from _hexchat import * -- cgit 1.4.1 From a2ff661d40bcd49a0be973b7b60583fde64e09c2 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 25 Jul 2018 21:41:05 +0200 Subject: python: Various cffi fixes - fixed /py exec behaviour - fixed hexchat.unload_hook() failing when passed a hook id - fixed get_list() calls in python3 --- plugins/python/_hexchat.py | 11 ++++++++++- plugins/python/python.py | 5 +++-- 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py index 52b3ec14..50ccfb83 100644 --- a/plugins/python/_hexchat.py +++ b/plugins/python/_hexchat.py @@ -150,6 +150,15 @@ class ListItem: return '<{} list item at {}>'.format(self._listname, id(self)) +# done this way for speed +if sys.version_info[0] == 2: + def get_getter(name): + return ord(name[0]) +else: + def get_getter(name): + return name[0] + + def get_list(name): # XXX: This function is extremely inefficient and could be interators and # lazily loaded properties, but for API compat we stay slow @@ -189,7 +198,7 @@ def get_list(name): while lib.hexchat_list_next(lib.ph, list_) is 1: item = ListItem(orig_name) for field in fields: - getter = getters.get(ord(field[0])) + getter = getters.get(get_getter(field)) if getter is not None: field_name = field[1:] setattr(item, __cached_decoded_str(field_name), getter(field_name)) diff --git a/plugins/python/python.py b/plugins/python/python.py index 3845a79f..e6a61b5e 100644 --- a/plugins/python/python.py +++ b/plugins/python/python.py @@ -98,7 +98,8 @@ else: return compile(data, filename, 'exec', optimize=2, dont_inherit=True) def compile_line(string): - return compile(string, '', 'eval', optimize=2, dont_inherit=True) + # newline appended to solve unexpected EOF issues + return compile(string + '\n', '', 'single', optimize=2, dont_inherit=True) class Plugin: @@ -122,7 +123,7 @@ class Plugin: def remove_hook(self, hook): for h in self.hooks: if id(h) == hook: - ud = hook.userdata + ud = h.userdata self.hooks.remove(h) return ud else: -- cgit 1.4.1 From ed55330153e7d85e753d1321c4e46e9bb6833735 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Wed, 5 Dec 2018 19:45:30 -0500 Subject: python: Fix console not eating commands --- plugins/python/python.py | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/python/python.py b/plugins/python/python.py index e6a61b5e..1eeb10b4 100644 --- a/plugins/python/python.py +++ b/plugins/python/python.py @@ -281,6 +281,7 @@ def _on_say_command(word, word_eol, userdata): python = ffi.string(word_eol[1]) lib.hexchat_print(lib.ph, b'>>> ' + python) exec_in_interp(__decode(python)) + return 1 return 0 -- cgit 1.4.1 From 3ebfa83fdd43335da1dd2d39f0bfae91d67b8c90 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Dec 2018 20:37:56 +0200 Subject: python: Made sure to set sys.argv if it is not set. fixes #2282 --- plugins/python/python.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins') diff --git a/plugins/python/python.py b/plugins/python/python.py index 1eeb10b4..942b0ce5 100644 --- a/plugins/python/python.py +++ b/plugins/python/python.py @@ -10,6 +10,9 @@ import traceback import weakref from _hexchat_embedded import ffi, lib +if not hasattr(sys, 'argv'): + sys.argv = [''] + VERSION = b'2.0' # Sync with hexchat.__version__ PLUGIN_NAME = ffi.new('char[]', b'Python') PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' -- cgit 1.4.1 From f7713a6a64ee55d3c20e9e27b8f8a5e98385ff57 Mon Sep 17 00:00:00 2001 From: linuxdaemon Date: Wed, 26 Dec 2018 16:15:25 -0600 Subject: python: Make the plugins table dynamically sized (#2291) Adjust the width of the columns depending on the length of the data in each element --- plugins/python/python.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/python/python.py b/plugins/python/python.py index 942b0ce5..371fbf40 100644 --- a/plugins/python/python.py +++ b/plugins/python/python.py @@ -349,15 +349,28 @@ def list_plugins(): lib.hexchat_print(lib.ph, b'No python modules loaded') return - lib.hexchat_print(lib.ph, b'Name Version Filename Description') - lib.hexchat_print(lib.ph, b'---- ------- -------- -----------') + tbl_headers = [b'Name', b'Version', b'Filename', b'Description'] + tbl = [ + tbl_headers, + [(b'-' * len(s)) for s in tbl_headers] + ] + for plugin in plugins: basename = os.path.basename(plugin.filename).encode() name = plugin.name.encode() version = plugin.version.encode() if plugin.version else b'' description = plugin.description.encode() if plugin.description else b'' - string = b'%-12s %-8s %-20s %-10s' %(name, version, basename, description) - lib.hexchat_print(lib.ph, string) + tbl.append((name, version, basename, description)) + + column_sizes = [ + max(len(item) for item in column) + for column in zip(*tbl) + ] + + for row in tbl: + lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i]) + for i, item in enumerate(row))) + lib.hexchat_print(lib.ph, b'') -- cgit 1.4.1 From a5a727122b66c9003b44fcdc199ad56dbe15a131 Mon Sep 17 00:00:00 2001 From: linuxdaemon Date: Thu, 27 Dec 2018 13:46:02 -0600 Subject: python: Make sure `help()` doesn't cause hexchat to hang (#2290) * Make sure `help()` doesn't cause hexchat to hang Replace `pydoc.help` with a copy of `pydoc.Helper` with an empty `StringIO` instead of stdin * Handle BytesIO vs StringIO on 2.7 --- plugins/python/python.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/python/python.py b/plugins/python/python.py index 371fbf40..1afb36c4 100644 --- a/plugins/python/python.py +++ b/plugins/python/python.py @@ -1,6 +1,7 @@ from __future__ import print_function import os +import pydoc import sys from contextlib import contextmanager import importlib @@ -8,6 +9,12 @@ import signal import site import traceback import weakref + +if sys.version_info < (3, 0): + from io import BytesIO as HelpEater +else: + from io import StringIO as HelpEater + from _hexchat_embedded import ffi, lib if not hasattr(sys, 'argv'): @@ -57,7 +64,6 @@ class Stdout: self.buffer += string def isatty(self): - # FIXME: help() locks app despite this? return False @@ -474,6 +480,7 @@ def _on_plugin_init(plugin_name, plugin_desc, plugin_version, arg, libdir): hexchat_stdout = Stdout() sys.stdout = hexchat_stdout sys.stderr = hexchat_stdout + pydoc.help = pydoc.Helper(HelpEater(), HelpEater()) lib.hexchat_hook_command(lib.ph, b'', 0, lib._on_say_command, ffi.NULL, ffi.NULL) lib.hexchat_hook_command(lib.ph, b'LOAD', 0, lib._on_load_command, ffi.NULL, ffi.NULL) @@ -505,6 +512,7 @@ def _on_plugin_deinit(): hexchat_stdout = None sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ + pydoc.help = pydoc.Helper() for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'): try: -- cgit 1.4.1 From 7abeb10cf1f82fbad4d167f9e6f6918e1f47650b Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Dec 2018 20:46:31 +0200 Subject: python: plugin cleanup and refactor --- plugins/python/_hexchat.py | 91 +++++++++++++++++++-------------- plugins/python/python.py | 99 +++++++++++++++++++++++------------- plugins/python/python_style_guide.md | 26 ++++++++++ 3 files changed, 143 insertions(+), 73 deletions(-) create mode 100644 plugins/python/python_style_guide.md (limited to 'plugins') diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py index 50ccfb83..ebee5657 100644 --- a/plugins/python/_hexchat.py +++ b/plugins/python/_hexchat.py @@ -1,6 +1,7 @@ -from contextlib import contextmanager import inspect import sys +from contextlib import contextmanager + from _hexchat_embedded import ffi, lib __all__ = [ @@ -40,13 +41,15 @@ def __get_current_plugin(): while '__plugin' not in frame.f_globals: frame = frame.f_back assert frame is not None + return frame.f_globals['__plugin'] # Keeping API compat -if sys.version_info[0] is 2: +if sys.version_info[0] == 2: def __decode(string): return string + else: def __decode(string): return string.decode() @@ -64,16 +67,18 @@ def emit_print(event_name, *args, **kwargs): arg = args[i].encode() if len(args) > i else b'' cstring = ffi.new('char[]', arg) cargs.append(cstring) - if time is 0: + + if time == 0: return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs) - else: - attrs = lib.hexchat_event_attrs_create(lib.ph) - attrs.server_time_utc = time - ret = lib.hexchat_emit_print(lib.ph, attrs, event_name.encode(), *cargs) - lib.hexchat_event_attrs_free(lib.ph, attrs) - return ret + + attrs = lib.hexchat_event_attrs_create(lib.ph) + attrs.server_time_utc = time + ret = lib.hexchat_emit_print(lib.ph, attrs, event_name.encode(), *cargs) + lib.hexchat_event_attrs_free(lib.ph, attrs) + return ret +# TODO: this shadows itself. command should be changed to cmd def command(command): lib.hexchat_command(lib.ph, command.encode()) @@ -97,21 +102,24 @@ def get_info(name): # Surely there is a less dumb way? ptr = repr(ret).rsplit(' ', 1)[1][:-1] return ptr + return __decode(ffi.string(ret)) def get_prefs(name): string_out = ffi.new('char**') int_out = ffi.new('int*') - type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out) - if type is 0: + _type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out) + if _type == 0: return None - elif type is 1: + + if _type == 1: return __decode(ffi.string(string_out[0])) - elif type is 2 or type is 3: # XXX: 3 should be a bool, but keeps API + + if _type in (2, 3): # XXX: 3 should be a bool, but keeps API return int_out[0] - else: - assert False + + raise AssertionError('Out of bounds pref storage') def __cstrarray_to_list(arr): @@ -120,6 +128,7 @@ def __cstrarray_to_list(arr): while arr[i] != ffi.NULL: ret.append(ffi.string(arr[i])) i += 1 + return ret @@ -127,8 +136,7 @@ __FIELD_CACHE = {} def __get_fields(name): - return __FIELD_CACHE.setdefault(name, - __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name))) + return __FIELD_CACHE.setdefault(name, __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name))) __FIELD_PROPERTY_CACHE = {} @@ -154,6 +162,7 @@ class ListItem: if sys.version_info[0] == 2: def get_getter(name): return ord(name[0]) + else: def get_getter(name): return name[0] @@ -179,6 +188,7 @@ def get_list(name): string = lib.hexchat_list_str(lib.ph, list_, field) if string != ffi.NULL: return __decode(ffi.string(string)) + return '' def ptr_getter(field): @@ -186,6 +196,7 @@ def get_list(name): ptr = lib.hexchat_list_str(lib.ph, list_, field) ctx = ffi.cast('hexchat_context*', ptr) return Context(ctx) + return None getters = { @@ -195,25 +206,27 @@ def get_list(name): ord('p'): ptr_getter, } - while lib.hexchat_list_next(lib.ph, list_) is 1: + while lib.hexchat_list_next(lib.ph, list_) == 1: item = ListItem(orig_name) - for field in fields: - getter = getters.get(get_getter(field)) + for _field in fields: + getter = getters.get(get_getter(_field)) if getter is not None: - field_name = field[1:] + field_name = _field[1:] setattr(item, __cached_decoded_str(field_name), getter(field_name)) + ret.append(item) lib.hexchat_list_free(lib.ph, list_) return ret +# TODO: 'command' here shadows command above, and should be renamed to cmd def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None): plugin = __get_current_plugin() hook = plugin.add_hook(callback, userdata) handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook, - help.encode() if help is not None else ffi.NULL, - hook.handle) + help.encode() if help is not None else ffi.NULL, hook.handle) + hook.hexchat_hook = handle return id(hook) @@ -221,8 +234,7 @@ def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None) def hook_print(name, callback, userdata=None, priority=PRI_NORM): plugin = __get_current_plugin() hook = plugin.add_hook(callback, userdata) - handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, - hook.handle) + handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, hook.handle) hook.hexchat_hook = handle return id(hook) @@ -230,8 +242,7 @@ def hook_print(name, callback, userdata=None, priority=PRI_NORM): def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM): plugin = __get_current_plugin() hook = plugin.add_hook(callback, userdata) - handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, - hook.handle) + handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, hook.handle) hook.hexchat_hook = handle return id(hook) @@ -239,8 +250,7 @@ def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM): def hook_server(name, callback, userdata=None, priority=PRI_NORM): plugin = __get_current_plugin() hook = plugin.add_hook(callback, userdata) - handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, - hook.handle) + handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, hook.handle) hook.hexchat_hook = handle return id(hook) @@ -248,8 +258,7 @@ def hook_server(name, callback, userdata=None, priority=PRI_NORM): def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM): plugin = __get_current_plugin() hook = plugin.add_hook(callback, userdata) - handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, - hook.handle) + handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, hook.handle) hook.hexchat_hook = handle return id(hook) @@ -276,17 +285,18 @@ def unhook(handle): def set_pluginpref(name, value): if isinstance(value, str): return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode())) - elif isinstance(value, int): + + if isinstance(value, int): return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value)) - else: - # XXX: This should probably raise but this keeps API - return False + + # XXX: This should probably raise but this keeps API + return False def get_pluginpref(name): name = name.encode() string_out = ffi.new('char[512]') - if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) is not 1: + if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) != 1: return None string = ffi.string(string_out) @@ -308,8 +318,9 @@ def del_pluginpref(name): def list_pluginpref(): prefs_str = ffi.new('char[4096]') - if lib.hexchat_pluginpref_list(lib.ph, prefs_str) is 1: + if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1: return __decode(prefs_str).split(',') + return [] @@ -320,6 +331,7 @@ class Context: def __eq__(self, value): if not isinstance(value, Context): return False + return self._ctx == value._ctx @contextmanager @@ -327,9 +339,9 @@ class Context: old_ctx = lib.hexchat_get_context(lib.ph) if not self.set(): # XXX: Behavior change, previously used wrong context - lib.hexchat_print(lib.ph, - b'Context object refers to closed context, ignoring call') + lib.hexchat_print(lib.ph, b'Context object refers to closed context, ignoring call') return + yield lib.hexchat_set_context(lib.ph, old_ctx) @@ -370,4 +382,5 @@ def find_context(server=None, channel=None): ctx = lib.hexchat_find_context(lib.ph, server, channel) if ctx == ffi.NULL: return None + return Context(ctx) diff --git a/plugins/python/python.py b/plugins/python/python.py index 1afb36c4..30694802 100644 --- a/plugins/python/python.py +++ b/plugins/python/python.py @@ -1,30 +1,30 @@ from __future__ import print_function +import importlib import os import pydoc -import sys -from contextlib import contextmanager -import importlib import signal -import site +import sys import traceback import weakref +from contextlib import contextmanager + +from _hexchat_embedded import ffi, lib if sys.version_info < (3, 0): from io import BytesIO as HelpEater else: from io import StringIO as HelpEater -from _hexchat_embedded import ffi, lib - if not hasattr(sys, 'argv'): sys.argv = [''] VERSION = b'2.0' # Sync with hexchat.__version__ PLUGIN_NAME = ffi.new('char[]', b'Python') -PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' - % (sys.version_info[0], sys.version_info[1])) +PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1])) PLUGIN_VERSION = ffi.new('char[]', VERSION) + +# TODO: Constants should be screaming snake case hexchat = None local_interp = None hexchat_stdout = None @@ -40,10 +40,11 @@ def redirected_stdout(): sys.stderr = hexchat_stdout -if os.environ.get('HEXCHAT_LOG_PYTHON'): +if os.getenv('HEXCHAT_LOG_PYTHON'): def log(*args): with redirected_stdout(): print(*args) + else: def log(*args): pass @@ -56,7 +57,7 @@ class Stdout: def write(self, string): string = string.encode() idx = string.rfind(b'\n') - if idx is not -1: + if idx != -1: self.buffer += string[:idx] lib.hexchat_print(lib.ph, bytes(self.buffer)) self.buffer = bytearray(string[idx + 1:]) @@ -91,13 +92,15 @@ class Hook: lib.hexchat_unhook(lib.ph, self.hexchat_hook) -if sys.version_info[0] is 2: +if sys.version_info[0] == 2: def compile_file(data, filename): return compile(data, filename, 'exec', dont_inherit=True) + def compile_line(string): try: return compile(string, '', 'eval', dont_inherit=True) + except SyntaxError: # For some reason `print` is invalid for eval # This will hide any return value though @@ -106,6 +109,7 @@ else: def compile_file(data, filename): return compile(data, filename, 'exec', optimize=2, dont_inherit=True) + def compile_line(string): # newline appended to solve unexpected EOF issues return compile(string + '\n', '', 'single', optimize=2, dont_inherit=True) @@ -135,8 +139,9 @@ class Plugin: ud = h.userdata self.hooks.remove(h) return ud - else: - log('Hook not found') + + log('Hook not found') + return None def loadfile(self, filename): try: @@ -148,21 +153,22 @@ class Plugin: try: self.name = self.globals['__module_name__'] + except KeyError: lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set') + return False self.version = self.globals.get('__module_version__', '') self.description = self.globals.get('__module_description__', '') - self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), - self.name.encode(), - self.description.encode(), - self.version.encode(), - ffi.NULL) + self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), self.name.encode(), + self.description.encode(), self.version.encode(), ffi.NULL) + except Exception as e: lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode()) traceback.print_exc() return False + return True def __del__(self): @@ -171,17 +177,20 @@ class Plugin: if hook.is_unload is True: try: hook.callback(hook.userdata) + except Exception as e: log('Failed to run hook:', e) traceback.print_exc() + del self.hooks if self.ph is not None: lib.hexchat_plugingui_remove(lib.ph, self.ph) -if sys.version_info[0] is 2: +if sys.version_info[0] == 2: def __decode(string): return string + else: def __decode(string): return string.decode() @@ -192,6 +201,7 @@ def wordlist_len(words): for i in range(31, 1, -1): if ffi.string(words[i]): return i + return 0 @@ -205,24 +215,26 @@ def create_wordlist(words): # This makes no sense to do... def create_wordeollist(words): words = reversed(words) - last = None accum = None ret = [] for word in words: if accum is None: accum = word + elif word: last = accum accum = ' '.join((word, last)) + ret.insert(0, accum) + return ret def to_cb_ret(value): if value is None: return 0 - else: - return int(value) + + return int(value) @ffi.def_extern() @@ -274,13 +286,14 @@ def _on_timer_hook(userdata): hook = ffi.from_handle(userdata) if hook.callback(hook.userdata) is True: return 1 - else: - hook.is_unload = True # Don't unhook - for h in hook.plugin.hooks: - if h == hook: - hook.plugin.hooks.remove(h) - break - return 0 + + hook.is_unload = True # Don't unhook + for h in hook.plugin.hooks: + if h == hook: + hook.plugin.hooks.remove(h) + break + + return 0 @ffi.def_extern(error=3) @@ -291,6 +304,7 @@ def _on_say_command(word, word_eol, userdata): lib.hexchat_print(lib.ph, b'>>> ' + python) exec_in_interp(__decode(python)) return 1 + return 0 @@ -298,33 +312,36 @@ def load_filename(filename): filename = os.path.expanduser(filename) if not os.path.isabs(filename): configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir'))) + filename = os.path.join(configdir, 'addons', filename) + if filename and not any(plugin.filename == filename for plugin in plugins): plugin = Plugin() if plugin.loadfile(filename): plugins.add(plugin) return True + return False def unload_name(name): if name: for plugin in plugins: - if name in (plugin.name, plugin.filename, - os.path.basename(plugin.filename)): + if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)): plugins.remove(plugin) return True + return False def reload_name(name): if name: for plugin in plugins: - if name in (plugin.name, plugin.filename, - os.path.basename(plugin.filename)): + if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)): filename = plugin.filename plugins.remove(plugin) return load_filename(filename) + return False @@ -346,6 +363,7 @@ def autoload(): log('Autoloading', f) # TODO: Set cwd load_filename(os.path.join(addondir, f)) + except FileNotFoundError as e: log('Autoload failed', e) @@ -376,7 +394,6 @@ def list_plugins(): for row in tbl: lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i]) for i, item in enumerate(row))) - lib.hexchat_print(lib.ph, b'') @@ -396,6 +413,7 @@ def exec_in_interp(python): ret = eval(code, local_interp.globals, local_interp.locals) if ret is not None: lib.hexchat_print(lib.ph, '{}'.format(ret).encode()) + except Exception as e: traceback.print_exc(file=hexchat_stdout) @@ -406,6 +424,7 @@ def _on_load_command(word, word_eol, userdata): if filename.endswith(b'.py'): load_filename(__decode(filename)) return 3 + return 0 @@ -415,6 +434,7 @@ def _on_unload_command(word, word_eol, userdata): if filename.endswith(b'.py'): unload_name(__decode(filename)) return 3 + return 0 @@ -424,6 +444,7 @@ def _on_reload_command(word, word_eol, userdata): if filename.endswith(b'.py'): reload_name(__decode(filename)) return 3 + return 0 @@ -434,23 +455,30 @@ def _on_py_command(word, word_eol, userdata): if subcmd == 'exec': python = __decode(ffi.string(word_eol[3])) exec_in_interp(python) + elif subcmd == 'load': filename = __decode(ffi.string(word[3])) load_filename(filename) + elif subcmd == 'unload': name = __decode(ffi.string(word[3])) if not unload_name(name): lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name') + elif subcmd == 'reload': name = __decode(ffi.string(word[3])) if not reload_name(name): lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name') + elif subcmd == 'console': lib.hexchat_command(lib.ph, b'QUERY >>python<<') + elif subcmd == 'list': list_plugins() + elif subcmd == 'about': lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION) + else: lib.hexchat_command(lib.ph, b'HELP PY') @@ -473,8 +501,10 @@ def _on_plugin_init(plugin_name, plugin_desc, plugin_version, arg, libdir): modpath = os.path.join(libdir, '..', 'python') sys.path.append(os.path.abspath(modpath)) hexchat = importlib.import_module('hexchat') + except (UnicodeDecodeError, ImportError) as e: lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode()) + return 0 hexchat_stdout = Stdout() @@ -517,6 +547,7 @@ def _on_plugin_deinit(): for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'): try: del sys.modules[mod] + except KeyError: pass diff --git a/plugins/python/python_style_guide.md b/plugins/python/python_style_guide.md new file mode 100644 index 00000000..41db2474 --- /dev/null +++ b/plugins/python/python_style_guide.md @@ -0,0 +1,26 @@ +# HexChat Python Module Style Guide + +(This is a work in progress). + +## General rules + +- PEP8 as general fallback recommendations +- Max line length: 120 +- Avoid overcomplex compound statements. i.e. dont do this: `somevar = x if x == y else z if a == b and c == b else x` + +## Indentation style + +### Multi-line functions + +```python +foo(really_long_arg_1, + really_long_arg_2) +``` + +### Mutli-line lists/dicts + +```python +foo = { + 'bar': 'baz', +} +``` -- cgit 1.4.1 From 586f089df6cdc465411ad1805b618ec44acc20ab Mon Sep 17 00:00:00 2001 From: jacob1 Date: Sun, 23 Jun 2019 15:43:44 -0400 Subject: Python: Fix error in hexchat.emit_print when passing time attribute --- plugins/python/_hexchat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py index ebee5657..567b3493 100644 --- a/plugins/python/_hexchat.py +++ b/plugins/python/_hexchat.py @@ -73,7 +73,7 @@ def emit_print(event_name, *args, **kwargs): attrs = lib.hexchat_event_attrs_create(lib.ph) attrs.server_time_utc = time - ret = lib.hexchat_emit_print(lib.ph, attrs, event_name.encode(), *cargs) + ret = lib.hexchat_emit_print_attrs(lib.ph, attrs, event_name.encode(), *cargs) lib.hexchat_event_attrs_free(lib.ph, attrs) return ret -- cgit 1.4.1 From ad5be08a0703659e1b90ec4a5c6ff58a42660bae Mon Sep 17 00:00:00 2001 From: Simon Levermann Date: Wed, 23 Oct 2019 09:06:03 +0200 Subject: Ignore some non-interesting filesystem types Generally, how much space we have in squashfs, or tmpfs shouldn't interest us. This becomes more relevant in distros like Ubuntu, where snaps are a thing, and each snap mounts their own FS in a squashfs that is always full, thus falsifying the output of sysinfo. --- plugins/sysinfo/shared/df.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/sysinfo/shared/df.c b/plugins/sysinfo/shared/df.c index ce0760a6..bb2c2131 100644 --- a/plugins/sysinfo/shared/df.c +++ b/plugins/sysinfo/shared/df.c @@ -26,7 +26,7 @@ int xs_parse_df(gint64 *out_total, gint64 *out_free) FILE *pipe; char buffer[bsize]; - pipe = popen("df -k -l -P", "r"); + pipe = popen("df -k -l -P --exclude-type=squashfs --exclude-type=devtmpfs --exclude-type=tmpfs", "r"); if(pipe==NULL) return 1; -- cgit 1.4.1 From c522ccce7fd10ccb22ee9dc314256c90232f8601 Mon Sep 17 00:00:00 2001 From: pkubaj Date: Sun, 22 Dec 2019 13:50:57 +0000 Subject: Fix build on FreeBSD --- plugins/sysinfo/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/sysinfo/meson.build b/plugins/sysinfo/meson.build index 8b67da2e..7e2cdb6c 100644 --- a/plugins/sysinfo/meson.build +++ b/plugins/sysinfo/meson.build @@ -13,13 +13,13 @@ sysinfo_includes = [] sysinfo_cargs = [] system = host_machine.system() -if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'darwin' +if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'darwin' or system == 'freebsd' sysinfo_includes += 'shared' sysinfo_sources += [ 'shared/df.c' ] - if system == 'linux' or system == 'gnu' or system.startswith('gnu/') + if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'freebsd' libpci = dependency('libpci', required: false, method: 'pkg-config') if libpci.found() sysinfo_deps += libpci -- cgit 1.4.1 From 5deb69591992d4fede9090b60d3dc847612a4d60 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Wed, 11 Mar 2020 11:07:56 -0700 Subject: build: Better support building against python 3.8+ Closes #2441 --- plugins/python/meson.build | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/python/meson.build b/plugins/python/meson.build index 2ad5128e..eb762134 100644 --- a/plugins/python/meson.build +++ b/plugins/python/meson.build @@ -1,6 +1,12 @@ python_opt = get_option('with-python') if python_opt.startswith('python3') - python_dep = dependency(python_opt, version: '>= 3.3') + # Python 3.8 introduced a new -embed variant + if not python_opt.endswith('-embed') + python_dep = dependency(python_opt + '-embed', version: '>= 3.3', required: false) + endif + if not python_dep.found() + python_dep = dependency(python_opt, version: '>= 3.3') + endif else python_dep = dependency(python_opt, version: '>= 2.7') endif -- cgit 1.4.1 From 3871fbaacb580e959f0d358cc18a76de6b8fd6a4 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Wed, 11 Mar 2020 11:13:25 -0700 Subject: build: Fix potential undefined variable --- plugins/python/meson.build | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/python/meson.build b/plugins/python/meson.build index eb762134..5fd7ec2f 100644 --- a/plugins/python/meson.build +++ b/plugins/python/meson.build @@ -3,8 +3,10 @@ if python_opt.startswith('python3') # Python 3.8 introduced a new -embed variant if not python_opt.endswith('-embed') python_dep = dependency(python_opt + '-embed', version: '>= 3.3', required: false) - endif - if not python_dep.found() + if not python_dep.found() + python_dep = dependency(python_opt, version: '>= 3.3') + endif + else python_dep = dependency(python_opt, version: '>= 3.3') endif else -- cgit 1.4.1 From c5a798beec0f24f6828cd3c43bbefd1e18a9d33a Mon Sep 17 00:00:00 2001 From: Bakasura Date: Mon, 13 Jul 2020 18:27:27 -0500 Subject: FiSHLiM: Support for CBC mode + more commands (#2347) --- plugins/fishlim/fish.c | 455 ++++++++++++++++++++------ plugins/fishlim/fish.h | 17 +- plugins/fishlim/fishlim.vcxproj.filters | 6 + plugins/fishlim/keystore.c | 61 +++- plugins/fishlim/keystore.h | 5 +- plugins/fishlim/meson.build | 3 + plugins/fishlim/plugin_hexchat.c | 342 ++++++++++++------- plugins/fishlim/tests/fake/keystore.c | 47 +++ plugins/fishlim/tests/meson.build | 17 + plugins/fishlim/tests/old_version/fish.c | 194 +++++++++++ plugins/fishlim/tests/old_version/fish.h | 39 +++ plugins/fishlim/tests/old_version/meson.build | 4 + plugins/fishlim/tests/tests.c | 248 ++++++++++++++ plugins/fishlim/utils.c | 70 ++++ plugins/fishlim/utils.h | 35 ++ 15 files changed, 1306 insertions(+), 237 deletions(-) create mode 100644 plugins/fishlim/tests/fake/keystore.c create mode 100644 plugins/fishlim/tests/meson.build create mode 100644 plugins/fishlim/tests/old_version/fish.c create mode 100644 plugins/fishlim/tests/old_version/fish.h create mode 100644 plugins/fishlim/tests/old_version/meson.build create mode 100644 plugins/fishlim/tests/tests.c create mode 100644 plugins/fishlim/utils.c create mode 100644 plugins/fishlim/utils.h (limited to 'plugins') diff --git a/plugins/fishlim/fish.c b/plugins/fishlim/fish.c index 00ecfbfa..9301d5d5 100644 --- a/plugins/fishlim/fish.c +++ b/plugins/fishlim/fish.c @@ -1,6 +1,7 @@ /* Copyright (c) 2010 Samuel Lidén Borell + Copyright (c) 2019 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27,15 +28,19 @@ #define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER #endif +#include #include #include +#include #include +#include #include "keystore.h" #include "fish.h" #define IB 64 -static const char fish_base64[64] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +static const char fish_base64[] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; static const signed char fish_unbase64[256] = { IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, @@ -53,6 +58,12 @@ static const signed char fish_unbase64[256] = { 27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB, }; +/** + * Convert Int to 4 Bytes (Big-endian) + * + * @param int source + * @param char* dest + */ #define GET_BYTES(dest, source) do { \ *((dest)++) = ((source) >> 24) & 0xFF; \ *((dest)++) = ((source) >> 16) & 0xFF; \ @@ -60,135 +71,387 @@ static const signed char fish_unbase64[256] = { *((dest)++) = (source) & 0xFF; \ } while (0); +/** + * Convert 4 Bytes to Int (Big-endian) + * + * @param char* source + * @param int dest + */ +#define GET_LONG(dest, source) do { \ + dest = ((uint8_t)*((source)++) << 24); \ + dest |= ((uint8_t)*((source)++) << 16); \ + dest |= ((uint8_t)*((source)++) << 8); \ + dest |= (uint8_t)*((source)++); \ +} while (0); + + +/** + * Encode ECB FiSH Base64 + * + * @param [in] message Bytes to encode + * @param [in] message_len Size of bytes to encode + * @return Array of char with encoded string + */ +char *fish_base64_encode(const char *message, size_t message_len) { + BF_LONG left = 0, right = 0; + int i, j; + char *encoded = NULL; + char *end = NULL; + char *msg = NULL; + + if (message_len == 0) + return NULL; + + /* Each 8-byte block becomes 12 bytes (fish base64 format) and add 1 byte for \0 */ + encoded = g_malloc(((message_len - 1) / 8) * 12 + 12 + 1); + end = encoded; + + /* Iterate over each 8-byte block (Blowfish block size) */ + for (j = 0; j < message_len; j += 8) { + msg = (char *) message; + + /* Set left and right longs */ + GET_LONG(left, msg); + GET_LONG(right, msg); -char *fish_encrypt(const char *key, size_t keylen, const char *message) { - BF_KEY bfkey; - size_t messagelen; - size_t i; - int j; - char *encrypted; - char *end; - unsigned char bit; - unsigned char word; - unsigned char d; - BF_set_key(&bfkey, keylen, (const unsigned char*)key); - - messagelen = strlen(message); - if (messagelen == 0) return NULL; - encrypted = g_malloc(((messagelen - 1) / 8) * 12 + 12 + 1); /* each 8-byte block becomes 12 bytes */ - end = encrypted; - - while (*message) { - /* Read 8 bytes (a Blowfish block) */ - BF_LONG binary[2] = { 0, 0 }; - unsigned char c; - for (i = 0; i < 8; i++) { - c = message[i]; - binary[i >> 2] |= c << 8*(3 - (i&3)); - if (c == '\0') break; + for (i = 0; i < 6; ++i) { + *end++ = fish_base64[right & 0x3fu]; + right = (right >> 6u); } - message += 8; - - /* Encrypt block */ - BF_encrypt(binary, &bfkey); - - /* Emit FiSH-BASE64 */ - bit = 0; - word = 1; - for (j = 0; j < 12; j++) { - d = fish_base64[(binary[word] >> bit) & 63]; - *(end++) = d; - bit += 6; - if (j == 5) { - bit = 0; - word = 0; - } + + for (i = 0; i < 6; ++i) { + *end++ = fish_base64[left & 0x3fu]; + left = (left >> 6u); } - - /* Stop if a null terminator was found */ - if (c == '\0') break; + + /* The previous for'd ensure fill all bytes of encoded, we don't need will with zeros */ + message += 8; } + *end = '\0'; - return encrypted; + return encoded; } +/** + * Decode ECB FiSH Base64 + * + * @param [in] message Base64 encoded string + * @param [out] final_len Real length of message + * @return Array of char with decoded message + */ +char *fish_base64_decode(const char *message, size_t *final_len) { + BF_LONG left, right; + int i; + char *bytes = NULL; + char *msg = NULL; + char *byt = NULL; + size_t message_len; -char *fish_decrypt(const char *key, size_t keylen, const char *data) { - BF_KEY bfkey; - size_t i; - char *decrypted; - char *end; - unsigned char bit; - unsigned char word; - unsigned char d; - BF_set_key(&bfkey, keylen, (const unsigned char*)key); - - decrypted = g_malloc(strlen(data) + 1); - end = decrypted; - - while (*data) { - /* Convert from FiSH-BASE64 */ - BF_LONG binary[2] = { 0, 0 }; - bit = 0; - word = 1; - for (i = 0; i < 12; i++) { - d = fish_unbase64[(const unsigned char)*(data++)]; - if (d == IB) goto decrypt_end; - binary[word] |= (unsigned long)d << bit; - bit += 6; - if (i == 5) { - bit = 0; - word = 0; - } + message_len = strlen(message); + + /* Ensure blocks of 12 bytes each one and valid characters */ + if (message_len == 0 || message_len % 12 != 0 || strspn(message, fish_base64) != message_len) + return NULL; + + /* Each 12 bytes becomes 8-byte block and add 1 byte for \0 */ + *final_len = ((message_len - 1) / 12) * 8 + 8 + 1; + (*final_len)--; /* We support binary data */ + bytes = (char *) g_malloc0(*final_len); + byt = bytes; + + msg = (char *) message; + + while (*msg) { + right = 0; + left = 0; + for (i = 0; i < 6; i++) right |= (uint8_t) fish_unbase64[*msg++] << (i * 6u); + for (i = 0; i < 6; i++) left |= (uint8_t) fish_unbase64[*msg++] << (i * 6u); + GET_BYTES(byt, left); + GET_BYTES(byt, right); + } + + return bytes; +} + +/** + * Encrypt or decrypt data with Blowfish cipher, support binary data. + * + * Good documentation for EVP: + * + * - https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption + * + * - https://stackoverflow.com/questions/5727646/what-is-the-length-parameter-of-aes-evp-decrypt + * + * - https://stackoverflow.com/questions/26345175/correct-way-to-free-allocate-the-context-in-the-openssl + * + * - https://stackoverflow.com/questions/29874150/working-with-evp-and-openssl-coding-in-c + * + * @param [in] plaintext Bytes to encrypt or decrypt + * @param [in] plaintext_len Size of plaintext + * @param [in] key Bytes of key + * @param [in] keylen Size of key + * @param [in] encode 1 or encrypt 0 for decrypt + * @param [in] mode EVP_CIPH_ECB_MODE or EVP_CIPH_CBC_MODE + * @param [out] ciphertext_len The bytes written + * @return Array of char with data encrypted or decrypted + */ +char *fish_cipher(const char *plaintext, size_t plaintext_len, const char *key, size_t keylen, int encode, int mode, size_t *ciphertext_len) { + EVP_CIPHER_CTX *ctx; + EVP_CIPHER *cipher = NULL; + int bytes_written = 0; + unsigned char *ciphertext = NULL; + unsigned char *iv_ciphertext = NULL; + unsigned char *iv = NULL; + size_t block_size = 0; + + *ciphertext_len = 0; + + if (plaintext_len == 0 || keylen == 0 || encode < 0 || encode > 1) + return NULL; + + block_size = plaintext_len; + + if (mode == EVP_CIPH_CBC_MODE) { + if (encode == 1) { + iv = (unsigned char *) g_malloc0(8); + RAND_bytes(iv, 8); + } else { + if (plaintext_len <= 8) /* IV + DATA */ + return NULL; + + iv = (unsigned char *) plaintext; + block_size -= 8; + plaintext += 8; + plaintext_len -= 8; } - - /* Decrypt block */ - BF_decrypt(binary, &bfkey); - - /* Copy to buffer */ - GET_BYTES(end, binary[0]); - GET_BYTES(end, binary[1]); + + cipher = (EVP_CIPHER *) EVP_bf_cbc(); + } else if (mode == EVP_CIPH_ECB_MODE) { + cipher = (EVP_CIPHER *) EVP_bf_ecb(); } - - decrypt_end: - *end = '\0'; - return decrypted; + + /* Zero Padding */ + if (block_size % 8 != 0) { + block_size = block_size + 8 - (block_size % 8); + } + + ciphertext = (unsigned char *) g_malloc0(block_size); + memcpy(ciphertext, plaintext, plaintext_len); + + /* Create and initialise the context */ + if (!(ctx = EVP_CIPHER_CTX_new())) + return NULL; + + /* Initialise the cipher operation only with mode */ + if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encode)) + return NULL; + + /* Set custom key length */ + if (!EVP_CIPHER_CTX_set_key_length(ctx, keylen)) + return NULL; + + /* Finish the initiation the cipher operation */ + if (1 != EVP_CipherInit_ex(ctx, NULL, NULL, (const unsigned char *) key, iv, encode)) + return NULL; + + /* We will manage this */ + EVP_CIPHER_CTX_set_padding(ctx, 0); + + /* Do cipher operation */ + if (1 != EVP_CipherUpdate(ctx, ciphertext, &bytes_written, ciphertext, block_size)) + return NULL; + + *ciphertext_len = bytes_written; + + /* Finalise the cipher. Further ciphertext bytes may be written at this stage */ + if (1 != EVP_CipherFinal_ex(ctx, ciphertext + bytes_written, &bytes_written)) + return NULL; + + *ciphertext_len += bytes_written; + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + + if (mode == EVP_CIPH_CBC_MODE && encode == 1) { + /* Join IV + DATA */ + iv_ciphertext = g_malloc0(8 + *ciphertext_len); + + memcpy(iv_ciphertext, iv, 8); + memcpy(&iv_ciphertext[8], ciphertext, *ciphertext_len); + *ciphertext_len += 8; + + g_free(ciphertext); + g_free(iv); + + return (char *) iv_ciphertext; + } else { + return (char *) ciphertext; + } +} + +/** + * Return a fish or standard Base64 encoded string with data encrypted + * is binary safe + * + * @param [in] key Bytes of key + * @param [in] keylen Size of key + * @param [in] message Bytes to encrypt + * @param [in] message_len Size of message + * @param [in] mode Chiper mode + * @return Array of char with data encrypted + */ +char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode) { + size_t ciphertext_len = 0; + char *ciphertext = NULL; + char *b64 = NULL; + + if (keylen == 0 || message_len == 0) + return NULL; + + ciphertext = fish_cipher(message, message_len, key, keylen, 1, mode, &ciphertext_len); + + if (ciphertext == NULL || ciphertext_len == 0) + return NULL; + + switch (mode) { + case FISH_CBC_MODE: + b64 = g_base64_encode((const unsigned char *) ciphertext, ciphertext_len); + break; + + case FISH_ECB_MODE: + b64 = fish_base64_encode((const char *) ciphertext, ciphertext_len); + } + + g_free(ciphertext); + + if (b64 == NULL) + return NULL; + + return b64; +} + +/** + * Return an array of bytes with data decrypted + * is binary safe + * + * @param [in] key Bytes of key + * @param [in] keylen Size of key + * @param [in] data Fish or standard Base64 encoded string + * @param [in] mode Chiper mode + * @param [out] final_len Length of returned array + * @return Array of char with data decrypted + */ +char *fish_decrypt(const char *key, size_t keylen, const char *data, enum fish_mode mode, size_t *final_len) { + size_t ciphertext_len = 0; + char *ciphertext = NULL; + char *plaintext = NULL; + + *final_len = 0; + + if (keylen == 0 || strlen(data) == 0) + return NULL; + + switch (mode) { + case FISH_CBC_MODE: + if (strspn(data, base64_chars) != strlen(data)) + return NULL; + ciphertext = (char *) g_base64_decode(data, &ciphertext_len); + break; + + case FISH_ECB_MODE: + ciphertext = fish_base64_decode(data, &ciphertext_len); + } + + if (ciphertext == NULL || ciphertext_len == 0) + return NULL; + + plaintext = fish_cipher(ciphertext, ciphertext_len, key, keylen, 0, mode, final_len); + g_free(ciphertext); + + if (*final_len == 0) + return NULL; + + return plaintext; +} + +/** + * Similar to fish_decrypt, but pad with zeros any after the first zero in the decrypted data + * + * @param [in] key Bytes of key + * @param [in] keylen Size of key + * @param [in] data Fish or standard Base64 encoded string + * @param [in] mode Chiper mode + * @return Array of char with data decrypted + */ +char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fish_mode mode) { + char *decrypted = NULL; + char *plaintext_str = NULL; + size_t decrypted_len = 0; + + decrypted = fish_decrypt(key, strlen(key), data, mode, &decrypted_len); + + if (decrypted == NULL || decrypted_len == 0) + return NULL; + + plaintext_str = g_strndup(decrypted, decrypted_len); + g_free(decrypted); + + return plaintext_str; } /** * Encrypts a message (see fish_decrypt). The key is searched for in the * key store. */ -char *fish_encrypt_for_nick(const char *nick, const char *data) { +char *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode) { char *key; - char *encrypted; + char *encrypted, *encrypted_cbc = NULL; + enum fish_mode mode; /* Look for key */ - key = keystore_get_key(nick); + key = keystore_get_key(nick, &mode); if (!key) return NULL; - + + *omode = mode; + /* Encrypt */ - encrypted = fish_encrypt(key, strlen(key), data); - + encrypted = fish_encrypt(key, strlen(key), data, strlen(data), mode); + g_free(key); - return encrypted; + + if (encrypted == NULL || mode == FISH_ECB_MODE) + return encrypted; + + /* Add '*' for CBC */ + encrypted_cbc = g_strdup_printf("*%s",encrypted); + g_free(encrypted); + + return encrypted_cbc; } /** * Decrypts a message (see fish_decrypt). The key is searched for in the * key store. */ -char *fish_decrypt_from_nick(const char *nick, const char *data) { +char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode) { char *key; char *decrypted; + enum fish_mode mode; + /* Look for key */ - key = keystore_get_key(nick); + key = keystore_get_key(nick, &mode); if (!key) return NULL; - + + *omode = mode; + + if (mode == FISH_CBC_MODE) + ++data; + /* Decrypt */ - decrypted = fish_decrypt(key, strlen(key), data); - + decrypted = fish_decrypt_str(key, strlen(key), data, mode); g_free(key); + return decrypted; } diff --git a/plugins/fishlim/fish.h b/plugins/fishlim/fish.h index 238f52e7..daf17acf 100644 --- a/plugins/fishlim/fish.h +++ b/plugins/fishlim/fish.h @@ -1,6 +1,7 @@ /* Copyright (c) 2010 Samuel Lidén Borell + Copyright (c) 2019 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,10 +30,18 @@ #include -char *fish_encrypt(const char *key, size_t keylen, const char *message); -char *fish_decrypt(const char *key, size_t keylen, const char *data); -char *fish_encrypt_for_nick(const char *nick, const char *data); -char *fish_decrypt_from_nick(const char *nick, const char *data); +enum fish_mode { + FISH_ECB_MODE = 0x1, + FISH_CBC_MODE = 0x2 +}; + +char *fish_base64_encode(const char *message, size_t message_len); +char *fish_base64_decode(const char *message, size_t *final_len); +char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode); +char *fish_decrypt(const char *key, size_t keylen, const char *data, enum fish_mode mode, size_t *final_len); +char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fish_mode mode); +char *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode); +char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode); #endif diff --git a/plugins/fishlim/fishlim.vcxproj.filters b/plugins/fishlim/fishlim.vcxproj.filters index d8fbf454..09649ec9 100644 --- a/plugins/fishlim/fishlim.vcxproj.filters +++ b/plugins/fishlim/fishlim.vcxproj.filters @@ -23,6 +23,9 @@ Header Files + + Header Files + Header Files @@ -37,6 +40,9 @@ + + Source Files + Source Files diff --git a/plugins/fishlim/keystore.c b/plugins/fishlim/keystore.c index 061faa03..adefc700 100644 --- a/plugins/fishlim/keystore.c +++ b/plugins/fishlim/keystore.c @@ -103,27 +103,55 @@ static gchar *get_nick_value(GKeyFile *keyfile, const char *nick, const char *it /** * Extracts a key from the key store file. */ -char *keystore_get_key(const char *nick) { +char *keystore_get_key(const char *nick, enum fish_mode *mode) { + GKeyFile *keyfile; + char *escaped_nick; + gchar *value, *key_mode; + int encrypted_mode; + char *password; + char *encrypted; + char *decrypted; + /* Get the key */ - GKeyFile *keyfile = getConfigFile(); - char *escaped_nick = escape_nickname(nick); - gchar *value = get_nick_value(keyfile, escaped_nick, "key"); + keyfile = getConfigFile(); + escaped_nick = escape_nickname(nick); + value = get_nick_value(keyfile, escaped_nick, "key"); + key_mode = get_nick_value(keyfile, escaped_nick, "mode"); g_key_file_free(keyfile); g_free(escaped_nick); + /* Determine cipher mode */ + *mode = FISH_ECB_MODE; + if (key_mode) { + if (*key_mode == '1') + *mode = FISH_ECB_MODE; + else if (*key_mode == '2') + *mode = FISH_CBC_MODE; + g_free(key_mode); + } + if (!value) return NULL; - - if (strncmp(value, "+OK ", 4) != 0) { - /* Key is stored in plaintext */ - return value; - } else { + + if (strncmp(value, "+OK ", 4) == 0) { /* Key is encrypted */ - const char *encrypted = value+4; - const char *password = get_keystore_password(); - char *decrypted = fish_decrypt(password, strlen(password), encrypted); + encrypted = (char *) value; + encrypted += 4; + + encrypted_mode = FISH_ECB_MODE; + + if (*encrypted == '*') { + ++encrypted; + encrypted_mode = FISH_CBC_MODE; + } + + password = (char *) get_keystore_password(); + decrypted = fish_decrypt_str((const char *) password, strlen(password), (const char *) encrypted, encrypted_mode); g_free(value); return decrypted; + } else { + /* Key is stored in plaintext */ + return value; } } @@ -189,7 +217,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS /** * Sets a key in the key store file. */ -gboolean keystore_store_key(const char *nick, const char *key) { +gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) { const char *password; char *encrypted; char *wrapped; @@ -204,11 +232,11 @@ gboolean keystore_store_key(const char *nick, const char *key) { password = get_keystore_password(); if (password) { /* Encrypt the password */ - encrypted = fish_encrypt(password, strlen(password), key); + encrypted = fish_encrypt(password, strlen(password), key, strlen(key), FISH_CBC_MODE); if (!encrypted) goto end; /* Prepend "+OK " */ - wrapped = g_strconcat("+OK ", encrypted, NULL); + wrapped = g_strconcat("+OK *", encrypted, NULL); g_free(encrypted); /* Store encrypted in file */ @@ -218,6 +246,9 @@ gboolean keystore_store_key(const char *nick, const char *key) { /* Store unencrypted in file */ g_key_file_set_string(keyfile, escaped_nick, "key", key); } + + /* Store cipher mode */ + g_key_file_set_integer(keyfile, escaped_nick, "mode", mode); /* Save key store file */ ok = save_keystore(keyfile); diff --git a/plugins/fishlim/keystore.h b/plugins/fishlim/keystore.h index 3d90606a..4af46693 100644 --- a/plugins/fishlim/keystore.h +++ b/plugins/fishlim/keystore.h @@ -28,9 +28,10 @@ #include #include +#include "fish.h" -char *keystore_get_key(const char *nick); -gboolean keystore_store_key(const char *nick, const char *key); +char *keystore_get_key(const char *nick, enum fish_mode *mode); +gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode); gboolean keystore_delete_nick(const char *nick); #endif diff --git a/plugins/fishlim/meson.build b/plugins/fishlim/meson.build index 894923aa..06cf5f9a 100644 --- a/plugins/fishlim/meson.build +++ b/plugins/fishlim/meson.build @@ -2,6 +2,9 @@ if not libssl_dep.found() error('fish plugin requires openssl') endif +# Run tests +subdir('tests') + fishlim_sources = [ 'dh1080.c', 'fish.c', diff --git a/plugins/fishlim/plugin_hexchat.c b/plugins/fishlim/plugin_hexchat.c index 3eaad986..ddb692da 100644 --- a/plugins/fishlim/plugin_hexchat.c +++ b/plugins/fishlim/plugin_hexchat.c @@ -2,6 +2,7 @@ Copyright (c) 2010-2011 Samuel Lidén Borell Copyright (c) 2015 + Copyright (c) 2019 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -30,19 +31,20 @@ #include #include "hexchat-plugin.h" -#define HEXCHAT_MAX_WORDS 32 #include "fish.h" #include "dh1080.h" #include "keystore.h" #include "irc.h" +static const char *fish_modes[] = {"", "ECB", "CBC", NULL}; + static const char plugin_name[] = "FiSHLiM"; static const char plugin_desc[] = "Encryption plugin for the FiSH protocol. Less is More!"; -static const char plugin_version[] = "0.1.0"; +static const char plugin_version[] = "1.0.0"; -static const char usage_setkey[] = "Usage: SETKEY [] , sets the key for a channel or nick"; -static const char usage_delkey[] = "Usage: DELKEY , deletes the key for a channel or nick"; +static const char usage_setkey[] = "Usage: SETKEY [] [:], sets the key for a channel or nick. Modes: ECB, CBC"; +static const char usage_delkey[] = "Usage: DELKEY [], deletes the key for a channel or nick"; static const char usage_keyx[] = "Usage: KEYX [], performs DH1080 key-exchange with "; static const char usage_topic[] = "Usage: TOPIC+ , sets a new encrypted topic for the current channel"; static const char usage_notice[] = "Usage: NOTICE+ "; @@ -53,6 +55,13 @@ static hexchat_plugin *ph; static GHashTable *pending_exchanges; +/** + * Compare two nicks using the current plugin + */ +int irc_nick_cmp(const char *a, const char *b) { + return hexchat_nickcmp (ph, a, b); +} + /** * Returns the path to the key store file. */ @@ -88,7 +97,7 @@ static hexchat_context *find_context_on_network (const char *name) { int chan_id = hexchat_list_int(ph, channels, "id"); const char *chan_name = hexchat_list_str(ph, channels, "channel"); - if (chan_id == id && chan_name && hexchat_nickcmp (ph, chan_name, name) == 0) { + if (chan_id == id && chan_name && irc_nick_cmp (chan_name, name) == 0) { ret = (hexchat_context*)hexchat_list_str(ph, channels, "context"); break; } @@ -98,8 +107,109 @@ static hexchat_context *find_context_on_network (const char *name) { return ret; } -int irc_nick_cmp(const char *a, const char *b) { - return hexchat_nickcmp (ph, a, b); +/** + * Retrive the prefix character for own nick in current context + * @return @ or + or NULL + */ +char *get_my_own_prefix(void) { + char *result = NULL; + const char *own_nick; + hexchat_list *list; + + /* Display message */ + own_nick = hexchat_get_info(ph, "nick"); + + if (!own_nick) + return NULL; + + /* Get prefix for own nick if any */ + list = hexchat_list_get (ph, "users"); + if (list) { + while (hexchat_list_next(ph, list)) { + if (irc_nick_cmp(own_nick, hexchat_list_str(ph, list, "nick")) == 0) + result = g_strdup(hexchat_list_str(ph, list, "prefix")); + } + hexchat_list_free(ph, list); + } + + return result; +} + +/** + * Try to decrypt the first occurrence of fish message + * + * @param message Message to decrypt + * @param key Key of message + * @return Array of char with decrypted message or NULL. The returned string + * should be freed with g_free() when no longer needed. + */ +char *decrypt_raw_message(const char *message, const char *key) { + const char *prefixes[] = {"+OK ", "mcps ", NULL}; + char *start = NULL, *end = NULL; + char *left = NULL, *right = NULL; + char *encrypted = NULL, *decrypted = NULL; + int length = 0; + int index_prefix; + enum fish_mode mode; + GString *message_decrypted; + char *result = NULL; + + if (message == NULL || key == NULL) + return NULL; + + for (index_prefix = 0; index_prefix < 2; index_prefix++) { + start = g_strstr_len(message, strlen(message), prefixes[index_prefix]); + if (start) { + /* Length ALWAYS will be less that original message + * add '[CBC] ' length */ + message_decrypted = g_string_sized_new(strlen(message) + 6); + + /* Left part of message */ + left = g_strndup(message, start - message); + g_string_append(message_decrypted, left); + g_free(left); + + /* Encrypted part */ + start += strlen(prefixes[index_prefix]); + end = g_strstr_len(start, strlen(message), " "); + if (end) { + length = end - start; + right = end; + } + + if (length > 0) { + encrypted = g_strndup(start, length); + } else { + encrypted = g_strdup(start); + } + decrypted = fish_decrypt_from_nick(key, encrypted, &mode); + g_free(encrypted); + + if (decrypted == NULL) { + g_string_free(message_decrypted, TRUE); + return NULL; + } + + /* Add encrypted flag */ + g_string_append(message_decrypted, "["); + g_string_append(message_decrypted, fish_modes[mode]); + g_string_append(message_decrypted, "] "); + /* Decrypted message */ + g_string_append(message_decrypted, decrypted); + g_free(decrypted); + + /* Right part of message */ + if (right) { + g_string_append(message_decrypted, right); + } + + result = message_decrypted->str; + g_string_free(message_decrypted, FALSE); + return result; + } + } + + return NULL; } /*static int handle_debug(char *word[], char *word_eol[], void *userdata) { @@ -115,15 +225,24 @@ int irc_nick_cmp(const char *a, const char *b) { * Called when a message is to be sent. */ static int handle_outgoing(char *word[], char *word_eol[], void *userdata) { - const char *own_nick; + char *prefix; + enum fish_mode mode; + char *message; /* Encrypt the message if possible */ const char *channel = hexchat_get_info(ph, "channel"); - char *encrypted = fish_encrypt_for_nick(channel, word_eol[1]); + char *encrypted = fish_encrypt_for_nick(channel, word_eol[1], &mode); if (!encrypted) return HEXCHAT_EAT_NONE; + /* Get prefix for own nick if any */ + prefix = get_my_own_prefix(); + + /* Add encrypted flag */ + message = g_strconcat("[", fish_modes[mode], "] ", word_eol[1], NULL); + /* Display message */ - own_nick = hexchat_get_info(ph, "nick"); - hexchat_emit_print(ph, "Your Message", own_nick, word_eol[1], NULL); + hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), message, prefix, NULL); + g_free(prefix); + g_free(message); /* Send message */ hexchat_commandf(ph, "PRIVMSG %s :+OK %s", channel, encrypted); @@ -139,96 +258,59 @@ static int handle_incoming(char *word[], char *word_eol[], hexchat_event_attrs * const char *prefix; const char *command; const char *recipient; - const char *encrypted; - const char *peice; + const char *raw_message = word_eol[1]; char *sender_nick; char *decrypted; - size_t w; - size_t ew; - size_t uw; - char prefix_char = 0; + size_t parameters_offset; GString *message; - if (!irc_parse_message((const char **)word, &prefix, &command, &w)) + if (!irc_parse_message((const char **)word, &prefix, &command, ¶meters_offset)) return HEXCHAT_EAT_NONE; - + /* Topic (command 332) has an extra parameter */ - if (!strcmp(command, "332")) w++; - - /* Look for encrypted data */ - for (ew = w+1; ew < HEXCHAT_MAX_WORDS-1; ew++) { - const char *s = (ew == w+1 ? word[ew]+1 : word[ew]); - if (*s && (s[1] == '+' || s[1] == 'm')) { prefix_char = *(s++); } - else { prefix_char = 0; } - if (strcmp(s, "+OK") == 0 || strcmp(s, "mcps") == 0) goto has_encrypted_data; + if (!strcmp(command, "332")) + parameters_offset++; + + /* Extract sender nick and recipient nick/channel and try to decrypt */ + recipient = word[parameters_offset]; + decrypted = decrypt_raw_message(raw_message, recipient); + if (decrypted == NULL) { + sender_nick = irc_prefix_get_nick(prefix); + decrypted = decrypt_raw_message(raw_message, sender_nick); + g_free(sender_nick); } - return HEXCHAT_EAT_NONE; - has_encrypted_data: ; - /* Extract sender nick and recipient nick/channel */ - sender_nick = irc_prefix_get_nick(prefix); - recipient = word[w]; - - /* Try to decrypt with these (the keys are searched for in the key store) */ - encrypted = word[ew+1]; - decrypted = fish_decrypt_from_nick(recipient, encrypted); - if (!decrypted) decrypted = fish_decrypt_from_nick(sender_nick, encrypted); - - /* Check for error */ - if (!decrypted) goto decrypt_error; - - /* Build unecrypted message */ - message = g_string_sized_new (100); /* TODO: more accurate estimation of size */ - g_string_append (message, "RECV"); + + /* Nothing to decrypt */ + if (decrypted == NULL) + return HEXCHAT_EAT_NONE; + + /* Build decrypted message */ + + /* decrypted + 'RECV ' + '@time=YYYY-MM-DDTHH:MM:SS.fffffZ ' */ + message = g_string_sized_new (strlen(decrypted) + 5 + 33); + g_string_append (message, "RECV "); if (attrs->server_time_utc) { GTimeVal tv = { (glong)attrs->server_time_utc, 0 }; char *timestamp = g_time_val_to_iso8601 (&tv); - g_string_append (message, " @time="); + g_string_append (message, "@time="); g_string_append (message, timestamp); + g_string_append (message, " "); g_free (timestamp); } - for (uw = 1; uw < HEXCHAT_MAX_WORDS; uw++) { - if (word[uw][0] != '\0') - g_string_append_c (message, ' '); - - if (uw == ew) { - /* Add the encrypted data */ - peice = decrypted; - uw++; /* Skip "OK+" */ - - if (ew == w+1) { - /* Prefix with colon, which gets stripped out otherwise */ - g_string_append_c (message, ':'); - } - - if (prefix_char) { - g_string_append_c (message, prefix_char); - } - - } else { - /* Add unencrypted data (for example, a prefix from a bouncer or bot) */ - peice = word[uw]; - } - - g_string_append (message, peice); - } + g_string_append (message, decrypted); g_free(decrypted); - /* Simulate unencrypted message */ - /* hexchat_printf(ph, "simulating: %s\n", message->str); */ + /* Fake server message + * RECV command will throw this function again, if message have multiple + * encrypted data, we will decrypt all */ hexchat_command(ph, message->str); - g_string_free (message, TRUE); - g_free(sender_nick); - return HEXCHAT_EAT_HEXCHAT; - decrypt_error: - g_free(decrypted); - g_free(sender_nick); - return HEXCHAT_EAT_NONE; + return HEXCHAT_EAT_HEXCHAT; } static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) { @@ -236,8 +318,8 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) { const char *dh_pubkey = word[5]; hexchat_context *query_ctx; const char *prefix; - gboolean cbc; char *sender, *secret_key, *priv_key = NULL; + enum fish_mode mode = FISH_ECB_MODE; if (!*dh_message || !*dh_pubkey || strlen(dh_pubkey) != 181) return HEXCHAT_EAT_NONE; @@ -248,25 +330,21 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) { sender = irc_prefix_get_nick(prefix); query_ctx = find_context_on_network(sender); if (query_ctx) - hexchat_set_context(ph, query_ctx); + g_assert(hexchat_set_context(ph, query_ctx) == 1); dh_message++; /* : prefix */ if (*dh_message == '+' || *dh_message == '-') dh_message++; /* identify-msg */ - cbc = g_strcmp0 (word[6], "CBC") == 0; + if (g_strcmp0 (word[6], "CBC") == 0) + mode = FISH_CBC_MODE; if (!strcmp(dh_message, "DH1080_INIT")) { char *pub_key; - if (cbc) { - hexchat_print(ph, "Received key exchange for CBC mode which is not supported."); - goto cleanup; - } - - hexchat_printf(ph, "Received public key from %s, sending mine...", sender); + hexchat_printf(ph, "Received public key from %s (%s), sending mine...", sender, fish_modes[mode]); if (dh1080_generate_key(&priv_key, &pub_key)) { - hexchat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s", sender, pub_key); + hexchat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s%s", sender, pub_key, (mode == FISH_CBC_MODE) ? " CBC" : ""); g_free(pub_key); } else { hexchat_print(ph, "Failed to generate keys"); @@ -279,11 +357,6 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) { g_hash_table_steal(pending_exchanges, sender_lower); g_free(sender_lower); - if (cbc) { - hexchat_print(ph, "Received key exchange for CBC mode which is not supported."); - goto cleanup; - } - if (!priv_key) { hexchat_printf(ph, "Received a key exchange response for unknown user: %s", sender); goto cleanup; @@ -295,8 +368,8 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) { } if (dh1080_compute_key(priv_key, dh_pubkey, &secret_key)) { - keystore_store_key(sender, secret_key); - hexchat_printf(ph, "Stored new key for %s", sender); + keystore_store_key(sender, secret_key, mode); + hexchat_printf(ph, "Stored new key for %s (%s)", sender, fish_modes[mode]); g_free(secret_key); } else { hexchat_print(ph, "Failed to create secret key!"); @@ -314,6 +387,7 @@ cleanup: static int handle_setkey(char *word[], char *word_eol[], void *userdata) { const char *nick; const char *key; + enum fish_mode mode; /* Check syntax */ if (*word[2] == '\0') { @@ -331,9 +405,17 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) { key = word_eol[3]; } + mode = FISH_ECB_MODE; + if (g_ascii_strncasecmp("cbc:", key, 4) == 0) { + key = key+4; + mode = FISH_CBC_MODE; + } else if (g_ascii_strncasecmp("ecb:", key, 4) == 0) { + key = key+4; + } + /* Set password */ - if (keystore_store_key(nick, key)) { - hexchat_printf(ph, "Stored key for %s\n", nick); + if (keystore_store_key(nick, key, mode)) { + hexchat_printf(ph, "Stored key for %s (%s)\n", nick, fish_modes[mode]); } else { hexchat_printf(ph, "\00305Failed to store key in addon_fishlim.conf\n"); } @@ -345,22 +427,30 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) { * Command handler for /delkey */ static int handle_delkey(char *word[], char *word_eol[], void *userdata) { - const char *nick; + char *nick = NULL; + int ctx_type = 0; + + /* Delete key from input */ + if (*word[2] != '\0') { + nick = g_strstrip(g_strdup(word_eol[2])); + } else { /* Delete key from current context */ + nick = g_strdup(hexchat_get_info(ph, "channel")); + ctx_type = hexchat_list_int(ph, NULL, "type"); - /* Check syntax */ - if (*word[2] == '\0' || *word[3] != '\0') { - hexchat_printf(ph, "%s\n", usage_delkey); - return HEXCHAT_EAT_HEXCHAT; + /* Only allow channel or dialog */ + if (ctx_type < 2 || ctx_type > 3) { + hexchat_printf(ph, "%s\n", usage_delkey); + return HEXCHAT_EAT_HEXCHAT; + } } - nick = g_strstrip (word_eol[2]); - /* Delete the given nick from the key store */ if (keystore_delete_nick(nick)) { hexchat_printf(ph, "Deleted key for %s\n", nick); } else { hexchat_printf(ph, "\00305Failed to delete key in addon_fishlim.conf!\n"); } + g_free(nick); return HEXCHAT_EAT_HEXCHAT; } @@ -379,7 +469,7 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) { } if (query_ctx) { - hexchat_set_context(ph, query_ctx); + g_assert(hexchat_set_context(ph, query_ctx) == 1); ctx_type = hexchat_list_int(ph, NULL, "type"); } @@ -391,8 +481,8 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) { if (dh1080_generate_key(&priv_key, &pub_key)) { g_hash_table_replace (pending_exchanges, g_ascii_strdown(target, -1), priv_key); - hexchat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s", target, pub_key); - hexchat_printf(ph, "Sent public key to %s, waiting for reply...", target); + hexchat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s CBC", target, pub_key); + hexchat_printf(ph, "Sent public key to %s (CBC), waiting for reply...", target); g_free(pub_key); } else { @@ -409,6 +499,7 @@ static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) { const char *target; const char *topic = word_eol[2]; char *buf; + enum fish_mode mode; if (!*topic) { hexchat_print(ph, usage_topic); @@ -421,7 +512,7 @@ static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) { } target = hexchat_get_info(ph, "channel"); - buf = fish_encrypt_for_nick(target, topic); + buf = fish_encrypt_for_nick(target, topic, &mode); if (buf == NULL) { hexchat_printf(ph, "/topic+ error, no key found for %s", target); return HEXCHAT_EAT_ALL; @@ -439,21 +530,25 @@ static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata) { const char *target = word[2]; const char *notice = word_eol[3]; + char *notice_flag; char *buf; + enum fish_mode mode; if (!*target || !*notice) { hexchat_print(ph, usage_notice); return HEXCHAT_EAT_ALL; } - buf = fish_encrypt_for_nick(target, notice); + buf = fish_encrypt_for_nick(target, notice, &mode); if (buf == NULL) { hexchat_printf(ph, "/notice+ error, no key found for %s.", target); return HEXCHAT_EAT_ALL; } hexchat_commandf(ph, "quote NOTICE %s :+OK %s", target, buf); - hexchat_emit_print(ph, "Notice Sent", target, notice); + notice_flag = g_strconcat("[", fish_modes[mode], "] ", notice, NULL); + hexchat_emit_print(ph, "Notice Send", target, notice_flag); + g_free(notice_flag); g_free(buf); return HEXCHAT_EAT_ALL; @@ -462,19 +557,21 @@ static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata) /** * Command handler for /msg+ */ -static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) -{ +static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) { const char *target = word[2]; const char *message = word_eol[3]; + char *message_flag; + char *prefix; hexchat_context *query_ctx; char *buf; + enum fish_mode mode; if (!*target || !*message) { hexchat_print(ph, usage_msg); return HEXCHAT_EAT_ALL; } - buf = fish_encrypt_for_nick(target, message); + buf = fish_encrypt_for_nick(target, message, &mode); if (buf == NULL) { hexchat_printf(ph, "/msg+ error, no key found for %s", target); return HEXCHAT_EAT_ALL; @@ -484,13 +581,17 @@ static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) query_ctx = find_context_on_network(target); if (query_ctx) { - hexchat_set_context(ph, query_ctx); + g_assert(hexchat_set_context(ph, query_ctx) == 1); + + prefix = get_my_own_prefix(); - /* FIXME: Mode char */ + /* Add encrypted flag */ + message_flag = g_strconcat("[", fish_modes[mode], "] ", message, NULL); hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), - message, "", ""); - } - else { + message_flag, prefix, NULL); + g_free(prefix); + g_free(message_flag); + } else { hexchat_emit_print(ph, "Message Send", target, message); } @@ -501,8 +602,9 @@ static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) static int handle_crypt_me(char *word[], char *word_eol[], void *userdata) { const char *channel = hexchat_get_info(ph, "channel"); char *buf; + enum fish_mode mode; - buf = fish_encrypt_for_nick(channel, word_eol[2]); + buf = fish_encrypt_for_nick(channel, word_eol[2], &mode); if (!buf) return HEXCHAT_EAT_NONE; diff --git a/plugins/fishlim/tests/fake/keystore.c b/plugins/fishlim/tests/fake/keystore.c new file mode 100644 index 00000000..855b3451 --- /dev/null +++ b/plugins/fishlim/tests/fake/keystore.c @@ -0,0 +1,47 @@ +/* + + Copyright (c) 2010 Samuel Lidén Borell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include "../../fish.h" + + +/** + * Extracts a key from the key store file. + */ +char *keystore_get_key(const char *nick, enum fish_mode *mode) { + return NULL; +} + +/** + * Sets a key in the key store file. + */ +gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) { + return NULL; +} + +/** + * Deletes a nick from the key store. + */ +gboolean keystore_delete_nick(const char *nick) { + return NULL; +} diff --git a/plugins/fishlim/tests/meson.build b/plugins/fishlim/tests/meson.build new file mode 100644 index 00000000..3b75018e --- /dev/null +++ b/plugins/fishlim/tests/meson.build @@ -0,0 +1,17 @@ +subdir('old_version') + +fishlim_test_sources = [ + 'tests.c', + 'fake/keystore.c', + '../fish.c', + '../utils.c', +] + +fishlim_tests = executable('fishlim_tests', fishlim_test_sources, + dependencies: [libgio_dep, libssl_dep], + link_with : fishlim_old_lib +) + +test('Fishlim Tests', fishlim_tests, + timeout: 90 +) diff --git a/plugins/fishlim/tests/old_version/fish.c b/plugins/fishlim/tests/old_version/fish.c new file mode 100644 index 00000000..ff0b8fea --- /dev/null +++ b/plugins/fishlim/tests/old_version/fish.c @@ -0,0 +1,194 @@ +/* + + Copyright (c) 2010 Samuel Lidén Borell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifdef __APPLE__ +#define __AVAILABILITYMACROS__ +#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER +#endif + +#include +#include +#include + +#include "fish.h" + +#define IB 64 +static const char fish_base64[64] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +static const signed char fish_unbase64[256] = { + IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, + IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, +/* ! " # $ % & ' ( ) * + , - . / */ + IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB, 0, 1, +/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + 2, 3, 4, 5, 6, 7, 8, 9, 10,11,IB,IB,IB,IB,IB,IB, +/* @ A B C D E F G H I J K L M N O */ + IB,38,39,40,41,42,43,44, 45,46,47,48,49,50,51,52, +/* P Q R S T U V W X Y Z [ \ ] ^ _*/ + 53,54,55,56,57,58,59,60, 61,62,63,IB,IB,IB,IB,IB, +/* ` a b c d e f g h i j k l m n o */ + IB,12,13,14,15,16,17,18, 19,20,21,22,23,24,25,26, +/* p q r s t u v w x y z { | } ~ */ + 27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB, +}; + +#define GET_BYTES(dest, source) do { \ + *((dest)++) = ((source) >> 24) & 0xFF; \ + *((dest)++) = ((source) >> 16) & 0xFF; \ + *((dest)++) = ((source) >> 8) & 0xFF; \ + *((dest)++) = (source) & 0xFF; \ +} while (0); + + +char *__old_fish_encrypt(const char *key, size_t keylen, const char *message) { + BF_KEY bfkey; + size_t messagelen; + size_t i; + int j; + char *encrypted; + char *end; + unsigned char bit; + unsigned char word; + unsigned char d; + BF_set_key(&bfkey, keylen, (const unsigned char*)key); + + messagelen = strlen(message); + if (messagelen == 0) return NULL; + encrypted = g_malloc(((messagelen - 1) / 8) * 12 + 12 + 1); /* each 8-byte block becomes 12 bytes */ + end = encrypted; + + while (*message) { + /* Read 8 bytes (a Blowfish block) */ + BF_LONG binary[2] = { 0, 0 }; + unsigned char c; + for (i = 0; i < 8; i++) { + c = message[i]; + binary[i >> 2] |= c << 8*(3 - (i&3)); + if (c == '\0') break; + } + message += 8; + + /* Encrypt block */ + BF_encrypt(binary, &bfkey); + + /* Emit FiSH-BASE64 */ + bit = 0; + word = 1; + for (j = 0; j < 12; j++) { + d = fish_base64[(binary[word] >> bit) & 63]; + *(end++) = d; + bit += 6; + if (j == 5) { + bit = 0; + word = 0; + } + } + + /* Stop if a null terminator was found */ + if (c == '\0') break; + } + *end = '\0'; + return encrypted; +} + + +char *__old_fish_decrypt(const char *key, size_t keylen, const char *data) { + BF_KEY bfkey; + size_t i; + char *decrypted; + char *end; + unsigned char bit; + unsigned char word; + unsigned char d; + BF_set_key(&bfkey, keylen, (const unsigned char*)key); + + decrypted = g_malloc(strlen(data) + 1); + end = decrypted; + + while (*data) { + /* Convert from FiSH-BASE64 */ + BF_LONG binary[2] = { 0, 0 }; + bit = 0; + word = 1; + for (i = 0; i < 12; i++) { + d = fish_unbase64[(const unsigned char)*(data++)]; + if (d == IB) goto decrypt_end; + binary[word] |= (unsigned long)d << bit; + bit += 6; + if (i == 5) { + bit = 0; + word = 0; + } + } + + /* Decrypt block */ + BF_decrypt(binary, &bfkey); + + /* Copy to buffer */ + GET_BYTES(end, binary[0]); + GET_BYTES(end, binary[1]); + } + + decrypt_end: + *end = '\0'; + return decrypted; +} + +/** + * Encrypts a message (see __old_fish_decrypt). The key is searched for in the + * key store. + */ +char *__old_fish_encrypt_for_nick(const char *nick, const char *data) { + char *key; + char *encrypted; + + /* Look for key */ + /*key = keystore_get_key(nick);*/ + if (!key) return NULL; + + /* Encrypt */ + encrypted = __old_fish_encrypt(key, strlen(key), data); + + g_free(key); + return encrypted; +} + +/** + * Decrypts a message (see __old_fish_decrypt). The key is searched for in the + * key store. + */ +char *__old_fish_decrypt_from_nick(const char *nick, const char *data) { + char *key; + char *decrypted; + /* Look for key */ + /*key = keystore_get_key(nick);*/ + if (!key) return NULL; + + /* Decrypt */ + decrypted = __old_fish_decrypt(key, strlen(key), data); + + g_free(key); + return decrypted; +} + + diff --git a/plugins/fishlim/tests/old_version/fish.h b/plugins/fishlim/tests/old_version/fish.h new file mode 100644 index 00000000..b4c66572 --- /dev/null +++ b/plugins/fishlim/tests/old_version/fish.h @@ -0,0 +1,39 @@ +/* + + Copyright (c) 2010 Samuel Lidén Borell + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef FISH_OLD_H +#define FISH_OLD_H + +#include + +#include + +char *__old_fish_encrypt(const char *key, size_t keylen, const char *message); +char *__old_fish_decrypt(const char *key, size_t keylen, const char *data); +char *__old_fish_encrypt_for_nick(const char *nick, const char *data); +char *__old_fish_decrypt_from_nick(const char *nick, const char *data); + +#endif + + diff --git a/plugins/fishlim/tests/old_version/meson.build b/plugins/fishlim/tests/old_version/meson.build new file mode 100644 index 00000000..54647d71 --- /dev/null +++ b/plugins/fishlim/tests/old_version/meson.build @@ -0,0 +1,4 @@ +fishlim_old_lib = shared_library('fishlim_old_lib', + ['fish.c'], + dependencies: [libgio_dep, libssl_dep], +) diff --git a/plugins/fishlim/tests/tests.c b/plugins/fishlim/tests/tests.c new file mode 100644 index 00000000..e34532bf --- /dev/null +++ b/plugins/fishlim/tests/tests.c @@ -0,0 +1,248 @@ +/* + + Copyright (c) 2020 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef PLUGIN_HEXCHAT_FISHLIM_TEST_H +#define PLUGIN_HEXCHAT_FISHLIM_TEST_H + +// Libs +#include +#include +// Project Libs +#include "../fish.h" +#include "../utils.h" +#include "old_version/fish.h" + +/** + * Auxiliary function: Generate a random string + * @param out Preallocated string to fill + * @param len Size of bytes to fill + */ +void random_string(char *out, size_t len) { + GRand *rand = NULL; + int i = 0; + + rand = g_rand_new(); + for (i = 0; i < len; ++i) { + out[i] = g_rand_int_range(rand, 1, 256); + } + + out[len] = 0; + + g_rand_free(rand); +} + + +/** + * Check encrypt and decrypt in ECB mode and compare with old implementation + */ +void __ecb() { + char *bo64 = NULL, *b64 = NULL; + char *deo = NULL, *de = NULL; + int key_len, message_len = 0; + char key[57]; + char message[1000]; + + /* Generate key 32–448 bits (Yes, I start with 8 bits) */ + for (key_len = 1; key_len < 57; ++key_len) { + + random_string(key, key_len); + + for (message_len = 1; message_len < 1000; ++message_len) { + random_string(message, message_len); + + /* Encrypt */ + bo64 = __old_fish_encrypt(key, key_len, message); + g_assert_nonnull(bo64); + b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE); + g_assert_nonnull(b64); + g_assert_cmpuint(g_strcmp0(b64, bo64), == , 0); + + /* Decrypt */ + /* Linear */ + deo = __old_fish_decrypt(key, key_len, bo64); + de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE); + g_assert_nonnull(deo); + g_assert_nonnull(de); + g_assert_cmpuint(g_strcmp0(de, message), == , 0); + g_assert_cmpuint(g_strcmp0(deo, message), == , 0); + g_assert_cmpuint(g_strcmp0(de, deo), == , 0); + g_free(deo); + g_free(de); + /* Mixed */ + deo = __old_fish_decrypt(key, key_len, b64); + de = fish_decrypt_str(key, key_len, bo64, FISH_ECB_MODE); + g_assert_nonnull(deo); + g_assert_nonnull(de); + g_assert_cmpuint(g_strcmp0(de, message), == , 0); + g_assert_cmpuint(g_strcmp0(deo, message), == , 0); + g_assert_cmpuint(g_strcmp0(de, deo), == , 0); + g_free(deo); + g_free(de); + + /* Free */ + g_free(bo64); + g_free(b64); + } + } +} + +/** + * Check encrypt and decrypt in CBC mode + */ +void __cbc() { + char *b64 = NULL; + char *de = NULL; + int key_len, message_len = 0; + char key[57]; + char message[1000]; + + /* Generate key 32–448 bits (Yes, I start with 8 bits) */ + for (key_len = 1; key_len < 57; ++key_len) { + + random_string(key, key_len); + + for (message_len = 1; message_len < 1000; ++message_len) { + random_string(message, message_len); + + /* Encrypt */ + b64 = fish_encrypt(key, key_len, message, message_len, FISH_CBC_MODE); + g_assert_nonnull(b64); + + /* Decrypt */ + /* Linear */ + de = fish_decrypt_str(key, key_len, b64, FISH_CBC_MODE); + g_assert_nonnull(de); + g_assert_cmpuint(g_strcmp0(de, message), == , 0); + g_free(de); + + /* Free */ + g_free(b64); + } + } +} + +/** + * Check the calculation of final length from an encoded string in Base64 + */ +void __base64_len() { + char *b64 = NULL; + int i, message_len = 0; + char message[1000]; + + for (i = 0; i < 10; ++i) { + + for (message_len = 1; message_len < 1000; ++message_len) { + random_string(message, message_len); + b64 = g_base64_encode((const unsigned char *) message, message_len); + g_assert_nonnull(b64); + g_assert_cmpuint(strlen(b64), == , base64_len(message_len)); + g_free(b64); + } + } +} + +/** + * Check the calculation of final length from an encoded string in BlowcryptBase64 + */ +void __base64_fish_len() { + char *b64 = NULL; + int i, message_len = 0; + char message[1000]; + + for (i = 0; i < 10; ++i) { + + for (message_len = 1; message_len < 1000; ++message_len) { + random_string(message, message_len); + b64 = fish_base64_encode(message, message_len); + g_assert_nonnull(b64); + g_assert_cmpuint(strlen(b64), == , base64_fish_len(message_len)); + g_free(b64); + } + } +} + + +/** + * Check the calculation of final length from an encrypted string in ECB mode + */ +void __base64_ecb_len() { + char *b64 = NULL; + int key_len, message_len = 0; + char key[57]; + char message[1000]; + + /* Generate key 32–448 bits (Yes, I start with 8 bits) */ + for (key_len = 1; key_len < 57; ++key_len) { + + random_string(key, key_len); + + for (message_len = 1; message_len < 1000; ++message_len) { + random_string(message, message_len); + b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE); + g_assert_nonnull(b64); + g_assert_cmpuint(strlen(b64), == , ecb_len(message_len)); + g_free(b64); + } + } +} + +/** + * Check the calculation of final length from an encrypted string in CBC mode + */ +void __base64_cbc_len() { + char *b64 = NULL; + int key_len, message_len = 0; + char key[57]; + char message[1000]; + + /* Generate key 32–448 bits (Yes, I start with 8 bits) */ + for (key_len = 1; key_len < 57; ++key_len) { + + random_string(key, key_len); + + for (message_len = 1; message_len < 1000; ++message_len) { + random_string(message, message_len); + b64 = fish_encrypt(key, key_len, message, message_len, FISH_CBC_MODE); + g_assert_nonnull(b64); + g_assert_cmpuint(strlen(b64), == , cbc_len(message_len)); + g_free(b64); + } + } +} + +int main(int argc, char *argv[]) { + + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/fishlim/__ecb", __ecb); + g_test_add_func("/fishlim/__cbc", __ecb); + g_test_add_func("/fishlim/__base64_len", __base64_len); + g_test_add_func("/fishlim/__base64_fish_len", __base64_fish_len); + g_test_add_func("/fishlim/__base64_ecb_len", __base64_ecb_len); + g_test_add_func("/fishlim/__base64_cbc_len", __base64_cbc_len); + + return g_test_run(); +} + +#endif //PLUGIN_HEXCHAT_FISHLIM_TEST_H \ No newline at end of file diff --git a/plugins/fishlim/utils.c b/plugins/fishlim/utils.c new file mode 100644 index 00000000..5af87404 --- /dev/null +++ b/plugins/fishlim/utils.c @@ -0,0 +1,70 @@ +/* + + Copyright (c) 2020 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include "utils.h" + +/** + * Calculate the length of Base64-encoded string + * + * @param plaintext_len Size of clear text to encode + * @return Size of encoded string + */ +unsigned long base64_len(size_t plaintext_len) { + int length_unpadded = (4 * plaintext_len) / 3; + /* Add padding */ + return length_unpadded % 4 != 0 ? length_unpadded + (4 - length_unpadded % 4) : length_unpadded; +} + +/** + * Calculate the length of BlowcryptBase64-encoded string + * + * @param plaintext_len Size of clear text to encode + * @return Size of encoded string + */ +unsigned long base64_fish_len(size_t plaintext_len) { + int length_unpadded = (12 * plaintext_len) / 8; + /* Add padding */ + return length_unpadded % 12 != 0 ? length_unpadded + (12 - length_unpadded % 12) : length_unpadded; +} + +/** + * Calculate the length of fish-encrypted string in CBC mode + * + * @param plaintext_len Size of clear text to encode + * @return Size of encoded string + */ +unsigned long cbc_len(size_t plaintext_len) { + /*IV + DATA + Zero Padding */ + return base64_len(8 + (plaintext_len % 8 != 0 ? plaintext_len + 8 - (plaintext_len % 8) : plaintext_len)); +} + +/** + * Calculate the length of fish-encrypted string in ECB mode + * + * @param plaintext_len Size of clear text to encode + * @return Size of encoded string + */ +unsigned long ecb_len(size_t plaintext_len) { + return base64_fish_len(plaintext_len); +} \ No newline at end of file diff --git a/plugins/fishlim/utils.h b/plugins/fishlim/utils.h new file mode 100644 index 00000000..9e2b3355 --- /dev/null +++ b/plugins/fishlim/utils.h @@ -0,0 +1,35 @@ +/* + + Copyright (c) 2020 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef PLUGIN_HEXCHAT_FISHLIM_UTILS_H +#define PLUGIN_HEXCHAT_FISHLIM_UTILS_H + +#include + +unsigned long base64_len(size_t plaintext_len); +unsigned long base64_fish_len(size_t plaintext_len); +unsigned long cbc_len(size_t plaintext_len); +unsigned long ecb_len(size_t plaintext_len); + +#endif \ No newline at end of file -- cgit 1.4.1 From 453cb7ca79ace05f53667e523dc534bdeb7ddee0 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Thu, 17 Sep 2020 15:50:28 -0700 Subject: Increase max number of words a line can be split into This may have unintended side-effects but 32 is a very low value and I was seeing real world bugs being caused by this. Specifically an ISUPPORT line with more features than this could store. --- plugins/lua/lua.c | 14 ++++++++------ plugins/perl/perl.c | 4 +++- src/common/hexchat.h | 2 +- src/common/plugin.c | 4 ++-- 4 files changed, 14 insertions(+), 10 deletions(-) (limited to 'plugins') diff --git a/plugins/lua/lua.c b/plugins/lua/lua.c index 941342bb..8d6b730e 100644 --- a/plugins/lua/lua.c +++ b/plugins/lua/lua.c @@ -35,6 +35,8 @@ #include +#define WORD_ARRAY_LEN 48 + static char plugin_name[] = "Lua"; static char plugin_description[] = "Lua scripting interface"; static char plugin_version[16] = "1.3"; @@ -275,13 +277,13 @@ static int api_command_closure(char *word[], char *word_eol[], void *udata) base = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref); lua_newtable(L); - for(i = 1; i < 32 && *word_eol[i]; i++) + for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++) { lua_pushstring(L, word[i]); lua_rawseti(L, -2, i); } lua_newtable(L); - for(i = 1; i < 32 && *word_eol[i]; i++) + for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++) { lua_pushstring(L, word_eol[i]); lua_rawseti(L, -2, i); @@ -462,13 +464,13 @@ static int api_server_closure(char *word[], char *word_eol[], void *udata) base = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref); lua_newtable(L); - for(i = 1; i < 32 && *word_eol[i]; i++) + for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++) { lua_pushstring(L, word[i]); lua_rawseti(L, -2, i); } lua_newtable(L); - for(i = 1; i < 32 && *word_eol[i]; i++) + for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++) { lua_pushstring(L, word_eol[i]); lua_rawseti(L, -2, i); @@ -521,13 +523,13 @@ static int api_server_attrs_closure(char *word[], char *word_eol[], hexchat_even base = lua_gettop(L); lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref); lua_newtable(L); - for(i = 1; i < 32 && *word_eol[i]; i++) + for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++) { lua_pushstring(L, word[i]); lua_rawseti(L, -2, i); } lua_newtable(L); - for(i = 1; i < 32 && *word_eol[i]; i++) + for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++) { lua_pushstring(L, word_eol[i]); lua_rawseti(L, -2, i); diff --git a/plugins/perl/perl.c b/plugins/perl/perl.c index ab06dc96..cb563b38 100644 --- a/plugins/perl/perl.c +++ b/plugins/perl/perl.c @@ -283,6 +283,8 @@ list_item_to_sv ( hexchat_list *list, const char *const *fields ) return sv_2mortal (newRV_noinc ((SV *) hash)); } +#define WORD_ARRAY_LEN 48 + static AV * array2av (char *array[]) { @@ -293,7 +295,7 @@ array2av (char *array[]) for ( count = 1; - count < 32 && array[count] != NULL && array[count][0] != 0; + count < WORD_ARRAY_LEN && array[count] != NULL && array[count][0] != 0; count++ ) { temp = newSVpv (array[count], 0); diff --git a/src/common/hexchat.h b/src/common/hexchat.h index a9e22372..73430f0f 100644 --- a/src/common/hexchat.h +++ b/src/common/hexchat.h @@ -75,7 +75,7 @@ #define DOMAINLEN 100 #define NICKLEN 64 /* including the NULL, so 63 really */ #define CHANLEN 300 -#define PDIWORDS 32 +#define PDIWORDS 48 #define USERNAMELEN 10 #define HIDDEN_CHAR 8 /* invisible character for xtext */ diff --git a/src/common/plugin.c b/src/common/plugin.c index 6b2ffd0c..5c09e921 100644 --- a/src/common/plugin.c +++ b/src/common/plugin.c @@ -662,11 +662,11 @@ plugin_emit_print (session *sess, char *word[], time_t server_time) int plugin_emit_dummy_print (session *sess, char *name) { - char *word[32]; + char *word[PDIWORDS]; int i; word[0] = name; - for (i = 1; i < 32; i++) + for (i = 1; i < PDIWORDS; i++) word[i] = "\000"; return plugin_hook_run (sess, name, word, NULL, NULL, HOOK_PRINT); -- cgit 1.4.1 From 7a275812c0002fe10db5c60e29a34ba6ce4cede1 Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Mon, 21 Sep 2020 11:22:50 -0700 Subject: Revert word array length change It turns out that the rfc sets a limit of 15 arguments and the server (irccloud) sending that many in ISUPPORT was updated to split it into multiple lines. --- plugins/lua/lua.c | 2 +- plugins/perl/perl.c | 2 +- src/common/hexchat.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/lua/lua.c b/plugins/lua/lua.c index 8d6b730e..d73fbb23 100644 --- a/plugins/lua/lua.c +++ b/plugins/lua/lua.c @@ -35,7 +35,7 @@ #include -#define WORD_ARRAY_LEN 48 +#define WORD_ARRAY_LEN 32 static char plugin_name[] = "Lua"; static char plugin_description[] = "Lua scripting interface"; diff --git a/plugins/perl/perl.c b/plugins/perl/perl.c index cb563b38..6f30400c 100644 --- a/plugins/perl/perl.c +++ b/plugins/perl/perl.c @@ -283,7 +283,7 @@ list_item_to_sv ( hexchat_list *list, const char *const *fields ) return sv_2mortal (newRV_noinc ((SV *) hash)); } -#define WORD_ARRAY_LEN 48 +#define WORD_ARRAY_LEN 32 static AV * array2av (char *array[]) diff --git a/src/common/hexchat.h b/src/common/hexchat.h index 73430f0f..f7cacfcc 100644 --- a/src/common/hexchat.h +++ b/src/common/hexchat.h @@ -75,7 +75,7 @@ #define DOMAINLEN 100 #define NICKLEN 64 /* including the NULL, so 63 really */ #define CHANLEN 300 -#define PDIWORDS 48 +#define PDIWORDS 32 #define USERNAMELEN 10 #define HIDDEN_CHAR 8 /* invisible character for xtext */ -- cgit 1.4.1 From bbbc2aad1bb3bce3ee5245d827af4afe9eb1b128 Mon Sep 17 00:00:00 2001 From: BakasuraRCE Date: Mon, 20 Jul 2020 17:57:13 -0500 Subject: fishlim: Fix cast --- plugins/fishlim/fish.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/fishlim/fish.c b/plugins/fishlim/fish.c index 9301d5d5..88f7caec 100644 --- a/plugins/fishlim/fish.c +++ b/plugins/fishlim/fish.c @@ -164,8 +164,8 @@ char *fish_base64_decode(const char *message, size_t *final_len) { while (*msg) { right = 0; left = 0; - for (i = 0; i < 6; i++) right |= (uint8_t) fish_unbase64[*msg++] << (i * 6u); - for (i = 0; i < 6; i++) left |= (uint8_t) fish_unbase64[*msg++] << (i * 6u); + for (i = 0; i < 6; i++) right |= (uint8_t) fish_unbase64[(int)*msg++] << (i * 6u); + for (i = 0; i < 6; i++) left |= (uint8_t) fish_unbase64[(int)*msg++] << (i * 6u); GET_BYTES(byt, left); GET_BYTES(byt, right); } -- cgit 1.4.1 From 4758d3705d652b8cda006e8902d8e8c6921cdee1 Mon Sep 17 00:00:00 2001 From: BakasuraRCE Date: Mon, 20 Jul 2020 17:58:15 -0500 Subject: fishlim: Fix result --- plugins/fishlim/tests/fake/keystore.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/fishlim/tests/fake/keystore.c b/plugins/fishlim/tests/fake/keystore.c index 855b3451..854f38dc 100644 --- a/plugins/fishlim/tests/fake/keystore.c +++ b/plugins/fishlim/tests/fake/keystore.c @@ -36,12 +36,12 @@ char *keystore_get_key(const char *nick, enum fish_mode *mode) { * Sets a key in the key store file. */ gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) { - return NULL; + return TRUE; } /** * Deletes a nick from the key store. */ gboolean keystore_delete_nick(const char *nick) { - return NULL; + return TRUE; } -- cgit 1.4.1 From c7844c775ac2f2ab71722227edb941b4086baf91 Mon Sep 17 00:00:00 2001 From: BakasuraRCE Date: Tue, 21 Jul 2020 18:35:00 -0500 Subject: fishlim: Remove needless functions for tests --- plugins/fishlim/tests/old_version/fish.c | 41 +------------------------------- plugins/fishlim/tests/old_version/fish.h | 2 -- 2 files changed, 1 insertion(+), 42 deletions(-) (limited to 'plugins') diff --git a/plugins/fishlim/tests/old_version/fish.c b/plugins/fishlim/tests/old_version/fish.c index ff0b8fea..99601398 100644 --- a/plugins/fishlim/tests/old_version/fish.c +++ b/plugins/fishlim/tests/old_version/fish.c @@ -152,43 +152,4 @@ char *__old_fish_decrypt(const char *key, size_t keylen, const char *data) { decrypt_end: *end = '\0'; return decrypted; -} - -/** - * Encrypts a message (see __old_fish_decrypt). The key is searched for in the - * key store. - */ -char *__old_fish_encrypt_for_nick(const char *nick, const char *data) { - char *key; - char *encrypted; - - /* Look for key */ - /*key = keystore_get_key(nick);*/ - if (!key) return NULL; - - /* Encrypt */ - encrypted = __old_fish_encrypt(key, strlen(key), data); - - g_free(key); - return encrypted; -} - -/** - * Decrypts a message (see __old_fish_decrypt). The key is searched for in the - * key store. - */ -char *__old_fish_decrypt_from_nick(const char *nick, const char *data) { - char *key; - char *decrypted; - /* Look for key */ - /*key = keystore_get_key(nick);*/ - if (!key) return NULL; - - /* Decrypt */ - decrypted = __old_fish_decrypt(key, strlen(key), data); - - g_free(key); - return decrypted; -} - - +} \ No newline at end of file diff --git a/plugins/fishlim/tests/old_version/fish.h b/plugins/fishlim/tests/old_version/fish.h index b4c66572..7037782b 100644 --- a/plugins/fishlim/tests/old_version/fish.h +++ b/plugins/fishlim/tests/old_version/fish.h @@ -31,8 +31,6 @@ char *__old_fish_encrypt(const char *key, size_t keylen, const char *message); char *__old_fish_decrypt(const char *key, size_t keylen, const char *data); -char *__old_fish_encrypt_for_nick(const char *nick, const char *data); -char *__old_fish_decrypt_from_nick(const char *nick, const char *data); #endif -- cgit 1.4.1 From df818ad7d99cecaec58b293724cc975801e19079 Mon Sep 17 00:00:00 2001 From: BakasuraRCE Date: Tue, 21 Jul 2020 18:35:57 -0500 Subject: fishlim: Remove compiler warnings --- plugins/fishlim/tests/tests.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'plugins') diff --git a/plugins/fishlim/tests/tests.c b/plugins/fishlim/tests/tests.c index e34532bf..b75ec653 100644 --- a/plugins/fishlim/tests/tests.c +++ b/plugins/fishlim/tests/tests.c @@ -56,7 +56,7 @@ void random_string(char *out, size_t len) { /** * Check encrypt and decrypt in ECB mode and compare with old implementation */ -void __ecb() { +void __ecb(void) { char *bo64 = NULL, *b64 = NULL; char *deo = NULL, *de = NULL; int key_len, message_len = 0; @@ -110,7 +110,7 @@ void __ecb() { /** * Check encrypt and decrypt in CBC mode */ -void __cbc() { +void __cbc(void) { char *b64 = NULL; char *de = NULL; int key_len, message_len = 0; @@ -145,7 +145,7 @@ void __cbc() { /** * Check the calculation of final length from an encoded string in Base64 */ -void __base64_len() { +void __base64_len(void) { char *b64 = NULL; int i, message_len = 0; char message[1000]; @@ -165,7 +165,7 @@ void __base64_len() { /** * Check the calculation of final length from an encoded string in BlowcryptBase64 */ -void __base64_fish_len() { +void __base64_fish_len(void) { char *b64 = NULL; int i, message_len = 0; char message[1000]; @@ -186,7 +186,7 @@ void __base64_fish_len() { /** * Check the calculation of final length from an encrypted string in ECB mode */ -void __base64_ecb_len() { +void __base64_ecb_len(void) { char *b64 = NULL; int key_len, message_len = 0; char key[57]; @@ -210,7 +210,7 @@ void __base64_ecb_len() { /** * Check the calculation of final length from an encrypted string in CBC mode */ -void __base64_cbc_len() { +void __base64_cbc_len(void) { char *b64 = NULL; int key_len, message_len = 0; char key[57]; -- cgit 1.4.1 From bd3f3fa5f70c624926d06884b328de59730fc93c Mon Sep 17 00:00:00 2001 From: BakasuraRCE Date: Tue, 21 Jul 2020 19:13:02 -0500 Subject: fishlim: Remove needless header --- plugins/fishlim/tests/tests.c | 1 - 1 file changed, 1 deletion(-) (limited to 'plugins') diff --git a/plugins/fishlim/tests/tests.c b/plugins/fishlim/tests/tests.c index b75ec653..45dfd41c 100644 --- a/plugins/fishlim/tests/tests.c +++ b/plugins/fishlim/tests/tests.c @@ -26,7 +26,6 @@ #define PLUGIN_HEXCHAT_FISHLIM_TEST_H // Libs -#include #include // Project Libs #include "../fish.h" -- cgit 1.4.1 From 078af20e8b3bff93dd42972e4ef01702e7e1fe2b Mon Sep 17 00:00:00 2001 From: BakasuraRCE Date: Tue, 21 Jul 2020 19:16:50 -0500 Subject: fishlim: Implement correct handling of long and UTF-8 messages --- plugins/fishlim/fish.c | 78 ++++++++-- plugins/fishlim/fish.h | 5 +- plugins/fishlim/fishlim.vcxproj | 2 + plugins/fishlim/fishlim.vcxproj.filters | 6 + plugins/fishlim/meson.build | 1 + plugins/fishlim/plugin_hexchat.c | 254 ++++++++++++++++++++++++++------ plugins/fishlim/tests/tests.c | 50 +++++++ plugins/fishlim/utils.c | 79 ++++++++++ plugins/fishlim/utils.h | 4 + 9 files changed, 413 insertions(+), 66 deletions(-) (limited to 'plugins') diff --git a/plugins/fishlim/fish.c b/plugins/fishlim/fish.c index 88f7caec..c2c2b9da 100644 --- a/plugins/fishlim/fish.c +++ b/plugins/fishlim/fish.c @@ -1,7 +1,7 @@ /* Copyright (c) 2010 Samuel Lidén Borell - Copyright (c) 2019 + Copyright (c) 2019-2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -37,8 +37,11 @@ #include "keystore.h" #include "fish.h" +#include "utils.h" #define IB 64 +/* rfc 2812; 512 - CR-LF = 510 */ +static const int MAX_COMMAND_LENGTH = 510; static const char fish_base64[] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; static const signed char fish_unbase64[256] = { @@ -401,13 +404,40 @@ char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fi } /** - * Encrypts a message (see fish_decrypt). The key is searched for in the - * key store. + * Determine if a nick have a key + * + * @param [in] nick Nickname + * @return TRUE if have a key or FALSE if not + */ +gboolean fish_nick_has_key(const char *nick) { + gboolean has_key = FALSE; + char *key; + enum fish_mode mode; + + key = keystore_get_key(nick, &mode); + if (key) { + has_key = TRUE; + g_free(key); + }; + + return has_key; +} + +/** + * Encrypts a message (see fish_encrypt). The key is searched for in the key store + * + * @param [in] nick Nickname + * @param [in] data Plaintext to encrypt + * @param [out] omode Mode of encryption + * @param [in] command_len Length of command to use without the message part + * @return A list of encoded strings with the message encrypted or NULL if any error occurred */ -char *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode) { +GSList *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode, size_t command_len) { char *key; - char *encrypted, *encrypted_cbc = NULL; + GSList *encrypted_list = NULL; + char *encrypted = NULL; enum fish_mode mode; + int max_len, max_chunks_len, chunks_len; /* Look for key */ key = keystore_get_key(nick, &mode); @@ -415,24 +445,40 @@ char *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode * *omode = mode; - /* Encrypt */ - encrypted = fish_encrypt(key, strlen(key), data, strlen(data), mode); + /* Calculate max length of each line */ + max_len = MAX_COMMAND_LENGTH - command_len; + /* Add '*' */ + if (mode == FISH_CBC_MODE) max_len--; - g_free(key); + max_chunks_len = max_text_command_len(max_len, mode); - if (encrypted == NULL || mode == FISH_ECB_MODE) - return encrypted; + const char *data_chunk = data; - /* Add '*' for CBC */ - encrypted_cbc = g_strdup_printf("*%s",encrypted); - g_free(encrypted); + while(foreach_utf8_data_chunks(data_chunk, max_chunks_len, &chunks_len)) { + encrypted = fish_encrypt(key, strlen(key), data_chunk, chunks_len, mode); - return encrypted_cbc; + if (mode == FISH_CBC_MODE) { + /* Add '*' for CBC */ + encrypted_list = g_slist_append(encrypted_list, g_strdup_printf("*%s", encrypted)); + g_free(encrypted); + } else { + encrypted_list = g_slist_append(encrypted_list, encrypted); + } + + /* Next chunk */ + data_chunk += chunks_len; + } + + return encrypted_list; } /** - * Decrypts a message (see fish_decrypt). The key is searched for in the - * key store. + * Decrypts a message (see fish_decrypt). The key is searched for in the key store + * + * @param [in] nick Nickname + * @param [in] data Plaintext to encrypt + * @param [out] omode Mode of encryption + * @return Plaintext message or NULL if any error occurred */ char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode) { char *key; diff --git a/plugins/fishlim/fish.h b/plugins/fishlim/fish.h index daf17acf..6a2e911c 100644 --- a/plugins/fishlim/fish.h +++ b/plugins/fishlim/fish.h @@ -1,7 +1,7 @@ /* Copyright (c) 2010 Samuel Lidén Borell - Copyright (c) 2019 + Copyright (c) 2019-2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -40,7 +40,8 @@ char *fish_base64_decode(const char *message, size_t *final_len); char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode); char *fish_decrypt(const char *key, size_t keylen, const char *data, enum fish_mode mode, size_t *final_len); char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fish_mode mode); -char *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode); +gboolean fish_nick_has_key(const char *nick); +GSList *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode, size_t command_len); char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode); #endif diff --git a/plugins/fishlim/fishlim.vcxproj b/plugins/fishlim/fishlim.vcxproj index 43ae1df9..157c7928 100644 --- a/plugins/fishlim/fishlim.vcxproj +++ b/plugins/fishlim/fishlim.vcxproj @@ -55,6 +55,7 @@ + @@ -62,6 +63,7 @@ + diff --git a/plugins/fishlim/fishlim.vcxproj.filters b/plugins/fishlim/fishlim.vcxproj.filters index 09649ec9..ee4e855a 100644 --- a/plugins/fishlim/fishlim.vcxproj.filters +++ b/plugins/fishlim/fishlim.vcxproj.filters @@ -29,6 +29,9 @@ Header Files + + Header Files + Header Files @@ -46,6 +49,9 @@ Source Files + + Source Files + Source Files diff --git a/plugins/fishlim/meson.build b/plugins/fishlim/meson.build index 06cf5f9a..65ccc9ea 100644 --- a/plugins/fishlim/meson.build +++ b/plugins/fishlim/meson.build @@ -8,6 +8,7 @@ subdir('tests') fishlim_sources = [ 'dh1080.c', 'fish.c', + 'utils.c', 'irc.c', 'keystore.c', 'plugin_hexchat.c' diff --git a/plugins/fishlim/plugin_hexchat.c b/plugins/fishlim/plugin_hexchat.c index ddb692da..83286e28 100644 --- a/plugins/fishlim/plugin_hexchat.c +++ b/plugins/fishlim/plugin_hexchat.c @@ -2,7 +2,7 @@ Copyright (c) 2010-2011 Samuel Lidén Borell Copyright (c) 2015 - Copyright (c) 2019 + Copyright (c) 2019-2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,7 +29,6 @@ #include #include #include - #include "hexchat-plugin.h" #include "fish.h" @@ -108,13 +107,14 @@ static hexchat_context *find_context_on_network (const char *name) { } /** - * Retrive the prefix character for own nick in current context - * @return @ or + or NULL + * Retrive the field for own user in current context + * @return the field value */ -char *get_my_own_prefix(void) { +char *get_my_info(const char *field, gboolean find_in_other_context) { char *result = NULL; const char *own_nick; hexchat_list *list; + hexchat_context *ctx_current, *ctx_channel; /* Display message */ own_nick = hexchat_get_info(ph, "nick"); @@ -122,12 +122,38 @@ char *get_my_own_prefix(void) { if (!own_nick) return NULL; - /* Get prefix for own nick if any */ - list = hexchat_list_get (ph, "users"); + /* Get field for own nick if any */ + list = hexchat_list_get(ph, "users"); if (list) { while (hexchat_list_next(ph, list)) { if (irc_nick_cmp(own_nick, hexchat_list_str(ph, list, "nick")) == 0) - result = g_strdup(hexchat_list_str(ph, list, "prefix")); + result = g_strdup(hexchat_list_str(ph, list, field)); + } + hexchat_list_free(ph, list); + } + + if (result) { + return result; + } + + /* Try to get from a channel (we are outside a channel) */ + if (!find_in_other_context) { + return NULL; + } + + list = hexchat_list_get(ph, "channels"); + if (list) { + ctx_current = hexchat_get_context(ph); + while (hexchat_list_next(ph, list)) { + ctx_channel = (hexchat_context *) hexchat_list_str(ph, list, "context"); + + hexchat_set_context(ph, ctx_channel); + result = get_my_info(field, FALSE); + hexchat_set_context(ph, ctx_current); + + if (result) { + break; + } } hexchat_list_free(ph, list); } @@ -135,6 +161,45 @@ char *get_my_own_prefix(void) { return result; } +/** + * Retrive the prefix character for own nick in current context + * @return @ or + or NULL + */ +char *get_my_own_prefix(void) { + return get_my_info("prefix", FALSE); +} + +/** + * Retrive the mask for own nick in current context + * @return Host name in the form: user@host (or NULL if not known) + */ +char *get_my_own_host(void) { + return get_my_info("host", TRUE); +} + +/** + * Calculate the length of prefix for current user in current context + * + * @return Length of prefix + */ +int get_prefix_length(void) { + char *own_host; + int prefix_len = 0; + + /* ':! ' + 'nick' + 'ident@host', e.g. ':user!~name@mynet.com ' */ + prefix_len = 3 + strlen(hexchat_get_info(ph, "nick")); + own_host = get_my_own_host(); + if (own_host) { + prefix_len += strlen(own_host); + } else { + /* https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix */ + prefix_len += 64; + } + g_free(own_host); + + return prefix_len; +} + /** * Try to decrypt the first occurrence of fish message * @@ -228,11 +293,23 @@ static int handle_outgoing(char *word[], char *word_eol[], void *userdata) { char *prefix; enum fish_mode mode; char *message; - /* Encrypt the message if possible */ + GString *command; + GSList *encrypted_list, *encrypted_item; + const char *channel = hexchat_get_info(ph, "channel"); - char *encrypted = fish_encrypt_for_nick(channel, word_eol[1], &mode); - if (!encrypted) return HEXCHAT_EAT_NONE; - + + /* Check if we can encrypt */ + if (!fish_nick_has_key(channel)) return HEXCHAT_EAT_NONE; + + command = g_string_new(""); + g_string_printf(command, "PRIVMSG %s :+OK ", channel); + + encrypted_list = fish_encrypt_for_nick(channel, word_eol[1], &mode, get_prefix_length() + command->len); + if (!encrypted_list) { + g_string_free(command, TRUE); + return HEXCHAT_EAT_NONE; + } + /* Get prefix for own nick if any */ prefix = get_my_own_prefix(); @@ -241,13 +318,21 @@ static int handle_outgoing(char *word[], char *word_eol[], void *userdata) { /* Display message */ hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), message, prefix, NULL); - g_free(prefix); g_free(message); - - /* Send message */ - hexchat_commandf(ph, "PRIVMSG %s :+OK %s", channel, encrypted); - - g_free(encrypted); + + /* Send encrypted messages */ + encrypted_item = encrypted_list; + while (encrypted_item) + { + hexchat_commandf(ph, "%s%s", command->str, (char *)encrypted_item->data); + + encrypted_item = encrypted_item->next; + } + + g_free(prefix); + g_string_free(command, TRUE); + g_slist_free_full(encrypted_list, g_free); + return HEXCHAT_EAT_HEXCHAT; } @@ -498,8 +583,9 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) { static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) { const char *target; const char *topic = word_eol[2]; - char *buf; enum fish_mode mode; + GString *command; + GSList *encrypted_list; if (!*topic) { hexchat_print(ph, usage_topic); @@ -512,44 +598,77 @@ static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) { } target = hexchat_get_info(ph, "channel"); - buf = fish_encrypt_for_nick(target, topic, &mode); - if (buf == NULL) { + + /* Check if we can encrypt */ + if (!fish_nick_has_key(target)) { hexchat_printf(ph, "/topic+ error, no key found for %s", target); return HEXCHAT_EAT_ALL; } - hexchat_commandf(ph, "TOPIC %s +OK %s", target, buf); - g_free(buf); - return HEXCHAT_EAT_ALL; + command = g_string_new(""); + g_string_printf(command, "TOPIC %s +OK ", target); + + encrypted_list = fish_encrypt_for_nick(target, topic, &mode, get_prefix_length() + command->len); + if (!encrypted_list) { + g_string_free(command, TRUE); + hexchat_printf(ph, "/topic+ error, can't encrypt %s", target); + return HEXCHAT_EAT_ALL; } + hexchat_commandf(ph, "%s%s", command->str, (char *) encrypted_list->data); + + g_string_free(command, TRUE); + g_slist_free_full(encrypted_list, g_free); + + return HEXCHAT_EAT_ALL; +} + /** * Command handler for /notice+ */ -static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata) -{ +static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata) { const char *target = word[2]; const char *notice = word_eol[3]; char *notice_flag; - char *buf; enum fish_mode mode; + GString *command; + GSList *encrypted_list, *encrypted_item; if (!*target || !*notice) { hexchat_print(ph, usage_notice); return HEXCHAT_EAT_ALL; } - buf = fish_encrypt_for_nick(target, notice, &mode); - if (buf == NULL) { + /* Check if we can encrypt */ + if (!fish_nick_has_key(target)) { hexchat_printf(ph, "/notice+ error, no key found for %s.", target); return HEXCHAT_EAT_ALL; } - hexchat_commandf(ph, "quote NOTICE %s :+OK %s", target, buf); + command = g_string_new(""); + g_string_printf(command, "quote NOTICE %s :+OK ", target); + + encrypted_list = fish_encrypt_for_nick(target, notice, &mode, get_prefix_length() + command->len); + if (!encrypted_list) { + g_string_free(command, TRUE); + hexchat_printf(ph, "/notice+ error, can't encrypt %s", target); + return HEXCHAT_EAT_ALL; + } + notice_flag = g_strconcat("[", fish_modes[mode], "] ", notice, NULL); hexchat_emit_print(ph, "Notice Send", target, notice_flag); + + /* Send encrypted messages */ + encrypted_item = encrypted_list; + while (encrypted_item) { + hexchat_commandf(ph, "%s%s", command->str, (char *) encrypted_item->data); + + encrypted_item = encrypted_item->next; + } + g_free(notice_flag); - g_free(buf); + g_string_free(command, TRUE); + g_slist_free_full(encrypted_list, g_free); return HEXCHAT_EAT_ALL; } @@ -563,21 +682,41 @@ static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) { char *message_flag; char *prefix; hexchat_context *query_ctx; - char *buf; enum fish_mode mode; + GString *command; + GSList *encrypted_list, *encrypted_item; if (!*target || !*message) { hexchat_print(ph, usage_msg); return HEXCHAT_EAT_ALL; } - buf = fish_encrypt_for_nick(target, message, &mode); - if (buf == NULL) { + /* Check if we can encrypt */ + if (!fish_nick_has_key(target)) { hexchat_printf(ph, "/msg+ error, no key found for %s", target); return HEXCHAT_EAT_ALL; } - hexchat_commandf(ph, "PRIVMSG %s :+OK %s", target, buf); + command = g_string_new(""); + g_string_printf(command, "PRIVMSG %s :+OK ", target); + + encrypted_list = fish_encrypt_for_nick(target, message, &mode, get_prefix_length() + command->len); + if (!encrypted_list) { + g_string_free(command, TRUE); + hexchat_printf(ph, "/msg+ error, can't encrypt %s", target); + return HEXCHAT_EAT_ALL; + } + + /* Send encrypted messages */ + encrypted_item = encrypted_list; + while (encrypted_item) { + hexchat_commandf(ph, "%s%s", command->str, (char *) encrypted_item->data); + + encrypted_item = encrypted_item->next; + } + + g_string_free(command, TRUE); + g_slist_free_full(encrypted_list, g_free); query_ctx = find_context_on_network(target); if (query_ctx) { @@ -587,33 +726,52 @@ static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) { /* Add encrypted flag */ message_flag = g_strconcat("[", fish_modes[mode], "] ", message, NULL); - hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), - message_flag, prefix, NULL); + hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), message_flag, prefix, NULL); g_free(prefix); g_free(message_flag); } else { hexchat_emit_print(ph, "Message Send", target, message); } - g_free(buf); return HEXCHAT_EAT_ALL; } static int handle_crypt_me(char *word[], char *word_eol[], void *userdata) { - const char *channel = hexchat_get_info(ph, "channel"); - char *buf; - enum fish_mode mode; + const char *channel = hexchat_get_info(ph, "channel"); + enum fish_mode mode; + GString *command; + GSList *encrypted_list, *encrypted_item; - buf = fish_encrypt_for_nick(channel, word_eol[2], &mode); - if (!buf) + /* Check if we can encrypt */ + if (!fish_nick_has_key(channel)) { return HEXCHAT_EAT_NONE; + } + + command = g_string_new(""); + g_string_printf(command, "PRIVMSG %s :\001ACTION +OK ", channel); + + /* 2 = ' \001' */ + encrypted_list = fish_encrypt_for_nick(channel, word_eol[2], &mode, get_prefix_length() + command->len + 2); + if (!encrypted_list) { + g_string_free(command, TRUE); + hexchat_printf(ph, "/me error, can't encrypt %s", channel); + return HEXCHAT_EAT_ALL; + } - hexchat_commandf(ph, "PRIVMSG %s :\001ACTION +OK %s \001", channel, buf); - hexchat_emit_print(ph, "Your Action", hexchat_get_info(ph, "nick"), - word_eol[2], NULL); + hexchat_emit_print(ph, "Your Action", hexchat_get_info(ph, "nick"), word_eol[2], NULL); - g_free(buf); - return HEXCHAT_EAT_ALL; + /* Send encrypted messages */ + encrypted_item = encrypted_list; + while (encrypted_item) { + hexchat_commandf(ph, "%s%s \001", command->str, (char *) encrypted_item->data); + + encrypted_item = encrypted_item->next; + } + + g_string_free(command, TRUE); + g_slist_free_full(encrypted_list, g_free); + + return HEXCHAT_EAT_ALL; } /** diff --git a/plugins/fishlim/tests/tests.c b/plugins/fishlim/tests/tests.c index 45dfd41c..bb841c5e 100644 --- a/plugins/fishlim/tests/tests.c +++ b/plugins/fishlim/tests/tests.c @@ -230,6 +230,54 @@ void __base64_cbc_len(void) { } } +/** + * Check the calculation of length limit for a plaintext in each encryption mode + */ +void __max_text_command_len(void) { + int max_encoded_len, plaintext_len; + enum fish_mode mode; + + for (max_encoded_len = 0; max_encoded_len < 10000; ++max_encoded_len) { + for (mode = FISH_ECB_MODE; mode <= FISH_CBC_MODE; ++mode) { + plaintext_len = max_text_command_len(max_encoded_len, mode); + g_assert_cmpuint(encoded_len(plaintext_len, mode), <= , max_encoded_len); + } + } +} + +/** + * Check the calculation of length limit for a plaintext in each encryption mode + */ +void __foreach_utf8_data_chunks(void) { + GRand *rand = NULL; + GString *chunks = NULL; + int tests, max_chunks_len, chunks_len; + char ascii_message[1001]; + char *data_chunk = NULL; + + rand = g_rand_new(); + + for (tests = 0; tests < 1000; ++tests) { + + max_chunks_len = g_rand_int_range(rand, 2, 301); + random_string(ascii_message, 1000); + + data_chunk = ascii_message; + + chunks = g_string_new(NULL); + + while (foreach_utf8_data_chunks(data_chunk, max_chunks_len, &chunks_len)) { + g_string_append(chunks, g_strndup(data_chunk, chunks_len)); + /* Next chunk */ + data_chunk += chunks_len; + } + /* Check data loss */ + g_assert_cmpstr(chunks->str, == , ascii_message); + g_string_free(chunks, TRUE); + } +} + + int main(int argc, char *argv[]) { g_test_init(&argc, &argv, NULL); @@ -240,6 +288,8 @@ int main(int argc, char *argv[]) { g_test_add_func("/fishlim/__base64_fish_len", __base64_fish_len); g_test_add_func("/fishlim/__base64_ecb_len", __base64_ecb_len); g_test_add_func("/fishlim/__base64_cbc_len", __base64_cbc_len); + g_test_add_func("/fishlim/__max_text_command_len", __max_text_command_len); + g_test_add_func("/fishlim/__foreach_utf8_data_chunks", __foreach_utf8_data_chunks); return g_test_run(); } diff --git a/plugins/fishlim/utils.c b/plugins/fishlim/utils.c index 5af87404..4052995a 100644 --- a/plugins/fishlim/utils.c +++ b/plugins/fishlim/utils.c @@ -23,6 +23,7 @@ */ #include "utils.h" +#include "fish.h" /** * Calculate the length of Base64-encoded string @@ -67,4 +68,82 @@ unsigned long cbc_len(size_t plaintext_len) { */ unsigned long ecb_len(size_t plaintext_len) { return base64_fish_len(plaintext_len); +} + +/** + * Calculate the length of encrypted string in 'mode' mode + * + * @param plaintext_len Length of plaintext + * @param mode Encryption mode + * @return Size of encoded string + */ +unsigned long encoded_len(size_t plaintext_len, enum fish_mode mode) { + switch (mode) { + + case FISH_CBC_MODE: + return cbc_len(plaintext_len); + break; + + case FISH_ECB_MODE: + return ecb_len(plaintext_len); + } + + return 0; +} + +/** + * Determine the maximum length of plaintext for a 'max_len' limit taking care the overload of encryption + * + * @param max_len Limit for plaintext + * @param mode Encryption mode + * @return Maximum allowed plaintext length + */ +int max_text_command_len(size_t max_len, enum fish_mode mode) { + int len; + + for (len = max_len; encoded_len(len, mode) > max_len; --len); + return len; +} + +/** + * Iterate over 'data' in chunks of 'max_chunk_len' taking care the UTF-8 characters + * + * @param data Data to iterate + * @param max_chunk_len Size of biggest chunk + * @param [out] chunk_len Current chunk length + * @return Pointer to current chunk position or NULL if not have more chunks + */ +const char *foreach_utf8_data_chunks(const char *data, int max_chunk_len, int *chunk_len) { + int data_len, last_chunk_len = 0; + + if (!*data) { + return NULL; + } + + /* Last chunk of data */ + data_len = strlen(data); + if (data_len <= max_chunk_len) { + *chunk_len = data_len; + return data; + } + + *chunk_len = 0; + const char *utf8_character = data; + + /* Not valid UTF-8, but maybe valid text, just split into max length */ + if (!g_utf8_validate(data, -1, NULL)) { + *chunk_len = max_chunk_len; + return utf8_character; + } + + while (*utf8_character && *chunk_len <= max_chunk_len) { + last_chunk_len = *chunk_len; + *chunk_len = (g_utf8_next_char(utf8_character) - data) * sizeof(*utf8_character); + utf8_character = g_utf8_next_char(utf8_character); + } + + /* We need the previous length before overflow the limit */ + *chunk_len = last_chunk_len; + + return utf8_character; } \ No newline at end of file diff --git a/plugins/fishlim/utils.h b/plugins/fishlim/utils.h index 9e2b3355..623c67a4 100644 --- a/plugins/fishlim/utils.h +++ b/plugins/fishlim/utils.h @@ -26,10 +26,14 @@ #define PLUGIN_HEXCHAT_FISHLIM_UTILS_H #include +#include "fish.h" unsigned long base64_len(size_t plaintext_len); unsigned long base64_fish_len(size_t plaintext_len); unsigned long cbc_len(size_t plaintext_len); unsigned long ecb_len(size_t plaintext_len); +unsigned long encoded_len(size_t plaintext_len, enum fish_mode mode); +int max_text_command_len(size_t max_len, enum fish_mode mode); +const char *foreach_utf8_data_chunks(const char *data, int max_chunk_len, int *chunk_len); #endif \ No newline at end of file -- cgit 1.4.1 From 090fd29acf4af0d8e13fcf2861b14a356db72641 Mon Sep 17 00:00:00 2001 From: Sbgodin Date: Sun, 7 Mar 2021 12:51:45 +0000 Subject: python: Fix exception with list_pluginpref() __decode cannot work (with Python3) because prefs_str has no attribute 'decode'. Related to https://github.com/hexchat/hexchat/issues/2531 --- plugins/python/_hexchat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/python/_hexchat.py b/plugins/python/_hexchat.py index 567b3493..5e4b0c65 100644 --- a/plugins/python/_hexchat.py +++ b/plugins/python/_hexchat.py @@ -319,7 +319,7 @@ def del_pluginpref(name): def list_pluginpref(): prefs_str = ffi.new('char[4096]') if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1: - return __decode(prefs_str).split(',') + return __decode(ffi.string(prefs_str)).split(',') return [] -- cgit 1.4.1 From 90c91d6c9aa048eff8f8f8f888d37a21fd714522 Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Sun, 4 Apr 2021 21:07:30 +0200 Subject: plugins/lua/lua.c: fix segfault on lua_pop with Lua 5.4.3 Closes #2558 Co-authored-by: "Jan Alexander Steffens (heftig)" Signed-off-by: Mateusz Gozdek --- plugins/lua/lua.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/lua/lua.c b/plugins/lua/lua.c index d73fbb23..d1370eaf 100644 --- a/plugins/lua/lua.c +++ b/plugins/lua/lua.c @@ -1189,11 +1189,11 @@ static void patch_clibs(lua_State *L) if(lua_type(L, -2) == LUA_TLIGHTUSERDATA && lua_type(L, -1) == LUA_TTABLE) { lua_setfield(L, LUA_REGISTRYINDEX, "_CLIBS"); + lua_pop(L, 1); break; } lua_pop(L, 1); } - lua_pop(L, 1); } static GPtrArray *scripts; -- cgit 1.4.1 From 939ec7a16e205e974cfe1a97c2ff4974e763c7fa Mon Sep 17 00:00:00 2001 From: DjLegolas Date: Sun, 12 Apr 2020 10:32:31 +0300 Subject: Updated Toolset to v142 --- plugins/checksum/checksum.vcxproj | 2 +- plugins/exec/exec.vcxproj | 2 +- plugins/fishlim/fishlim.vcxproj | 2 +- plugins/lua/lua.vcxproj | 2 +- plugins/perl/perl.vcxproj | 2 +- plugins/python/python2.vcxproj | 2 +- plugins/python/python3.vcxproj | 2 +- plugins/sysinfo/sysinfo.vcxproj | 2 +- plugins/upd/upd.vcxproj | 2 +- plugins/winamp/winamp.vcxproj | 2 +- src/common/common.vcxproj | 2 +- src/fe-gtk/fe-gtk.vcxproj | 2 +- .../notifications/notifications-winrt.vcxproj | 124 ++++++++++----------- src/fe-text/fe-text.vcxproj | 2 +- src/htm/Properties/Resources.Designer.cs | 2 +- src/htm/Properties/Settings.Designer.cs | 2 +- src/htm/app.config | 2 +- src/htm/htm.csproj | 2 +- src/libenchant_win8/libenchant_win8.vcxproj | 2 +- win32/copy/copy.vcxproj | 2 +- win32/installer/installer.vcxproj | 2 +- win32/nls/nls.vcxproj | 2 +- 22 files changed, 83 insertions(+), 83 deletions(-) (limited to 'plugins') diff --git a/plugins/checksum/checksum.vcxproj b/plugins/checksum/checksum.vcxproj index 7924be88..fba08cce 100644 --- a/plugins/checksum/checksum.vcxproj +++ b/plugins/checksum/checksum.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/exec/exec.vcxproj b/plugins/exec/exec.vcxproj index e34f10e6..ceb11843 100644 --- a/plugins/exec/exec.vcxproj +++ b/plugins/exec/exec.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/fishlim/fishlim.vcxproj b/plugins/fishlim/fishlim.vcxproj index 157c7928..579c2436 100644 --- a/plugins/fishlim/fishlim.vcxproj +++ b/plugins/fishlim/fishlim.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/lua/lua.vcxproj b/plugins/lua/lua.vcxproj index 22afe729..5c0be68e 100644 --- a/plugins/lua/lua.vcxproj +++ b/plugins/lua/lua.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/perl/perl.vcxproj b/plugins/perl/perl.vcxproj index 8b5069c0..92c27408 100644 --- a/plugins/perl/perl.vcxproj +++ b/plugins/perl/perl.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/python/python2.vcxproj b/plugins/python/python2.vcxproj index 0b098112..42895ce4 100644 --- a/plugins/python/python2.vcxproj +++ b/plugins/python/python2.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/python/python3.vcxproj b/plugins/python/python3.vcxproj index 5868d3b0..3eb86c2a 100644 --- a/plugins/python/python3.vcxproj +++ b/plugins/python/python3.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/sysinfo/sysinfo.vcxproj b/plugins/sysinfo/sysinfo.vcxproj index b1c7c8f0..a3ff0f8a 100644 --- a/plugins/sysinfo/sysinfo.vcxproj +++ b/plugins/sysinfo/sysinfo.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary Unicode diff --git a/plugins/upd/upd.vcxproj b/plugins/upd/upd.vcxproj index 16f96e45..5dc497b4 100644 --- a/plugins/upd/upd.vcxproj +++ b/plugins/upd/upd.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/plugins/winamp/winamp.vcxproj b/plugins/winamp/winamp.vcxproj index ccc04e72..78367ae9 100644 --- a/plugins/winamp/winamp.vcxproj +++ b/plugins/winamp/winamp.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 DynamicLibrary diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 33a883bf..bc191f43 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 StaticLibrary diff --git a/src/fe-gtk/fe-gtk.vcxproj b/src/fe-gtk/fe-gtk.vcxproj index 61d0a074..06df36dc 100644 --- a/src/fe-gtk/fe-gtk.vcxproj +++ b/src/fe-gtk/fe-gtk.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 Application diff --git a/src/fe-gtk/notifications/notifications-winrt.vcxproj b/src/fe-gtk/notifications/notifications-winrt.vcxproj index 1f392b6b..d720c864 100644 --- a/src/fe-gtk/notifications/notifications-winrt.vcxproj +++ b/src/fe-gtk/notifications/notifications-winrt.vcxproj @@ -1,62 +1,62 @@ - - - - - Release - Win32 - - - Release - x64 - - - - - true - - - - {C53145CC-D021-40C9-B97C-0249AB9A43C9} - Win32Proj - notifications-winrt - notifications-winrt - - - - v140 - DynamicLibrary - Unicode - - - - - - hcnotifications-winrt - $(HexChatRel)plugins\ - - - - WIN32;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT;NDEBUG;_WINDOWS;_USRDLL;NOTIFICATIONS_EXPORTS;%(PreprocessorDefinitions) - true - $(VCInstallDir)vcpackages;$(FrameworkSdkDir)References\CommonConfiguration\Neutral;%(AdditionalUsingDirectories) - - - $(DepLibs);mincore.lib;runtimeobject.lib;%(AdditionalDependencies) - 6.03 - $(DepsRoot)\lib;%(AdditionalLibraryDirectories) - - - - - WIN32;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT;NDEBUG;_WINDOWS;_USRDLL;NOTIFICATIONS_EXPORTS;%(PreprocessorDefinitions) - true - $(VCInstallDir)vcpackages;$(FrameworkSdkDir)References\CommonConfiguration\Neutral;%(AdditionalUsingDirectories) - - - $(DepLibs);mincore.lib;runtimeobject.lib;%(AdditionalDependencies) - 6.03 - $(DepsRoot)\lib;%(AdditionalLibraryDirectories) - - - - + + + + + Release + Win32 + + + Release + x64 + + + + + true + + + + {C53145CC-D021-40C9-B97C-0249AB9A43C9} + Win32Proj + notifications-winrt + notifications-winrt + + + + v142 + DynamicLibrary + Unicode + + + + + + hcnotifications-winrt + $(HexChatRel)plugins\ + + + + WIN32;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT;NDEBUG;_WINDOWS;_USRDLL;NOTIFICATIONS_EXPORTS;%(PreprocessorDefinitions) + true + $(VCInstallDir)vcpackages;$(FrameworkSdkDir)References\CommonConfiguration\Neutral;%(AdditionalUsingDirectories) + + + $(DepLibs);mincore.lib;runtimeobject.lib;%(AdditionalDependencies) + 6.03 + $(DepsRoot)\lib;%(AdditionalLibraryDirectories) + + + + + WIN32;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES_MEMORY;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT;NDEBUG;_WINDOWS;_USRDLL;NOTIFICATIONS_EXPORTS;%(PreprocessorDefinitions) + true + $(VCInstallDir)vcpackages;$(FrameworkSdkDir)References\CommonConfiguration\Neutral;%(AdditionalUsingDirectories) + + + $(DepLibs);mincore.lib;runtimeobject.lib;%(AdditionalDependencies) + 6.03 + $(DepsRoot)\lib;%(AdditionalLibraryDirectories) + + + + diff --git a/src/fe-text/fe-text.vcxproj b/src/fe-text/fe-text.vcxproj index 6c7c25a8..75b64a12 100644 --- a/src/fe-text/fe-text.vcxproj +++ b/src/fe-text/fe-text.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 Application diff --git a/src/htm/Properties/Resources.Designer.cs b/src/htm/Properties/Resources.Designer.cs index 7627ee06..8e2cd74a 100644 --- a/src/htm/Properties/Resources.Designer.cs +++ b/src/htm/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace thememan.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/src/htm/Properties/Settings.Designer.cs b/src/htm/Properties/Settings.Designer.cs index cf679280..c8c467e8 100644 --- a/src/htm/Properties/Settings.Designer.cs +++ b/src/htm/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace thememan.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/src/htm/app.config b/src/htm/app.config index ad1957f6..b33a2051 100644 --- a/src/htm/app.config +++ b/src/htm/app.config @@ -1,3 +1,3 @@ - + diff --git a/src/htm/htm.csproj b/src/htm/htm.csproj index eaf57a0f..7c96d274 100644 --- a/src/htm/htm.csproj +++ b/src/htm/htm.csproj @@ -12,7 +12,7 @@ thememan 512 false - v4.5.2 + v4.6.1 publish\ diff --git a/src/libenchant_win8/libenchant_win8.vcxproj b/src/libenchant_win8/libenchant_win8.vcxproj index 3496f978..a6cbdad6 100644 --- a/src/libenchant_win8/libenchant_win8.vcxproj +++ b/src/libenchant_win8/libenchant_win8.vcxproj @@ -19,7 +19,7 @@ DynamicLibrary true - v140 + v142 diff --git a/win32/copy/copy.vcxproj b/win32/copy/copy.vcxproj index 72f2c032..b26d7e28 100644 --- a/win32/copy/copy.vcxproj +++ b/win32/copy/copy.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 Application diff --git a/win32/installer/installer.vcxproj b/win32/installer/installer.vcxproj index 3458b658..3cc11e44 100644 --- a/win32/installer/installer.vcxproj +++ b/win32/installer/installer.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 Application diff --git a/win32/nls/nls.vcxproj b/win32/nls/nls.vcxproj index aa60abff..759ae14d 100644 --- a/win32/nls/nls.vcxproj +++ b/win32/nls/nls.vcxproj @@ -1,7 +1,7 @@  - v140 + v142 Application -- cgit 1.4.1