1
0
mirror of https://github.com/weechat/weechat.git synced 2026-07-09 03:03:12 +02:00

relay: add "api" protocol (HTTP REST API) (issue #2066)

This commit is contained in:
Sébastien Helleu
2024-01-06 10:42:22 +01:00
parent 179d53a12e
commit 8971fc069a
41 changed files with 4811 additions and 381 deletions
+37 -2
View File
@@ -5,7 +5,41 @@ on:
- pull_request
env:
WEECHAT_DEPENDENCIES: devscripts equivs python3-pip cmake ninja-build lcov pkg-config libncurses-dev gem2deb libperl-dev python3-dev libaspell-dev liblua5.3-dev tcl8.6-dev guile-3.0-dev libv8-dev libcurl4-gnutls-dev libgcrypt20-dev libgnutls28-dev libzstd-dev zlib1g-dev curl libcpputest-dev php-dev libphp-embed libargon2-dev libsodium-dev flake8 pylint python3-bandit asciidoctor ruby-pygments.rb shellcheck
WEECHAT_DEPENDENCIES: >-
asciidoctor
cmake
curl
devscripts
equivs
flake8
gem2deb
guile-3.0-dev
lcov
libargon2-dev
libaspell-dev
libcjson-dev
libcpputest-dev
libcurl4-gnutls-dev
libgcrypt20-dev
libgnutls28-dev
liblua5.3-dev
libncurses-dev
libperl-dev
libphp-embed
libsodium-dev
libv8-dev
libzstd-dev
ninja-build
php-dev
pkg-config
pylint
python3-bandit
python3-dev
python3-pip
ruby-pygments.rb
shellcheck
tcl8.6-dev
zlib1g-dev
jobs:
@@ -20,6 +54,7 @@ jobs:
- { name: "gcc_ninja", cc: "gcc", cxx: "g++", buildargs: "-G Ninja" }
- { name: "gcc_no_nls", cc: "gcc", cxx: "g++", buildargs: "-DENABLE_NLS=OFF -DENABLE_DOC=OFF" }
- { name: "gcc_no_zstd", cc: "gcc", cxx: "g++", buildargs: "-DENABLE_ZSTD=OFF -DENABLE_DOC=OFF" }
- { name: "gcc_no_cjson", cc: "gcc", cxx: "g++", buildargs: "-DENABLE_CJSON=OFF -DENABLE_DOC=OFF" }
- { name: "gcc_coverage", cc: "gcc", cxx: "g++", buildargs: "-DENABLE_CODE_COVERAGE=ON" }
- { name: "clang", cc: "clang", cxx: "clang++", buildargs: "" }
@@ -115,7 +150,7 @@ jobs:
/usr/local/bin/python3.11 \
/usr/local/bin/python3.11-config \
;
brew install asciidoctor guile lua pkg-config ruby
brew install asciidoctor cjson guile lua pkg-config ruby
- uses: actions/checkout@v2
+7
View File
@@ -100,6 +100,7 @@ option(ENABLE_HEADLESS "Compile the headless binary" ON)
option(ENABLE_NLS "Enable Native Language Support" ON)
option(ENABLE_LARGEFILE "Enable Large File Support" ON)
option(ENABLE_ZSTD "Enable Zstandard compression" ON)
option(ENABLE_CJSON "Enable cJSON support" ON)
option(ENABLE_ALIAS "Enable Alias plugin" ON)
option(ENABLE_BUFLIST "Enable Buflist plugin" ON)
option(ENABLE_CHARSET "Enable Charset plugin" ON)
@@ -222,6 +223,12 @@ if(ENABLE_ZSTD)
add_definitions(-DHAVE_ZSTD)
endif()
# Check for cJSON
if(ENABLE_CJSON)
pkg_check_modules(LIBCJSON REQUIRED libcjson)
add_definitions(-DHAVE_CJSON)
endif()
# Check for iconv
find_package(Iconv)
if(ICONV_FOUND)
+1
View File
@@ -18,6 +18,7 @@ New features::
* core: allow case insensitive search of partial buffer name with `(?i)name` in command `/buffer`
* core: use function util_strftimeval in evaluation of expression `date:xxx`
* api: add support of specifier `%!` for timestamp in function util_strftimeval
* relay: add "api" protocol (HTTP REST API), add option relay.look.display_clients, change option type relay.look.auto_open_buffer to string (issue #2066)
Bug fixes::
+2 -1
View File
@@ -21,7 +21,8 @@ Build-Depends:
libgcrypt20-dev,
libgnutls28-dev,
libzstd-dev,
zlib1g-dev
zlib1g-dev,
libcjson-dev
Standards-Version: 4.6.2
Homepage: https://weechat.org/
Vcs-Git: https://salsa.debian.org/kolter/weechat.git
+2 -1
View File
@@ -21,7 +21,8 @@ Build-Depends:
libgcrypt20-dev,
libgnutls28-dev,
libzstd-dev,
zlib1g-dev
zlib1g-dev,
libcjson-dev
Standards-Version: 4.6.2
Homepage: https://weechat.org/
Vcs-Git: https://salsa.debian.org/kolter/weechat.git
+8
View File
@@ -159,6 +159,10 @@ WeeChat optional sind:
| ca-certificates |
| Zertifikate für TLS Verbindungen.
// TRANSLATION MISSING
| libcjson-dev |
| Relay plugin: protocol "api" (HTTP REST API).
| libzstd-dev | ≥ 0.8.1
| Logger-Erweiterung: Kompression von rotierenden Protokolldateien (zstandard). +
Relay-Erweiterung: Kompression von Nachrichten (WeeChat -> client) with https://facebook.github.io/zstd/[Zstandard ^↗^^] (weechat protocol).
@@ -279,6 +283,10 @@ Liste der verfügbaren Optionen:
| ENABLE_CHARSET | `ON`, `OFF` | ON
| kompiliert <<charset,Charset Erweiterung>>.
// TRANSLATION MISSING
| ENABLE_CJSON | `ON`, `OFF` | ON
| Support of JSON with https://github.com/DaveGamble/cJSON[cJSON ^↗^^] library.
| ENABLE_MAN | `ON`, `OFF` | OFF
| erstellt die man page.
+7 -1
View File
@@ -331,15 +331,20 @@ WeeChat "core" is located in following directories:
|       relay-command.c | Relay commands.
|       relay-completion.c | Relay completions.
|       relay-config.c | Relay config options (file relay.conf).
|       relay-http.c | HTTP functions.
|       relay-info.c | Relay info/infolists/hdata.
|       relay-network.c | Network functions for relay.
|       relay-raw.c | Relay raw buffer.
|       relay-server.c | Relay server.
|       relay-upgrade.c | Save/restore of relay data when upgrading WeeChat.
|       relay-websocket.c | WebSocket server functions (RFC 6455).
|       api/ | Relay for remote interfaces (using HTTP REST API).
|          relay-api.c | Main API functions for HTTP REST API.
|          relay-api-msg.c | Send JSON messages to clients.
|          relay-api-protocol.c | HTTP REST API protocol.
|       irc/ | IRC proxy.
|          relay-irc.c | Main IRC proxy functions.
|       weechat/ | Relay for remote interfaces.
|       weechat/ | Relay for remote interfaces (using "weechat" binary protocol).
|          relay-weechat.c | Relay for remote interfaces (main functions).
|          relay-weechat-msg.c | Send binary messages to clients.
|          relay-weechat-nicklist.c | Nicklist functions.
@@ -469,6 +474,7 @@ WeeChat "core" is located in following directories:
|             test-typing-status.cpp | Tests: typing status.
|          relay/ | Root of unit tests for Relay plugin.
|             test-relay-auth.cpp | Tests: clients authentication.
|             test-relay-http.cpp | Tests: HTTP functions for Relay plugin.
|             irc/ | Root of unit tests for Relay "irc" protocol.
|                test-relay-irc.cpp | Tests: Relay "irc" protocol.
|          xfer/ | Root of unit tests for Xfer plugin.
+6
View File
@@ -154,6 +154,9 @@ WeeChat:
| ca-certificates |
| Certificates for TLS connections.
| libcjson-dev |
| Relay plugin: protocol "api" (HTTP REST API).
| libzstd-dev | ≥ 0.8.1
| Logger plugin: compression of rotated log files (zstandard). +
Relay plugin: compression of messages (WeeChat -> client) with https://facebook.github.io/zstd/[Zstandard ^↗^^] (weechat protocol).
@@ -273,6 +276,9 @@ List of available options:
| ENABLE_CHARSET | `ON`, `OFF` | ON
| Compile <<charset,Charset plugin>>.
| ENABLE_CJSON | `ON`, `OFF` | ON
| Support of JSON with https://github.com/DaveGamble/cJSON[cJSON ^↗^^] library.
| ENABLE_MAN | `ON`, `OFF` | OFF
| Build man page.
+7 -1
View File
@@ -333,15 +333,20 @@ Le cœur de WeeChat est situé dans les répertoires suivants :
|       relay-command.c | Commandes de Relay.
|       relay-completion.c | Complétions de Relay.
|       relay-config.c | Options de configuration pour Relay (fichier relay.conf).
|       relay-http.c | Fonctions HTTP.
|       relay-info.c | Info/infolists/hdata pour Relay.
|       relay-network.c | Fonctions de réseau pour Relay.
|       relay-raw.c | Tampon des données brutes de Relay.
|       relay-server.c | Serveur Relay.
|       relay-upgrade.c | Sauvegarde/restauration des données Relay lors de la mise à jour de WeeChat.
|       relay-websocket.c | Fonctions pour le serveur WebSocket (RFC 6455).
|       api/ | Relai pour les interfaces distantes (en utilisant une API REST HTTP).
|          relay-api.c | Fonctions principales pour l'API REST HTTP.
|          relay-api-msg.c | Envoi de messages JSON aux clients.
|          relay-api-protocol.c | Protocole HTTP REST API.
|       irc/ | Proxy IRC.
|          relay-irc.c | Fonctions principales pour le proxy IRC.
|       weechat/ | Relai pour les interfaces distantes.
|       weechat/ | Relai pour les interfaces distantes (en utilisant le protocole binaire "weechat").
|          relay-weechat.c | Relai pour les interfaces distantes (fonctions principales).
|          relay-weechat-msg.c | Envoi de messages binaires aux clients.
|          relay-weechat-nicklist.c | Fonctions pour la liste de pseudos.
@@ -471,6 +476,7 @@ Le cœur de WeeChat est situé dans les répertoires suivants :
|             test-typing-status.cpp | Tests : statut d'écriture.
|          relay/ | Racine des tests unitaires pour l'extension Relay.
|             test-relay-auth.cpp | Tests : authentification des clients.
|             test-relay-http.cpp | Tests : fonctions HTTP pour l'extension Relay.
|             irc/ | Racine des tests unitaires pour le protocole relay "irc".
|                test-relay-irc.cpp | Tests : Protocole relay "irc".
|          xfer/ | Racine des tests unitaires pour l'extension Xfer.
+6
View File
@@ -153,6 +153,9 @@ Le tableau suivant liste les paquets optionnels pour compiler WeeChat :
| ca-certificates |
| Certificats pour les connexions TLS.
| libcjson-dev |
| Extension Relay : protocole "api" (API REST HTTP).
| libzstd-dev | ≥ 0.8.1
| Extension Logger : compression des fichiers de log qui tournent (zstandard). +
Extension Relay : compression des messages (WeeChat -> client) avec https://facebook.github.io/zstd/[Zstandard ^↗^^] (protocole weechat).
@@ -272,6 +275,9 @@ Liste des options disponibles :
| ENABLE_CHARSET | `ON`, `OFF` | ON
| Compiler <<charset,l'extension Charset>>.
| ENABLE_CJSON | `ON`, `OFF` | ON
| Support du JSON avec la bibliothèque https://github.com/DaveGamble/cJSON[cJSON ^↗^^].
| ENABLE_MAN | `ON`, `OFF` | OFF
| Construire la page man.
+8
View File
@@ -188,6 +188,10 @@ WeeChat:
| ca-certificates |
| Certificati per le connessioni TLS.
// TRANSLATION MISSING
| libcjson-dev |
| Relay plugin: protocol "api" (HTTP REST API).
// TRANSLATION MISSING
| libzstd-dev | ≥ 0.8.1
| Logger plugin: compression of rotated log files (zstandard). +
@@ -320,6 +324,10 @@ List of available options:
| ENABLE_CHARSET | `ON`, `OFF` | ON
| Compile <<charset,Charset plugin>>.
// TRANSLATION MISSING
| ENABLE_CJSON | `ON`, `OFF` | ON
| Support of JSON with https://github.com/DaveGamble/cJSON[cJSON ^↗^^] library.
| ENABLE_MAN | `ON`, `OFF` | OFF
| Build man page.
+14 -1
View File
@@ -352,15 +352,26 @@ WeeChat "core" は以下のディレクトリに配置されています:
|       relay-command.c | relay コマンド
|       relay-completion.c | relay 補完
|       relay-config.c | relay 設定オプション (relay.conf ファイル)
// TRANSLATION MISSING
|       relay-http.c | HTTP functions.
|       relay-info.c | relay の情報/インフォリスト/hdata
|       relay-network.c | relay 用のネットワーク関数
|       relay-raw.c | relay 生バッファ
|       relay-server.c | relay サーバ
|       relay-upgrade.c | WeeChat をアップグレードする際にデータを保存/回復
|       relay-websocket.c | リレー用の websocket サーバ関数 (RFC 6455)
// TRANSLATION MISSING
|       api/ | Relay for remote interfaces (using HTTP REST API).
// TRANSLATION MISSING
|          relay-api.c | Main API functions for HTTP REST API.
// TRANSLATION MISSING
|          relay-api-msg.c | Send JSON messages to clients.
// TRANSLATION MISSING
|          relay-api-protocol.c | HTTP REST API protocol.
|       irc/ | IRC プロキシ
|          relay-irc.c | IRC プロキシの主要関数
|       weechat/ | リモートインターフェースへの中継
// TRANSLATION MISSING
|       weechat/ | Relay for remote interfaces (using "weechat" binary protocol).
|          relay-weechat.c | リモートインターフェースへの中継 (主要関数)
|          relay-weechat-msg.c | クライアントにバイナリメッセージを送信
|          relay-weechat-nicklist.c | ニックネームリスト関数
@@ -543,6 +554,8 @@ WeeChat "core" は以下のディレクトリに配置されています:
// TRANSLATION MISSING
|             test-relay-auth.cpp | Tests: clients authentication.
// TRANSLATION MISSING
|             test-relay-http.cpp | Tests: HTTP functions for Relay plugin.
// TRANSLATION MISSING
|             irc/ | Root of unit tests for Relay "irc" protocol.
// TRANSLATION MISSING
|                test-relay-irc.cpp | Tests: Relay "irc" protocol.
+8
View File
@@ -173,6 +173,10 @@ WeeChat:
| ca-certificates |
| TLS 接続に必要な証明書、relay プラグインで TLS サポート
// TRANSLATION MISSING
| libcjson-dev |
| Relay plugin: protocol "api" (HTTP REST API).
// TRANSLATION MISSING
| libzstd-dev | ≥ 0.8.1
| Logger plugin: compression of rotated log files (zstandard). +
@@ -298,6 +302,10 @@ List of available options:
| ENABLE_CHARSET | `ON`, `OFF` | ON
| <<charset,Charset プラグイン>>のコンパイル。
// TRANSLATION MISSING
| ENABLE_CJSON | `ON`, `OFF` | ON
| Support of JSON with https://github.com/DaveGamble/cJSON[cJSON ^↗^^] library.
| ENABLE_MAN | `ON`, `OFF` | OFF
| man ページのコンパイル。
+8
View File
@@ -158,6 +158,10 @@ WeeChat:
| ca-certificates |
| Certyfikaty dla połączeń TLS.
// TRANSLATION MISSING
| libcjson-dev |
| Relay plugin: protocol "api" (HTTP REST API).
| libzstd-dev | ≥ 0.8.1
| Wtyczka logger: kompresja rotowanych plików z logami (zstandard). +
Wtyczka Relay: kompresja wiadomości (WeeChat -> klient) za pomocą https://facebook.github.io/zstd/[Zstandard ^↗^^] (protokół weechat).
@@ -277,6 +281,10 @@ Lista dostępnych opcji:
| ENABLE_CHARSET | `ON`, `OFF` | ON
| Kompilacja <<charset,wtyczki charset>>.
// TRANSLATION MISSING
| ENABLE_CJSON | `ON`, `OFF` | ON
| Support of JSON with https://github.com/DaveGamble/cJSON[cJSON ^↗^^] library.
| ENABLE_MAN | `ON`, `OFF` | OFF
| Generowanie strony manuala.
+14 -1
View File
@@ -333,15 +333,26 @@ WeeChat „језгро” се налази у следећим директо
|       relay-command.c | Релеј команде.
|       relay-completion.c | Релеј довршавања.
|       relay-config.c | Релеј опције конфиг (фајл relay.conf).
// TRANSLATION MISSING
|       relay-http.c | HTTP functions.
|       relay-info.c | Релеј info/infolists/hdata.
|       relay-network.c | Мрежне функције за релеј.
|       relay-raw.c | Релеј сирови бафер.
|       relay-server.c | Релеј сервер.
|       relay-upgrade.c | Save/restore of relay data when upgrading WeeChat.
|       relay-websocket.c | WebSocket сервер функције (RFC 6455).
// TRANSLATION MISSING
|       api/ | Relay for remote interfaces (using HTTP REST API).
// TRANSLATION MISSING
|          relay-api.c | Main API functions for HTTP REST API.
// TRANSLATION MISSING
|          relay-api-msg.c | Send JSON messages to clients.
// TRANSLATION MISSING
|          relay-api-protocol.c | HTTP REST API protocol.
|       irc/ | IRC прокси.
|          relay-irc.c | Главне IRC прокси функције.
|       weechat/ | Релеј за удаљене интерфејсе.
// TRANSLATION MISSING
|       weechat/ | Relay for remote interfaces (using "weechat" binary protocol).
|          relay-weechat.c | Релеј за удаљене интерфејсе (главне функције).
|          relay-weechat-msg.c | Слање бинарних порука клијентима.
|          relay-weechat-nicklist.c | Функције листе надимака.
@@ -471,6 +482,8 @@ WeeChat „језгро” се налази у следећим директо
|             test-typing-status.cpp | Тестови: typing статус.
|          relay/ | Корен unit тестова за Релеј додатак.
|             test-relay-auth.cpp | Тестови: аутентификација клијената.
// TRANSLATION MISSING
|             test-relay-http.cpp | Tests: HTTP functions for Relay plugin.
|             irc/ | Корен unit тестова за Релеј „irc” протокол.
|                test-relay-irc.cpp | Тестови: РЕлеј „irc” протокол.
|          xfer/ | Корен unit тестова за Xfer додатак.
+8
View File
@@ -151,6 +151,10 @@ WeeChat мора да се изгради са CMake.
| ca-certificates |
| Сертификати за TLS везе.
// TRANSLATION MISSING
| libcjson-dev |
| Relay plugin: protocol "api" (HTTP REST API).
| libzstd-dev | ≥ 0.8.1
| Logger додатак: компресија ротирајућих лог фајлова (zstandard). +
Relay додатак: компресија порука (WeeChat -> клијент) са https://facebook.github.io/zstd/[Zstandard ^↗^^] (weechat протокол).
@@ -268,6 +272,10 @@ $ make install
| ENABLE_CHARSET | `ON`, `OFF` | ON
| Компајлира <<charset,Charset додатак>>.
// TRANSLATION MISSING
| ENABLE_CJSON | `ON`, `OFF` | ON
| Support of JSON with https://github.com/DaveGamble/cJSON[cJSON ^↗^^] library.
| ENABLE_MAN | `ON`, `OFF` | OFF
| Изграђује man страницу.
+8
View File
@@ -344,6 +344,12 @@ SET(WEECHAT_SOURCES
./src/plugins/python/weechat-python-api.h
./src/plugins/python/weechat-python.c
./src/plugins/python/weechat-python.h
./src/plugins/relay/api/relay-api.c
./src/plugins/relay/api/relay-api.h
./src/plugins/relay/api/relay-api-msg.c
./src/plugins/relay/api/relay-api-msg.h
./src/plugins/relay/api/relay-api-protocol.c
./src/plugins/relay/api/relay-api-protocol.h
./src/plugins/relay/irc/relay-irc.c
./src/plugins/relay/irc/relay-irc.h
./src/plugins/relay/relay-auth.c
@@ -360,6 +366,8 @@ SET(WEECHAT_SOURCES
./src/plugins/relay/relay-config.c
./src/plugins/relay/relay-config.h
./src/plugins/relay/relay.h
./src/plugins/relay/relay-http.c
./src/plugins/relay/relay-http.h
./src/plugins/relay/relay-info.c
./src/plugins/relay/relay-info.h
./src/plugins/relay/relay-network.c
+4
View File
@@ -95,6 +95,10 @@ if(ENABLE_ZSTD)
include_directories(${ZSTD_INCLUDE_DIRS})
endif()
if(ENABLE_CJSON)
include_directories(${CJSON_INCLUDE_DIRS})
endif()
include_directories("${CMAKE_BINARY_DIR}")
add_library(weechat_core STATIC ${LIB_CORE_SRC})
target_link_libraries(weechat_core coverage_config)
+13
View File
@@ -36,6 +36,9 @@
#ifdef HAVE_ZSTD
#include <zstd.h>
#endif
#ifdef HAVE_CJSON
#include <cjson/cJSON.h>
#endif
#include <gnutls/gnutls.h>
@@ -702,6 +705,16 @@ debug_libs_cb (const void *pointer, void *data,
gui_chat_printf (NULL, " zstd: %s", _("not available"));
#endif /* HAVE_ZSTD */
/* display cJSON version */
#ifdef HAVE_CJSON
gui_chat_printf (NULL, " cJSON: %d.%d.%d",
CJSON_VERSION_MAJOR,
CJSON_VERSION_MINOR,
CJSON_VERSION_PATCH);
#else
gui_chat_printf (NULL, " cJSON: %s", _("not available"));
#endif /* HAVE_CJSON */
return WEECHAT_RC_OK;
}
+19 -1
View File
@@ -17,7 +17,7 @@
# along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
#
add_library(relay MODULE
set(RELAY_SRC
relay.c relay.h
relay-auth.c relay-auth.h
relay-buffer.c relay-buffer.h
@@ -25,6 +25,7 @@ add_library(relay MODULE
relay-command.c relay-command.h
relay-completion.c relay-completion.h
relay-config.c relay-config.h
relay-http.c relay-http.h
relay-info.c relay-info.h
relay-network.c relay-network.h
relay-raw.c relay-raw.h
@@ -38,7 +39,19 @@ add_library(relay MODULE
weechat/relay-weechat-msg.c weechat/relay-weechat-msg.h
weechat/relay-weechat-nicklist.c weechat/relay-weechat-nicklist.h
weechat/relay-weechat-protocol.c weechat/relay-weechat-protocol.h
)
if(ENABLE_CJSON)
list(APPEND RELAY_SRC
# API relay
api/relay-api.c api/relay-api.h
api/relay-api-msg.c api/relay-api-msg.h
api/relay-api-protocol.c api/relay-api-protocol.h
)
endif()
add_library(relay MODULE ${RELAY_SRC})
set_target_properties(relay PROPERTIES PREFIX "")
set(LINK_LIBS)
@@ -55,6 +68,11 @@ if(ENABLE_ZSTD)
list(APPEND LINK_LIBS ${LIBZSTD_LDFLAGS})
endif()
if(ENABLE_CJSON)
include_directories(${CJSON_INCLUDE_DIRS})
list(APPEND LINK_LIBS ${LIBCJSON_LDFLAGS})
endif()
target_link_libraries(relay ${LINK_LIBS} coverage_config)
install(TARGETS relay LIBRARY DESTINATION "${WEECHAT_LIBDIR}/plugins")
+601
View File
@@ -0,0 +1,601 @@
/*
* relay-api-msg.c - build JSON messages for "api" protocol
*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <cjson/cJSON.h>
#include "../../weechat-plugin.h"
#include "../relay.h"
#include "../relay-client.h"
#include "../relay-http.h"
#include "relay-api.h"
#include "relay-api-msg.h"
#include "relay-api-protocol.h"
#define MSG_ADD_STR_BUF(__json_name, __string) \
cJSON_AddItemToObject( \
json, __json_name, \
cJSON_CreateString (__string));
#define MSG_ADD_STR_PTR(__json_name, __string) \
cJSON_AddItemToObject( \
json, __json_name, \
cJSON_CreateString ((__string) ? __string : ""));
#define MSG_ADD_HDATA_VAR(__json_type, __json_name, \
__var_type, __var_name) \
cJSON_AddItemToObject( \
json, __json_name, \
cJSON_Create##__json_type ( \
weechat_hdata_##__var_type (hdata, pointer, __var_name)));
#define MSG_ADD_HDATA_TIME(__json_name, __var_name) \
date = weechat_hdata_time (hdata, pointer, __var_name); \
strftime (str_time, sizeof (str_time), "%FT%TZ", gmtime (&date)); \
MSG_ADD_STR_BUF(__json_name, str_time);
#define MSG_ADD_HDATA_TIME_USEC(__json_name, \
__var_name, __var_name_usec) \
time_value = weechat_hdata_time (hdata, pointer, __var_name); \
gmtime_r (&time_value, &gm_time); \
tv.tv_sec = mktime (&gm_time); \
tv.tv_usec = weechat_hdata_integer (hdata, pointer, \
__var_name_usec); \
weechat_util_strftimeval (str_time, sizeof (str_time), \
"%FT%T.%fZ", &tv); \
MSG_ADD_STR_BUF(__json_name, str_time);
#define MSG_ADD_HDATA_STR(__json_name, __var_name) \
ptr_string = weechat_hdata_string (hdata, pointer, __var_name); \
MSG_ADD_STR_PTR(__json_name, ptr_string);
#define MSG_ADD_HDATA_STR_COLORS(__json_name, __var_name) \
ptr_string = weechat_hdata_string (hdata, pointer, __var_name); \
switch (colors) \
{ \
case RELAY_API_COLORS_ANSI: \
string = weechat_hook_modifier_exec ( \
"color_encode_ansi", NULL, \
(ptr_string) ? ptr_string : ""); \
if (string) \
{ \
MSG_ADD_STR_PTR(__json_name, string); \
free (string); \
} \
break; \
case RELAY_API_COLORS_WEECHAT: \
MSG_ADD_STR_PTR(__json_name, ptr_string); \
break; \
case RELAY_API_COLORS_STRIP: \
string = weechat_string_remove_color ( \
(ptr_string) ? ptr_string : "", NULL); \
if (string) \
{ \
MSG_ADD_STR_PTR(__json_name, string); \
free (string); \
} \
case RELAY_API_NUM_COLORS: \
break; \
}
/*
* Sends JSON response to client (internal use).
*
* Returns number of bytes sent to client, -1 if error.
*/
int
relay_api_msg_send_json_internal (struct t_relay_client *client,
int return_code,
const char *message,
const char *event_name,
const char *event_type,
const char *event_context,
const char *headers,
cJSON *json_body)
{
cJSON *json, *json_event;
int num_bytes;
char *string;
if (!client || !message)
return -1;
num_bytes = -1;
if (client->websocket == RELAY_CLIENT_WEBSOCKET_READY)
{
/*
* with established websocket, we return JSON string instead of
* an HTTP response
*/
json = cJSON_CreateObject ();
if (json)
{
cJSON_AddItemToObject (json, "code", cJSON_CreateNumber (return_code));
cJSON_AddItemToObject (json, "message", cJSON_CreateString (message));
if (event_name)
{
json_event = cJSON_CreateObject ();
if (json_event)
{
cJSON_AddItemToObject (
json_event, "name",
cJSON_CreateString ((event_name) ? event_name : ""));
cJSON_AddItemToObject (
json_event, "type",
cJSON_CreateString ((event_type) ? event_type : ""));
cJSON_AddItemToObject (
json_event, "context",
cJSON_CreateString ((event_context) ? event_context : ""));
cJSON_AddItemToObject (json, "event", json_event);
}
}
cJSON_AddItemToObject (json, "body", json_body);
string = cJSON_PrintUnformatted (json);
num_bytes = relay_client_send (
client,
RELAY_CLIENT_MSG_STANDARD,
string,
(string) ? strlen (string) : 0,
NULL); /* raw_message */
if (string)
free (string);
cJSON_DetachItemFromObject (json, "body");
cJSON_Delete (json);
}
}
else
{
string = (json_body) ? cJSON_PrintUnformatted (json_body) : NULL;
num_bytes = relay_http_send_json (client, return_code, message, headers,
string);
if (string)
free (string);
}
return num_bytes;
}
/*
* Sends JSON response to client (internal use).
*
* Returns number of bytes sent to client, -1 if error.
*/
int
relay_api_msg_send_json (struct t_relay_client *client,
int return_code,
const char *message,
cJSON *json_body)
{
return relay_api_msg_send_json_internal (client,
return_code,
message,
NULL, /* event_name */
NULL, /* event_type */
NULL, /* event_context */
NULL, /* headers */
json_body);
}
/*
* Sends JSON error to client.
*
* Returns number of bytes sent to client, -1 if error.
*/
int
relay_api_msg_send_error_json (struct t_relay_client *client,
int return_code,
const char *message,
const char *headers,
const char *format, ...)
{
cJSON *json;
int num_bytes, length;
char *error_msg, *str_json;
if (!client || !message || !format)
return -1;
weechat_va_format (format);
if (!vbuffer)
return -1;
num_bytes = -1;
if (client->websocket == RELAY_CLIENT_WEBSOCKET_READY)
{
/*
* with established websocket, we return JSON string instead of
* an HTTP response
*/
json = cJSON_CreateObject ();
if (json)
{
cJSON_AddItemToObject (json, "error", cJSON_CreateString (vbuffer));
num_bytes = relay_api_msg_send_json_internal (
client,
return_code,
message,
NULL, /* event_name */
NULL, /* event_type */
NULL, /* event_context */
headers,
json);
cJSON_Delete (json);
}
}
else
{
error_msg = weechat_string_replace (vbuffer, "\"", "\\\"");
if (error_msg)
{
length = strlen (error_msg) + 64;
str_json = malloc (length);
if (str_json)
{
snprintf (str_json, length, "{\"error\": \"%s\"}", error_msg);
num_bytes = relay_http_send_json (client, return_code, message,
headers, str_json);
free (str_json);
}
free (error_msg);
}
}
free (vbuffer);
return num_bytes;
}
/*
* Sends event to the client.
*
* Returns number of bytes sent to client, -1 if error.
*/
int
relay_api_msg_send_event (struct t_relay_client *client,
const char *name,
const char *type,
const char *context,
cJSON *json_body)
{
return relay_api_msg_send_json_internal (client,
RELAY_API_HTTP_0_EVENT,
name,
type,
context,
NULL, /* headers */
json_body);
}
/*
* Adds a buffer local variable into a JSON object.
*/
void
relay_api_msg_buffer_add_local_vars_cb (void *data,
struct t_hashtable *hashtable,
const void *key,
const void *value)
{
cJSON *json;
/* make C compiler happy */
(void) hashtable;
json = (cJSON *)data;
cJSON_AddItemToObject (
json,
(const char *)key,
cJSON_CreateString ((value) ? (const char *)value : ""));
}
/*
* Creates a JSON object with a buffer.
*/
cJSON *
relay_api_msg_buffer_to_json (struct t_gui_buffer *buffer,
long lines,
int nicks,
enum t_relay_api_colors colors)
{
struct t_hdata *hdata;
struct t_gui_buffer *pointer;
cJSON *json, *json_local_vars, *json_lines, *json_nicks;
const char *ptr_string;
char *string;
hdata = relay_hdata_buffer;
pointer = buffer;
json = cJSON_CreateObject ();
if (!json)
return NULL;
if (!buffer)
return json;
MSG_ADD_HDATA_STR("name", "full_name");
MSG_ADD_HDATA_STR("short_name", "short_name");
MSG_ADD_HDATA_VAR(Number, "number", integer, "number");
ptr_string = weechat_buffer_get_string (buffer, "type");
MSG_ADD_STR_PTR("type", ptr_string);
MSG_ADD_HDATA_STR_COLORS("title", "title");
/* local_variables */
json_local_vars = cJSON_CreateObject ();
if (json_local_vars)
{
weechat_hashtable_map (
weechat_hdata_pointer (hdata, buffer, "local_variables"),
&relay_api_msg_buffer_add_local_vars_cb,
json_local_vars);
cJSON_AddItemToObject (json, "local_variables", json_local_vars);
}
/* lines */
if (lines != 0)
{
json_lines = relay_api_msg_lines_to_json (buffer, lines, colors);
if (json_lines)
cJSON_AddItemToObject (json, "lines", json_lines);
}
/* nicks */
if (nicks)
{
json_nicks = relay_api_msg_nick_group_to_json (
weechat_hdata_pointer (hdata, buffer, "nicklist_root"));
if (json_nicks)
cJSON_AddItemToObject (json, "nicks", json_nicks);
}
return json;
}
/*
* Creates a JSON object with a buffer line data.
*/
cJSON *
relay_api_msg_line_data_to_json (struct t_gui_line_data *line_data,
enum t_relay_api_colors colors)
{
struct t_hdata *hdata;
struct t_gui_line_data *pointer;
cJSON *json, *json_tags;
const char *ptr_string;
char *string, str_time[256], str_var[64];
int i, tags_count;
time_t time_value;
struct timeval tv;
struct tm gm_time;
hdata = relay_hdata_line_data;
pointer = line_data;
json = cJSON_CreateObject ();
if (!json)
return NULL;
if (!line_data)
return json;
MSG_ADD_HDATA_VAR(Number, "id", integer, "id");
MSG_ADD_HDATA_VAR(Number, "index", integer, "y");
MSG_ADD_HDATA_TIME_USEC("date", "date", "date_usec");
MSG_ADD_HDATA_TIME_USEC("date_printed", "date_printed", "date_usec_printed");
MSG_ADD_HDATA_VAR(Bool, "highlight", char, "highlight");
MSG_ADD_HDATA_STR_COLORS("prefix", "prefix");
MSG_ADD_HDATA_STR_COLORS("message", "message");
/* tags */
json_tags = cJSON_CreateArray ();
if (json_tags)
{
tags_count = weechat_hdata_integer (hdata, line_data, "tags_count");
for (i = 0; i < tags_count; i++)
{
snprintf (str_var, sizeof (str_var), "%d|tags_array", i);
cJSON_AddItemToArray (
json_tags,
cJSON_CreateString (weechat_hdata_string (hdata, line_data, str_var)));
}
}
cJSON_AddItemToObject(json, "tags", json_tags);
return json;
}
/*
* Creates a JSON object with an array of buffer lines.
*/
cJSON *
relay_api_msg_lines_to_json (struct t_gui_buffer *buffer,
long lines,
enum t_relay_api_colors colors)
{
cJSON *json;
struct t_gui_lines *ptr_lines;
struct t_gui_line *ptr_line;
struct t_gui_line_data *ptr_line_data;
long i, count;
json = cJSON_CreateArray ();
if (!json)
return NULL;
if (lines == 0)
return json;
ptr_lines = weechat_hdata_pointer (relay_hdata_buffer, buffer, "own_lines");
if (!ptr_lines)
return json;
if (lines < 0)
{
/* search start line from the last line */
ptr_line = weechat_hdata_pointer (relay_hdata_lines, ptr_lines, "last_line");
if (ptr_line)
{
for (i = 1; i < lines * -1; i++)
{
ptr_line = weechat_hdata_move (relay_hdata_line, ptr_line, -1);
if (!ptr_line)
break;
}
if (!ptr_line)
ptr_line = weechat_hdata_pointer (relay_hdata_lines, ptr_lines, "first_line");
}
}
else
{
ptr_line = weechat_hdata_pointer (relay_hdata_lines, ptr_lines, "first_line");
}
if (!ptr_line)
return json;
count = 0;
while (ptr_line)
{
ptr_line_data = weechat_hdata_pointer (relay_hdata_line, ptr_line, "data");
if (ptr_line_data)
{
cJSON_AddItemToArray (
json,
relay_api_msg_line_data_to_json (ptr_line_data, colors));
}
count++;
if ((lines > 0) && (count >= lines))
break;
ptr_line = weechat_hdata_move (relay_hdata_line, ptr_line, 1);
}
return json;
}
/*
* Creates a nick JSON object.
*/
cJSON *
relay_api_msg_nick_to_json (struct t_gui_nick *nick)
{
struct t_hdata *hdata;
struct t_gui_nick *pointer;
cJSON *json;
const char *ptr_string;
hdata = relay_hdata_nick;
pointer = nick;
json = cJSON_CreateObject ();
if (!json)
return NULL;
if (!nick)
return json;
MSG_ADD_HDATA_STR("prefix", "prefix");
MSG_ADD_HDATA_STR("prefix_color", "prefix_color");
MSG_ADD_HDATA_STR("name", "name");
MSG_ADD_HDATA_STR("color", "color");
return json;
}
/*
* Creates a nick group JSON object.
*/
cJSON *
relay_api_msg_nick_group_to_json (struct t_gui_nick_group *nick_group)
{
struct t_hdata *hdata;
struct t_gui_nick_group *pointer;
cJSON *json, *json_groups, *json_nicks;
const char *ptr_string;
struct t_gui_nick_group *ptr_group;
struct t_gui_nick *ptr_nick;
hdata = relay_hdata_nick_group;
pointer = nick_group;
json = cJSON_CreateObject ();
if (!json)
return NULL;
if (!nick_group)
return json;
MSG_ADD_HDATA_STR("name", "name");
MSG_ADD_HDATA_STR("color", "color");
json_groups = cJSON_CreateArray ();
if (json_groups)
{
ptr_group = weechat_hdata_pointer (relay_hdata_nick_group, nick_group, "children");
if (ptr_group)
{
while (ptr_group)
{
cJSON_AddItemToArray (
json_groups,
relay_api_msg_nick_group_to_json (ptr_group));
ptr_group = weechat_hdata_move (relay_hdata_nick_group, ptr_group, 1);
}
}
cJSON_AddItemToObject (json, "groups", json_groups);
}
json_nicks = cJSON_CreateArray ();
if (json_nicks)
{
ptr_nick = weechat_hdata_pointer (relay_hdata_nick_group, nick_group, "nicks");
if (ptr_nick)
{
while (ptr_nick)
{
cJSON_AddItemToArray (
json_nicks,
relay_api_msg_nick_to_json (ptr_nick));
ptr_nick = weechat_hdata_move (relay_hdata_nick, ptr_nick, 1);
}
}
cJSON_AddItemToObject (json, "nicks", json_nicks);
}
return json;
}
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef WEECHAT_PLUGIN_RELAY_API_MSG_H
#define WEECHAT_PLUGIN_RELAY_API_MSG_H
enum t_relay_api_colors;
extern int relay_api_msg_send_json (struct t_relay_client *client,
int return_code,
const char *message,
cJSON *json_body);
extern int relay_api_msg_send_error_json (struct t_relay_client *client,
int return_code,
const char *message,
const char *headers,
const char *format, ...);
extern int relay_api_msg_send_event (struct t_relay_client *client,
const char *name,
const char *type,
const char *context,
cJSON *json_body);
extern cJSON *relay_api_msg_buffer_to_json (struct t_gui_buffer *buffer,
long lines,
int nicks,
enum t_relay_api_colors colors);
extern cJSON *relay_api_msg_lines_to_json (struct t_gui_buffer *pointer,
long lines,
enum t_relay_api_colors colors);
extern cJSON *relay_api_msg_line_data_to_json (struct t_gui_line_data *line_data,
enum t_relay_api_colors colors);
extern cJSON *relay_api_msg_nick_to_json (struct t_gui_nick *nick);
extern cJSON *relay_api_msg_nick_group_to_json (struct t_gui_nick_group *nick_group);
#endif /* WEECHAT_PLUGIN_RELAY_API_MSG_H */
+878
View File
@@ -0,0 +1,878 @@
/*
* relay-api-protocol.c - API protocol for relay to client
*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <cjson/cJSON.h>
#include "../../weechat-plugin.h"
#include "../relay.h"
#include "../relay-buffer.h"
#include "../relay-client.h"
#include "../relay-config.h"
#include "../relay-http.h"
#include "relay-api.h"
#include "relay-api-msg.h"
#include "relay-api-protocol.h"
/*
* Checks authentication from client.
*
* Returns:
* 1: OK, client authenticated
* 0: client NOT authenticated
*/
int
relay_api_protocol_check_auth (struct t_relay_client *client,
struct t_relay_http_request *request)
{
if (client->status == RELAY_STATUS_CONNECTED)
return 1;
switch (relay_http_check_auth (request))
{
case 0: /* OK */
return 1;
case -1: /* missing password */
relay_api_msg_send_error_json (client,
RELAY_HTTP_401_UNAUTHORIZED,
"WWW-Authenticate: Basic realm=Password",
RELAY_HTTP_ERROR_MISSING_PASSWORD);
break;
case -2: /* invalid password */
relay_api_msg_send_error_json (client,
RELAY_HTTP_401_UNAUTHORIZED,
NULL,
RELAY_HTTP_ERROR_INVALID_PASSWORD);
break;
case -3: /* missing TOTP */
relay_api_msg_send_error_json (client,
RELAY_HTTP_401_UNAUTHORIZED,
NULL,
RELAY_HTTP_ERROR_MISSING_TOTP);
break;
case -4: /* invalid TOTP */
relay_api_msg_send_error_json (client,
RELAY_HTTP_401_UNAUTHORIZED,
NULL,
RELAY_HTTP_ERROR_INVALID_TOTP);
break;
case -5: /* out of memory */
relay_api_msg_send_error_json (client,
RELAY_HTTP_503_SERVICE_UNAVAILABLE,
NULL,
RELAY_HTTP_ERROR_OUT_OF_MEMORY);
break;
}
return 0;
}
/*
* Returns value of an URL parameter as boolean (0 or 1), using a default value
* if the parameter is not set or if it's not a valid boolean.
*/
int
relay_api_protocol_get_param_boolean (struct t_relay_http_request *request,
const char *name,
int default_value)
{
const char *ptr_value;
ptr_value = weechat_hashtable_get (request->params, name);
if (!ptr_value)
return default_value;
return weechat_config_string_to_boolean (ptr_value);
}
/*
* Returns value of an URL parameter as long, using a default value if the
* parameter is not set or if it's not a valid long integer.
*/
long
relay_api_protocol_get_param_long (struct t_relay_http_request *request,
const char *name,
long default_value)
{
const char *ptr_value;
char *error;
long number;
ptr_value = weechat_hashtable_get (request->params, name);
if (!ptr_value)
return default_value;
number = strtol (ptr_value, &error, 10);
if (error && !error[0])
return number;
return default_value;
}
/*
* Callback for signals "buffer_*".
*/
int
relay_api_protocol_signal_buffer_cb (const void *pointer, void *data,
const char *signal,
const char *type_data,
void *signal_data)
{
struct t_relay_client *ptr_client;
struct t_gui_buffer *ptr_buffer;
struct t_gui_line *ptr_line;
struct t_gui_line_data *ptr_line_data;
cJSON *json;
long lines;
/* make C compiler happy */
(void) data;
(void) type_data;
ptr_client = (struct t_relay_client *)pointer;
if (!ptr_client || !relay_client_valid (ptr_client))
return WEECHAT_RC_OK;
if ((strcmp (signal, "buffer_opened") == 0)
|| (strcmp (signal, "buffer_type_changed") == 0)
|| (strcmp (signal, "buffer_moved") == 0)
|| (strcmp (signal, "buffer_merged") == 0)
|| (strcmp (signal, "buffer_unmerged") == 0)
|| (strcmp (signal, "buffer_hidden") == 0)
|| (strcmp (signal, "buffer_unhidden") == 0)
|| (strcmp (signal, "buffer_renamed") == 0)
|| (strcmp (signal, "buffer_title_changed") == 0)
|| (strncmp (signal, "buffer_localvar_", 16) == 0)
|| (strcmp (signal, "buffer_cleared") == 0)
|| (strcmp (signal, "buffer_closing") == 0))
{
ptr_buffer = (struct t_gui_buffer *)signal_data;
if (!ptr_buffer)
return WEECHAT_RC_OK;
lines = (strcmp (signal, "buffer_opened") == 0) ? LONG_MIN : 0;
json = relay_api_msg_buffer_to_json (
ptr_buffer, lines, 0, RELAY_API_DATA(ptr_client, sync_colors));
if (json)
{
relay_api_msg_send_event (ptr_client, signal, "buffer", "", json);
cJSON_Delete (json);
}
}
else if (strcmp (signal, "buffer_line_added") == 0)
{
ptr_line = (struct t_gui_line *)signal_data;
if (!ptr_line)
return WEECHAT_RC_OK;
ptr_line_data = weechat_hdata_pointer (relay_hdata_line,
ptr_line, "data");
if (!ptr_line_data)
return WEECHAT_RC_OK;
ptr_buffer = weechat_hdata_pointer (relay_hdata_line_data,
ptr_line_data, "buffer");
if (!ptr_buffer || relay_buffer_is_relay (ptr_buffer))
return WEECHAT_RC_OK;
json = relay_api_msg_line_data_to_json (
ptr_line_data, RELAY_API_DATA(ptr_client, sync_colors));
if (json)
{
relay_api_msg_send_event (
ptr_client,
signal,
"line",
weechat_buffer_get_string (ptr_buffer, "full_name"),
json);
cJSON_Delete (json);
}
}
return WEECHAT_RC_OK;
}
/*
* Callback for hsignals "nicklist_*".
*/
int
relay_api_protocol_hsignal_nicklist_cb (const void *pointer, void *data,
const char *signal,
struct t_hashtable *hashtable)
{
struct t_relay_client *ptr_client;
struct t_gui_buffer *ptr_buffer;
struct t_gui_nick_group *ptr_parent_group, *ptr_group;
struct t_gui_nick *ptr_nick;
cJSON *json;
/* make C compiler happy */
(void) data;
ptr_client = (struct t_relay_client *)pointer;
if (!ptr_client || !relay_client_valid (ptr_client))
return WEECHAT_RC_OK;
ptr_buffer = weechat_hashtable_get (hashtable, "buffer");
ptr_parent_group = weechat_hashtable_get (hashtable, "parent_group");
ptr_group = weechat_hashtable_get (hashtable, "group");
ptr_nick = weechat_hashtable_get (hashtable, "nick");
/* if there is no parent group (for example "root" group), ignore the signal */
if (!ptr_parent_group)
return WEECHAT_RC_OK;
if ((strcmp (signal, "nicklist_group_added") == 0)
|| (strcmp (signal, "nicklist_group_changed") == 0)
|| (strcmp (signal, "nicklist_group_removing") == 0))
{
json = relay_api_msg_nick_group_to_json (ptr_group);
if (json)
{
relay_api_msg_send_event (
ptr_client,
signal,
"nick_group",
weechat_buffer_get_string (ptr_buffer, "full_name"),
json);
cJSON_Delete (json);
}
}
else if ((strcmp (signal, "nicklist_nick_added") == 0)
|| (strcmp (signal, "nicklist_nick_changed") == 0)
|| (strcmp (signal, "nicklist_nick_removing") == 0))
{
json = relay_api_msg_nick_to_json (ptr_nick);
if (json)
{
relay_api_msg_send_event (
ptr_client,
signal,
"nick",
weechat_buffer_get_string (ptr_buffer, "full_name"),
json);
cJSON_Delete (json);
}
}
return WEECHAT_RC_OK;
}
/*
* Callback for signals "upgrade*".
*/
int
relay_api_protocol_signal_upgrade_cb (const void *pointer, void *data,
const char *signal,
const char *type_data,
void *signal_data)
{
struct t_relay_client *ptr_client;
/* make C compiler happy */
(void) data;
(void) type_data;
(void) signal_data;
ptr_client = (struct t_relay_client *)pointer;
if (!ptr_client || !relay_client_valid (ptr_client))
return WEECHAT_RC_OK;
if ((strcmp (signal, "upgrade") == 0)
|| (strcmp (signal, "upgrade_ended") == 0))
{
relay_api_msg_send_event (ptr_client, signal, NULL, NULL, NULL);
}
return WEECHAT_RC_OK;
}
/*
* Callback for resource "version".
*
* Routes:
* GET /api/version
*/
RELAY_API_PROTOCOL_CALLBACK(version)
{
cJSON *json;
char *version, *error;
long number;
/* make C compiler happy */
(void) request;
json = cJSON_CreateObject ();
if (!json)
return WEECHAT_RC_ERROR;
version = weechat_info_get ("version", NULL);
cJSON_AddItemToObject (json,
"weechat_version", cJSON_CreateString (version));
free (version);
version = weechat_info_get ("version_git", NULL);
cJSON_AddItemToObject (json,
"weechat_version_git", cJSON_CreateString (version));
free (version);
version = weechat_info_get ("version_number", NULL);
error = NULL;
number = strtol (version, &error, 10);
if (error && !error[0])
{
cJSON_AddItemToObject (json,
"weechat_version_number",
cJSON_CreateNumber (number));
}
free (version);
cJSON_AddItemToObject (json, "relay_api_version",
cJSON_CreateString (RELAY_API_VERSION_STR));
cJSON_AddItemToObject (json,
"relay_api_version_number",
cJSON_CreateNumber (RELAY_API_VERSION_NUMBER));
relay_api_msg_send_json (client, RELAY_HTTP_200_OK, json);
cJSON_Delete (json);
return WEECHAT_RC_OK;
}
/*
* Callback for resource "buffers".
*
* Routes:
* GET /api/buffers
* GET /api/buffers/{buffer_name}
* GET /api/buffers/{buffer_name}/lines
* GET /api/buffers/{buffer_name}/lines/{line_id}
* GET /api/buffers/{buffer_name}/nicks
*/
RELAY_API_PROTOCOL_CALLBACK(buffers)
{
cJSON *json;
struct t_gui_buffer *ptr_buffer;
long lines;
int nicks;
enum t_relay_api_colors colors;
ptr_buffer = NULL;
if (request->num_path_items > 2)
{
ptr_buffer = weechat_buffer_search ("==", request->path_items[2]);
if (!ptr_buffer)
{
relay_api_msg_send_error_json (client, RELAY_HTTP_404_NOT_FOUND, NULL,
"Buffer \"%s\" not found",
request->path_items[2]);
return WEECHAT_RC_OK;
}
}
nicks = relay_api_protocol_get_param_boolean (request, "nicks", 0);
colors = relay_api_search_colors (
weechat_hashtable_get (request->params, "colors"));
if (request->num_path_items > 3)
{
/* sub-resource of buffers */
if (strcmp (request->path_items[3], "lines") == 0)
{
lines = relay_api_protocol_get_param_long (request, "lines", -100L);
json = relay_api_msg_lines_to_json (ptr_buffer, lines, colors);
}
else if (strcmp (request->path_items[3], "nicks") == 0)
{
json = relay_api_msg_nick_group_to_json (
weechat_hdata_pointer (relay_hdata_buffer,
ptr_buffer, "nicklist_root"));
}
else
{
relay_api_msg_send_error_json (
client, RELAY_HTTP_404_NOT_FOUND, NULL,
"Sub-resource of buffers not found: \"%s\"",
request->path_items[3]);
return WEECHAT_RC_OK;
}
}
else
{
lines = relay_api_protocol_get_param_long (request, "lines", 0L);
if (ptr_buffer)
{
json = relay_api_msg_buffer_to_json (ptr_buffer, lines, nicks, colors);
}
else
{
json = cJSON_CreateArray ();
if (!json)
return WEECHAT_RC_ERROR;
ptr_buffer = weechat_hdata_get_list (relay_hdata_buffer,
"gui_buffers");
while (ptr_buffer)
{
cJSON_AddItemToArray (
json,
relay_api_msg_buffer_to_json (ptr_buffer,
lines, nicks, colors));
ptr_buffer = weechat_hdata_move (relay_hdata_buffer, ptr_buffer, 1);
}
}
}
if (!json)
goto error;
relay_api_msg_send_json (client, RELAY_HTTP_200_OK, json);
cJSON_Delete (json);
return WEECHAT_RC_OK;
error:
relay_api_msg_send_error_json (client,
RELAY_HTTP_503_SERVICE_UNAVAILABLE,
NULL,
RELAY_HTTP_ERROR_OUT_OF_MEMORY);
return WEECHAT_RC_OK;
}
/*
* Callback for resource "input".
*
* Routes:
* POST /api/input/{buffer_name}
*/
RELAY_API_PROTOCOL_CALLBACK(input)
{
cJSON *json_body, *json_buffer, *json_command;
const char *ptr_buffer_name, *ptr_command, *ptr_weechat_commands;
struct t_gui_buffer *ptr_buffer;
struct t_hashtable *options;
json_body = cJSON_Parse(request->body);
if (!json_body)
return WEECHAT_RC_ERROR;
json_buffer = cJSON_GetObjectItem (json_body, "buffer");
if (json_buffer)
{
ptr_buffer_name = cJSON_GetStringValue (json_buffer);
ptr_buffer = weechat_buffer_search ("==", ptr_buffer_name);
if (!ptr_buffer)
{
cJSON_Delete (json_body);
relay_api_msg_send_error_json (client,
RELAY_HTTP_404_NOT_FOUND, NULL,
"Buffer \"%s\" not found",
ptr_buffer_name);
return WEECHAT_RC_OK;
}
}
else
{
ptr_buffer = weechat_buffer_search_main ();
}
if (!ptr_buffer)
{
cJSON_Delete (json_body);
return WEECHAT_RC_ERROR;
}
json_command = cJSON_GetObjectItem (json_body, "command");
if (!json_command)
{
cJSON_Delete (json_body);
return WEECHAT_RC_ERROR;
}
ptr_command = cJSON_GetStringValue (json_command);
if (!ptr_command)
{
cJSON_Delete (json_body);
return WEECHAT_RC_ERROR;
}
options = weechat_hashtable_new (8,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
if (!options)
{
relay_api_msg_send_error_json (client,
RELAY_HTTP_503_SERVICE_UNAVAILABLE,
NULL,
RELAY_HTTP_ERROR_OUT_OF_MEMORY);
cJSON_Delete (json_body);
return WEECHAT_RC_OK;
}
ptr_weechat_commands = weechat_config_string (
relay_config_weechat_commands);
if (ptr_weechat_commands && ptr_weechat_commands[0])
{
weechat_hashtable_set (
options,
"commands",
weechat_config_string (relay_config_weechat_commands));
}
/*
* delay the execution of command after we go back in the WeeChat
* main loop (some commands like /upgrade executed now can cause
* a crash)
*/
weechat_hashtable_set (options, "delay", "1");
/* execute the command, with the delay */
weechat_command_options (ptr_buffer, ptr_command, options);
weechat_hashtable_free (options);
cJSON_Delete (json_body);
relay_api_msg_send_json (client, RELAY_HTTP_204_NO_CONTENT, NULL);
return WEECHAT_RC_OK;
}
/*
* Callback for resource "ping".
*
* Routes:
* POST /api/ping
*/
RELAY_API_PROTOCOL_CALLBACK(ping)
{
cJSON *json, *json_body, *json_data;
const char *ptr_data;
ptr_data = NULL;
json_body = cJSON_Parse(request->body);
if (json_body)
{
json_data = cJSON_GetObjectItem (json_body, "data");
if (json_data && cJSON_IsString (json_data))
ptr_data = cJSON_GetStringValue (json_data);
}
if (ptr_data)
{
json = cJSON_CreateObject ();
if (!json)
{
cJSON_Delete (json_body);
return WEECHAT_RC_ERROR;
}
cJSON_AddItemToObject (json, "data",
cJSON_CreateString ((ptr_data) ? ptr_data : ""));
relay_api_msg_send_json (client, RELAY_HTTP_200_OK, json);
cJSON_Delete (json);
cJSON_Delete (json_body);
}
else
{
relay_api_msg_send_json (client, RELAY_HTTP_204_NO_CONTENT, NULL);
}
return WEECHAT_RC_OK;
}
/*
* Callback for resource "sync".
*
* Routes:
* POST /api/sync
*/
RELAY_API_PROTOCOL_CALLBACK(sync)
{
cJSON *json_body, *json_sync, *json_nicks, *json_colors;
if (client->websocket != RELAY_CLIENT_WEBSOCKET_READY)
{
relay_api_msg_send_error_json (
client,
RELAY_HTTP_403_FORBIDDEN,
NULL,
"Sync resource is available only with a websocket connection");
return WEECHAT_RC_OK;
}
RELAY_API_DATA(client, sync_enabled) = 1;
RELAY_API_DATA(client, sync_nicks) = 1;
RELAY_API_DATA(client, sync_colors) = RELAY_API_COLORS_ANSI;
json_body = cJSON_Parse(request->body);
if (json_body)
{
json_sync = cJSON_GetObjectItem (json_body, "sync");
if (json_sync && cJSON_IsBool (json_sync))
RELAY_API_DATA(client, sync_enabled) = (cJSON_IsTrue (json_sync)) ? 1 : 0;
json_nicks = cJSON_GetObjectItem (json_body, "nicks");
if (json_nicks && cJSON_IsBool (json_nicks))
RELAY_API_DATA(client, sync_nicks) = (cJSON_IsTrue (json_nicks)) ? 1 : 0;
json_colors = cJSON_GetObjectItem (json_body, "colors");
if (json_colors && cJSON_IsString (json_colors))
RELAY_API_DATA(client, sync_colors) = relay_api_search_colors (
cJSON_GetStringValue (json_colors));
}
if (RELAY_API_DATA(client, sync_enabled))
relay_api_hook_signals (client);
else
relay_api_unhook_signals (client);
relay_api_msg_send_json (client, RELAY_HTTP_204_NO_CONTENT, NULL);
return WEECHAT_RC_OK;
}
/*
* Reads a HTTP request from a client.
*/
void
relay_api_protocol_recv_http (struct t_relay_client *client,
struct t_relay_http_request *request)
{
int i, return_code, num_args;
struct t_relay_api_protocol_cb protocol_cb[] =
{ { "GET", "version", 0, 0, &relay_api_protocol_cb_version },
{ "GET", "buffers", 0, 3, &relay_api_protocol_cb_buffers },
{ "POST", "input", 0, 0, &relay_api_protocol_cb_input },
{ "POST", "ping", 0, 0, &relay_api_protocol_cb_ping },
{ "POST", "sync", 0, 0, &relay_api_protocol_cb_sync },
{ NULL, NULL, 0, 0, NULL },
};
if (!request || RELAY_CLIENT_HAS_ENDED(client))
return;
if (!relay_api_protocol_check_auth (client, request))
{
relay_client_set_status (client, RELAY_STATUS_AUTH_FAILED);
return;
}
/* display debug message */
if (weechat_relay_plugin->debug >= 2)
{
weechat_printf (NULL,
"%s: recv from client %s%s%s: \"%s %s\", body: \"%s\"",
RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
request->method,
request->path,
request->body);
}
if ((request->num_path_items < 2) || !request->path_items
|| !request->path_items[0] || !request->path_items[1])
{
goto error404;
}
if (strcmp (request->path_items[0], "api") != 0)
goto error404;
num_args = request->num_path_items - 2;
for (i = 0; protocol_cb[i].resource; i++)
{
if ((strcmp (protocol_cb[i].method, request->method) == 0)
&& (strcmp (protocol_cb[i].resource, request->path_items[1]) == 0))
{
if (num_args < protocol_cb[i].min_args)
{
if (weechat_relay_plugin->debug >= 1)
{
weechat_printf (
NULL,
_("%s%s: too few arguments received from client "
"%s%s%s for resource \"%s\" "
"(received: %d arguments, expected: at least %d)"),
weechat_prefix ("error"),
RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
request->path_items[1],
num_args,
protocol_cb[i].min_args);
}
goto error404;
}
if (num_args > protocol_cb[i].max_args)
{
if (weechat_relay_plugin->debug >= 1)
{
weechat_printf (
NULL,
_("%s%s: too many arguments received from client "
"%s%s%s for resource \"%s\" "
"(received: %d arguments, expected: at most %d)"),
weechat_prefix ("error"),
RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
request->path_items[1],
num_args,
protocol_cb[i].max_args);
}
goto error404;
}
return_code = (int) (protocol_cb[i].cmd_function) (
client, request);
if (return_code == WEECHAT_RC_OK)
return;
else
goto error400;
}
}
goto error404;
error400:
relay_api_msg_send_json (client, RELAY_HTTP_400_BAD_REQUEST, NULL);
goto error;
error404:
relay_api_msg_send_json (client, RELAY_HTTP_404_NOT_FOUND, NULL);
goto error;
error:
if (weechat_relay_plugin->debug >= 1)
{
weechat_printf (NULL,
_("%s%s: failed to execute route \"%s %s\" "
"for client %s%s%s"),
weechat_prefix ("error"),
RELAY_PLUGIN_NAME,
request->method,
request->path,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT);
}
}
/*
* Reads JSON string from a client: when connected via websocket (persistent
* connection), the client is sending JSON data as a request, which is
* converted to HTTP request by this function, before calling the function
* relay_api_protocol_recv_http.
*
* Example of JSON received:
*
* {
* "request": "POST /api/input",
* "body": {
* "buffer": "irc.libera.#weechat",
* "command": "hello!"
* }
* }
*
* It is converted to an HTTP request which could have been:
*
* POST /api/input HTTP/1.1
* Content-Length: 53
* Content-Type: application/json
*
* {"buffer": "irc.libera.#weechat","command": "hello!"}
*/
void
relay_api_protocol_recv_json (struct t_relay_client *client, const char *json)
{
cJSON *json_obj, *json_request, *json_body;
char *string_body;
int length;
struct t_relay_http_request *request;
request = NULL;
json_obj = cJSON_Parse(json);
if (!json_obj)
goto error;
json_request = cJSON_GetObjectItem (json_obj, "request");
if (!json_request)
goto error;
request = relay_http_request_alloc ();
if (!request)
goto error;
if (!relay_http_parse_method_path (request,
cJSON_GetStringValue (json_request)))
{
goto error;
}
json_body = cJSON_GetObjectItem (json_obj, "body");
if (json_body)
{
string_body = cJSON_PrintUnformatted (json_body);
if (string_body)
{
length = strlen (string_body);
request->body = malloc (length + 1);
if (request->body)
{
memcpy (request->body, string_body, length + 1);
request->content_length = length;
request->body_size = length;
}
free (string_body);
}
}
relay_api_protocol_recv_http (client, request);
goto end;
error:
relay_api_msg_send_json (client, RELAY_HTTP_400_BAD_REQUEST, NULL);
end:
if (json_obj)
cJSON_Delete (json_obj);
if (request)
relay_http_request_free (request);
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef WEECHAT_PLUGIN_RELAY_API_PROTOCOL_H
#define WEECHAT_PLUGIN_RELAY_API_PROTOCOL_H
#define RELAY_API_PROTOCOL_CALLBACK(__command) \
int \
relay_api_protocol_cb_##__command ( \
struct t_relay_client *client, \
struct t_relay_http_request *request)
struct t_relay_api_protocol_ctxt
{
struct t_relay_client *client;
struct t_relay_http_request *request;
char *resource;
};
typedef int (t_relay_api_cmd_func)(struct t_relay_client *client,
struct t_relay_http_request *request);
struct t_relay_api_protocol_cb
{
char *method; /* method (eg: "GET", "POST", etc.) */
char *resource; /* resource (eg: "buffers") */
int min_args; /* min number of items in path */
int max_args; /* max number of items in path */
t_relay_api_cmd_func *cmd_function; /* callback */
};
extern int relay_api_protocol_signal_buffer_cb (const void *pointer,
void *data,
const char *signal,
const char *type_data,
void *signal_data);
extern int relay_api_protocol_hsignal_nicklist_cb (const void *pointer,
void *data,
const char *signal,
struct t_hashtable *hashtable);
extern int relay_api_protocol_signal_upgrade_cb (const void *pointer,
void *data,
const char *signal,
const char *type_data,
void *signal_data);
extern void relay_api_protocol_recv_http (struct t_relay_client *client,
struct t_relay_http_request *request);
extern void relay_api_protocol_recv_json (struct t_relay_client *client,
const char *json);
#endif /* WEECHAT_PLUGIN_RELAY_API_PROTOCOL_H */
+311
View File
@@ -0,0 +1,311 @@
/*
* relay-api.c - API protocol for relay to client
*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <errno.h>
#include <arpa/inet.h>
#include "../../weechat-plugin.h"
#include "../relay.h"
#include "../relay-client.h"
#include "../relay-config.h"
#include "../relay-http.h"
#include "../relay-raw.h"
#include "../relay-raw.h"
#include "relay-api.h"
#include "relay-api-protocol.h"
/*
* Returns value of "colors" URL parameter, an enum with one of these values:
* - RELAY_API_COLORS_ANSI (default)
* - RELAY_API_COLORS_WEECHAT
* - RELAY_API_COLORS_STRIP
*/
enum t_relay_api_colors
relay_api_search_colors (const char *colors)
{
if (!colors)
return RELAY_API_COLORS_ANSI;
if (strcmp (colors, "weechat") == 0)
return RELAY_API_COLORS_WEECHAT;
if (strcmp (colors, "strip") == 0)
return RELAY_API_COLORS_STRIP;
return RELAY_API_COLORS_ANSI;
}
/*
* Hooks signals for a client.
*/
void
relay_api_hook_signals (struct t_relay_client *client)
{
if (!RELAY_API_DATA(client, hook_signal_buffer))
{
RELAY_API_DATA(client, hook_signal_buffer) =
weechat_hook_signal ("buffer_*",
&relay_api_protocol_signal_buffer_cb,
client, NULL);
}
if (RELAY_API_DATA(client, sync_nicks))
{
if (!RELAY_API_DATA(client, hook_hsignal_nicklist))
{
RELAY_API_DATA(client, hook_hsignal_nicklist) =
weechat_hook_hsignal ("nicklist_*",
&relay_api_protocol_hsignal_nicklist_cb,
client, NULL);
}
}
else
{
if (RELAY_API_DATA(client, hook_hsignal_nicklist))
{
weechat_unhook (RELAY_API_DATA(client, hook_hsignal_nicklist));
RELAY_API_DATA(client, hook_hsignal_nicklist) = NULL;
}
}
if (!RELAY_API_DATA(client, hook_signal_upgrade))
{
RELAY_API_DATA(client, hook_signal_upgrade) =
weechat_hook_signal ("upgrade*",
&relay_api_protocol_signal_upgrade_cb,
client, NULL);
}
}
/*
* Unhooks signals for a client.
*/
void
relay_api_unhook_signals (struct t_relay_client *client)
{
if (RELAY_API_DATA(client, hook_signal_buffer))
{
weechat_unhook (RELAY_API_DATA(client, hook_signal_buffer));
RELAY_API_DATA(client, hook_signal_buffer) = NULL;
}
if (RELAY_API_DATA(client, hook_hsignal_nicklist))
{
weechat_unhook (RELAY_API_DATA(client, hook_hsignal_nicklist));
RELAY_API_DATA(client, hook_hsignal_nicklist) = NULL;
}
if (RELAY_API_DATA(client, hook_signal_upgrade))
{
weechat_unhook (RELAY_API_DATA(client, hook_signal_upgrade));
RELAY_API_DATA(client, hook_signal_upgrade) = NULL;
}
}
/*
* Reads HTTP request from a client.
*/
void
relay_api_recv_http (struct t_relay_client *client,
struct t_relay_http_request *request)
{
relay_api_protocol_recv_http (client, request);
}
/*
* Reads JSON string from a client.
*/
void
relay_api_recv_json (struct t_relay_client *client, const char *json)
{
relay_api_protocol_recv_json (client, json);
}
/*
* Closes connection with a client.
*/
void
relay_api_close_connection (struct t_relay_client *client)
{
/*
* IMPORTANT: if changes are made in this function or sub-functions called,
* please also update the function relay_api_add_to_infolist:
* when the flag force_disconnected_state is set to 1 we simulate
* a disconnected state for client in infolist (used on /upgrade -save)
*/
relay_api_unhook_signals (client);
}
/*
* Initializes relay data specific to API protocol.
*/
void
relay_api_alloc (struct t_relay_client *client)
{
client->protocol_data = malloc (sizeof (struct t_relay_api_data));
if (!client->protocol_data)
return;
RELAY_API_DATA(client, hook_signal_buffer) = NULL;
RELAY_API_DATA(client, hook_hsignal_nicklist) = NULL;
RELAY_API_DATA(client, hook_signal_upgrade) = NULL;
RELAY_API_DATA(client, sync_enabled) = 0;
RELAY_API_DATA(client, sync_nicks) = 0;
RELAY_API_DATA(client, sync_colors) = RELAY_API_COLORS_ANSI;
}
/*
* Initializes relay data specific to API protocol with an infolist.
*
* This is called after /upgrade.
*/
void
relay_api_alloc_with_infolist (struct t_relay_client *client,
struct t_infolist *infolist)
{
client->protocol_data = malloc (sizeof (struct t_relay_api_data));
if (!client->protocol_data)
return;
RELAY_API_DATA(client, hook_signal_buffer) = NULL;
RELAY_API_DATA(client, hook_hsignal_nicklist) = NULL;
RELAY_API_DATA(client, hook_signal_upgrade) = NULL;
RELAY_API_DATA(client, sync_enabled) = weechat_infolist_integer (
infolist, "sync_enabled");
RELAY_API_DATA(client, sync_nicks) = weechat_infolist_integer (
infolist, "sync_nicks");
RELAY_API_DATA(client, sync_colors) = weechat_infolist_integer (
infolist, "sync_colors");
if (!RELAY_CLIENT_HAS_ENDED(client) && RELAY_API_DATA(client, sync_enabled))
relay_api_hook_signals (client);
}
/*
* Returns the client initial status: it is always "waiting_auth" for API
* protocol because we always expect the "init" command, even without any
* password.
*/
enum t_relay_status
relay_api_get_initial_status (struct t_relay_client *client)
{
/* make C compiler happy */
(void) client;
return RELAY_STATUS_WAITING_AUTH;
}
/*
* Frees relay data specific to API protocol.
*/
void
relay_api_free (struct t_relay_client *client)
{
if (!client)
return;
if (client->protocol_data)
{
if (RELAY_API_DATA(client, hook_signal_buffer))
weechat_unhook (RELAY_API_DATA(client, hook_signal_buffer));
if (RELAY_API_DATA(client, hook_hsignal_nicklist))
weechat_unhook (RELAY_API_DATA(client, hook_hsignal_nicklist));
if (RELAY_API_DATA(client, hook_signal_upgrade))
weechat_unhook (RELAY_API_DATA(client, hook_signal_upgrade));
free (client->protocol_data);
client->protocol_data = NULL;
}
}
/*
* Adds client API data in an infolist.
*
* If force_disconnected_state == 1, the infolist contains the client
* in a disconnected state (but the client is unchanged, still connected if it
* was).
*
* Returns:
* 1: OK
* 0: error
*/
int
relay_api_add_to_infolist (struct t_infolist_item *item,
struct t_relay_client *client,
int force_disconnected_state)
{
if (!item || !client)
return 0;
/* parameter not used today, it may be in future */
(void) force_disconnected_state;
if (!weechat_infolist_new_var_pointer (item, "hook_signal_buffer", RELAY_API_DATA(client, hook_signal_buffer)))
return 0;
if (!weechat_infolist_new_var_pointer (item, "hook_hsignal_nicklist", RELAY_API_DATA(client, hook_hsignal_nicklist)))
return 0;
if (!weechat_infolist_new_var_pointer (item, "hook_signal_upgrade", RELAY_API_DATA(client, hook_signal_upgrade)))
return 0;
if (!weechat_infolist_new_var_integer (item, "sync_enabled", RELAY_API_DATA(client, sync_enabled)))
return 0;
if (!weechat_infolist_new_var_integer (item, "sync_nicks", RELAY_API_DATA(client, sync_nicks)))
return 0;
if (!weechat_infolist_new_var_integer (item, "sync_colors", RELAY_API_DATA(client, sync_colors)))
return 0;
return 1;
}
/*
* Prints client API data in WeeChat log file (usually for crash dump).
*/
void
relay_api_print_log (struct t_relay_client *client)
{
if (client->protocol_data)
{
weechat_log_printf (" hook_signal_buffer. . . : 0x%lx", RELAY_API_DATA(client, hook_signal_buffer));
weechat_log_printf (" hook_hsignal_nicklist . : 0x%lx", RELAY_API_DATA(client, hook_hsignal_nicklist));
weechat_log_printf (" hook_signal_upgrade . . : 0x%lx", RELAY_API_DATA(client, hook_signal_upgrade));
weechat_log_printf (" sync_enabled. . . . . . : %d", RELAY_API_DATA(client, sync_enabled));
weechat_log_printf (" sync_nicks. . . . . . . : %d", RELAY_API_DATA(client, sync_nicks));
weechat_log_printf (" sync_colors . . . . . . : %d", RELAY_API_DATA(client, sync_colors));
}
}
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef WEECHAT_PLUGIN_RELAY_API_H
#define WEECHAT_PLUGIN_RELAY_API_H
struct t_relay_client;
struct t_relay_http_request;
enum t_relay_status;
#define RELAY_API_VERSION_MAJOR 0
#define RELAY_API_VERSION_MINOR 0
#define RELAY_API_VERSION_PATCH 1
#define RELAY_API_VERSION_NUMBER \
((RELAY_API_VERSION_MAJOR << 16) \
+ (RELAY_API_VERSION_MINOR << 8) \
+ RELAY_API_VERSION_PATCH)
#define RELAY_API_VERSION_STR \
(TO_STR(RELAY_API_VERSION_MAJOR) \
"." \
TO_STR(RELAY_API_VERSION_MINOR) \
"." \
TO_STR(RELAY_API_VERSION_PATCH))
#define RELAY_API_DATA(client, var) \
(((struct t_relay_api_data *)client->protocol_data)->var)
#define RELAY_API_HTTP_0_EVENT 0, "Event"
enum t_relay_api_colors
{
RELAY_API_COLORS_ANSI = 0, /* convert colors to ANSI colors */
RELAY_API_COLORS_WEECHAT, /* keep WeeChat internal color codes */
RELAY_API_COLORS_STRIP, /* strip colors */
/* number of data types */
RELAY_API_NUM_COLORS,
};
struct t_relay_api_data
{
struct t_hook *hook_signal_buffer; /* hook for signals "buffer_*" */
struct t_hook *hook_hsignal_nicklist; /* hook for hsignals "nicklist_*" */
struct t_hook *hook_signal_upgrade; /* hook for signals "upgrade*" */
int sync_enabled; /* 1 if sync is enabled */
int sync_nicks; /* 1 if nicks are synchronized */
enum t_relay_api_colors sync_colors; /* colors to send with sync */
};
extern enum t_relay_api_colors relay_api_search_colors (const char *colors);
extern void relay_api_hook_signals (struct t_relay_client *client);
extern void relay_api_unhook_signals (struct t_relay_client *client);
extern void relay_api_recv_http (struct t_relay_client *client,
struct t_relay_http_request *request);
extern void relay_api_recv_json (struct t_relay_client *client,
const char *json);
extern void relay_api_close_connection (struct t_relay_client *client);
extern void relay_api_alloc (struct t_relay_client *client);
extern void relay_api_alloc_with_infolist (struct t_relay_client *client,
struct t_infolist *infolist);
extern enum t_relay_status relay_api_get_initial_status (struct t_relay_client *client);
extern void relay_api_free (struct t_relay_client *client);
extern int relay_api_add_to_infolist (struct t_infolist_item *item,
struct t_relay_client *client,
int force_disconnected_state);
extern void relay_api_print_log (struct t_relay_client *client);
#endif /* WEECHAT_PLUGIN_RELAY_API_H */
+281 -260
View File
@@ -37,10 +37,14 @@
#include "relay-auth.h"
#include "relay-config.h"
#include "relay-buffer.h"
#include "relay-http.h"
#include "relay-network.h"
#include "relay-raw.h"
#include "relay-server.h"
#include "relay-websocket.h"
#ifdef HAVE_CJSON
#include "api/relay-api.h"
#endif
#include "irc/relay-irc.h"
#include "weechat/relay-weechat.h"
@@ -55,7 +59,7 @@ char *relay_client_status_name[] = /* name of status (for signal/info) */
};
char *relay_client_data_type_string[] = /* strings for data types */
{ "text", "binary" };
{ "text", "binary", "http" };
char *relay_client_msg_type_string[] = /* prefix in raw buffer for message */
{ "", "[PING]\n", "[PONG]\n", "[CLOSE]\n" };
@@ -266,6 +270,13 @@ relay_client_handshake_timer_cb (const void *pointer, void *data,
client,
relay_irc_get_initial_status (client));
break;
case RELAY_PROTOCOL_API:
#ifdef HAVE_CJSON
relay_client_set_status (
client,
relay_api_get_initial_status (client));
#endif /* HAVE_CJSON */
break;
case RELAY_NUM_PROTOCOLS:
break;
}
@@ -312,6 +323,124 @@ relay_client_handshake_timer_cb (const void *pointer, void *data,
return WEECHAT_RC_OK;
}
/*
* Reads text data from a client (messages on a single line): splits data on
* '\n' and keeps a partial message if data does not end with '\n'.
*/
void
relay_client_recv_text_single_line (struct t_relay_client *client)
{
char *pos, *raw_msg, **lines, *tmp;
int i, num_lines, length;
if (!client->partial_message)
return;
pos = strrchr (client->partial_message, '\n');
if (!pos)
return;
/* print message in raw buffer */
raw_msg = weechat_strndup (client->partial_message,
pos - client->partial_message + 1);
if (raw_msg)
{
relay_raw_print (client, RELAY_CLIENT_MSG_STANDARD,
RELAY_RAW_FLAG_RECV,
raw_msg, strlen (raw_msg) + 1);
free (raw_msg);
}
pos[0] = '\0';
lines = weechat_string_split (client->partial_message, "\n", NULL,
WEECHAT_STRING_SPLIT_STRIP_LEFT
| WEECHAT_STRING_SPLIT_STRIP_RIGHT
| WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
0, &num_lines);
if (lines)
{
for (i = 0; i < num_lines; i++)
{
/* remove final '\r' */
length = strlen (lines[i]);
if ((length > 0) && (lines[i][length - 1] == '\r'))
lines[i][length - 1] = '\0';
/*
* interpret text from client, according to the relay
* protocol used
*/
switch (client->protocol)
{
case RELAY_PROTOCOL_WEECHAT:
relay_weechat_recv (client, lines[i]);
break;
case RELAY_PROTOCOL_IRC:
relay_irc_recv (client, lines[i]);
break;
case RELAY_PROTOCOL_API:
/* api is multi-line only (JSON) */
break;
case RELAY_NUM_PROTOCOLS:
break;
}
}
weechat_string_free_split (lines);
}
if (pos[1])
{
tmp = strdup (pos + 1);
free (client->partial_message);
client->partial_message = tmp;
}
else
{
free (client->partial_message);
client->partial_message = NULL;
}
}
/*
* Reads text data from a client (multi-line messages).
*/
void
relay_client_recv_text_multi_line (struct t_relay_client *client)
{
if (!client->partial_message)
return;
/* print message in raw buffer */
relay_raw_print (client, RELAY_CLIENT_MSG_STANDARD,
RELAY_RAW_FLAG_RECV,
client->partial_message,
strlen (client->partial_message) + 1);
/*
* interpret text from client, according to the relay
* protocol used
*/
switch (client->protocol)
{
case RELAY_PROTOCOL_WEECHAT:
/* weechat is single line only */
break;
case RELAY_PROTOCOL_IRC:
/* irc is single line only */
break;
case RELAY_PROTOCOL_API:
relay_api_recv_json (client, client->partial_message);
break;
case RELAY_NUM_PROTOCOLS:
break;
}
free (client->partial_message);
client->partial_message = NULL;
}
/*
* Reads text data from a client: splits data on '\n' and keeps a partial
* message if data does not end with '\n'.
@@ -320,9 +449,7 @@ relay_client_handshake_timer_cb (const void *pointer, void *data,
void
relay_client_recv_text (struct t_relay_client *client, const char *data)
{
char *new_partial, *raw_msg, **lines, *pos, *tmp, *handshake;
const char *ptr_real_ip;
int i, num_lines, length, rc;
char *new_partial;
if (client->partial_message)
{
@@ -337,172 +464,12 @@ relay_client_recv_text (struct t_relay_client *client, const char *data)
else
client->partial_message = strdup (data);
pos = strrchr (client->partial_message, '\n');
if (pos)
if (client->partial_message)
{
/* print message in raw buffer */
raw_msg = weechat_strndup (client->partial_message,
pos - client->partial_message + 1);
if (raw_msg)
{
relay_raw_print (client, RELAY_CLIENT_MSG_STANDARD,
RELAY_RAW_FLAG_RECV,
raw_msg, strlen (raw_msg) + 1);
free (raw_msg);
}
pos[0] = '\0';
lines = weechat_string_split (client->partial_message, "\n", NULL,
WEECHAT_STRING_SPLIT_STRIP_LEFT
| WEECHAT_STRING_SPLIT_STRIP_RIGHT
| WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
0, &num_lines);
if (lines)
{
for (i = 0; i < num_lines; i++)
{
/* remove final '\r' */
length = strlen (lines[i]);
if ((length > 0) && (lines[i][length - 1] == '\r'))
lines[i][length - 1] = '\0';
/* if websocket is initializing */
if (client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING)
{
if (lines[i][0])
{
/* web socket is initializing, read HTTP headers */
relay_websocket_save_header (client, lines[i]);
}
else
{
/*
* empty line means that we have received all HTTP
* headers: then we check the validity of websocket, and
* if it is OK, we'll do the handshake and answer to the
* client
*/
rc = relay_websocket_client_handshake_valid (client);
if (rc == 0)
{
/* handshake from client is valid */
handshake = relay_websocket_build_handshake (client);
if (handshake)
{
relay_client_send (client,
RELAY_CLIENT_MSG_STANDARD,
handshake,
strlen (handshake), NULL);
free (handshake);
client->websocket = RELAY_CLIENT_WEBSOCKET_READY;
}
}
else
{
switch (rc)
{
case -1:
relay_websocket_send_http (client,
"400 Bad Request");
if (weechat_relay_plugin->debug >= 1)
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s%s: invalid websocket "
"handshake received for client "
"%s%s%s"),
weechat_prefix ("error"),
RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT);
}
break;
case -2:
relay_websocket_send_http (client,
"403 Forbidden");
if (weechat_relay_plugin->debug >= 1)
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s%s: origin \"%s\" "
"not allowed for websocket"),
weechat_prefix ("error"),
RELAY_PLUGIN_NAME,
weechat_hashtable_get (client->http_headers,
"origin"));
}
break;
}
relay_client_set_status (client, RELAY_STATUS_DISCONNECTED);
}
ptr_real_ip = weechat_hashtable_get (
client->http_headers, "x-real-ip");
if (ptr_real_ip)
{
if (client->real_ip)
free (client->real_ip);
client->real_ip = strdup (ptr_real_ip);
relay_client_set_desc (client);
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s: websocket client %s%s%s has real IP "
"address \"%s\""),
RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
ptr_real_ip);
}
/* remove HTTP headers */
weechat_hashtable_free (client->http_headers);
client->http_headers = NULL;
/*
* discard all received data after the handshake
* received from client, and return immediately
*/
free (client->partial_message);
client->partial_message = NULL;
weechat_string_free_split (lines);
return;
}
}
else
{
/*
* interpret text from client, according to the relay
* protocol used
*/
switch (client->protocol)
{
case RELAY_PROTOCOL_WEECHAT:
relay_weechat_recv (client, lines[i]);
break;
case RELAY_PROTOCOL_IRC:
relay_irc_recv (client, lines[i]);
break;
case RELAY_NUM_PROTOCOLS:
break;
}
}
}
weechat_string_free_split (lines);
}
if (pos[1])
{
tmp = strdup (pos + 1);
free (client->partial_message);
client->partial_message = tmp;
}
else
{
free (client->partial_message);
client->partial_message = NULL;
}
if (client->recv_data_type == RELAY_CLIENT_DATA_TEXT_LINE)
relay_client_recv_text_single_line (client);
else if (client->recv_data_type == RELAY_CLIENT_DATA_TEXT_MULTILINE)
relay_client_recv_text_multi_line (client);
}
}
@@ -566,7 +533,17 @@ relay_client_recv_text_buffer (struct t_relay_client *client,
}
if (msg_type == RELAY_CLIENT_MSG_STANDARD)
relay_client_recv_text (client, buffer + index);
{
if ((client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING)
|| (client->recv_data_type == RELAY_CLIENT_DATA_HTTP))
{
relay_http_recv (client, buffer + index);
}
else
{
relay_client_recv_text (client, buffer + index);
}
}
index += strlen (buffer + index) + 1;
}
@@ -622,7 +599,7 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
*/
if (client->bytes_recv == 0)
{
if (relay_websocket_is_http_get_weechat (buffer))
if (relay_websocket_is_valid_http_get (client, buffer))
{
/*
* web socket is just initializing for now, it's not accepted
@@ -630,11 +607,6 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
* valid or not)
*/
client->websocket = RELAY_CLIENT_WEBSOCKET_INITIALIZING;
client->http_headers = weechat_hashtable_new (
32,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
}
}
@@ -679,9 +651,11 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
}
if ((client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING)
|| (client->recv_data_type == RELAY_CLIENT_DATA_TEXT))
|| (client->recv_data_type == RELAY_CLIENT_DATA_TEXT_LINE)
|| (client->recv_data_type == RELAY_CLIENT_DATA_TEXT_MULTILINE)
|| (client->recv_data_type == RELAY_CLIENT_DATA_HTTP))
{
/* websocket initializing or text data for this client */
/* websocket initializing or text/HTTP data for this client */
relay_client_recv_text_buffer (client, ptr_buffer, length_buffer);
}
else
@@ -698,17 +672,20 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
if ((num_read == 0)
|| ((num_read != GNUTLS_E_AGAIN) && (num_read != GNUTLS_E_INTERRUPTED)))
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s%s: reading data on socket for client %s%s%s: "
"error %d %s"),
weechat_prefix ("error"), RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
num_read,
(num_read == 0) ? _("(connection closed by peer)") :
gnutls_strerror (num_read));
if (relay_config_display_clients[client->protocol])
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s%s: reading data on socket for client %s%s%s: "
"error %d %s"),
weechat_prefix ("error"), RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
num_read,
(num_read == 0) ? _("(connection closed by peer)") :
gnutls_strerror (num_read));
}
relay_client_set_status (client, RELAY_STATUS_DISCONNECTED);
}
}
@@ -717,17 +694,20 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
if ((num_read == 0)
|| ((errno != EAGAIN) && (errno != EWOULDBLOCK)))
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s%s: reading data on socket for client %s%s%s: "
"error %d %s"),
weechat_prefix ("error"), RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
errno,
(num_read == 0) ? _("(connection closed by peer)") :
strerror (errno));
if (relay_config_display_clients[client->protocol])
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s%s: reading data on socket for client %s%s%s: "
"error %d %s"),
weechat_prefix ("error"), RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT,
errno,
(num_read == 0) ? _("(connection closed by peer)") :
strerror (errno));
}
relay_client_set_status (client, RELAY_STATUS_DISCONNECTED);
}
}
@@ -1067,7 +1047,8 @@ relay_client_send (struct t_relay_client *client,
raw_size[1] = data_size;
raw_flags[1] |= RELAY_RAW_FLAG_BINARY;
if ((client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING)
|| (client->send_data_type == RELAY_CLIENT_DATA_TEXT))
|| (client->send_data_type == RELAY_CLIENT_DATA_TEXT_LINE)
|| (client->send_data_type == RELAY_CLIENT_DATA_TEXT_MULTILINE))
{
raw_size[1]--;
}
@@ -1117,7 +1098,8 @@ relay_client_send (struct t_relay_client *client,
opcode = WEBSOCKET_FRAME_OPCODE_CLOSE;
break;
default:
opcode = (client->send_data_type == RELAY_CLIENT_DATA_TEXT) ?
opcode = ((client->send_data_type == RELAY_CLIENT_DATA_TEXT_LINE)
|| (client->send_data_type == RELAY_CLIENT_DATA_TEXT_MULTILINE)) ?
WEBSOCKET_FRAME_OPCODE_TEXT : WEBSOCKET_FRAME_OPCODE_BINARY;
break;
}
@@ -1325,7 +1307,7 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
new_client->hook_timer_handshake = NULL;
new_client->gnutls_handshake_ok = 0;
new_client->websocket = RELAY_CLIENT_WEBSOCKET_NOT_USED;
new_client->http_headers = NULL;
new_client->http_req = relay_http_request_alloc ();
new_client->address = strdup ((address && address[0]) ?
address : "local");
new_client->real_ip = NULL;
@@ -1353,16 +1335,20 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
switch (new_client->protocol)
{
case RELAY_PROTOCOL_WEECHAT:
new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT;
new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT_LINE;
new_client->send_data_type = RELAY_CLIENT_DATA_BINARY;
break;
case RELAY_PROTOCOL_IRC:
new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT;
new_client->send_data_type = RELAY_CLIENT_DATA_TEXT;
new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT_LINE;
new_client->send_data_type = RELAY_CLIENT_DATA_TEXT_LINE;
break;
case RELAY_PROTOCOL_API:
new_client->recv_data_type = RELAY_CLIENT_DATA_HTTP;
new_client->send_data_type = RELAY_CLIENT_DATA_HTTP;
break;
default:
new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT;
new_client->send_data_type = RELAY_CLIENT_DATA_TEXT;
new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT_MULTILINE;
new_client->send_data_type = RELAY_CLIENT_DATA_TEXT_MULTILINE;
break;
}
new_client->partial_message = NULL;
@@ -1439,6 +1425,16 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
relay_irc_get_initial_status (new_client);
}
break;
case RELAY_PROTOCOL_API:
#ifdef HAVE_CJSON
relay_api_alloc (new_client);
if (!new_client->tls)
{
new_client->status =
relay_api_get_initial_status (new_client);
}
#endif /* HAVE_CJSON */
break;
case RELAY_NUM_PROTOCOLS:
break;
}
@@ -1454,29 +1450,32 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
last_relay_client = new_client;
relay_clients = new_client;
if (server->unix_socket)
if (relay_config_display_clients[new_client->protocol])
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s: new client on path %s: %s%s%s (%s)"),
RELAY_PLUGIN_NAME,
server->path,
RELAY_COLOR_CHAT_CLIENT,
new_client->desc,
RELAY_COLOR_CHAT,
_(relay_client_status_string[new_client->status]));
}
else
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s: new client on port %s: %s%s%s (%s)"),
RELAY_PLUGIN_NAME,
server->path,
RELAY_COLOR_CHAT_CLIENT,
new_client->desc,
RELAY_COLOR_CHAT,
_(relay_client_status_string[new_client->status]));
if (server->unix_socket)
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s: new client on path %s: %s%s%s (%s)"),
RELAY_PLUGIN_NAME,
server->path,
RELAY_COLOR_CHAT_CLIENT,
new_client->desc,
RELAY_COLOR_CHAT,
_(relay_client_status_string[new_client->status]));
}
else
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s: new client on port %s: %s%s%s (%s)"),
RELAY_PLUGIN_NAME,
server->path,
RELAY_COLOR_CHAT_CLIENT,
new_client->desc,
RELAY_COLOR_CHAT,
_(relay_client_status_string[new_client->status]));
}
}
if (new_client->sock >= 0)
@@ -1489,11 +1488,8 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
relay_client_count++;
if (!relay_buffer
&& weechat_config_boolean (relay_config_look_auto_open_buffer))
{
if (!relay_buffer && relay_config_auto_open_buffer[new_client->protocol])
relay_buffer_open ();
}
relay_client_send_signal (new_client);
@@ -1537,7 +1533,7 @@ relay_client_new_with_infolist (struct t_infolist *infolist)
new_client->hook_timer_handshake = NULL;
new_client->gnutls_handshake_ok = 0;
new_client->websocket = weechat_infolist_integer (infolist, "websocket");
new_client->http_headers = NULL;
new_client->http_req = relay_http_request_alloc ();
new_client->address = strdup (weechat_infolist_string (infolist, "address"));
str = weechat_infolist_string (infolist, "real_ip");
new_client->real_ip = (str) ? strdup (str) : NULL;
@@ -1597,12 +1593,15 @@ relay_client_new_with_infolist (struct t_infolist *infolist)
switch (new_client->protocol)
{
case RELAY_PROTOCOL_WEECHAT:
relay_weechat_alloc_with_infolist (new_client,
infolist);
relay_weechat_alloc_with_infolist (new_client, infolist);
break;
case RELAY_PROTOCOL_IRC:
relay_irc_alloc_with_infolist (new_client,
infolist);
relay_irc_alloc_with_infolist (new_client, infolist);
break;
case RELAY_PROTOCOL_API:
#ifdef HAVE_CJSON
relay_api_alloc_with_infolist (new_client, infolist);
#endif /* HAVE_CJSON */
break;
case RELAY_NUM_PROTOCOLS:
break;
@@ -1644,7 +1643,8 @@ relay_client_set_status (struct t_relay_client *client,
client->status = status;
if (client->status == RELAY_STATUS_CONNECTED)
if ((client->status == RELAY_STATUS_CONNECTED)
&& relay_config_display_clients[client->protocol])
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
@@ -1688,6 +1688,11 @@ relay_client_set_status (struct t_relay_client *client,
case RELAY_PROTOCOL_IRC:
relay_irc_close_connection (client);
break;
case RELAY_PROTOCOL_API:
#ifdef HAVE_CJSON
relay_api_close_connection (client);
#endif /* HAVE_CJSON */
break;
case RELAY_NUM_PROTOCOLS:
break;
}
@@ -1704,13 +1709,16 @@ relay_client_set_status (struct t_relay_client *client,
RELAY_COLOR_CHAT);
break;
case RELAY_STATUS_DISCONNECTED:
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s: disconnected from client %s%s%s"),
RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT);
if (relay_config_display_clients[client->protocol])
{
weechat_printf_date_tags (
NULL, 0, "relay_client",
_("%s: disconnected from client %s%s%s"),
RELAY_PLUGIN_NAME,
RELAY_COLOR_CHAT_CLIENT,
client->desc,
RELAY_COLOR_CHAT);
}
break;
default:
break;
@@ -1772,8 +1780,7 @@ relay_client_free (struct t_relay_client *client)
free (client->nonce);
if (client->hook_timer_handshake)
weechat_unhook (client->hook_timer_handshake);
if (client->http_headers)
weechat_hashtable_free (client->http_headers);
relay_http_request_free (client->http_req);
if (client->hook_fd)
weechat_unhook (client->hook_fd);
if (client->hook_timer_send)
@@ -1790,6 +1797,11 @@ relay_client_free (struct t_relay_client *client)
case RELAY_PROTOCOL_IRC:
relay_irc_free (client);
break;
case RELAY_PROTOCOL_API:
#ifdef HAVE_CJSON
relay_api_free (client);
#endif /* HAVE_CJSON */
break;
case RELAY_NUM_PROTOCOLS:
break;
}
@@ -1968,6 +1980,12 @@ relay_client_add_to_infolist (struct t_infolist *infolist,
relay_irc_add_to_infolist (ptr_item, client,
force_disconnected_state);
break;
case RELAY_PROTOCOL_API:
#ifdef HAVE_CJSON
relay_api_add_to_infolist (ptr_item, client,
force_disconnected_state);
#endif /* HAVE_CJSON */
break;
case RELAY_NUM_PROTOCOLS:
break;
}
@@ -1989,20 +2007,18 @@ relay_client_print_log ()
{
weechat_log_printf ("");
weechat_log_printf ("[relay client (addr:0x%lx)]", ptr_client);
weechat_log_printf (" id. . . . . . . . . . . . : %d", ptr_client->id);
weechat_log_printf (" desc. . . . . . . . . . . : '%s'", ptr_client->desc);
weechat_log_printf (" sock. . . . . . . . . . . : %d", ptr_client->sock);
weechat_log_printf (" server_port . . . . . . . : %d", ptr_client->server_port);
weechat_log_printf (" tls . . . . . . . . . . . : %d", ptr_client->tls);
weechat_log_printf (" id. . . . . . . . . . . . : %d", ptr_client->id);
weechat_log_printf (" desc. . . . . . . . . . . : '%s'", ptr_client->desc);
weechat_log_printf (" sock. . . . . . . . . . . : %d", ptr_client->sock);
weechat_log_printf (" server_port . . . . . . . : %d", ptr_client->server_port);
weechat_log_printf (" tls . . . . . . . . . . . : %d", ptr_client->tls);
weechat_log_printf (" gnutls_sess . . . . . . . : 0x%lx", ptr_client->gnutls_sess);
weechat_log_printf (" hook_timer_handshake. . . : 0x%lx", ptr_client->hook_timer_handshake);
weechat_log_printf (" gnutls_handshake_ok . . . : 0x%lx", ptr_client->gnutls_handshake_ok);
weechat_log_printf (" websocket . . . . . . . . : %d", ptr_client->websocket);
weechat_log_printf (" http_headers. . . . . . . : 0x%lx (hashtable: '%s')",
ptr_client->http_headers,
weechat_hashtable_get_string (ptr_client->http_headers, "keys_values"));
weechat_log_printf (" address . . . . . . . . . : '%s'", ptr_client->address);
weechat_log_printf (" real_ip . . . . . . . . . : '%s'", ptr_client->real_ip);
weechat_log_printf (" websocket . . . . . . . . : %d", ptr_client->websocket);
relay_http_print_log (ptr_client->http_req);
weechat_log_printf (" address . . . . . . . . . : '%s'", ptr_client->address);
weechat_log_printf (" real_ip . . . . . . . . . : '%s'", ptr_client->real_ip);
weechat_log_printf (" status. . . . . . . . . . : %d (%s)",
ptr_client->status,
relay_client_status_string[ptr_client->status]);
@@ -2041,6 +2057,11 @@ relay_client_print_log ()
case RELAY_PROTOCOL_IRC:
relay_irc_print_log (ptr_client);
break;
case RELAY_PROTOCOL_API:
#ifdef HAVE_CJSON
relay_api_print_log (ptr_client);
#endif /* HAVE_CJSON */
break;
case RELAY_NUM_PROTOCOLS:
break;
}
+5 -3
View File
@@ -25,6 +25,7 @@
#include <gnutls/gnutls.h>
struct t_relay_server;
struct t_relay_http_request;
/* relay status */
@@ -43,8 +44,10 @@ enum t_relay_status
enum t_relay_client_data_type
{
RELAY_CLIENT_DATA_TEXT = 0, /* text messages */
RELAY_CLIENT_DATA_TEXT_LINE = 0, /* text messages (on one line) */
RELAY_CLIENT_DATA_BINARY, /* binary messages */
RELAY_CLIENT_DATA_HTTP, /* HTTP messages (mostly text) */
RELAY_CLIENT_DATA_TEXT_MULTILINE, /* multi-line text (eg: JSON) */
/* number of data types */
RELAY_NUM_CLIENT_DATA_TYPES,
};
@@ -53,7 +56,6 @@ enum t_relay_client_data_type
enum t_relay_client_websocket_status
{
/* 0=not a ws, 1=init ws, 2=ws ready */
RELAY_CLIENT_WEBSOCKET_NOT_USED = 0, /* no webseocket or not yet init. */
RELAY_CLIENT_WEBSOCKET_INITIALIZING, /* websocket used, initializing */
RELAY_CLIENT_WEBSOCKET_READY, /* websocket used, ready */
@@ -106,7 +108,7 @@ struct t_relay_client
struct t_hook *hook_timer_handshake; /* timer for doing gnutls handshake*/
int gnutls_handshake_ok; /* 1 if handshake was done and OK */
enum t_relay_client_websocket_status websocket; /* websocket status */
struct t_hashtable *http_headers; /* HTTP headers for websocket */
struct t_relay_http_request *http_req; /* HTTP request */
char *address; /* string with IP address */
char *real_ip; /* real IP (X-Real-IP HTTP header) */
enum t_relay_status status; /* status (connecting, active,..) */
+119 -11
View File
@@ -54,6 +54,7 @@ struct t_config_section *relay_config_section_path = NULL;
/* relay config, look section */
struct t_config_option *relay_config_look_auto_open_buffer = NULL;
struct t_config_option *relay_config_look_display_clients = NULL;
struct t_config_option *relay_config_look_raw_messages = NULL;
/* relay config, color section */
@@ -101,12 +102,103 @@ struct t_config_option *relay_config_weechat_commands = NULL;
/* other */
int relay_config_auto_open_buffer[RELAY_NUM_PROTOCOLS];
int relay_config_display_clients[RELAY_NUM_PROTOCOLS];
regex_t *relay_config_regex_allowed_ips = NULL;
regex_t *relay_config_regex_websocket_allowed_origins = NULL;
struct t_hashtable *relay_config_hashtable_irc_backlog_tags = NULL;
char **relay_config_network_password_hash_algo_list = NULL;
/*
* Callback for changes on option "relay.look.auto_open_buffer".
*/
void
relay_config_change_auto_open_buffer_cb (const void *pointer, void *data,
struct t_config_option *option)
{
const char *auto_open_buffer;
const char *value_on = "irc,weechat", *value_off = "";
char **items;
int i, num_items, protocol;
/* make C compiler happy */
(void) pointer;
(void) data;
(void) option;
auto_open_buffer = weechat_config_string (relay_config_look_auto_open_buffer);
/* old option was a boolean, use these values for compatibility */
if (strcmp (auto_open_buffer, "on") == 0)
auto_open_buffer = value_on;
else if (strcmp (auto_open_buffer, "off") == 0)
auto_open_buffer = value_off;
for (i = 0; i < RELAY_NUM_PROTOCOLS; i++)
{
relay_config_auto_open_buffer[i] = 0;
}
if (auto_open_buffer[0])
{
items = weechat_string_split (auto_open_buffer, ",", NULL, 0, 0,
&num_items);
if (items)
{
for (i = 0; i < num_items; i++)
{
protocol = relay_protocol_search (items[i]);
if (protocol >= 0)
relay_config_auto_open_buffer[protocol] = 1;
}
weechat_string_free_split (items);
}
}
}
/*
* Callback for changes on option "relay.look.display_clients".
*/
void
relay_config_change_display_clients_cb (const void *pointer, void *data,
struct t_config_option *option)
{
const char *display_clients;
char **items;
int i, num_items, protocol;
/* make C compiler happy */
(void) pointer;
(void) data;
(void) option;
display_clients = weechat_config_string (relay_config_look_display_clients);
for (i = 0; i < RELAY_NUM_PROTOCOLS; i++)
{
relay_config_display_clients[i] = 0;
}
if (display_clients[0])
{
items = weechat_string_split (display_clients, ",", NULL, 0, 0,
&num_items);
if (items)
{
for (i = 0; i < num_items; i++)
{
protocol = relay_protocol_search (items[i]);
if (protocol >= 0)
relay_config_display_clients[protocol] = 1;
}
weechat_string_free_split (items);
}
}
}
/*
* Callback for changes on options that require a refresh of relay list.
*/
@@ -1030,10 +1122,24 @@ relay_config_init ()
{
relay_config_look_auto_open_buffer = weechat_config_new_option (
relay_config_file, relay_config_section_look,
"auto_open_buffer", "boolean",
N_("auto open relay buffer when a new client is connecting"),
NULL, 0, 0, "on", NULL, 0,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
"auto_open_buffer", "string",
N_("auto open relay buffer when a new client is connecting "
"using one of these protocols (comma-separated list); "
"allowed protocols: \"irc\", \"weechat\", \"api\""),
NULL, 0, 0, "irc,weechat", NULL, 0,
NULL, NULL, NULL,
&relay_config_change_auto_open_buffer_cb, NULL, NULL,
NULL, NULL, NULL);
relay_config_look_display_clients = weechat_config_new_option (
relay_config_file, relay_config_section_look,
"display_clients", "string",
N_("display messages when clients connect/disconnect from relay "
"using one of these protocols (comma-separated list); "
"allowed protocols: \"irc\", \"weechat\", \"api\""),
NULL, 0, 0, "irc,weechat", NULL, 0,
NULL, NULL, NULL,
&relay_config_change_display_clients_cb, NULL, NULL,
NULL, NULL, NULL);
relay_config_look_raw_messages = weechat_config_new_option (
relay_config_file, relay_config_section_look,
"raw_messages", "integer",
@@ -1185,11 +1291,11 @@ relay_config_init ()
relay_config_network_compression = weechat_config_new_option (
relay_config_file, relay_config_section_network,
"compression", "integer",
N_("compression of messages sent to clients with \"weechat\" "
"protocol: 0 = disable compression, 1 = low compression / fast "
"... 100 = best compression / slow; the value is a percentage "
"converted to 1-9 for zlib and 1-19 for zstd; "
"the default value is recommended, it offers a good "
N_("compression of messages sent to clients with weechat and "
"api protocols: 0 = disable compression, "
"1 = low compression / fast ... 100 = best compression / slow; "
"the value is a percentage converted to 1-9 for zlib and 1-19 "
"for zstd; the default value is recommended, it offers a good "
"compromise between compression and speed"),
NULL, 0, 100, "20", NULL, 0,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
@@ -1279,8 +1385,8 @@ relay_config_init ()
"totp_secret", "string",
N_("secret for the generation of the Time-based One-Time Password "
"(TOTP), encoded in base32 (only letters and digits from 2 to 7); "
"it is used as second factor in weechat protocol, in addition to "
"the password, which must not be empty "
"it is used as second factor in weechat and api protocols, "
"in addition to the password, which must not be empty "
"(empty value means no TOTP is required) "
"(note: content is evaluated, see /help eval)"),
NULL, 0, 0, "", NULL, 0,
@@ -1435,6 +1541,8 @@ relay_config_read ()
rc = weechat_config_read (relay_config_file);
if (rc == WEECHAT_CONFIG_READ_OK)
{
relay_config_change_auto_open_buffer_cb (NULL, NULL, NULL);
relay_config_change_display_clients_cb (NULL, NULL, NULL);
relay_config_change_network_allowed_ips (NULL, NULL, NULL);
relay_config_change_network_password_hash_algo (NULL, NULL, NULL);
relay_config_change_irc_backlog_tags (NULL, NULL, NULL);
+2
View File
@@ -67,6 +67,8 @@ extern struct t_config_option *relay_config_irc_backlog_time_format;
extern struct t_config_option *relay_config_weechat_commands;
extern int relay_config_auto_open_buffer[];
extern int relay_config_display_clients[];
extern regex_t *relay_config_regex_allowed_ips;
extern regex_t *relay_config_regex_websocket_allowed_origins;
extern struct t_hashtable *relay_config_hashtable_irc_backlog_tags;
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef WEECHAT_PLUGIN_RELAY_HTTP_H
#define WEECHAT_PLUGIN_RELAY_HTTP_H
struct t_relay_client;
enum t_relay_client_http_status
{
RELAY_HTTP_METHOD = 0, /* reading method (eg: GET, POST) */
RELAY_HTTP_HEADERS, /* reading headers */
RELAY_HTTP_BODY, /* reading body */
RELAY_HTTP_END, /* end of HTTP request */
/* number of HTTP status */
RELAY_NUM_HTTP_STATUS,
};
#define RELAY_HTTP_200_OK 200, "OK"
#define RELAY_HTTP_204_NO_CONTENT 204, "No Content"
#define RELAY_HTTP_400_BAD_REQUEST 400, "Bad Request"
#define RELAY_HTTP_401_UNAUTHORIZED 401, "Unauthorized"
#define RELAY_HTTP_403_FORBIDDEN 403, "Forbidden"
#define RELAY_HTTP_404_NOT_FOUND 404, "Not Found"
#define RELAY_HTTP_500_INTERNAL_SERVER_ERROR 500, "Internal Server Error"
#define RELAY_HTTP_503_SERVICE_UNAVAILABLE 503, "Service Unvavailable"
#define RELAY_HTTP_ERROR_MISSING_PASSWORD "Missing password"
#define RELAY_HTTP_ERROR_INVALID_PASSWORD "Invalid password"
#define RELAY_HTTP_ERROR_MISSING_TOTP "Missing TOTP"
#define RELAY_HTTP_ERROR_INVALID_TOTP "Invalid TOTP"
#define RELAY_HTTP_ERROR_OUT_OF_MEMORY "Out of memory"
struct t_relay_http_request
{
enum t_relay_client_http_status status; /* HTTP status */
char **raw; /* raw request */
char *method; /* method (GET, POST, etc.) */
char *path; /* path after method */
char **path_items; /* list of items in path, eg: */
/* "/api/a/b" => ["api", "a", "b"] */
int num_path_items; /* number of path items */
struct t_hashtable *params; /* optional parameters ("?p=a&q=b") */
char *http_version; /* HTTP version (eg: "HTTP/1.1") */
struct t_hashtable *headers; /* HTTP headers for websocket */
/* and API protocol */
struct t_hashtable *accept_encoding; /* allowed encoding for response */
int content_length; /* value of header "Content-Length" */
int body_size; /* size of HTTP body read so far */
char *body; /* HTTP body (can be NULL) */
};
extern void relay_http_request_reinit (struct t_relay_http_request *request);
extern struct t_relay_http_request *relay_http_request_alloc ();
extern int relay_http_parse_method_path (struct t_relay_http_request *request,
const char *method_path);
extern int relay_http_check_auth (struct t_relay_http_request *request);
extern void relay_http_recv (struct t_relay_client *client, const char *data);
extern int relay_http_send (struct t_relay_client *client,
int return_code, const char *message,
const char *headers,
const char *body, int body_size);
extern int relay_http_send_json (struct t_relay_client *client,
int return_code,
const char *message,
const char *headers,
const char *json_string);
extern int relay_http_send_error_json (struct t_relay_client *client,
int return_code,
const char *message,
const char *headers,
const char *format, ...);
extern void relay_http_request_free (struct t_relay_http_request *request);
extern void relay_http_print_log (struct t_relay_http_request *request);
#endif /* WEECHAT_PLUGIN_RELAY_HTTP_H */
+16 -1
View File
@@ -52,7 +52,7 @@ struct t_relay_server *last_relay_server = NULL;
* Examples:
*
* string ipv4 ipv6 tls unix protocol protocol_args
* ---------------------------------------------------------------
* -------------------------------------------------------------------
* irc.libera 1 1 0 0 irc libera
* tls.irc.libera 1 1 1 0 irc libera
* ipv4.irc.libera 1 0 0 0 irc libera
@@ -63,6 +63,9 @@ struct t_relay_server *last_relay_server = NULL;
* tls.weechat 1 1 1 0 weechat
* ipv6.tls.weechat 0 1 1 0 weechat
* unix.weechat 0 0 0 1 weechat
* tls.api 1 1 1 0 api
* ipv6.tls.api 0 1 1 0 api
* unix.api 0 0 0 1 api
*
* Note: *protocol and *protocol_args must be freed after use.
*/
@@ -801,6 +804,18 @@ relay_server_new (const char *protocol_string, enum t_relay_protocol protocol,
if (!protocol_string)
return NULL;
#ifndef HAVE_CJSON
if (protocol == RELAY_PROTOCOL_API)
{
weechat_printf (NULL,
_("%s%s: error: unable to add relay \"%s\" "
"(cJSON support is not enabled)"),
weechat_prefix ("error"), RELAY_PLUGIN_NAME,
protocol_string);
return NULL;
}
#endif /* HAVE_CJSON */
/* look for duplicate ports/paths */
dup_server = (unix_socket) ?
relay_server_search_path (path) : relay_server_search_port (port);
+39 -86
View File
@@ -28,6 +28,7 @@
#include "relay.h"
#include "relay-client.h"
#include "relay-config.h"
#include "relay-http.h"
#include "relay-websocket.h"
@@ -39,81 +40,44 @@
/*
* Checks if a message is a HTTP GET with resource "/weechat".
* Checks if a message is a HTTP GET with resource "/weechat" (for weechat
* protocol) or "/api" (for api protocol).
*
* Returns:
* 1: message is a HTTP GET with resource "/weechat"
* 0: message is NOT a HTTP GET with resource "/weechat"
* 1: message is a HTTP GET with appropriate resource
* 0: message is NOT a HTTP GET with appropriate resource
*/
int
relay_websocket_is_http_get_weechat (const char *message)
relay_websocket_is_valid_http_get (struct t_relay_client *client,
const char *message)
{
/* the message must start with "GET /weechat" */
if (strncmp (message, "GET /weechat", 12) != 0)
char string[128];
int length;
/* the message must start with "GET /weechat" or "GET /api" */
snprintf (string, sizeof (string),
"GET /%s", relay_protocol_string[client->protocol]);
length = strlen (string);
if (strncmp (message, string, length) != 0)
return 0;
/* after "GET /weechat", only a new line or " HTTP" is allowed */
if ((message[12] != '\r') && (message[12] != '\n')
&& (strncmp (message + 12, " HTTP", 5) != 0))
/* after "GET /weechat" or "GET /api", only a new line or " HTTP" is allowed */
if ((message[length] != '\r') && (message[length] != '\n')
&& (strncmp (message + length, " HTTP", 5) != 0))
{
return 0;
}
/* valid HTTP GET for resource "/weechat" */
/* valid HTTP GET */
return 1;
}
/*
* Saves a HTTP header in hashtable "http_header" of client.
*/
void
relay_websocket_save_header (struct t_relay_client *client,
const char *message)
{
char *pos, *name, *name_lower;
const char *ptr_value;
/* ignore the "GET" request */
if (strncmp (message, "GET ", 4) == 0)
return;
pos = strchr (message, ':');
/* not a valid header */
if (!pos || (pos == message))
return;
/* get header name, which is case-insensitive */
name = weechat_strndup (message, pos - message);
if (!name)
return;
name_lower = weechat_string_tolower (name);
if (!name_lower)
{
free (name);
return;
}
/* get pointer on header value */
ptr_value = pos + 1;
while (ptr_value[0] == ' ')
{
ptr_value++;
}
/* add header in the hashtable */
weechat_hashtable_set (client->http_headers, name_lower, ptr_value);
free (name);
free (name_lower);
}
/*
* Checks if a client handshake is valid.
*
* A websocket query looks like:
* A websocket query looks like (weechat protocol):
* GET /weechat HTTP/1.1
* Upgrade: websocket
* Connection: Upgrade
@@ -126,6 +90,20 @@ relay_websocket_save_header (struct t_relay_client *client,
* Sec-WebSocket-Extensions: x-webkit-deflate-frame
* Cookie: csrftoken=acb65377798f32dc377ebb50316a12b5
*
* A websocket query looks like (api protocol):
* GET /api HTTP/1.1
* Upgrade: websocket
* Connection: Upgrade
* Host: myhost:5000
* Origin: https://example.org
* Pragma: no-cache
* Cache-Control: no-cache
* Sec-WebSocket-Key: fo1J9uHSsrfDP3BkwUylzQ==
* Sec-WebSocket-Version: 13
* Sec-WebSocket-Extensions: x-webkit-deflate-frame
* Cookie: csrftoken=acb65377798f32dc377ebb50316a12b5
*
*
* Expected HTTP headers with values are:
*
* header | value
@@ -149,20 +127,20 @@ relay_websocket_client_handshake_valid (struct t_relay_client *client)
const char *value;
/* check if we have header "Upgrade" with value "websocket" */
value = weechat_hashtable_get (client->http_headers, "upgrade");
value = weechat_hashtable_get (client->http_req->headers, "upgrade");
if (!value)
return -1;
if (weechat_strcasecmp (value, "websocket") != 0)
return -1;
/* check if we have header "Sec-WebSocket-Key" with non-empty value */
value = weechat_hashtable_get (client->http_headers, "sec-websocket-key");
value = weechat_hashtable_get (client->http_req->headers, "sec-websocket-key");
if (!value || !value[0])
return -1;
if (relay_config_regex_websocket_allowed_origins)
{
value = weechat_hashtable_get (client->http_headers, "origin");
value = weechat_hashtable_get (client->http_req->headers, "origin");
if (!value || !value[0])
return -2;
if (regexec (relay_config_regex_websocket_allowed_origins, value, 0,
@@ -196,7 +174,7 @@ relay_websocket_build_handshake (struct t_relay_client *client)
char *key, sec_websocket_accept[128], handshake[1024], hash[160 / 8];
int length, hash_size;
sec_websocket_key = weechat_hashtable_get (client->http_headers,
sec_websocket_key = weechat_hashtable_get (client->http_req->headers,
"sec-websocket-key");
if (!sec_websocket_key || !sec_websocket_key[0])
return NULL;
@@ -238,31 +216,6 @@ relay_websocket_build_handshake (struct t_relay_client *client)
return strdup (handshake);
}
/*
* Sends a HTTP message to client.
*
* Argument "http" is a HTTP code + message, for example:
* "403 Forbidden".
*/
void
relay_websocket_send_http (struct t_relay_client *client,
const char *http)
{
char *message;
int length;
length = 32 + strlen (http) + 1;
message = malloc (length);
if (message)
{
snprintf (message, length, "HTTP/1.1 %s\r\n\r\n", http);
relay_client_send (client, RELAY_CLIENT_MSG_STANDARD,
message, strlen (message), NULL);
free (message);
}
}
/*
* Decodes a websocket frame.
*
+2 -5
View File
@@ -27,13 +27,10 @@
#define WEBSOCKET_FRAME_OPCODE_PING 0x09
#define WEBSOCKET_FRAME_OPCODE_PONG 0x0A
extern int relay_websocket_is_http_get_weechat (const char *message);
extern void relay_websocket_save_header (struct t_relay_client *client,
const char *message);
extern int relay_websocket_is_valid_http_get (struct t_relay_client *client,
const char *message);
extern int relay_websocket_client_handshake_valid (struct t_relay_client *client);
extern char *relay_websocket_build_handshake (struct t_relay_client *client);
extern void relay_websocket_send_http (struct t_relay_client *client,
const char *http);
extern int relay_websocket_decode_frame (const unsigned char *buffer,
unsigned long long length,
unsigned char *decoded,
+1 -1
View File
@@ -47,7 +47,7 @@ WEECHAT_PLUGIN_PRIORITY(RELAY_PLUGIN_PRIORITY);
struct t_weechat_plugin *weechat_relay_plugin = NULL;
char *relay_protocol_string[] = /* strings for protocols */
{ "weechat", "irc" };
{ "weechat", "irc", "api" };
struct t_hdata *relay_hdata_buffer = NULL;
struct t_hdata *relay_hdata_lines = NULL;
+1
View File
@@ -43,6 +43,7 @@ enum t_relay_protocol
{
RELAY_PROTOCOL_WEECHAT = 0, /* WeeChat protocol */
RELAY_PROTOCOL_IRC, /* IRC protocol (IRC proxy) */
RELAY_PROTOCOL_API, /* HTTP REST API */
/* number of relay protocols */
RELAY_NUM_PROTOCOLS,
};
+1
View File
@@ -107,6 +107,7 @@ endif()
if (ENABLE_RELAY)
list(APPEND LIB_WEECHAT_UNIT_TESTS_PLUGINS_SRC
unit/plugins/relay/test-relay-auth.cpp
unit/plugins/relay/test-relay-http.cpp
unit/plugins/relay/irc/test-relay-irc.cpp
)
endif()
@@ -0,0 +1,742 @@
/*
* test-relay-http.cpp - test HTTP functions
*
* Copyright (C) 2023-2024 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CppUTest/TestHarness.h"
#include "tests/tests.h"
extern "C"
{
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "src/core/wee-config-file.h"
#include "src/core/wee-hashtable.h"
#include "src/core/wee-hook.h"
#include "src/core/wee-string.h"
#include "src/plugins/relay/relay-config.h"
#include "src/plugins/relay/relay-http.h"
#include "src/plugins/weechat-plugin.h"
extern char *relay_http_url_decode (const char *url);
extern void relay_http_parse_path (const char *url,
char ***paths, int *num_paths,
struct t_hashtable *params);
extern int relay_http_parse_header (struct t_relay_http_request *request,
const char *header);
extern void relay_http_add_to_body (struct t_relay_http_request *request,
char **partial_message);
extern char *relay_http_compress (struct t_relay_http_request *request,
const char *data, int data_size,
int *compressed_size,
char *http_content_encoding,
int http_content_encoding_size);
}
#define WEE_PARSE_PATH(__path) \
paths = (char **)0x1; \
num_paths = -1; \
hashtable_remove_all (params); \
relay_http_parse_path (__path, &paths, &num_paths, params);
TEST_GROUP(RelayHttp)
{
};
/*
* Tests functions:
* relay_http_request_alloc
* relay_http_request_reinit
* relay_http_request_free
*/
TEST(RelayHttp, AllocReinitFree)
{
struct t_relay_http_request *request;
request = relay_http_request_alloc ();
CHECK(request);
LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
CHECK(request->raw);
STRCMP_EQUAL("", *(request->raw));
POINTERS_EQUAL(NULL, request->method);
POINTERS_EQUAL(NULL, request->path);
POINTERS_EQUAL(NULL, request->path_items);
LONGS_EQUAL(0, request->num_path_items);
CHECK(request->params);
LONGS_EQUAL(0, request->params->items_count);
POINTERS_EQUAL(NULL, request->http_version);
CHECK(request->headers);
LONGS_EQUAL(0, request->headers->items_count);
CHECK(request->accept_encoding);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
request->status = RELAY_HTTP_HEADERS;
string_dyn_concat (request->raw, "test", -1);
request->method = strdup ("test");
request->path = strdup ("test");
request->path_items = string_split ("test,1,2,3", ",", NULL, 0, 0, &request->num_path_items);
hashtable_set (request->params, "test", "value");
request->http_version = strdup ("HTTP/1.1");
hashtable_set (request->headers, "x-test", "value");
hashtable_set (request->accept_encoding, "gzip", "");
request->content_length = 100;
request->body_size = 16;
request->body = (char *)malloc (16);
memset (request->body, 0, 16);
relay_http_request_reinit (request);
LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
CHECK(request->raw);
STRCMP_EQUAL("", *(request->raw));
POINTERS_EQUAL(NULL, request->method);
POINTERS_EQUAL(NULL, request->path);
POINTERS_EQUAL(NULL, request->path_items);
LONGS_EQUAL(0, request->num_path_items);
CHECK(request->params);
LONGS_EQUAL(0, request->params->items_count);
POINTERS_EQUAL(NULL, request->http_version);
CHECK(request->headers);
LONGS_EQUAL(0, request->headers->items_count);
CHECK(request->accept_encoding);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
relay_http_request_free (request);
}
/*
* Tests functions:
* relay_http_url_decode
*/
TEST(RelayHttp, UrlDecode)
{
char *str;
POINTERS_EQUAL(NULL, relay_http_url_decode (NULL));
WEE_TEST_STR("", relay_http_url_decode (""));
WEE_TEST_STR("test", relay_http_url_decode ("test"));
WEE_TEST_STR("%", relay_http_url_decode ("%"));
WEE_TEST_STR("%%", relay_http_url_decode ("%%"));
WEE_TEST_STR("%test", relay_http_url_decode ("%test"));
WEE_TEST_STR("#test", relay_http_url_decode ("#test"));
WEE_TEST_STR("#test", relay_http_url_decode ("%23test"));
WEE_TEST_STR("%#test", relay_http_url_decode ("%%23test"));
WEE_TEST_STR("%#test hello", relay_http_url_decode ("%%23test%20hello"));
WEE_TEST_STR("test+z", relay_http_url_decode ("test%2bz"));
WEE_TEST_STR("test+z", relay_http_url_decode ("test%2Bz"));
WEE_TEST_STR("test*", relay_http_url_decode ("test%2a"));
}
/*
* Tests functions:
* relay_http_parse_path
*/
TEST(RelayHttp, ParsePath)
{
char **paths;
int num_paths;
struct t_hashtable *params;
params = hashtable_new (
32,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
WEE_PARSE_PATH(NULL);
POINTERS_EQUAL(NULL, paths);
LONGS_EQUAL(0, num_paths);
LONGS_EQUAL(0, params->items_count);
WEE_PARSE_PATH("");
POINTERS_EQUAL(NULL, paths);
LONGS_EQUAL(0, num_paths);
LONGS_EQUAL(0, params->items_count);
WEE_PARSE_PATH("api");
CHECK(paths);
LONGS_EQUAL(1, num_paths);
STRCMP_EQUAL("api", paths[0]);
POINTERS_EQUAL(NULL, paths[1]);
LONGS_EQUAL(0, params->items_count);
WEE_PARSE_PATH("/api");
CHECK(paths);
LONGS_EQUAL(1, num_paths);
STRCMP_EQUAL("api", paths[0]);
POINTERS_EQUAL(NULL, paths[1]);
LONGS_EQUAL(0, params->items_count);
WEE_PARSE_PATH("/api/");
CHECK(paths);
LONGS_EQUAL(1, num_paths);
STRCMP_EQUAL("api", paths[0]);
POINTERS_EQUAL(NULL, paths[1]);
LONGS_EQUAL(0, params->items_count);
WEE_PARSE_PATH("/api/buffers");
CHECK(paths);
LONGS_EQUAL(2, num_paths);
STRCMP_EQUAL("api", paths[0]);
STRCMP_EQUAL("buffers", paths[1]);
POINTERS_EQUAL(NULL, paths[2]);
LONGS_EQUAL(0, params->items_count);
WEE_PARSE_PATH("/api/buffers?");
CHECK(paths);
LONGS_EQUAL(2, num_paths);
STRCMP_EQUAL("api", paths[0]);
STRCMP_EQUAL("buffers", paths[1]);
POINTERS_EQUAL(NULL, paths[2]);
LONGS_EQUAL(0, params->items_count);
WEE_PARSE_PATH("/api/buffers?param");
CHECK(paths);
LONGS_EQUAL(2, num_paths);
STRCMP_EQUAL("api", paths[0]);
STRCMP_EQUAL("buffers", paths[1]);
POINTERS_EQUAL(NULL, paths[2]);
LONGS_EQUAL(1, params->items_count);
STRCMP_EQUAL("", (const char *)hashtable_get (params, "param"));
WEE_PARSE_PATH("/api/buffers?param=");
CHECK(paths);
LONGS_EQUAL(2, num_paths);
STRCMP_EQUAL("api", paths[0]);
STRCMP_EQUAL("buffers", paths[1]);
POINTERS_EQUAL(NULL, paths[2]);
LONGS_EQUAL(1, params->items_count);
STRCMP_EQUAL("", (const char *)hashtable_get (params, "param"));
WEE_PARSE_PATH("/api/buffers?param=off");
CHECK(paths);
LONGS_EQUAL(2, num_paths);
STRCMP_EQUAL("api", paths[0]);
STRCMP_EQUAL("buffers", paths[1]);
POINTERS_EQUAL(NULL, paths[2]);
LONGS_EQUAL(1, params->items_count);
STRCMP_EQUAL("off", (const char *)hashtable_get (params, "param"));
WEE_PARSE_PATH("/api/buffers?param=off&test=value2");
CHECK(paths);
LONGS_EQUAL(2, num_paths);
STRCMP_EQUAL("api", paths[0]);
STRCMP_EQUAL("buffers", paths[1]);
POINTERS_EQUAL(NULL, paths[2]);
LONGS_EQUAL(2, params->items_count);
STRCMP_EQUAL("off", (const char *)hashtable_get (params, "param"));
STRCMP_EQUAL("value2", (const char *)hashtable_get (params, "test"));
WEE_PARSE_PATH("/api/buffers/irc.libera.%23weechat?param=off&test=value%202");
CHECK(paths);
LONGS_EQUAL(3, num_paths);
STRCMP_EQUAL("api", paths[0]);
STRCMP_EQUAL("buffers", paths[1]);
STRCMP_EQUAL("irc.libera.#weechat", paths[2]);
POINTERS_EQUAL(NULL, paths[3]);
LONGS_EQUAL(2, params->items_count);
STRCMP_EQUAL("off", (const char *)hashtable_get (params, "param"));
STRCMP_EQUAL("value 2", (const char *)hashtable_get (params, "test"));
hashtable_free (params);
}
/*
* Tests functions:
* relay_http_parse_method_path
*/
TEST(RelayHttp, ParseMethodPath)
{
struct t_relay_http_request *request;
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, NULL);
LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
STRCMP_EQUAL("", *(request->raw));
POINTERS_EQUAL(NULL, request->path);
POINTERS_EQUAL(NULL, request->path_items);
LONGS_EQUAL(0, request->num_path_items);
LONGS_EQUAL(0, request->params->items_count);
POINTERS_EQUAL(NULL, request->http_version);
LONGS_EQUAL(0, request->headers->items_count);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "");
LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
STRCMP_EQUAL("", *(request->raw));
POINTERS_EQUAL(NULL, request->path);
POINTERS_EQUAL(NULL, request->path_items);
LONGS_EQUAL(0, request->num_path_items);
LONGS_EQUAL(0, request->params->items_count);
POINTERS_EQUAL(NULL, request->http_version);
LONGS_EQUAL(0, request->headers->items_count);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET");
LONGS_EQUAL(RELAY_HTTP_END, request->status);
STRCMP_EQUAL("GET\n", *(request->raw));
POINTERS_EQUAL(NULL, request->path);
POINTERS_EQUAL(NULL, request->path_items);
LONGS_EQUAL(0, request->num_path_items);
LONGS_EQUAL(0, request->params->items_count);
POINTERS_EQUAL(NULL, request->http_version);
LONGS_EQUAL(0, request->headers->items_count);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
STRCMP_EQUAL("GET /api\n", *(request->raw));
STRCMP_EQUAL("/api", request->path);
CHECK(request->path_items);
STRCMP_EQUAL("api", request->path_items[0]);
POINTERS_EQUAL(NULL, request->path_items[1]);
LONGS_EQUAL(1, request->num_path_items);
LONGS_EQUAL(0, request->params->items_count);
POINTERS_EQUAL(NULL, request->http_version);
LONGS_EQUAL(0, request->headers->items_count);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/buffers HTTP/1.1");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
STRCMP_EQUAL("GET /api/buffers HTTP/1.1\n", *(request->raw));
STRCMP_EQUAL("/api/buffers", request->path);
CHECK(request->path_items);
STRCMP_EQUAL("api", request->path_items[0]);
STRCMP_EQUAL("buffers", request->path_items[1]);
POINTERS_EQUAL(NULL, request->path_items[2]);
LONGS_EQUAL(2, request->num_path_items);
LONGS_EQUAL(0, request->params->items_count);
STRCMP_EQUAL("HTTP/1.1", request->http_version);
LONGS_EQUAL(0, request->headers->items_count);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/buffers?test=1&var=abc HTTP/1.1");
/* do it 2 times, to be sure it has no side effect */
relay_http_parse_method_path (request, "GET /api/buffers?test=1&var=abc HTTP/1.1");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
STRCMP_EQUAL("GET /api/buffers?test=1&var=abc HTTP/1.1\n"
"GET /api/buffers?test=1&var=abc HTTP/1.1\n",
*(request->raw));
STRCMP_EQUAL("/api/buffers?test=1&var=abc", request->path);
CHECK(request->path_items);
STRCMP_EQUAL("api", request->path_items[0]);
STRCMP_EQUAL("buffers", request->path_items[1]);
POINTERS_EQUAL(NULL, request->path_items[2]);
LONGS_EQUAL(2, request->num_path_items);
LONGS_EQUAL(2, request->params->items_count);
STRCMP_EQUAL("1", (const char *)hashtable_get (request->params, "test"));
STRCMP_EQUAL("abc", (const char *)hashtable_get (request->params, "var"));
STRCMP_EQUAL("HTTP/1.1", request->http_version);
LONGS_EQUAL(0, request->headers->items_count);
LONGS_EQUAL(0, request->accept_encoding->items_count);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
free (request);
}
/*
* Tests functions:
* relay_http_parse_header
*/
TEST(RelayHttp, ParseHeader)
{
struct t_relay_http_request *request;
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/version");
relay_http_parse_header (request, NULL);
LONGS_EQUAL(RELAY_HTTP_END, request->status);
STRCMP_EQUAL("GET /api/version\n"
"\n",
*(request->raw));
LONGS_EQUAL(0, request->headers->items_count);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/version");
relay_http_parse_header (request, "");
LONGS_EQUAL(RELAY_HTTP_END, request->status);
STRCMP_EQUAL("GET /api/version\n"
"\n",
*(request->raw));
LONGS_EQUAL(0, request->headers->items_count);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/version");
relay_http_parse_header (request, "Test");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
STRCMP_EQUAL("GET /api/version\n"
"Test\n",
*(request->raw));
LONGS_EQUAL(0, request->headers->items_count);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/version");
relay_http_parse_header (request, "X-Test: value");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
STRCMP_EQUAL("GET /api/version\n"
"X-Test: value\n",
*(request->raw));
LONGS_EQUAL(1, request->headers->items_count);
STRCMP_EQUAL("value", (const char *)hashtable_get (request->headers, "x-test"));
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/version");
relay_http_parse_header (request, "Accept-Encoding: gzip, zstd, br");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
STRCMP_EQUAL("GET /api/version\n"
"Accept-Encoding: gzip, zstd, br\n",
*(request->raw));
LONGS_EQUAL(1, request->headers->items_count);
STRCMP_EQUAL("gzip, zstd, br",
(const char *)hashtable_get (request->headers, "accept-encoding"));
LONGS_EQUAL(3, request->accept_encoding->items_count);
CHECK(hashtable_has_key (request->accept_encoding, "gzip"));
CHECK(hashtable_has_key (request->accept_encoding, "zstd"));
CHECK(hashtable_has_key (request->accept_encoding, "br"));
free (request);
request = relay_http_request_alloc ();
CHECK(request);
relay_http_parse_method_path (request, "GET /api/version");
relay_http_parse_header (request, "Content-Length: 123");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
STRCMP_EQUAL("GET /api/version\n"
"Content-Length: 123\n",
*(request->raw));
LONGS_EQUAL(1, request->headers->items_count);
LONGS_EQUAL(123, request->content_length);
free (request);
}
/*
* Tests functions:
* relay_http_add_to_body
*/
TEST(RelayHttp, AddToBody)
{
struct t_relay_http_request *request;
const char *body_part1 = "abc", *body_part2 = "defghij";
char *partial_message;
request = relay_http_request_alloc ();
CHECK(request);
LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
relay_http_parse_method_path (request, "GET /api/version");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
relay_http_parse_header (request, "Content-Length: 10");
relay_http_parse_header (request, "");
LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
LONGS_EQUAL(10, request->content_length);
LONGS_EQUAL(0, request->body_size);
partial_message = strdup (body_part1);
relay_http_add_to_body (request, &partial_message);
LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
LONGS_EQUAL(3, request->body_size);
MEMCMP_EQUAL("abc", request->body, 3);
POINTERS_EQUAL(NULL, partial_message);
partial_message = strdup (body_part2);
relay_http_add_to_body (request, &partial_message);
LONGS_EQUAL(RELAY_HTTP_END, request->status);
LONGS_EQUAL(10, request->body_size);
MEMCMP_EQUAL("abcdefghij", request->body, 10);
POINTERS_EQUAL(NULL, partial_message);
free (request);
request = relay_http_request_alloc ();
CHECK(request);
LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
relay_http_parse_method_path (request, "GET /api/version");
LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
relay_http_parse_header (request, "Content-Length: 5");
relay_http_parse_header (request, "");
LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
LONGS_EQUAL(5, request->content_length);
LONGS_EQUAL(0, request->body_size);
partial_message = strdup (body_part1);
relay_http_add_to_body (request, &partial_message);
LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
LONGS_EQUAL(3, request->body_size);
MEMCMP_EQUAL("abc", request->body, 3);
POINTERS_EQUAL(NULL, partial_message);
partial_message = strdup (body_part2);
relay_http_add_to_body (request, &partial_message);
LONGS_EQUAL(RELAY_HTTP_END, request->status);
LONGS_EQUAL(5, request->body_size);
MEMCMP_EQUAL("abcde", request->body, 5);
STRCMP_EQUAL("fghij", partial_message);
free (partial_message);
free (request);
}
/*
* Tests functions:
* relay_http_check_auth
*/
TEST(RelayHttp, CheckAuth)
{
struct t_relay_http_request *request;
char *totp, *totp2;
config_file_option_set (relay_config_network_password, "secret_password", 1);
request = relay_http_request_alloc ();
CHECK(request);
/* test password */
LONGS_EQUAL(-1, relay_http_check_auth (request));
hashtable_set (request->headers, "authorization", "Basic ");
LONGS_EQUAL(-2, relay_http_check_auth (request));
hashtable_set (request->headers, "authorization", "Basic \u26c4");
LONGS_EQUAL(-2, relay_http_check_auth (request));
/* set invalid user/password: "weechat:test" */
hashtable_set (request->headers, "authorization", "Basic d2VlY2hhdDp0ZXN0");
LONGS_EQUAL(-2, relay_http_check_auth (request));
/* set valid user/password: "weechat:secret_password" */
hashtable_set (request->headers,
"authorization", "Basic d2VlY2hhdDpzZWNyZXRfcGFzc3dvcmQ");
LONGS_EQUAL(0, relay_http_check_auth (request));
/* test missing/invalid TOTP */
config_file_option_set (relay_config_network_totp_secret, "secretbase32", 1);
config_file_option_set (relay_config_network_totp_window, "1", 1);
LONGS_EQUAL(-3, relay_http_check_auth (request));
totp = hook_info_get (NULL, "totp_generate", "secretbase32");
CHECK(totp);
totp2 = strdup (totp);
totp2[0] = (totp2[0] == '1') ? '2' : '1';
hashtable_set (request->headers, "x-weechat-totp", totp2);
LONGS_EQUAL(-4, relay_http_check_auth (request));
hashtable_set (request->headers, "x-weechat-totp", totp);
LONGS_EQUAL(0, relay_http_check_auth (request));
free (totp);
free (totp2);
config_file_option_reset (relay_config_network_totp_secret, 1);
config_file_option_reset (relay_config_network_totp_window, 1);
config_file_option_reset (relay_config_network_password, 1);
}
/*
* Tests functions:
* relay_http_process_websocket
*/
TEST(RelayHttp, ProcessWebsocket)
{
/* TODO: write tests */
}
/*
* Tests functions:
* relay_http_process_request
*/
TEST(RelayHttp, ProcessRequest)
{
/* TODO: write tests */
}
/*
* Tests functions:
* relay_http_recv
*/
TEST(RelayHttp, Recv)
{
/* TODO: write tests */
}
/*
* Tests functions:
* relay_http_compress
*/
TEST(RelayHttp, Compress)
{
struct t_relay_http_request *request;
char body[256], *buffer, encoding[256];
int i, size;
request = relay_http_request_alloc ();
for (i = 0; i < (int)sizeof (body); i++)
{
body[i] = i % 64;
}
hashtable_remove_all (request->accept_encoding);
buffer = (char *)0x1;
size = -1;
buffer = relay_http_compress (request, NULL, 0, &size, NULL, 0);
POINTERS_EQUAL(NULL, buffer);
LONGS_EQUAL(0, size);
hashtable_remove_all (request->accept_encoding);
buffer = (char *)0x1;
size = -1;
snprintf (encoding, sizeof (encoding), "test");
buffer = relay_http_compress (request, NULL, 0, &size,
encoding, sizeof (encoding));
POINTERS_EQUAL(NULL, buffer);
LONGS_EQUAL(0, size);
STRCMP_EQUAL("", encoding);
/* no "Accept-Encoding" header was received => no compression */
hashtable_remove_all (request->accept_encoding);
buffer = (char *)0x1;
size = -1;
snprintf (encoding, sizeof (encoding), "test");
buffer = relay_http_compress (request, body, sizeof (body), &size,
encoding, sizeof (encoding));
POINTERS_EQUAL(NULL, buffer);
LONGS_EQUAL(0, size);
STRCMP_EQUAL("", encoding);
/* "Accept-Encoding: gzip" => gzip compression */
hashtable_remove_all (request->accept_encoding);
hashtable_set (request->accept_encoding, "gzip", "");
buffer = (char *)0x1;
size = -1;
snprintf (encoding, sizeof (encoding), "test");
buffer = relay_http_compress (request, body, sizeof (body), &size,
encoding, sizeof (encoding));
CHECK(buffer);
CHECK((size > 0) && (size < (int)sizeof (body)));
STRCMP_EQUAL("Content-Encoding: gzip\r\n", encoding);
free (buffer);
#ifdef HAVE_ZSTD
/* "Accept-Encoding: gzip, zstd" => zstd compression */
hashtable_remove_all (request->accept_encoding);
hashtable_set (request->accept_encoding, "gzip", "");
hashtable_set (request->accept_encoding, "zstd", "");
buffer = (char *)0x1;
size = -1;
snprintf (encoding, sizeof (encoding), "test");
buffer = relay_http_compress (request, body, sizeof (body), &size,
encoding, sizeof (encoding));
CHECK(buffer);
CHECK((size > 0) && (size < (int)sizeof (body)));
STRCMP_EQUAL("Content-Encoding: zstd\r\n", encoding);
free (buffer);
#endif
}
/*
* Tests functions:
* relay_http_send
*/
TEST(RelayHttp, Send)
{
/* TODO: write tests */
}
/*
* Tests functions:
* relay_http_send_json
*/
TEST(RelayHttp, SendJson)
{
/* TODO: write tests */
}
/*
* Tests functions:
* relay_http_send_error_json
*/
TEST(RelayHttp, SendErrorJson)
{
/* TODO: write tests */
}
/*
* Tests functions:
* relay_http_print_log
*/
TEST(RelayHttp, PrintLog)
{
/* TODO: write tests */
}
@@ -6,10 +6,10 @@ index 48082f72f..b4de39476 100644
-12
+11
diff --git a/debian-devel/control b/debian-devel/control
index 4229b00f6..41addd80b 100644
index a5d24b6a8..81ae98045 100644
--- a/debian-devel/control
+++ b/debian-devel/control
@@ -5,17 +5,17 @@ Maintainer: Sébastien Helleu <flashcode@flashtux.org>
@@ -5,24 +5,23 @@ Maintainer: Sébastien Helleu <flashcode@flashtux.org>
Build-Depends:
asciidoctor (>= 1.5.4),
ruby-pygments.rb,
@@ -31,6 +31,26 @@ index 4229b00f6..41addd80b 100644
libxml2-dev,
libcurl4-gnutls-dev,
libgcrypt20-dev,
libgnutls28-dev,
libzstd-dev,
- zlib1g-dev,
- libcjson-dev
+ zlib1g-dev
Standards-Version: 4.6.2
Homepage: https://weechat.org/
Vcs-Git: https://salsa.debian.org/kolter/weechat.git
diff --git a/debian-devel/rules b/debian-devel/rules
index cab713c93..d2756333b 100755
--- a/debian-devel/rules
+++ b/debian-devel/rules
@@ -8,6 +8,7 @@ override_dh_auto_configure:
dh_auto_configure --buildsystem=cmake -- \
-DCMAKE_INSTALL_PREFIX:FILEPATH=/usr \
-DLIBDIR=/usr/lib/${DEB_HOST_MULTIARCH} \
+ -DENABLE_CJSON:BOOL=OFF \
-DENABLE_DOC:BOOL=ON \
-DENABLE_MAN:BOOL=ON \
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo \
diff --git a/debian-stable/compat b/debian-stable/compat
index 48082f72f..b4de39476 100644
--- a/debian-stable/compat
@@ -39,10 +59,10 @@ index 48082f72f..b4de39476 100644
-12
+11
diff --git a/debian-stable/control b/debian-stable/control
index ad3f771b7..e88f167b8 100644
index a75e6fee5..b39bf88dc 100644
--- a/debian-stable/control
+++ b/debian-stable/control
@@ -5,17 +5,17 @@ Maintainer: Emmanuel Bouthenot <kolter@debian.org>
@@ -5,24 +5,23 @@ Maintainer: Emmanuel Bouthenot <kolter@debian.org>
Build-Depends:
asciidoctor (>= 1.5.4),
ruby-pygments.rb,
@@ -64,3 +84,23 @@ index ad3f771b7..e88f167b8 100644
libxml2-dev,
libcurl4-gnutls-dev,
libgcrypt20-dev,
libgnutls28-dev,
libzstd-dev,
- zlib1g-dev,
- libcjson-dev
+ zlib1g-dev
Standards-Version: 4.6.2
Homepage: https://weechat.org/
Vcs-Git: https://salsa.debian.org/kolter/weechat.git
diff --git a/debian-stable/rules b/debian-stable/rules
index cab713c93..d2756333b 100755
--- a/debian-stable/rules
+++ b/debian-stable/rules
@@ -8,6 +8,7 @@ override_dh_auto_configure:
dh_auto_configure --buildsystem=cmake -- \
-DCMAKE_INSTALL_PREFIX:FILEPATH=/usr \
-DLIBDIR=/usr/lib/${DEB_HOST_MULTIARCH} \
+ -DENABLE_CJSON:BOOL=OFF \
-DENABLE_DOC:BOOL=ON \
-DENABLE_MAN:BOOL=ON \
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo \
+4 -1
View File
@@ -61,7 +61,10 @@ zlib-devel"
# 1. set prefix to /usr
# 2. enable build of man pages and documentation
#
CYGCMAKE_ARGS="-DCMAKE_INSTALL_PREFIX=/usr -DENABLE_MAN=ON -DENABLE_DOC=ON"
CYGCMAKE_ARGS="-DCMAKE_INSTALL_PREFIX=/usr \
-DENABLE_CJSON=OFF \
-DENABLE_MAN=ON \
-DENABLE_DOC=ON"
PKG_IGNORE="
etc/postinstall/weechat.sh