summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog.adoc1
-rw-r--r--doc/en/weechat_dev.en.adoc169
-rw-r--r--doc/fr/weechat_dev.fr.adoc169
-rw-r--r--doc/ja/weechat_dev.ja.adoc170
-rw-r--r--doc/sr/weechat_dev.sr.adoc170
-rw-r--r--src/plugins/relay/api/relay-api-msg.c1
-rw-r--r--src/plugins/relay/api/relay-api-protocol.c1
-rw-r--r--src/plugins/relay/relay-client.c238
-rw-r--r--src/plugins/relay/relay-client.h1
-rw-r--r--src/plugins/relay/relay-http.c33
-rw-r--r--src/plugins/relay/relay-http.h1
-rw-r--r--src/plugins/relay/relay-websocket.c588
-rw-r--r--src/plugins/relay/relay-websocket.h55
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/unit/plugins/relay/test-relay-http.cpp41
-rw-r--r--tests/unit/plugins/relay/test-relay-websocket.cpp415
16 files changed, 1613 insertions, 441 deletions
diff --git a/ChangeLog.adoc b/ChangeLog.adoc
index 593167886..0a9a8a172 100644
--- a/ChangeLog.adoc
+++ b/ChangeLog.adoc
@@ -19,6 +19,7 @@ New features::
* 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)
+ * relay: add support of websocket extension "permessage-deflate" (issue #1549)
Bug fixes::
diff --git a/doc/en/weechat_dev.en.adoc b/doc/en/weechat_dev.en.adoc
index 24833ee65..50b54bcf4 100644
--- a/doc/en/weechat_dev.en.adoc
+++ b/doc/en/weechat_dev.en.adoc
@@ -396,90 +396,91 @@ WeeChat "core" is located in following directories:
[width="100%",cols="2m,3",options="header"]
|===
-| Path/file | Description
-| tests/ | Root of tests.
-|    tests.cpp | Program used to run all tests.
-|    tests-record.cpp | Record and search in messages displayed.
-|    scripts/ | Root of scripting API tests.
-|       test-scripts.cpp | Program used to run the scripting API tests.
-|       python/ | Python scripts to generate and run the scripting API tests.
-|          testapigen.py | Python script generating scripts in all languages to test the scripting API.
-|          testapi.py | Python script with scripting API tests, used by script testapigen.py.
-|          unparse.py | Convert Python code to other languages, used by script testapigen.py.
-|    unit/ | Root of unit tests.
-|       test-plugins.cpp | Tests: plugins.
-|       test-plugin-api-info.cpp | Tests: plugin API info functions.
-|       test-plugin-config.cpp | Tests: plugin config functions.
-|       core/ | Root of unit tests for core.
-|          test-core-arraylist.cpp | Tests: arraylists.
-|          test-core-calc.cpp | Tests: calculation of expressions.
-|          test-core-command.cpp | Tests: commands.
-|          test-core-config-file.cpp | Tests: configuration files.
-|          test-core-crypto.cpp | Tests: cryptographic functions.
-|          test-core-dir.cpp | Tests: directory/file functions.
-|          test-core-eval.cpp | Tests: evaluation of expressions.
-|          test-core-hashtble.cpp | Tests: hashtables.
-|          test-core-hdata.cpp | Tests: hdata.
-|          test-core-hook.cpp | Tests: hooks.
-|          test-core-infolist.cpp | Tests: infolists.
-|          test-core-list.cpp | Tests: lists.
-|          test-core-network.cpp | Tests: network functions.
-|          test-core-secure.cpp | Tests: secured data.
-|          test-core-signal.cpp | Tests: signals.
-|          test-core-string.cpp | Tests: strings.
-|          test-core-url.cpp | Tests: URLs.
-|          test-core-utf8.cpp | Tests: UTF-8.
-|          test-core-util.cpp | Tests: utility functions.
-|          test-core-sys.cpp | Tests: system functions.
-|          hook/ | Root of unit tests for hooks.
-|             test-hook-command.cpp | Tests: hooks "command".
-|       gui/ | Root of unit tests for interfaces.
-|          test-gui-bar-window.cpp | Tests: bar window functions.
-|          test-gui-buffer.cpp | Tests: buffer functions.
-|          test-gui-chat.cpp | Tests: chat functions.
-|          test-gui-color.cpp | Tests: colors.
-|          test-gui-filter.cpp | Tests: filters.
-|          test-gui-input.cpp | Tests: input functions.
-|          test-gui-key.cpp | Tests: keys.
-|          test-gui-line.cpp | Tests: lines.
-|          test-gui-nick.cpp | Tests: nicks.
-|       plugins/ | Root of unit tests for plugins.
-|          irc/ | Root of unit tests for IRC plugin.
-|             test-irc-batch.cpp | Tests: IRC batched events.
-|             test-irc-buffer.cpp | Tests: IRC buffers.
-|             test-irc-channel.cpp | Tests: IRC channels.
-|             test-irc-color.cpp | Tests: IRC colors.
-|             test-irc-config.cpp | Tests: IRC configuration.
-|             test-irc-ctcp.cpp | Tests: IRC CTCP.
-|             test-irc-ignore.cpp | Tests: IRC ignores.
-|             test-irc-info.cpp | Tests: IRC info.
-|             test-irc-join.cpp | Tests: IRC join functions.
-|             test-irc-list.cpp | Tests: IRC buffer for reply to /list command.
-|             test-irc-message.cpp | Tests: IRC messages.
-|             test-irc-mode.cpp | Tests: IRC modes.
-|             test-irc-nick.cpp | Tests: IRC nicks.
-|             test-irc-protocol.cpp | Tests: IRC protocol.
-|             test-irc-sasl.cpp | Tests: SASL authentication with IRC protocol.
-|             test-irc-server.cpp | Tests: IRC server.
-|             test-irc-tag.cpp | Tests: IRC message tags.
-|          logger/ | Root of unit tests for logger plugin.
-|             test-logger.cpp | Tests: logger.
-|             test-logger-backlog.cpp | Tests: logger backlog.
-|             test-logger-tail.cpp | Tests: logger tail functions.
-|          trigger/ | Root of unit tests for trigger plugin.
-|             test-trigger.cpp | Tests: triggers.
-|             test-trigger-config.cpp | Tests: trigger configuration.
-|          typing/ | Root of unit tests for typing plugin.
-|             test-typing.cpp | Tests: typing.
-|             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.
-|             test-xfer-file.cpp | Tests: file functions.
-|             test-xfer-network.cpp | Tests: network functions.
+| Path/file | Description
+| tests/ | Root of tests.
+|    tests.cpp | Program used to run all tests.
+|    tests-record.cpp | Record and search in messages displayed.
+|    scripts/ | Root of scripting API tests.
+|       test-scripts.cpp | Program used to run the scripting API tests.
+|       python/ | Python scripts to generate and run the scripting API tests.
+|          testapigen.py | Python script generating scripts in all languages to test the scripting API.
+|          testapi.py | Python script with scripting API tests, used by script testapigen.py.
+|          unparse.py | Convert Python code to other languages, used by script testapigen.py.
+|    unit/ | Root of unit tests.
+|       test-plugins.cpp | Tests: plugins.
+|       test-plugin-api-info.cpp | Tests: plugin API info functions.
+|       test-plugin-config.cpp | Tests: plugin config functions.
+|       core/ | Root of unit tests for core.
+|          test-core-arraylist.cpp | Tests: arraylists.
+|          test-core-calc.cpp | Tests: calculation of expressions.
+|          test-core-command.cpp | Tests: commands.
+|          test-core-config-file.cpp | Tests: configuration files.
+|          test-core-crypto.cpp | Tests: cryptographic functions.
+|          test-core-dir.cpp | Tests: directory/file functions.
+|          test-core-eval.cpp | Tests: evaluation of expressions.
+|          test-core-hashtble.cpp | Tests: hashtables.
+|          test-core-hdata.cpp | Tests: hdata.
+|          test-core-hook.cpp | Tests: hooks.
+|          test-core-infolist.cpp | Tests: infolists.
+|          test-core-list.cpp | Tests: lists.
+|          test-core-network.cpp | Tests: network functions.
+|          test-core-secure.cpp | Tests: secured data.
+|          test-core-signal.cpp | Tests: signals.
+|          test-core-string.cpp | Tests: strings.
+|          test-core-url.cpp | Tests: URLs.
+|          test-core-utf8.cpp | Tests: UTF-8.
+|          test-core-util.cpp | Tests: utility functions.
+|          test-core-sys.cpp | Tests: system functions.
+|          hook/ | Root of unit tests for hooks.
+|             test-hook-command.cpp | Tests: hooks "command".
+|       gui/ | Root of unit tests for interfaces.
+|          test-gui-bar-window.cpp | Tests: bar window functions.
+|          test-gui-buffer.cpp | Tests: buffer functions.
+|          test-gui-chat.cpp | Tests: chat functions.
+|          test-gui-color.cpp | Tests: colors.
+|          test-gui-filter.cpp | Tests: filters.
+|          test-gui-input.cpp | Tests: input functions.
+|          test-gui-key.cpp | Tests: keys.
+|          test-gui-line.cpp | Tests: lines.
+|          test-gui-nick.cpp | Tests: nicks.
+|       plugins/ | Root of unit tests for plugins.
+|          irc/ | Root of unit tests for IRC plugin.
+|             test-irc-batch.cpp | Tests: IRC batched events.
+|             test-irc-buffer.cpp | Tests: IRC buffers.
+|             test-irc-channel.cpp | Tests: IRC channels.
+|             test-irc-color.cpp | Tests: IRC colors.
+|             test-irc-config.cpp | Tests: IRC configuration.
+|             test-irc-ctcp.cpp | Tests: IRC CTCP.
+|             test-irc-ignore.cpp | Tests: IRC ignores.
+|             test-irc-info.cpp | Tests: IRC info.
+|             test-irc-join.cpp | Tests: IRC join functions.
+|             test-irc-list.cpp | Tests: IRC buffer for reply to /list command.
+|             test-irc-message.cpp | Tests: IRC messages.
+|             test-irc-mode.cpp | Tests: IRC modes.
+|             test-irc-nick.cpp | Tests: IRC nicks.
+|             test-irc-protocol.cpp | Tests: IRC protocol.
+|             test-irc-sasl.cpp | Tests: SASL authentication with IRC protocol.
+|             test-irc-server.cpp | Tests: IRC server.
+|             test-irc-tag.cpp | Tests: IRC message tags.
+|          logger/ | Root of unit tests for logger plugin.
+|             test-logger.cpp | Tests: logger.
+|             test-logger-backlog.cpp | Tests: logger backlog.
+|             test-logger-tail.cpp | Tests: logger tail functions.
+|          trigger/ | Root of unit tests for trigger plugin.
+|             test-trigger.cpp | Tests: triggers.
+|             test-trigger-config.cpp | Tests: trigger configuration.
+|          typing/ | Root of unit tests for typing plugin.
+|             test-typing.cpp | Tests: typing.
+|             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.
+|             test-relay-websocket.cpp | Tests: websocket 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.
+|             test-xfer-file.cpp | Tests: file functions.
+|             test-xfer-network.cpp | Tests: network functions.
|===
[[documentation_translations]]
diff --git a/doc/fr/weechat_dev.fr.adoc b/doc/fr/weechat_dev.fr.adoc
index 08ccbc83d..51fb1045a 100644
--- a/doc/fr/weechat_dev.fr.adoc
+++ b/doc/fr/weechat_dev.fr.adoc
@@ -398,90 +398,91 @@ Le cœur de WeeChat est situé dans les répertoires suivants :
[width="100%",cols="2m,3",options="header"]
|===
-| Chemin/fichier | Description
-| tests/ | Racine des tests.
-|    tests.cpp | Programme utilisé pour lancer tous les tests.
-|    tests-record.cpp | Enregistrement et recherche dans les messages affichés.
-|    scripts/ | Racine des tests de l'API script.
-|       test-scripts.cpp | Programme utilisé pour lancer les tests de l'API script.
-|       python/ | Scripts Python pour générer et lancer les tests de l'API script.
-|          testapigen.py | Script Python générant des scripts dans tous les languages pour tester l'API script.
-|          testapi.py | Script Python avec les tests API, utilisé par le script testapigen.py.
-|          unparse.py | Conversion de code Python vers d'autres langages, utilisé par le script testapigen.py.
-|    unit/ | Racine des tests unitaires.
-|       test-plugins.cpp | Tests : extensions.
-|       test-plugin-api-info.cpp | Tests : fonctions info de l'API extension.
-|       test-plugin-config.cpp | Tests : fonctions config de l'extension.
-|       core/ | Racine des tests unitaires pour le cœur.
-|          test-core-arraylist.cpp | Tests : listes avec tableau (« arraylists »).
-|          test-core-calc.cpp | Tests : calcul d'expressions.
-|          test-core-command.cpp | Tests : commandes.
-|          test-core-config-file.cpp | Tests : fichiers de configuration.
-|          test-core-crypto.cpp | Tests : fonctions cryptographiques.
-|          test-core-dir.cpp | Tests : répertoires/fichiers.
-|          test-core-eval.cpp | Tests : évaluation d'expressions.
-|          test-core-hashtble.cpp | Tests : tables de hachage.
-|          test-core-hdata.cpp | Tests : hdata.
-|          test-core-hook.cpp | Tests : hooks.
-|          test-core-infolist.cpp | Tests : infolists.
-|          test-core-list.cpp | Tests : listes.
-|          test-core-network.cpp | Tests : fonctions réseau.
-|          test-core-secure.cpp | Tests : données sécurisées.
-|          test-core-signal.cpp | Tests : signaux.
-|          test-core-string.cpp | Tests : chaînes.
-|          test-core-url.cpp | Tests : URLs.
-|          test-core-utf8.cpp | Tests : UTF-8.
-|          test-core-util.cpp | Tests : fonctions utiles.
-|          test-core-sys.cpp | Tests : fonctions système.
-|          hook/ | Racine des tests pour les hooks.
-|             test-hook-command.cpp | Tests : hooks "command".
-|       gui/ | Racine des tests unitaires pour les interfaces.
-|          test-gui-bar-window.cpp | Tests : fonctions de fenêtres de barre.
-|          test-gui-buffer.cpp | Tests : fonctions de tampons.
-|          test-gui-chat.cpp | Tests : fonctions de discussion.
-|          test-gui-color.cpp | Tests : couleurs.
-|          test-gui-filter.cpp | Tests : filtres.
-|          test-gui-input.cpp | Tests : fonctions d'entrée.
-|          test-gui-key.cpp | Tests : touches.
-|          test-gui-line.cpp | Tests : lignes.
-|          test-gui-nick.cpp | Tests : pseudos.
-|       plugins/ | Racine des tests unitaires pour les extensions.
-|          irc/ | Racine des tests unitaires pour l'extension IRC.
-|             test-irc-batch.cpp | Tests : évènements batch IRC.
-|             test-irc-buffer.cpp | Tests : tampons IRC.
-|             test-irc-channel.cpp | Tests : canaux IRC.
-|             test-irc-color.cpp | Tests : couleurs IRC.
-|             test-irc-config.cpp | Tests : configuration IRC.
-|             test-irc-ctcp.cpp | Tests : CTCP IRC.
-|             test-irc-ignore.cpp | Tests : ignores IRC.
-|             test-irc-info.cpp | Tests : infos IRC.
-|             test-irc-join.cpp | Tests : fonctions de join IRC.
-|             test-irc-list.cpp | Tests : tampon IRC pour la réponse à la commande /list.
-|             test-irc-message.cpp | Tests : messages IRC.
-|             test-irc-mode.cpp | Tests : modes IRC.
-|             test-irc-nick.cpp | Tests : pseudos IRC.
-|             test-irc-protocol.cpp | Tests : protocole IRC.
-|             test-irc-sasl.cpp | Tests : authentification SASL avec le protocole IRC.
-|             test-irc-server.cpp | Tests : serveur IRC.
-|             test-irc-tag.cpp | Tests : étiquettes des messages IRC.
-|          logger/ | Racine des tests unitaires pour l'extension logger.
-|             test-logger.cpp | Tests : logger.
-|             test-logger-backlog.cpp | Tests : backlog logger.
-|             test-logger-tail.cpp | Tests : fonctions "tail".
-|          trigger/ | Racine des tests unitaires pour l'extension trigger.
-|             test-trigger.cpp | Tests : triggers.
-|             test-trigger-config.cpp | Tests : configuration trigger.
-|          typing/ | Racine des tests unitaires pour l'extension typing.
-|             test-typing.cpp | Tests : typing.
-|             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.
-|             test-xfer-file.cpp | Tests : fonctions sur les fichiers.
-|             test-xfer-network.cpp | Tests : fonctions réseau.
+| Chemin/fichier | Description
+| tests/ | Racine des tests.
+|    tests.cpp | Programme utilisé pour lancer tous les tests.
+|    tests-record.cpp | Enregistrement et recherche dans les messages affichés.
+|    scripts/ | Racine des tests de l'API script.
+|       test-scripts.cpp | Programme utilisé pour lancer les tests de l'API script.
+|       python/ | Scripts Python pour générer et lancer les tests de l'API script.
+|          testapigen.py | Script Python générant des scripts dans tous les languages pour tester l'API script.
+|          testapi.py | Script Python avec les tests API, utilisé par le script testapigen.py.
+|          unparse.py | Conversion de code Python vers d'autres langages, utilisé par le script testapigen.py.
+|    unit/ | Racine des tests unitaires.
+|       test-plugins.cpp | Tests : extensions.
+|       test-plugin-api-info.cpp | Tests : fonctions info de l'API extension.
+|       test-plugin-config.cpp | Tests : fonctions config de l'extension.
+|       core/ | Racine des tests unitaires pour le cœur.
+|          test-core-arraylist.cpp | Tests : listes avec tableau (« arraylists »).
+|          test-core-calc.cpp | Tests : calcul d'expressions.
+|          test-core-command.cpp | Tests : commandes.
+|          test-core-config-file.cpp | Tests : fichiers de configuration.
+|          test-core-crypto.cpp | Tests : fonctions cryptographiques.
+|          test-core-dir.cpp | Tests : répertoires/fichiers.
+|          test-core-eval.cpp | Tests : évaluation d'expressions.
+|          test-core-hashtble.cpp | Tests : tables de hachage.
+|          test-core-hdata.cpp | Tests : hdata.
+|          test-core-hook.cpp | Tests : hooks.
+|          test-core-infolist.cpp | Tests : infolists.
+|          test-core-list.cpp | Tests : listes.
+|          test-core-network.cpp | Tests : fonctions réseau.
+|          test-core-secure.cpp | Tests : données sécurisées.
+|          test-core-signal.cpp | Tests : signaux.
+|          test-core-string.cpp | Tests : chaînes.
+|          test-core-url.cpp | Tests : URLs.
+|          test-core-utf8.cpp | Tests : UTF-8.
+|          test-core-util.cpp | Tests : fonctions utiles.
+|          test-core-sys.cpp | Tests : fonctions système.
+|          hook/ | Racine des tests pour les hooks.
+|             test-hook-command.cpp | Tests : hooks "command".
+|       gui/ | Racine des tests unitaires pour les interfaces.
+|          test-gui-bar-window.cpp | Tests : fonctions de fenêtres de barre.
+|          test-gui-buffer.cpp | Tests : fonctions de tampons.
+|          test-gui-chat.cpp | Tests : fonctions de discussion.
+|          test-gui-color.cpp | Tests : couleurs.
+|          test-gui-filter.cpp | Tests : filtres.
+|          test-gui-input.cpp | Tests : fonctions d'entrée.
+|          test-gui-key.cpp | Tests : touches.
+|          test-gui-line.cpp | Tests : lignes.
+|          test-gui-nick.cpp | Tests : pseudos.
+|       plugins/ | Racine des tests unitaires pour les extensions.
+|          irc/ | Racine des tests unitaires pour l'extension IRC.
+|             test-irc-batch.cpp | Tests : évènements batch IRC.
+|             test-irc-buffer.cpp | Tests : tampons IRC.
+|             test-irc-channel.cpp | Tests : canaux IRC.
+|             test-irc-color.cpp | Tests : couleurs IRC.
+|             test-irc-config.cpp | Tests : configuration IRC.
+|             test-irc-ctcp.cpp | Tests : CTCP IRC.
+|             test-irc-ignore.cpp | Tests : ignores IRC.
+|             test-irc-info.cpp | Tests : infos IRC.
+|             test-irc-join.cpp | Tests : fonctions de join IRC.
+|             test-irc-list.cpp | Tests : tampon IRC pour la réponse à la commande /list.
+|             test-irc-message.cpp | Tests : messages IRC.
+|             test-irc-mode.cpp | Tests : modes IRC.
+|             test-irc-nick.cpp | Tests : pseudos IRC.
+|             test-irc-protocol.cpp | Tests : protocole IRC.
+|             test-irc-sasl.cpp | Tests : authentification SASL avec le protocole IRC.
+|             test-irc-server.cpp | Tests : serveur IRC.
+|             test-irc-tag.cpp | Tests : étiquettes des messages IRC.
+|          logger/ | Racine des tests unitaires pour l'extension logger.
+|             test-logger.cpp | Tests : logger.
+|             test-logger-backlog.cpp | Tests : backlog logger.
+|             test-logger-tail.cpp | Tests : fonctions "tail".
+|          trigger/ | Racine des tests unitaires pour l'extension trigger.
+|             test-trigger.cpp | Tests : triggers.
+|             test-trigger-config.cpp | Tests : configuration trigger.
+|          typing/ | Racine des tests unitaires pour l'extension typing.
+|             test-typing.cpp | Tests : typing.
+|             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.
+|             test-relay-websocket.cpp | Tests : fonctions websocket 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.
+|             test-xfer-file.cpp | Tests : fonctions sur les fichiers.
+|             test-xfer-network.cpp | Tests : fonctions réseau.
|===
[[documentation_translations]]
diff --git a/doc/ja/weechat_dev.ja.adoc b/doc/ja/weechat_dev.ja.adoc
index e95e72b57..3c7da75b8 100644
--- a/doc/ja/weechat_dev.ja.adoc
+++ b/doc/ja/weechat_dev.ja.adoc
@@ -428,143 +428,145 @@ WeeChat "core" は以下のディレクトリに配置されています:
[width="100%",cols="2m,3",options="header"]
|===
-| パス/ファイル名 | 説明
-| tests/ | テスト用のルートディレクトリ
-|    tests.cpp | 全テストの実行時に使われるプログラム
+| パス/ファイル名 | 説明
+| tests/ | テスト用のルートディレクトリ
+|    tests.cpp | 全テストの実行時に使われるプログラム
// TRANSLATION MISSING
-|    tests-record.cpp | Record and search in messages displayed.
-|    scripts/ | スクリプト API テスト用のルートディレクトリ
-|       test-scripts.cpp | スクリプト API テストの実行時に使われるプログラム
-|       python/ | スクリプト API テストを生成、実行する Python スクリプト
-|          testapigen.py | スクリプト API のテスト時にすべての言語に関するスクリプトを生成する Python スクリプト
-|          testapi.py | スクリプト API テスト時に使われる Python スクリプト (スクリプト testapigen.py から使われます)
-|          unparse.py | Python コードを別の言語に変換 (スクリプト testapigen.py から使われます)
-|    unit/ | 単体テスト用のルートディレクトリ
-|       test-plugins.cpp | テスト: プラグイン
+|    tests-record.cpp | Record and search in messages displayed.
+|    scripts/ | スクリプト API テスト用のルートディレクトリ
+|       test-scripts.cpp | スクリプト API テストの実行時に使われるプログラム
+|       python/ | スクリプト API テストを生成、実行する Python スクリプト
+|          testapigen.py | スクリプト API のテスト時にすべての言語に関するスクリプトを生成する Python スクリプト
+|          testapi.py | スクリプト API テスト時に使われる Python スクリプト (スクリプト testapigen.py から使われます)
+|          unparse.py | Python コードを別の言語に変換 (スクリプト testapigen.py から使われます)
+|    unit/ | 単体テスト用のルートディレクトリ
+|       test-plugins.cpp | テスト: プラグイン
// TRANSLATION MISSING
-|       test-plugin-api-info.cpp | Tests: plugin API info functions.
+|       test-plugin-api-info.cpp | Tests: plugin API info functions.
// TRANSLATION MISSING
-|       test-plugin-config.cpp | Tests: plugin config functions.
-|       core/ | core 向け単体テスト用のルートディレクトリ
-|          test-core-arraylist.cpp | テスト: 配列リスト
+|       test-plugin-config.cpp | Tests: plugin config functions.
+|       core/ | core 向け単体テスト用のルートディレクトリ
+|          test-core-arraylist.cpp | テスト: 配列リスト
// TRANSLATION MISSING
-|          test-core-calc.cpp | Tests: calculation of expressions.
+|          test-core-calc.cpp | Tests: calculation of expressions.
// TRANSLATION MISSING
-|          test-core-command.cpp | Tests: commands.
+|          test-core-command.cpp | Tests: commands.
// TRANSLATION MISSING
-|          test-core-config-file.cpp | Tests: configuration files.
+|          test-core-config-file.cpp | Tests: configuration files.
// TRANSLATION MISSING
-|          test-core-crypto.cpp | Tests: cryptographic functions.
+|          test-core-crypto.cpp | Tests: cryptographic functions.
// TRANSLATION MISSING
-|          test-core-dir.cpp | Tests: directory/file functions.
-|          test-core-eval.cpp | テスト: 式の評価
-|          test-core-hashtble.cpp | テスト: ハッシュテーブル
-|          test-core-hdata.cpp | テスト: hdata
-|          test-core-hook.cpp | テスト: フック
-|          test-core-infolist.cpp | テスト: インフォリスト
-|          test-core-list.cpp | テスト: リスト
+|          test-core-dir.cpp | Tests: directory/file functions.
+|          test-core-eval.cpp | テスト: 式の評価
+|          test-core-hashtble.cpp | テスト: ハッシュテーブル
+|          test-core-hdata.cpp | テスト: hdata
+|          test-core-hook.cpp | テスト: フック
+|          test-core-infolist.cpp | テスト: インフォリスト
+|          test-core-list.cpp | テスト: リスト
// TRANSLATION MISSING
-|          test-core-network.cpp | Tests: network functions.
-|          test-core-secure.cpp | テスト: データ保護
+|          test-core-network.cpp | Tests: network functions.
+|          test-core-secure.cpp | テスト: データ保護
// TRANSLATION MISSING
-|          test-core-signal.cpp | テスト: signals.
-|          test-core-string.cpp | テスト: 文字列
-|          test-core-url.cpp | テスト: URL
-|          test-core-utf8.cpp | テスト: UTF-8
-|          test-core-util.cpp | テスト: ユーティリティ関数
+|          test-core-signal.cpp | テスト: signals.
+|          test-core-string.cpp | テスト: 文字列
+|          test-core-url.cpp | テスト: URL
+|          test-core-utf8.cpp | テスト: UTF-8
+|          test-core-util.cpp | テスト: ユーティリティ関数
// TRANSLATION MISSING
-|          test-core-sys.cpp | Tests: system functions.
+|          test-core-sys.cpp | Tests: system functions.
// TRANSLATION MISSING
-|          hook/ | Root of unit tests for hooks.
+|          hook/ | Root of unit tests for hooks.
// TRANSLATION MISSING
-|             test-hook-command.cpp | Tests: hooks "command".
-|       gui/ | インターフェースの単体テストを収める最上位ディレクトリ
+|             test-hook-command.cpp | Tests: hooks "command".
+|       gui/ | インターフェースの単体テストを収める最上位ディレクトリ
// TRANSLATION MISSING
-|          test-gui-bar-window.cpp | Tests: bar window functions.
+|          test-gui-bar-window.cpp | Tests: bar window functions.
// TRANSLATION MISSING
-|          test-gui-buffer.cpp | Tests: buffer functions.
+|          test-gui-buffer.cpp | Tests: buffer functions.
// TRANSLATION MISSING
-|          test-gui-chat.cpp | Tests: chat functions.
+|          test-gui-chat.cpp | Tests: chat functions.
// TRANSLATION MISSING
-|          test-gui-color.cpp | Tests: colors.
+|          test-gui-color.cpp | Tests: colors.
// TRANSLATION MISSING
-|          test-gui-filter.cpp | Tests: filters.
+|          test-gui-filter.cpp | Tests: filters.
// TRANSLATION MISSING
-|          test-gui-input.cpp | Tests: input functions.
+|          test-gui-input.cpp | Tests: input functions.
// TRANSLATION MISSING
-|          test-gui-key.cpp | Tests: keys.
-|          test-gui-line.cpp | テスト: 行
+|          test-gui-key.cpp | Tests: keys.
+|          test-gui-line.cpp | テスト: 行
// TRANSLATION MISSING
-|          test-gui-nick.cpp | テスト: nicks
-|       plugins/ | プラグインの単体テストを収める最上位ディレクトリ
-|          irc/ | IRC プラグインの単体テストを収める最上位ディレクトリ
+|          test-gui-nick.cpp | テスト: nicks
+|       plugins/ | プラグインの単体テストを収める最上位ディレクトリ
+|          irc/ | IRC プラグインの単体テストを収める最上位ディレクトリ
// TRANSLATION MISSING
-|             test-irc-batch.cpp | Tests: IRC batched events.
+|             test-irc-batch.cpp | Tests: IRC batched events.
// TRANSLATION MISSING
-|             test-irc-buffer.cpp | Tests: IRC buffers.
+|             test-irc-buffer.cpp | Tests: IRC buffers.
// TRANSLATION MISSING
-|             test-irc-channel.cpp | Tests: IRC channels.
-|             test-irc-color.cpp | Tests: IRC colors.
-|             test-irc-config.cpp | テスト: IRC 設定
+|             test-irc-channel.cpp | Tests: IRC channels.
+|             test-irc-color.cpp | Tests: IRC colors.
+|             test-irc-config.cpp | テスト: IRC 設定
// TRANSLATION MISSING
-|             test-irc-ctcp.cpp | Tests: IRC CTCP.
+|             test-irc-ctcp.cpp | Tests: IRC CTCP.
// TRANSLATION MISSING
-|             test-irc-ignore.cpp | Tests: IRC ignores.
+|             test-irc-ignore.cpp | Tests: IRC ignores.
// TRANSLATION MISSING
-|             test-irc-info.cpp | Tests: IRC info.
+|             test-irc-info.cpp | Tests: IRC info.
// TRANSLATION MISSING
-|             test-irc-join.cpp | Tests: IRC join functions.
+|             test-irc-join.cpp | Tests: IRC join functions.
// TRANSLATION MISSING
-|             test-irc-list.cpp | Tests: IRC buffer for reply to /list command.
+|             test-irc-list.cpp | Tests: IRC buffer for reply to /list command.
// TRANSLATION MISSING
-|             test-irc-message.cpp | Tests: IRC messages.
+|             test-irc-message.cpp | Tests: IRC messages.
// TRANSLATION MISSING
-|             test-irc-mode.cpp | Tests: IRC modes.
+|             test-irc-mode.cpp | Tests: IRC modes.
// TRANSLATION MISSING
-|             test-irc-nick.cpp | Tests: IRC nicks.
-|             test-irc-protocol.cpp | テスト: IRC プロトコル
+|             test-irc-nick.cpp | Tests: IRC nicks.
+|             test-irc-protocol.cpp | テスト: IRC プロトコル
// TRANSLATION MISSING
-|             test-irc-sasl.cpp | Tests: SASL authentication with IRC protocol.
+|             test-irc-sasl.cpp | Tests: SASL authentication with IRC protocol.
// TRANSLATION MISSING
-|             test-irc-server.cpp | Tests: IRC server.
+|             test-irc-server.cpp | Tests: IRC server.
// TRANSLATION MISSING
-|             test-irc-tag.cpp | Tests: IRC message tags.
+|             test-irc-tag.cpp | Tests: IRC message tags.
// TRANSLATION MISSING
-|          logger/ | Root of unit tests for logger plugin.
+|          logger/ | Root of unit tests for logger plugin.
// TRANSLATION MISSING
-|             test-logger.cpp | Tests: logger.
+|             test-logger.cpp | Tests: logger.
// TRANSLATION MISSING
-|             test-logger-backlog.cpp | Tests: logger backlog.
+|             test-logger-backlog.cpp | Tests: logger backlog.
// TRANSLATION MISSING
-|             test-logger-tail.cpp | Tests: logger tail functions.
+|             test-logger-tail.cpp | Tests: logger tail functions.
// TRANSLATION MISSING
-|          trigger/ | Root of unit tests for trigger plugin.
+|          trigger/ | Root of unit tests for trigger plugin.
// TRANSLATION MISSING
-|             test-trigger.cpp | Tests: triggers.
+|             test-trigger.cpp | Tests: triggers.
// TRANSLATION MISSING
-|             test-trigger-config.cpp | Tests: trigger configuration.
+|             test-trigger-config.cpp | Tests: trigger configuration.
// TRANSLATION MISSING
-|          typing/ | Root of unit tests for typing plugin.
+|          typing/ | Root of unit tests for typing plugin.
// TRANSLATION MISSING
-|             test-typing.cpp | Tests: typing.
+|             test-typing.cpp | Tests: typing.
// TRANSLATION MISSING
-|             test-typing-status.cpp | Tests: typing status.
+|             test-typing-status.cpp | Tests: typing status.
// TRANSLATION MISSING
-|          relay/ | Root of unit tests for Relay plugin.
+|          relay/ | Root of unit tests for Relay plugin.
// TRANSLATION MISSING
-|             test-relay-auth.cpp | Tests: clients authentication.
+|             test-relay-auth.cpp | Tests: clients authentication.
// TRANSLATION MISSING
-|             test-relay-http.cpp | Tests: HTTP functions for Relay plugin.
+|             test-relay-http.cpp | Tests: HTTP functions for Relay plugin.
// TRANSLATION MISSING
-|             irc/ | Root of unit tests for Relay "irc" protocol.
+|             test-relay-websocket.cpp | Tests: websocket functions for Relay plugin.
// TRANSLATION MISSING
-|                test-relay-irc.cpp | Tests: Relay "irc" protocol.
+|             irc/ | Root of unit tests for Relay "irc" protocol.
// TRANSLATION MISSING
-|          xfer/ | Root of unit tests for Xfer plugin.
+|                test-relay-irc.cpp | Tests: Relay "irc" protocol.
// TRANSLATION MISSING
-|             test-xfer-file.cpp | Tests: file functions.
+|          xfer/ | Root of unit tests for Xfer plugin.
// TRANSLATION MISSING
-|             test-xfer-network.cpp | Tests: network functions.
+|             test-xfer-file.cpp | Tests: file functions.
+// TRANSLATION MISSING
+|             test-xfer-network.cpp | Tests: network functions.
|===
[[documentation_translations]]
diff --git a/doc/sr/weechat_dev.sr.adoc b/doc/sr/weechat_dev.sr.adoc
index b6924ecec..7473624d2 100644
--- a/doc/sr/weechat_dev.sr.adoc
+++ b/doc/sr/weechat_dev.sr.adoc
@@ -404,91 +404,93 @@ WeeChat „језгро” се налази у следећим директо
[width="100%", cols="2m,3", options="header"]
|===
-| Путања/фајл | Опис
-| tests/ | Корен тестова.
-|    tests.cpp | Програм који се користи за извршавање свих тестова.
-|    tests-record.cpp | Бележење и претрага у приказаним порукама.
-|    scripts/ | Корен тестова за API скриптовања.
-|       test-scripts.cpp | Програм који се користи за извршавање тестова API скриптовања.
-|       python/ | Python скрипте које генеришу и покрећу тестове API скриптовања.
-|          testapigen.py | Python скрипта која генерише скрипте на свим језицима за тестирање API скриптовања.
-|          testapi.py | Python скрипта са тестовима API скриптовања, користи је скрипта testapigen.py.
-|          unparse.py | Конверзија Python кода у остале језике, користи је скрипта testapigen.py.
-|    unit/ | Корен unit тестова.
-|       test-plugins.cpp | Тестови: plugins.
-|       test-plugin-api-info.cpp | Тестови: инфо функције API додатака.
-|       test-plugin-config.cpp | Тестови: функције конфигурације додатка.
-|       core/ | Корен unit тестова језгра.
-|          test-core-arraylist.cpp | Тестови: arraylists.
-|          test-core-calc.cpp | Тестови: калкулација израза.
-|          test-core-command.cpp | Тестови: команде.
-|          test-core-config-file.cpp | Тестови: конфигурациони фајлови.
-|          test-core-crypto.cpp | Тестови: криптографске функције.
-|          test-core-dir.cpp | Тестови: функције директоријума/фајла.
-|          test-core-eval.cpp | Тестови: израчунавање израза.
-|          test-core-hashtble.cpp | Тестови: hashtables.
-|          test-core-hdata.cpp | Тестови: hdata.
-|          test-core-hook.cpp | Тестови: куке.
-|          test-core-infolist.cpp | Тестови: infolists.
-|          test-core-list.cpp | Тестови: листе.
-|          test-core-network.cpp | Тестови: мрежне функције.
-|          test-core-secure.cpp | Тестови: обезбеђени подаци.
-|          test-core-signal.cpp | Тестови: сигнали.
-|          test-core-string.cpp | Тестови: стрингови.
-|          test-core-url.cpp | Тестови: URL адресе.
-|          test-core-utf8.cpp | Тестови: UTF-8.
-|          test-core-util.cpp | Тестови: помоћне функције.
-|          test-core-sys.cpp | Тестови: системске функције.
-|          hook/ | Корен unit тестова за куке.
-|             test-hook-command.cpp | Тестови: куке „command”.
-|       gui/ | Корен unit тестова интерфејса.
-|          test-gui-bar-window.cpp | Тестови: функције прозора траке.
-|          test-gui-buffer.cpp | Тестови: бафер функције.
-|          test-gui-chat.cpp | Тестови: чет функције.
-|          test-gui-color.cpp | Тестови: боје.
-|          test-gui-filter.cpp | Тестови: филтери.
-|          test-gui-input.cpp | Тестови: улазне функкције.
-|          test-gui-key.cpp | Тестови: тастери.
-|          test-gui-line.cpp | Тестови: линије.
-|          test-gui-nick.cpp | Тестови: надимци.
-|       plugins/ | Корен unit тестова додатака.
-|          irc/ | Корен unit тестова IRC додатка.
-|             test-irc-batch.cpp | Тестови: IRC пакетни догађаји.
-|             test-irc-buffer.cpp | Тестови: IRC бафери.
-|             test-irc-channel.cpp | Тестови: IRC канали.
-|             test-irc-color.cpp | Тестови: IRC боје.
-|             test-irc-config.cpp | Тестови: IRC конфигурација.
-|             test-irc-ctcp.cpp | Тестови: IRC CTCP.
-|             test-irc-ignore.cpp | Тестови: IRC игнорисања.
-|             test-irc-info.cpp | Тестови: IRC информације.
-|             test-irc-join.cpp | Тестови: IRC функције приступања.
-|             test-irc-list.cpp | Тестови: IRC бафер за одговор на /list команду.
-|             test-irc-message.cpp | Тестови: IRC поруке.
-|             test-irc-mode.cpp | Тестови: IRC режими.
-|             test-irc-nick.cpp | Тестови: IRC надимци.
-|             test-irc-protocol.cpp | Тестови: IRC протокол.
-|             test-irc-sasl.cpp | Тестови: SASL аутентификација са IRC протоколом.
-|             test-irc-server.cpp | Тестови: IRC сервер.
-|             test-irc-tag.cpp | Тестови: IRC ознаке порука.
-|          logger/ | Корен unit тестива за logger додатак.
-|             test-logger.cpp | Тестови: logger.
-|             test-logger-backlog.cpp | Тестови: logger заостатак.
-|             test-logger-tail.cpp | Тестови: logger tail фунцкије.
-|          trigger/ | Корен unit тестова за окидач додатак.
-|             test-trigger.cpp | Тестови: окидачи.
-|             test-trigger-config.cpp | Тестови: конфигурација окидача.
-|          typing/ | Корен unit тестова за typing додатак.
-|             test-typing.cpp | Тестови: typing.
-|             test-typing-status.cpp | Тестови: typing статус.
-|          relay/ | Корен unit тестова за Релеј додатак.
-|             test-relay-auth.cpp | Тестови: аутентификација клијената.
+| Путања/фајл | Опис
+| tests/ | Корен тестова.
+|    tests.cpp | Програм који се користи за извршавање свих тестова.
+|    tests-record.cpp | Бележење и претрага у приказаним порукама.
+|    scripts/ | Корен тестова за API скриптовања.
+|       test-scripts.cpp | Програм који се користи за извршавање тестова API скриптовања.
+|       python/ | Python скрипте које генеришу и покрећу тестове API скриптовања.
+|          testapigen.py | Python скрипта која генерише скрипте на свим језицима за тестирање API скриптовања.
+|          testapi.py | Python скрипта са тестовима API скриптовања, користи је скрипта testapigen.py.
+|          unparse.py | Конверзија Python кода у остале језике, користи је скрипта testapigen.py.
+|    unit/ | Корен unit тестова.
+|       test-plugins.cpp | Тестови: plugins.
+|       test-plugin-api-info.cpp | Тестови: инфо функције API додатака.
+|       test-plugin-config.cpp | Тестови: функције конфигурације додатка.
+|       core/ | Корен unit тестова језгра.
+|          test-core-arraylist.cpp | Тестови: arraylists.
+|          test-core-calc.cpp | Тестови: калкулација израза.
+|          test-core-command.cpp | Тестови: команде.
+|          test-core-config-file.cpp | Тестови: конфигурациони фајлови.
+|          test-core-crypto.cpp | Тестови: криптографске функције.
+|          test-core-dir.cpp | Тестови: функције директоријума/фајла.
+|          test-core-eval.cpp | Тестови: израчунавање израза.
+|          test-core-hashtble.cpp | Тестови: hashtables.
+|          test-core-hdata.cpp | Тестови: hdata.
+|          test-core-hook.cpp | Тестови: куке.
+|          test-core-infolist.cpp | Тестови: infolists.
+|          test-core-list.cpp | Тестови: листе.
+|          test-core-network.cpp | Тестови: мрежне функције.
+|          test-core-secure.cpp | Тестови: обезбеђени подаци.
+|          test-core-signal.cpp | Тестови: сигнали.
+|          test-core-string.cpp | Тестови: стрингови.
+|          test-core-url.cpp | Тестови: URL адресе.
+|          test-core-utf8.cpp | Тестови: UTF-8.
+|          test-core-util.cpp | Тестови: помоћне функције.
+|          test-core-sys.cpp | Тестови: системске функције.
+|          hook/ | Корен unit тестова за куке.
+|             test-hook-command.cpp | Тестови: куке „command”.
+|       gui/ | Корен unit тестова интерфејса.
+|          test-gui-bar-window.cpp | Тестови: функције прозора траке.
+|          test-gui-buffer.cpp | Тестови: бафер функције.
+|          test-gui-chat.cpp | Тестови: чет функције.
+|          test-gui-color.cpp | Тестови: боје.
+|          test-gui-filter.cpp | Тестови: филтери.
+|          test-gui-input.cpp | Тестови: улазне функкције.
+|          test-gui-key.cpp | Тестови: тастери.
+|          test-gui-line.cpp | Тестови: линије.
+|          test-gui-nick.cpp | Тестови: надимци.
+|       plugins/ | Корен unit тестова додатака.
+|          irc/ | Корен unit тестова IRC додатка.
+|             test-irc-batch.cpp | Тестови: IRC пакетни догађаји.
+|             test-irc-buffer.cpp | Тестови: IRC бафери.
+|             test-irc-channel.cpp | Тестови: IRC канали.
+|             test-irc-color.cpp | Тестови: IRC боје.
+|             test-irc-config.cpp | Тестови: IRC конфигурација.
+|             test-irc-ctcp.cpp | Тестови: IRC CTCP.
+|             test-irc-ignore.cpp | Тестови: IRC игнорисања.
+|             test-irc-info.cpp | Тестови: IRC информације.
+|             test-irc-join.cpp | Тестови: IRC функције приступања.
+|             test-irc-list.cpp | Тестови: IRC бафер за одговор на /list команду.
+|             test-irc-message.cpp | Тестови: IRC поруке.
+|             test-irc-mode.cpp | Тестови: IRC режими.
+|             test-irc-nick.cpp | Тестови: IRC надимци.
+|             test-irc-protocol.cpp | Тестови: IRC протокол.
+|             test-irc-sasl.cpp | Тестови: SASL аутентификација са IRC протоколом.
+|             test-irc-server.cpp | Тестови: IRC сервер.
+|             test-irc-tag.cpp | Тестови: IRC ознаке порука.
+|          logger/ | Корен unit тестива за logger додатак.
+|             test-logger.cpp | Тестови: logger.
+|             test-logger-backlog.cpp | Тестови: logger заостатак.
+|             test-logger-tail.cpp | Тестови: logger tail фунцкије.
+|          trigger/ | Корен unit тестова за окидач додатак.
+|             test-trigger.cpp | Тестови: окидачи.
+|             test-trigger-config.cpp | Тестови: конфигурација окидача.
+|          typing/ | Корен unit тестова за typing додатак.
+|             test-typing.cpp | Тестови: typing.
+|             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 додатак.
-|             test-xfer-file.cpp | Тестови: фајл функције.
-|             test-xfer-network.cpp | Тестови: мрежне функције.
+|             test-relay-http.cpp | Tests: HTTP functions for Relay plugin.
+// TRANSLATION MISSING
+|             test-relay-websocket.cpp | Tests: websocket functions for Relay plugin.
+|             irc/ | Корен unit тестова за Релеј „irc” протокол.
+|                test-relay-irc.cpp | Тестови: РЕлеј „irc” протокол.
+|          xfer/ | Корен unit тестова за Xfer додатак.
+|             test-xfer-file.cpp | Тестови: фајл функције.
+|             test-xfer-network.cpp | Тестови: мрежне функције.
|===
[[documentation_translations]]
diff --git a/src/plugins/relay/api/relay-api-msg.c b/src/plugins/relay/api/relay-api-msg.c
index df9c139cc..930603f26 100644
--- a/src/plugins/relay/api/relay-api-msg.c
+++ b/src/plugins/relay/api/relay-api-msg.c
@@ -32,6 +32,7 @@
#include "../relay.h"
#include "../relay-client.h"
#include "../relay-http.h"
+#include "../relay-websocket.h"
#include "relay-api.h"
#include "relay-api-msg.h"
#include "relay-api-protocol.h"
diff --git a/src/plugins/relay/api/relay-api-protocol.c b/src/plugins/relay/api/relay-api-protocol.c
index 02601c745..fb1983422 100644
--- a/src/plugins/relay/api/relay-api-protocol.c
+++ b/src/plugins/relay/api/relay-api-protocol.c
@@ -32,6 +32,7 @@
#include "../relay-client.h"
#include "../relay-config.h"
#include "../relay-http.h"
+#include "../relay-websocket.h"
#include "relay-api.h"
#include "relay-api-msg.h"
#include "relay-api-protocol.h"
diff --git a/src/plugins/relay/relay-client.c b/src/plugins/relay/relay-client.c
index 1bcc8294f..fa62d8f20 100644
--- a/src/plugins/relay/relay-client.c
+++ b/src/plugins/relay/relay-client.c
@@ -28,8 +28,8 @@
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
-
#include <gnutls/gnutls.h>
+#include <zlib.h>
#include "../weechat-plugin.h"
#include "relay.h"
@@ -550,6 +550,85 @@ relay_client_recv_text_buffer (struct t_relay_client *client,
}
/*
+ * Reads websocket frames.
+ */
+
+void
+relay_client_read_websocket_frames (struct t_relay_client *client,
+ struct t_relay_websocket_frame *frames,
+ int num_frames)
+{
+ int i;
+
+ if (!frames || (num_frames <= 0))
+ return;
+
+ for (i = 0; i < num_frames; i++)
+ {
+ if (frames[i].payload_size == 0)
+ {
+ /*
+ * When decoded length is 0, assume client sent a PONG frame.
+ *
+ * RFC 6455 Section 5.5.3:
+ *
+ * "A Pong frame MAY be sent unsolicited. This serves as a
+ * unidirectional heartbeat. A response to an unsolicited
+ * Pong frame is not expected."
+ */
+ continue;
+ }
+ switch (frames[i].opcode)
+ {
+ case RELAY_CLIENT_MSG_PING:
+ /* print message in raw buffer */
+ relay_raw_print (client, RELAY_CLIENT_MSG_PING,
+ RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY,
+ frames[i].payload,
+ frames[i].payload_size);
+ /* answer with a PONG */
+ relay_client_send (client,
+ RELAY_CLIENT_MSG_PONG,
+ frames[i].payload,
+ frames[i].payload_size,
+ NULL);
+ break;
+ case RELAY_CLIENT_MSG_CLOSE:
+ /* print message in raw buffer */
+ relay_raw_print (client, RELAY_CLIENT_MSG_CLOSE,
+ RELAY_RAW_FLAG_RECV | RELAY_RAW_FLAG_BINARY,
+ frames[i].payload,
+ frames[i].payload_size);
+ /* answer with a CLOSE */
+ relay_client_send (client,
+ RELAY_CLIENT_MSG_CLOSE,
+ frames[i].payload,
+ frames[i].payload_size,
+ NULL);
+ /* close the connection */
+ relay_client_set_status (client, RELAY_STATUS_DISCONNECTED);
+ /* ignore any other message after the close */
+ return;
+ default:
+ if (frames[i].payload)
+ {
+ if ((client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING)
+ || (client->recv_data_type == RELAY_CLIENT_DATA_HTTP))
+ {
+ relay_http_recv (client, frames[i].payload);
+ }
+ else if ((client->recv_data_type == RELAY_CLIENT_DATA_TEXT_LINE)
+ || (client->recv_data_type == RELAY_CLIENT_DATA_TEXT_MULTILINE))
+ {
+ relay_client_recv_text (client, frames[i].payload);
+ }
+ }
+ break;
+ }
+ }
+}
+
+/*
* Reads data from a client.
*/
@@ -557,10 +636,9 @@ int
relay_client_recv_cb (const void *pointer, void *data, int fd)
{
struct t_relay_client *client;
- static char buffer[4096], decoded[8192 + 1];
- const char *ptr_buffer;
- int num_read, rc;
- unsigned long long decoded_length, length_buffer;
+ static char buffer[4096];
+ int i, num_read, rc, num_frames;
+ struct t_relay_websocket_frame *frames;
/* make C compiler happy */
(void) data;
@@ -590,8 +668,6 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
if (num_read > 0)
{
buffer[num_read] = '\0';
- ptr_buffer = buffer;
- length_buffer = num_read;
/*
* if we are receiving the first message from client, check if it looks
@@ -599,7 +675,7 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
*/
if (client->bytes_recv == 0)
{
- if (relay_websocket_is_valid_http_get (client, buffer))
+ if (relay_websocket_is_valid_http_get (client->protocol, buffer))
{
/*
* web socket is just initializing for now, it's not accepted
@@ -615,23 +691,12 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
if (client->websocket == RELAY_CLIENT_WEBSOCKET_READY)
{
/* websocket used, decode message */
- rc = relay_websocket_decode_frame ((unsigned char *)buffer,
+ frames = NULL;
+ num_frames = 0;
+ rc = relay_websocket_decode_frame (client,
+ (unsigned char *)buffer,
(unsigned long long)num_read,
- (unsigned char *)decoded,
- &decoded_length);
- if (decoded_length == 0)
- {
- /*
- * When decoded length is 0, assume client sent a PONG frame.
- *
- * RFC 6455 Section 5.5.3:
- *
- * "A Pong frame MAY be sent unsolicited. This serves as a
- * unidirectional heartbeat. A response to an unsolicited
- * Pong frame is not expected."
- */
- return WEECHAT_RC_OK;
- }
+ &frames, &num_frames);
if (!rc)
{
/* error when decoding frame: close connection */
@@ -646,22 +711,26 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
relay_client_set_status (client, RELAY_STATUS_DISCONNECTED);
return WEECHAT_RC_OK;
}
- ptr_buffer = decoded;
- length_buffer = decoded_length;
- }
-
- if ((client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING)
- || (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/HTTP data for this client */
- relay_client_recv_text_buffer (client, ptr_buffer, length_buffer);
+ relay_client_read_websocket_frames (client, frames, num_frames);
+ for (i = 0; i < num_frames; i++)
+ {
+ if (frames[i].payload)
+ free (frames[i].payload);
+ }
+ free (frames);
}
else
{
- /* receive buffer as-is (binary data) */
- /* currently, all supported protocols receive only text, no binary */
+ if ((client->websocket == RELAY_CLIENT_WEBSOCKET_INITIALIZING)
+ || (client->recv_data_type == RELAY_CLIENT_DATA_HTTP))
+ {
+ relay_http_recv (client, buffer);
+ }
+ else if ((client->recv_data_type == RELAY_CLIENT_DATA_TEXT_LINE)
+ || (client->recv_data_type == RELAY_CLIENT_DATA_TEXT_MULTILINE))
+ {
+ relay_client_recv_text (client, buffer);
+ }
}
relay_buffer_refresh (NULL);
}
@@ -1103,9 +1172,8 @@ relay_client_send (struct t_relay_client *client,
WEBSOCKET_FRAME_OPCODE_TEXT : WEBSOCKET_FRAME_OPCODE_BINARY;
break;
}
- websocket_frame = relay_websocket_encode_frame (opcode, data,
- data_size,
- &length_frame);
+ websocket_frame = relay_websocket_encode_frame (
+ client, opcode, data, data_size, &length_frame);
if (websocket_frame)
{
ptr_data = websocket_frame;
@@ -1307,6 +1375,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->ws_deflate = relay_websocket_deflate_alloc ();
new_client->http_req = relay_http_request_alloc ();
new_client->address = strdup ((address && address[0]) ?
address : "local");
@@ -1516,6 +1585,8 @@ relay_client_new_with_infolist (struct t_infolist *infolist)
{
struct t_relay_client *new_client;
const char *str;
+ Bytef *ptr_dict;
+ int dict_size;
new_client = malloc (sizeof (*new_client));
if (new_client)
@@ -1533,6 +1604,46 @@ 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->ws_deflate = relay_websocket_deflate_alloc ();
+ new_client->ws_deflate->enabled = weechat_infolist_integer (infolist, "ws_deflate_enabled");
+ new_client->ws_deflate->server_context_takeover = weechat_infolist_integer (infolist, "ws_deflate_server_context_takeover");
+ new_client->ws_deflate->client_context_takeover = weechat_infolist_integer (infolist, "ws_deflate_client_context_takeover");
+ new_client->ws_deflate->window_bits_deflate = weechat_infolist_integer (infolist, "ws_deflate_window_bits_deflate");
+ new_client->ws_deflate->window_bits_inflate = weechat_infolist_integer (infolist, "ws_deflate_window_bits_inflate");
+ new_client->ws_deflate->strm_deflate = NULL;
+ new_client->ws_deflate->strm_inflate = NULL;
+ if (weechat_infolist_search_var (infolist, "ws_deflate_strm_deflate_dict"))
+ {
+ ptr_dict = weechat_infolist_buffer (infolist, "ws_deflate_strm_deflate_dict", &dict_size);
+ if (ptr_dict)
+ {
+ new_client->ws_deflate->strm_deflate = calloc (1, sizeof (*new_client->ws_deflate->strm_deflate));
+ if (new_client->ws_deflate->strm_deflate)
+ {
+ if (relay_websocket_deflate_init_stream_deflate (new_client->ws_deflate))
+ {
+ deflateSetDictionary (new_client->ws_deflate->strm_deflate,
+ ptr_dict, dict_size);
+ }
+ }
+ }
+ }
+ if (weechat_infolist_search_var (infolist, "ws_deflate_strm_inflate_dict"))
+ {
+ ptr_dict = weechat_infolist_buffer (infolist, "ws_deflate_strm_inflate_dict", &dict_size);
+ if (ptr_dict)
+ {
+ new_client->ws_deflate->strm_inflate = calloc (1, sizeof (*new_client->ws_deflate->strm_inflate));
+ if (new_client->ws_deflate->strm_inflate)
+ {
+ if (relay_websocket_deflate_init_stream_inflate (new_client->ws_deflate))
+ {
+ inflateSetDictionary (new_client->ws_deflate->strm_inflate,
+ ptr_dict, dict_size);
+ }
+ }
+ }
+ }
new_client->http_req = relay_http_request_alloc ();
new_client->address = strdup (weechat_infolist_string (infolist, "address"));
str = weechat_infolist_string (infolist, "real_ip");
@@ -1780,6 +1891,7 @@ relay_client_free (struct t_relay_client *client)
free (client->nonce);
if (client->hook_timer_handshake)
weechat_unhook (client->hook_timer_handshake);
+ relay_websocket_deflate_free (client->ws_deflate);
relay_http_request_free (client->http_req);
if (client->hook_fd)
weechat_unhook (client->hook_fd);
@@ -1881,6 +1993,8 @@ relay_client_add_to_infolist (struct t_infolist *infolist,
{
struct t_infolist_item *ptr_item;
char value[128];
+ Bytef *dict;
+ uInt dict_size;
if (!infolist || !client)
return 0;
@@ -1929,6 +2043,47 @@ relay_client_add_to_infolist (struct t_infolist *infolist,
return 0;
if (!weechat_infolist_new_var_integer (ptr_item, "websocket", client->websocket))
return 0;
+ if (!weechat_infolist_new_var_integer (ptr_item, "ws_deflate_enabled", client->ws_deflate->enabled))
+ return 0;
+ if (!weechat_infolist_new_var_integer (ptr_item, "ws_deflate_server_context_takeover", client->ws_deflate->server_context_takeover))
+ return 0;
+ if (!weechat_infolist_new_var_integer (ptr_item, "ws_deflate_client_context_takeover", client->ws_deflate->client_context_takeover))
+ return 0;
+ if (!weechat_infolist_new_var_integer (ptr_item, "ws_deflate_window_bits_deflate", client->ws_deflate->window_bits_deflate))
+ return 0;
+ if (!weechat_infolist_new_var_integer (ptr_item, "ws_deflate_window_bits_inflate", client->ws_deflate->window_bits_inflate))
+ return 0;
+ if (!weechat_infolist_new_var_pointer (ptr_item, "ws_deflate_strm_deflate", client->ws_deflate->strm_deflate))
+ return 0;
+ if (!weechat_infolist_new_var_pointer (ptr_item, "ws_deflate_strm_inflate", client->ws_deflate->strm_inflate))
+ return 0;
+ if (client->ws_deflate->strm_deflate || client->ws_deflate->strm_inflate)
+ {
+ /* save the deflate/inflate dictionary, as it's required after /upgrade */
+ dict = malloc (32768);
+ if (dict)
+ {
+ if (client->ws_deflate->strm_deflate)
+ {
+ if (deflateGetDictionary (client->ws_deflate->strm_deflate, dict, &dict_size) == Z_OK)
+ {
+ weechat_infolist_new_var_buffer (ptr_item,
+ "ws_deflate_strm_deflate_dict",
+ dict, dict_size);
+ }
+ }
+ if (client->ws_deflate->strm_inflate)
+ {
+ if (inflateGetDictionary (client->ws_deflate->strm_inflate, dict, &dict_size) == Z_OK)
+ {
+ weechat_infolist_new_var_buffer (ptr_item,
+ "ws_deflate_strm_inflate_dict",
+ dict, dict_size);
+ }
+ }
+ free (dict);
+ }
+ }
if (!weechat_infolist_new_var_string (ptr_item, "address", client->address))
return 0;
if (!weechat_infolist_new_var_string (ptr_item, "real_ip", client->real_ip))
@@ -2015,7 +2170,8 @@ relay_client_print_log ()
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 (" websocket . . . . . . . . ; %d", ptr_client->websocket);
+ relay_websocket_deflate_print_log (ptr_client->ws_deflate, "");
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);
diff --git a/src/plugins/relay/relay-client.h b/src/plugins/relay/relay-client.h
index 003fd47c6..0afec9387 100644
--- a/src/plugins/relay/relay-client.h
+++ b/src/plugins/relay/relay-client.h
@@ -108,6 +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_relay_websocket_deflate *ws_deflate; /* websocket deflate data */
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) */
diff --git a/src/plugins/relay/relay-http.c b/src/plugins/relay/relay-http.c
index d0bf015ff..a4a9a4050 100644
--- a/src/plugins/relay/relay-http.c
+++ b/src/plugins/relay/relay-http.c
@@ -78,6 +78,11 @@ relay_http_request_reinit (struct t_relay_http_request *request)
}
weechat_hashtable_remove_all (request->headers);
weechat_hashtable_remove_all (request->accept_encoding);
+ if (request->ws_deflate)
+ {
+ relay_websocket_deflate_free (request->ws_deflate);
+ request->ws_deflate = relay_websocket_deflate_alloc ();
+ }
request->content_length = 0;
request->body_size = 0;
if (request->body)
@@ -122,6 +127,7 @@ relay_http_request_alloc ()
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
+ new_request->ws_deflate = relay_websocket_deflate_alloc ();
new_request->content_length = 0;
new_request->body_size = 0;
new_request->body = NULL;
@@ -400,18 +406,12 @@ relay_http_parse_header (struct t_relay_http_request *request,
/* if header is "Accept-Encoding", save the allowed encoding */
if (strcmp (name_lower, "accept-encoding") == 0)
{
- items = weechat_string_split (ptr_value, ",", NULL, 0, 0, &num_items);
+ items = weechat_string_split (ptr_value, ",", " ", 0, 0, &num_items);
if (items)
{
for (i = 0; i < num_items; i++)
{
- pos = items[i];
- while (pos[0] == ' ')
- {
- pos++;
- }
- weechat_hashtable_set (request->accept_encoding,
- pos, NULL);
+ weechat_hashtable_set (request->accept_encoding, items[i], NULL);
}
weechat_string_free_split (items);
}
@@ -426,6 +426,13 @@ relay_http_parse_header (struct t_relay_http_request *request,
request->content_length = (int)number;
}
+ /*
+ * if header is "Sec-WebSocket-Extensions", save supported websocket
+ * extensions
+ */
+ if (strcmp (name_lower, "sec-websocket-extensions") == 0)
+ relay_websocket_parse_extensions (ptr_value, request->ws_deflate);
+
free (name);
free (name_lower);
@@ -635,7 +642,7 @@ relay_http_process_websocket (struct t_relay_client *client)
char *handshake;
int rc;
- rc = relay_websocket_client_handshake_valid (client);
+ rc = relay_websocket_client_handshake_valid (client->http_req);
if (rc == -1)
{
@@ -712,7 +719,7 @@ relay_http_process_websocket (struct t_relay_client *client)
}
}
- handshake = relay_websocket_build_handshake (client);
+ handshake = relay_websocket_build_handshake (client->http_req);
if (handshake)
{
relay_client_send (client,
@@ -721,6 +728,8 @@ relay_http_process_websocket (struct t_relay_client *client)
strlen (handshake), NULL);
free (handshake);
client->websocket = RELAY_CLIENT_WEBSOCKET_READY;
+ memcpy (client->ws_deflate, client->http_req->ws_deflate,
+ sizeof (*(client->ws_deflate)));
if (client->protocol == RELAY_PROTOCOL_API)
{
/* "api" protocol uses JSON in input/output (multi-line text) */
@@ -977,6 +986,7 @@ relay_http_compress (struct t_relay_http_request *request,
dest = malloc (dest_size);
if (dest)
{
+ memset (&strm, 0, sizeof (strm));
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
@@ -1252,7 +1262,7 @@ relay_http_print_log (struct t_relay_http_request *request)
{
int i;
- weechat_log_printf (" [http_request]");
+ weechat_log_printf (" http_request:");
weechat_log_printf (" raw . . . . . . . . . . : '%s'",
(request->raw) ? *(request->raw) : NULL);
weechat_log_printf (" status. . . . . . . . . : %d", request->status);
@@ -1278,6 +1288,7 @@ relay_http_print_log (struct t_relay_http_request *request)
request->accept_encoding,
weechat_hashtable_get_string (request->accept_encoding,
"keys_values"));
+ relay_websocket_deflate_print_log (request->ws_deflate, " ");
weechat_log_printf (" content_length. . . . . : %d", request->content_length);
weechat_log_printf (" body_size . . . . . . . : %d", request->body_size);
weechat_log_printf (" body. . . . . . . . . . : '%s'", request->body);
diff --git a/src/plugins/relay/relay-http.h b/src/plugins/relay/relay-http.h
index 231916ebd..8ed0bcd88 100644
--- a/src/plugins/relay/relay-http.h
+++ b/src/plugins/relay/relay-http.h
@@ -61,6 +61,7 @@ struct t_relay_http_request
struct t_hashtable *headers; /* HTTP headers for websocket */
/* and API protocol */
struct t_hashtable *accept_encoding; /* allowed encoding for response */
+ struct t_relay_websocket_deflate *ws_deflate; /* websocket deflate data */
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) */
diff --git a/src/plugins/relay/relay-websocket.c b/src/plugins/relay/relay-websocket.c
index 4379119d8..4ea3208a5 100644
--- a/src/plugins/relay/relay-websocket.c
+++ b/src/plugins/relay/relay-websocket.c
@@ -23,6 +23,7 @@
#include <unistd.h>
#include <stdio.h>
#include <string.h>
+#include <zlib.h>
#include "../weechat-plugin.h"
#include "relay.h"
@@ -40,6 +41,120 @@
/*
+ * Allocates a t_relay_websocket_deflate structure.
+ */
+
+struct t_relay_websocket_deflate *
+relay_websocket_deflate_alloc ()
+{
+ struct t_relay_websocket_deflate *new_ws_deflate;
+
+ new_ws_deflate = (struct t_relay_websocket_deflate *)malloc (sizeof (*new_ws_deflate));
+ if (!new_ws_deflate)
+ return NULL;
+
+ new_ws_deflate->enabled = 0;
+ new_ws_deflate->server_context_takeover = 0;
+ new_ws_deflate->server_context_takeover = 0;
+ new_ws_deflate->window_bits_deflate = 0;
+ new_ws_deflate->window_bits_inflate = 0;
+ new_ws_deflate->strm_deflate = NULL;
+ new_ws_deflate->strm_inflate = NULL;
+
+ return new_ws_deflate;
+}
+
+/*
+ * Initializes stream for deflate (compression).
+ *
+ * Returns:
+ * 1: OK
+ * 0: error
+ */
+
+int
+relay_websocket_deflate_init_stream_deflate (struct t_relay_websocket_deflate *ws_deflate)
+{
+ int rc, compression, compression_level;
+
+ compression = weechat_config_integer (relay_config_network_compression);
+
+ /* convert % to zlib compression level (1-9) */
+ compression_level = (((compression - 1) * 9) / 100) + 1;
+
+ rc = deflateInit2 (
+ ws_deflate->strm_deflate,
+ compression_level,
+ Z_DEFLATED, /* method */
+ -1 * ws_deflate->window_bits_deflate,
+ 8, /* memLevel */
+ Z_DEFAULT_STRATEGY); /* strategy */
+
+ return (rc == Z_OK) ? 1 : 0;
+}
+
+/*
+ * Frees a deflate stream in a deflate structure.
+ */
+
+void
+relay_websocket_deflate_free_stream_deflate (struct t_relay_websocket_deflate *ws_deflate)
+{
+ if (ws_deflate->strm_deflate)
+ {
+ deflateEnd (ws_deflate->strm_deflate);
+ free (ws_deflate->strm_deflate);
+ ws_deflate->strm_deflate = NULL;
+ }
+}
+
+/*
+ * Initializes stream for inflate (decompression).
+ *
+ * Returns:
+ * 1: OK
+ * 0: error
+ */
+
+int
+relay_websocket_deflate_init_stream_inflate (struct t_relay_websocket_deflate *ws_deflate)
+{
+ int rc;
+
+ rc = inflateInit2 (ws_deflate->strm_inflate,
+ -1 * ws_deflate->window_bits_inflate);
+
+ return (rc == Z_OK) ? 1 : 0;
+}
+
+/*
+ * Frees an inflate stream in a deflate structure.
+ */
+
+void
+relay_websocket_deflate_free_stream_inflate (struct t_relay_websocket_deflate *ws_deflate)
+{
+ if (ws_deflate->strm_inflate)
+ {
+ inflateEnd (ws_deflate->strm_inflate);
+ free (ws_deflate->strm_inflate);
+ ws_deflate->strm_inflate = NULL;
+ }
+}
+
+/*
+ * Frees a websocket deflate structure.
+ */
+
+void
+relay_websocket_deflate_free (struct t_relay_websocket_deflate *ws_deflate)
+{
+ relay_websocket_deflate_free_stream_deflate (ws_deflate);
+ relay_websocket_deflate_free_stream_inflate (ws_deflate);
+ free (ws_deflate);
+}
+
+/*
* Checks if a message is a HTTP GET with resource "/weechat" (for weechat
* protocol) or "/api" (for api protocol).
*
@@ -49,15 +164,18 @@
*/
int
-relay_websocket_is_valid_http_get (struct t_relay_client *client,
+relay_websocket_is_valid_http_get (enum t_relay_protocol protocol,
const char *message)
{
char string[128];
int length;
+ if (!message)
+ return 0;
+
/* the message must start with "GET /weechat" or "GET /api" */
snprintf (string, sizeof (string),
- "GET /%s", relay_protocol_string[client->protocol]);
+ "GET /%s", relay_protocol_string[protocol]);
length = strlen (string);
if (strncmp (message, string, length) != 0)
@@ -122,25 +240,28 @@ relay_websocket_is_valid_http_get (struct t_relay_client *client,
*/
int
-relay_websocket_client_handshake_valid (struct t_relay_client *client)
+relay_websocket_client_handshake_valid (struct t_relay_http_request *request)
{
const char *value;
+ if (!request || !request->headers)
+ return -1;
+
/* check if we have header "Upgrade" with value "websocket" */
- value = weechat_hashtable_get (client->http_req->headers, "upgrade");
+ value = weechat_hashtable_get (request->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_req->headers, "sec-websocket-key");
+ value = weechat_hashtable_get (request->headers, "sec-websocket-key");
if (!value || !value[0])
return -1;
if (relay_config_regex_websocket_allowed_origins)
{
- value = weechat_hashtable_get (client->http_req->headers, "origin");
+ value = weechat_hashtable_get (request->headers, "origin");
if (!value || !value[0])
return -2;
if (regexec (relay_config_regex_websocket_allowed_origins, value, 0,
@@ -155,6 +276,89 @@ relay_websocket_client_handshake_valid (struct t_relay_client *client)
}
/*
+ * Parses websocket extensions (header "Sec-WebSocket-Extensions").
+ *
+ * Header is for example:
+ * Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
+ */
+
+void
+relay_websocket_parse_extensions (const char *extensions,
+ struct t_relay_websocket_deflate *ws_deflate)
+{
+ char **exts, **params, **items, *error;
+ int i, j, num_exts, num_params, num_items;
+ long number;
+
+ if (!extensions || !ws_deflate)
+ return;
+
+ exts = weechat_string_split (extensions, ",", " ", 0, 0, &num_exts);
+ if (!exts)
+ return;
+
+ for (i = 0; i < num_exts; i++)
+ {
+ params = weechat_string_split (exts[i], ";", " ", 0, 0, &num_params);
+ if (params && (num_params >= 1)
+ && (strcmp (params[0], "permessage-deflate") == 0))
+ {
+ ws_deflate->enabled = 1;
+ ws_deflate->server_context_takeover = 1;
+ ws_deflate->client_context_takeover = 1;
+ ws_deflate->window_bits_deflate = 15;
+ ws_deflate->window_bits_inflate = 15;
+ for (j = 1; j < num_params; j++)
+ {
+ items = weechat_string_split (params[j], "=", " ", 0, 0, &num_items);
+ if (items && (num_items >= 1))
+ {
+ if (strcmp (items[0], "server_no_context_takeover") == 0)
+ {
+ ws_deflate->server_context_takeover = 0;
+ }
+ else if (strcmp (items[0], "client_no_context_takeover") == 0)
+ {
+ ws_deflate->client_context_takeover = 0;
+ }
+ else if ((strcmp (items[0], "server_max_window_bits") == 0)
+ || (strcmp (items[0], "client_max_window_bits") == 0))
+ {
+ number = 15;
+ if (num_items >= 2)
+ {
+ error = NULL;
+ number = strtol (items[1], &error, 10);
+ if (error && !error[0])
+ {
+ if (number < 8)
+ number = 8;
+ else if (number > 15)
+ number = 15;
+ }
+ else
+ {
+ number = 15;
+ }
+ }
+ if (strcmp (items[0], "server_max_window_bits") == 0)
+ ws_deflate->window_bits_deflate = (int)number;
+ else
+ ws_deflate->window_bits_inflate = (int)number;
+ }
+ }
+ if (items)
+ weechat_string_free_split (items);
+ }
+ }
+ if (params)
+ weechat_string_free_split (params);
+ }
+
+ weechat_string_free_split (exts);
+}
+
+/*
* Builds the handshake that will be returned to client, to initialize and use
* the websocket.
*
@@ -168,13 +372,17 @@ relay_websocket_client_handshake_valid (struct t_relay_client *client)
*/
char *
-relay_websocket_build_handshake (struct t_relay_client *client)
+relay_websocket_build_handshake (struct t_relay_http_request *request)
{
const char *sec_websocket_key;
- char *key, sec_websocket_accept[128], handshake[1024], hash[160 / 8];
+ char *key, sec_websocket_accept[128], handshake[4096], hash[160 / 8];
+ char sec_websocket_extensions[512];
int length, hash_size;
- sec_websocket_key = weechat_hashtable_get (client->http_req->headers,
+ if (!request)
+ return NULL;
+
+ sec_websocket_key = weechat_hashtable_get (request->headers,
"sec-websocket-key");
if (!sec_websocket_key || !sec_websocket_key[0])
return NULL;
@@ -204,20 +412,135 @@ relay_websocket_build_handshake (struct t_relay_client *client)
free (key);
+ if (request->ws_deflate->enabled)
+ {
+ snprintf (
+ sec_websocket_extensions, sizeof (sec_websocket_extensions),
+ "Sec-WebSocket-Extensions: permessage-deflate; "
+ "%s"
+ "%s"
+ "server_max_window_bits=%d; "
+ "client_max_window_bits=%d\r\n",
+ (!request->ws_deflate->server_context_takeover) ? "server_no_context_takeover; " : "",
+ (!request->ws_deflate->client_context_takeover) ? "client_no_context_takeover; " : "",
+ request->ws_deflate->window_bits_deflate,
+ request->ws_deflate->window_bits_inflate);
+ }
+ else
+ {
+ sec_websocket_extensions[0] = '\0';
+ }
+
/* build the handshake (it will be sent as-is to client) */
snprintf (handshake, sizeof (handshake),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n"
+ "%s"
"\r\n",
- sec_websocket_accept);
+ sec_websocket_accept,
+ sec_websocket_extensions);
return strdup (handshake);
}
/*
- * Decodes a websocket frame.
+ * Decompresses a decoded and compressed websocket frame compressed with
+ * "deflate" (when websocket extension "permessage-deflate" is enabled).
+ *
+ * A final '\0' is added after the decompressed data (the size_decompressed
+ * does not count this final '\0').
+ *
+ * Returns pointer to decompressed data, NULL if error.
+ */
+
+char *
+relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
+ size_t *size_decompressed)
+{
+ int rc;
+ unsigned char append_bytes[4] = { 0x00, 0x00, 0xFF, 0xFF };
+ Bytef *data2, *dest, *dest2;
+ uLongf size2, dest_size;
+
+ if (!data || (size == 0) || !strm || !size_decompressed)
+ return NULL;
+
+ dest = NULL;
+
+ *size_decompressed = 0;
+
+ /* append "0x00 0x00 0xFF 0xFF" to data */
+ size2 = size + sizeof (append_bytes);
+ data2 = malloc (size2);
+ if (!data2)
+ goto error;
+ memcpy (data2, data, size);
+ memcpy (data2 + size, append_bytes, sizeof (append_bytes));
+
+ /* estimate the decompressed size, by default 10 * size */
+ dest_size = 10 * size2;
+ dest = malloc (dest_size);
+ if (!dest)
+ goto error;
+
+ strm->avail_in = (uInt)size2;
+ strm->next_in = (Bytef *)data2;
+ strm->total_in = 0;
+
+ strm->avail_out = (uInt)dest_size;
+ strm->next_out = (Bytef *)dest;
+ strm->total_out = 0;
+
+ /* loop until we manage to decompress whole data in dest */
+ while (1)
+ {
+ rc = inflate (strm, Z_SYNC_FLUSH);
+ if ((rc == Z_STREAM_END) || (rc == Z_OK))
+ {
+ /* data successfully decompressed */
+ *size_decompressed = strm->total_out;
+ break;
+ }
+ else if (rc == Z_BUF_ERROR)
+ {
+ strm->avail_out += dest_size;
+ dest_size *= 2;
+ dest2 = realloc (dest, dest_size);
+ if (!dest2)
+ goto error;
+ dest = dest2;
+ strm->next_out = dest + strm->total_out;
+ }
+ else
+ {
+ /* any other error is fatal */
+ goto error;
+ }
+ }
+
+ dest2 = realloc (dest, *size_decompressed + 1);
+ if (!dest2)
+ goto error;
+ dest = dest2;
+ dest[*size_decompressed] = '\0';
+ if (data2)
+ free (data2);
+ return (char *)dest;
+
+error:
+ if (data2)
+ free (data2);
+ if (dest)
+ free (dest);
+ return NULL;
+}
+
+/*
+ * Decodes a websocket frame and return a list of frames in "*frames" (each
+ * frame is first decompressed if "permessage-deflate" websocket extension
+ * is used).
*
* Returns:
* 1: frame decoded successfully
@@ -225,20 +548,42 @@ relay_websocket_build_handshake (struct t_relay_client *client)
*/
int
-relay_websocket_decode_frame (const unsigned char *buffer,
+relay_websocket_decode_frame (struct t_relay_client *client,
+ const unsigned char *buffer,
unsigned long long buffer_length,
- unsigned char *decoded,
- unsigned long long *decoded_length)
+ struct t_relay_websocket_frame **frames,
+ int *num_frames)
{
unsigned long long i, index_buffer, length_frame_size, length_frame;
unsigned char opcode;
+ size_t size_decompressed;
+ char *payload_decompressed;
+ struct t_relay_websocket_frame *frames2, *ptr_frame;
+
+ if (!buffer || !frames || !num_frames)
+ return 0;
+
+ *frames = NULL;
+ *num_frames = 0;
- *decoded_length = 0;
index_buffer = 0;
/* loop to decode all frames in message */
while (index_buffer + 1 < buffer_length)
{
+ (*num_frames)++;
+
+ frames2 = realloc (*frames, sizeof (**frames) * (*num_frames));
+ if (!frames2)
+ return 0;
+ *frames = frames2;
+
+ ptr_frame = &((*frames)[*num_frames - 1]);
+
+ ptr_frame->opcode = 0;
+ ptr_frame->payload_size = 0;
+ ptr_frame->payload = NULL;
+
opcode = buffer[index_buffer] & 15;
/*
@@ -276,20 +621,19 @@ relay_websocket_decode_frame (const unsigned char *buffer,
}
index_buffer += 4;
- /* copy opcode in decoded data */
+ /* save opcode */
switch (opcode)
{
case WEBSOCKET_FRAME_OPCODE_PING:
- decoded[*decoded_length] = RELAY_CLIENT_MSG_PING;
+ ptr_frame->opcode = RELAY_CLIENT_MSG_PING;
break;
case WEBSOCKET_FRAME_OPCODE_CLOSE:
- decoded[*decoded_length] = RELAY_CLIENT_MSG_CLOSE;
+ ptr_frame->opcode = RELAY_CLIENT_MSG_CLOSE;
break;
default:
- decoded[*decoded_length] = RELAY_CLIENT_MSG_STANDARD;
+ ptr_frame->opcode = RELAY_CLIENT_MSG_STANDARD;
break;
}
- *decoded_length += 1;
/* decode data using masks */
if ((length_frame > buffer_length)
@@ -297,12 +641,48 @@ relay_websocket_decode_frame (const unsigned char *buffer,
{
return 0;
}
+
+ ptr_frame->payload = malloc (length_frame + 1);
+ if (!ptr_frame->payload)
+ return 0;
+ ptr_frame->payload_size = length_frame;
+
+ /* fill payload */
for (i = 0; i < length_frame; i++)
{
- decoded[*decoded_length + i] = (int)((unsigned char)buffer[index_buffer + i]) ^ masks[i % 4];
+ ptr_frame->payload[i] = (int)((unsigned char)buffer[index_buffer + i]) ^ masks[i % 4];
}
- decoded[*decoded_length + length_frame] = '\0';
- *decoded_length += length_frame + 1;
+ ptr_frame->payload[length_frame] = '\0';
+
+ /*
+ * decompress data if frame is not empty and if "permessage-deflate"
+ * is enabled
+ */
+ if ((length_frame > 0) && client->ws_deflate->enabled)
+ {
+ if (!client->ws_deflate->strm_inflate)
+ {
+ client->ws_deflate->strm_inflate = calloc (
+ 1, sizeof (*client->ws_deflate->strm_inflate));
+ if (!client->ws_deflate->strm_inflate)
+ return 0;
+ if (!relay_websocket_deflate_init_stream_inflate (client->ws_deflate))
+ return 0;
+ }
+ payload_decompressed = relay_websocket_inflate (
+ ptr_frame->payload,
+ ptr_frame->payload_size,
+ client->ws_deflate->strm_inflate,
+ &size_decompressed);
+ if (!payload_decompressed)
+ return 0;
+ free (ptr_frame->payload);
+ ptr_frame->payload = payload_decompressed;
+ ptr_frame->payload_size = size_decompressed;
+ if (!client->ws_deflate->client_context_takeover)
+ relay_websocket_deflate_free_stream_inflate (client->ws_deflate);
+ }
+
index_buffer += length_frame;
}
@@ -310,6 +690,50 @@ relay_websocket_decode_frame (const unsigned char *buffer,
}
/*
+ * Compresses data to send in a websocket frame (when websocket extension
+ * "permessage-deflate" is enabled).
+ *
+ * Returns pointer to compressed data, NULL if error.
+ */
+
+char *
+relay_websocket_deflate (const void *data, size_t size, z_stream *strm,
+ size_t *size_compressed)
+{
+ int rc;
+ uLongf dest_size;
+ Bytef *dest;
+
+ if (!data || (size == 0) || !strm || !size_compressed)
+ return NULL;
+
+ *size_compressed = 0;
+
+ dest_size = compressBound (size);
+ dest = malloc (dest_size);
+ if (!dest)
+ return NULL;
+
+ strm->avail_in = (uInt)size;
+ strm->next_in = (Bytef *)data;
+ strm->total_in = 0;
+
+ strm->avail_out = (uInt)dest_size;
+ strm->next_out = (Bytef *)dest;
+ strm->total_out = 0;
+
+ rc = deflate (strm, Z_SYNC_FLUSH);
+ if ((rc == Z_STREAM_END) || (rc == Z_OK))
+ {
+ *size_compressed = strm->total_out;
+ return (char *)dest;
+ }
+
+ free (dest);
+ return NULL;
+}
+
+/*
* Encodes data in a websocket frame.
*
* Returns websocket frame, NULL if error.
@@ -319,56 +743,132 @@ relay_websocket_decode_frame (const unsigned char *buffer,
*/
char *
-relay_websocket_encode_frame (int opcode,
- const char *buffer,
- unsigned long long length,
+relay_websocket_encode_frame (struct t_relay_client *client,
+ int opcode,
+ const char *payload,
+ unsigned long long payload_size,
unsigned long long *length_frame)
{
+ const char *ptr_data;
+ char *payload_compressed;
+ size_t size_compressed;
unsigned char *frame;
- unsigned long long index;
+ unsigned long long index, data_size;
*length_frame = 0;
- frame = malloc (length + 10);
+ ptr_data = payload;
+ data_size = payload_size;
+
+ payload_compressed = NULL;
+ size_compressed = 0;
+
+ /*
+ * compress data if payload is not empty and if "permessage-deflate"
+ * is enabled
+ */
+ if (((opcode == WEBSOCKET_FRAME_OPCODE_TEXT)
+ || (opcode == WEBSOCKET_FRAME_OPCODE_BINARY))
+ && (payload_size > 0)
+ && client->ws_deflate->enabled)
+ {
+ if (!client->ws_deflate->strm_deflate)
+ {
+ client->ws_deflate->strm_deflate = calloc (
+ 1, sizeof (*client->ws_deflate->strm_deflate));
+ if (!client->ws_deflate->strm_deflate)
+ return NULL;
+ if (!relay_websocket_deflate_init_stream_deflate (client->ws_deflate))
+ return NULL;
+ }
+ payload_compressed = relay_websocket_deflate (
+ payload,
+ payload_size,
+ client->ws_deflate->strm_deflate,
+ &size_compressed);
+ if (!payload_compressed)
+ return NULL;
+ ptr_data = payload_compressed;
+ data_size = size_compressed;
+ if ((data_size > 4)
+ && ((unsigned char)ptr_data[data_size - 4] == 0x00)
+ && ((unsigned char)ptr_data[data_size - 3] == 0x00)
+ && ((unsigned char)ptr_data[data_size - 2] == 0xFF)
+ && ((unsigned char)ptr_data[data_size - 1] == 0xFF))
+ {
+ data_size -= 4;
+ }
+ if (!client->ws_deflate->server_context_takeover)
+ relay_websocket_deflate_free_stream_deflate (client->ws_deflate);
+ /* set bit RSV1: indicate permessage-deflate compressed data */
+ opcode |= 0x40;
+ }
+
+ frame = malloc (data_size + 10);
if (!frame)
+ {
+ if (payload_compressed)
+ free (payload_compressed);
return NULL;
+ }
frame[0] = 0x80;
frame[0] |= opcode;
- if (length <= 125)
+ if (data_size <= 125)
{
/* length on one byte */
- frame[1] = length;
+ frame[1] = data_size;
index = 2;
}
- else if (length <= 65535)
+ else if (data_size <= 65535)
{
/* length on 2 bytes */
frame[1] = 126;
- frame[2] = (length >> 8) & 0xFF;
- frame[3] = length & 0xFF;
+ frame[2] = (data_size >> 8) & 0xFF;
+ frame[3] = data_size & 0xFF;
index = 4;
}
else
{
/* length on 8 bytes */
frame[1] = 127;
- frame[2] = (length >> 56) & 0xFF;
- frame[3] = (length >> 48) & 0xFF;
- frame[4] = (length >> 40) & 0xFF;
- frame[5] = (length >> 32) & 0xFF;
- frame[6] = (length >> 24) & 0xFF;
- frame[7] = (length >> 16) & 0xFF;
- frame[8] = (length >> 8) & 0xFF;
- frame[9] = length & 0xFF;
+ frame[2] = (data_size >> 56) & 0xFF;
+ frame[3] = (data_size >> 48) & 0xFF;
+ frame[4] = (data_size >> 40) & 0xFF;
+ frame[5] = (data_size >> 32) & 0xFF;
+ frame[6] = (data_size >> 24) & 0xFF;
+ frame[7] = (data_size >> 16) & 0xFF;
+ frame[8] = (data_size >> 8) & 0xFF;
+ frame[9] = data_size & 0xFF;
index = 10;
}
- /* copy buffer after length */
- memcpy (frame + index, buffer, length);
+ /* copy buffer after data_size */
+ memcpy (frame + index, ptr_data, data_size);
- *length_frame = index + length;
+ *length_frame = index + data_size;
+
+ if (payload_compressed)
+ free (payload_compressed);
return (char *)frame;
}
+
+/*
+ * Prints websocket deflate data in WeeChat log file (usually for crash dump).
+ */
+
+void
+relay_websocket_deflate_print_log (struct t_relay_websocket_deflate *ws_deflate,
+ const char *prefix)
+{
+ weechat_log_printf ("%s ws_deflate:", prefix);
+ weechat_log_printf ("%s enabled . . . . . . . . : %d", prefix, ws_deflate->enabled);
+ weechat_log_printf ("%s server_context_takeover : %d", prefix, ws_deflate->server_context_takeover);
+ weechat_log_printf ("%s client_context_takeover : %d", prefix, ws_deflate->client_context_takeover);
+ weechat_log_printf ("%s window_bits_deflate . . : %d", prefix, ws_deflate->window_bits_deflate);
+ weechat_log_printf ("%s window_bits_inflate . . : %d", prefix, ws_deflate->window_bits_inflate);
+ weechat_log_printf ("%s strm_deflate. . . . . . : 0x%lx", prefix, ws_deflate->strm_deflate);
+ weechat_log_printf ("%s strm_inflate. . . . . . : 0x%lx", prefix, ws_deflate->strm_inflate);
+}
diff --git a/src/plugins/relay/relay-websocket.h b/src/plugins/relay/relay-websocket.h
index 18e2002fb..6e48569fd 100644
--- a/src/plugins/relay/relay-websocket.h
+++ b/src/plugins/relay/relay-websocket.h
@@ -20,6 +20,10 @@
#ifndef WEECHAT_PLUGIN_RELAY_WEBSOCKET_H
#define WEECHAT_PLUGIN_RELAY_WEBSOCKET_H
+#include "relay.h"
+
+#include <zlib.h>
+
#define WEBSOCKET_FRAME_OPCODE_CONTINUATION 0x00
#define WEBSOCKET_FRAME_OPCODE_TEXT 0x01
#define WEBSOCKET_FRAME_OPCODE_BINARY 0x02
@@ -27,17 +31,50 @@
#define WEBSOCKET_FRAME_OPCODE_PING 0x09
#define WEBSOCKET_FRAME_OPCODE_PONG 0x0A
-extern int relay_websocket_is_valid_http_get (struct t_relay_client *client,
+struct t_relay_client;
+struct t_relay_http_request;
+
+struct t_relay_websocket_deflate
+{
+ int enabled; /* 1 if permessage-deflate is enabled*/
+ int server_context_takeover; /* context takeover for server */
+ int client_context_takeover; /* context takeover for client */
+ int window_bits_deflate; /* window bits for server (comp.) */
+ /* ("server_max_window_bits") */
+ int window_bits_inflate; /* window bits for client (decomp.) */
+ /* ("client_max_window_bits") */
+ z_stream *strm_deflate; /* stream for deflate (compression) */
+ z_stream *strm_inflate; /* stream for inflate (decompression)*/
+};
+
+struct t_relay_websocket_frame
+{
+ int opcode; /* frame opcode */
+ int payload_size; /* size of payload */
+ char *payload; /* payload */
+};
+
+extern struct t_relay_websocket_deflate *relay_websocket_deflate_alloc ();
+extern int relay_websocket_deflate_init_stream_deflate (struct t_relay_websocket_deflate *ws_deflate);
+extern int relay_websocket_deflate_init_stream_inflate (struct t_relay_websocket_deflate *ws_deflate);
+extern void relay_websocket_deflate_free (struct t_relay_websocket_deflate *ws_deflate);
+extern int relay_websocket_is_valid_http_get (enum t_relay_protocol protocol,
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 int relay_websocket_decode_frame (const unsigned char *buffer,
+extern int relay_websocket_client_handshake_valid (struct t_relay_http_request *request);
+extern void relay_websocket_parse_extensions (const char *extensions,
+ struct t_relay_websocket_deflate *ws_deflate);
+extern char *relay_websocket_build_handshake (struct t_relay_http_request *request);
+extern int relay_websocket_decode_frame (struct t_relay_client *client,
+ const unsigned char *buffer,
unsigned long long length,
- unsigned char *decoded,
- unsigned long long *decoded_length);
-extern char *relay_websocket_encode_frame (int opcode,
- const char *buffer,
- unsigned long long length,
+ struct t_relay_websocket_frame **frames,
+ int *num_frames);
+extern char *relay_websocket_encode_frame (struct t_relay_client *client,
+ int opcode,
+ const char *payload,
+ unsigned long long payload_size,
unsigned long long *length_frame);
+extern void relay_websocket_deflate_print_log (struct t_relay_websocket_deflate *ws_deflate,
+ const char *prefix);
#endif /* WEECHAT_PLUGIN_RELAY_WEBSOCKET_H */
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 13574ba26..7a12de527 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -108,6 +108,7 @@ 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/test-relay-websocket.cpp
unit/plugins/relay/irc/test-relay-irc.cpp
)
endif()
diff --git a/tests/unit/plugins/relay/test-relay-http.cpp b/tests/unit/plugins/relay/test-relay-http.cpp
index b3ad96890..64bb7b752 100644
--- a/tests/unit/plugins/relay/test-relay-http.cpp
+++ b/tests/unit/plugins/relay/test-relay-http.cpp
@@ -34,6 +34,7 @@ extern "C"
#include "src/core/wee-string.h"
#include "src/plugins/relay/relay-config.h"
#include "src/plugins/relay/relay-http.h"
+#include "src/plugins/relay/relay-websocket.h"
#include "src/plugins/weechat-plugin.h"
extern char *relay_http_url_decode (const char *url);
@@ -89,6 +90,14 @@ TEST(RelayHttp, AllocReinitFree)
LONGS_EQUAL(0, request->headers->items_count);
CHECK(request->accept_encoding);
LONGS_EQUAL(0, request->accept_encoding->items_count);
+ CHECK(request->ws_deflate);
+ LONGS_EQUAL(0, request->ws_deflate->enabled);
+ LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
+ LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
+ LONGS_EQUAL(0, request->ws_deflate->window_bits_deflate);
+ LONGS_EQUAL(0, request->ws_deflate->window_bits_inflate);
+ POINTERS_EQUAL(NULL, request->ws_deflate->strm_deflate);
+ POINTERS_EQUAL(NULL, request->ws_deflate->strm_inflate);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
@@ -102,6 +111,7 @@ TEST(RelayHttp, AllocReinitFree)
request->http_version = strdup ("HTTP/1.1");
hashtable_set (request->headers, "x-test", "value");
hashtable_set (request->accept_encoding, "gzip", "");
+ request->ws_deflate->enabled = 1;
request->content_length = 100;
request->body_size = 16;
request->body = (char *)malloc (16);
@@ -123,6 +133,14 @@ TEST(RelayHttp, AllocReinitFree)
LONGS_EQUAL(0, request->headers->items_count);
CHECK(request->accept_encoding);
LONGS_EQUAL(0, request->accept_encoding->items_count);
+ CHECK(request->ws_deflate);
+ LONGS_EQUAL(0, request->ws_deflate->enabled);
+ LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
+ LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
+ LONGS_EQUAL(0, request->ws_deflate->window_bits_deflate);
+ LONGS_EQUAL(0, request->ws_deflate->window_bits_inflate);
+ POINTERS_EQUAL(NULL, request->ws_deflate->strm_deflate);
+ POINTERS_EQUAL(NULL, request->ws_deflate->strm_inflate);
LONGS_EQUAL(0, request->content_length);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, request->body);
@@ -477,6 +495,29 @@ TEST(RelayHttp, ParseHeader)
LONGS_EQUAL(1, request->headers->items_count);
LONGS_EQUAL(123, request->content_length);
free (request);
+
+ /* websocket request */
+ request = relay_http_request_alloc ();
+ CHECK(request);
+ relay_http_parse_method_path (request, "GET /api HTTP/1.1");
+ relay_http_parse_header (
+ request,
+ "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits");
+ LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
+ STRCMP_EQUAL(
+ "GET /api HTTP/1.1\n"
+ "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\n",
+ *(request->raw));
+ LONGS_EQUAL(1, request->headers->items_count);
+ CHECK(request->ws_deflate);
+ LONGS_EQUAL(1, request->ws_deflate->enabled);
+ LONGS_EQUAL(1, request->ws_deflate->server_context_takeover);
+ LONGS_EQUAL(1, request->ws_deflate->server_context_takeover);
+ LONGS_EQUAL(15, request->ws_deflate->window_bits_deflate);
+ LONGS_EQUAL(15, request->ws_deflate->window_bits_inflate);
+ POINTERS_EQUAL(NULL, request->ws_deflate->strm_deflate);
+ POINTERS_EQUAL(NULL, request->ws_deflate->strm_inflate);
+ free (request);
}
/*
diff --git a/tests/unit/plugins/relay/test-relay-websocket.cpp b/tests/unit/plugins/relay/test-relay-websocket.cpp
new file mode 100644
index 000000000..e413a8b10
--- /dev/null
+++ b/tests/unit/plugins/relay/test-relay-websocket.cpp
@@ -0,0 +1,415 @@
+/*
+ * test-relay-websocket.cpp - test websocket functions
+ *
+ * Copyright (C) 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 <string.h>
+#include <zlib.h>
+#include "src/core/wee-config-file.h"
+#include "src/core/wee-hashtable.h"
+#include "src/plugins/relay/relay-config.h"
+#include "src/plugins/relay/relay-http.h"
+#include "src/plugins/relay/relay-websocket.h"
+
+extern void relay_websocket_deflate_free_stream_deflate (struct t_relay_websocket_deflate *ws_deflate);
+extern void relay_websocket_deflate_free_stream_inflate (struct t_relay_websocket_deflate *ws_deflate);
+extern char *relay_websocket_deflate (const void *data, size_t size, z_stream *strm,
+ size_t *size_compressed);
+extern char *relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
+ size_t *size_decompressed);
+}
+
+TEST_GROUP(RelayWebsocket)
+{
+};
+
+/*
+ * Tests functions:
+ * relay_websocket_deflate_alloc
+ * relay_websocket_deflate_init_stream_deflate
+ * relay_websocket_deflate_free_stream_deflate
+ * relay_websocket_deflate_init_stream_inflate
+ * relay_websocket_deflate_free_stream_inflate
+ * relay_websocket_deflate_free
+ */
+
+TEST(RelayWebsocket, DeflateAllocFree)
+{
+ struct t_relay_websocket_deflate *ws_deflate;
+
+ ws_deflate = relay_websocket_deflate_alloc ();
+ LONGS_EQUAL(0, ws_deflate->enabled);
+ LONGS_EQUAL(0, ws_deflate->server_context_takeover);
+ LONGS_EQUAL(0, ws_deflate->server_context_takeover);
+ LONGS_EQUAL(0, ws_deflate->window_bits_deflate);
+ LONGS_EQUAL(0, ws_deflate->window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate->strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate->strm_inflate);
+
+ ws_deflate->window_bits_deflate = 15;
+ ws_deflate->window_bits_inflate = 15;
+
+ ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate));
+ CHECK(ws_deflate->strm_deflate);
+ relay_websocket_deflate_init_stream_deflate (ws_deflate);
+ relay_websocket_deflate_free_stream_deflate (ws_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate->strm_deflate);
+
+ ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate));
+ CHECK(ws_deflate->strm_inflate);
+ relay_websocket_deflate_init_stream_inflate (ws_deflate);
+ relay_websocket_deflate_free_stream_inflate (ws_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate->strm_inflate);
+
+ relay_websocket_deflate_free (ws_deflate);
+}
+
+/*
+ * Tests functions:
+ * relay_websocket_is_valid_http_get
+ */
+
+TEST(RelayWebsocket, IsValidHttpGet)
+{
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, NULL));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, ""));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "xxx"));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /api\r\n"));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /api test\r\n"));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /api HTTP/1.1\r\n"));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /weechat\r\n"));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /weechat test\r\n"));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /weechat HTTP/1.1\r\n"));
+
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /weechat test\r\n"));
+ LONGS_EQUAL(0, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /api test\r\n"));
+
+ LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /weechat\r\n"));
+ LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_WEECHAT, "GET /weechat HTTP/1.1\r\n"));
+ LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /api\r\n"));
+ LONGS_EQUAL(1, relay_websocket_is_valid_http_get (RELAY_PROTOCOL_API, "GET /api HTTP/1.1\r\n"));
+}
+
+/*
+ * Tests functions:
+ * relay_websocket_client_handshake_valid
+ * relay_websocket_build_handshake
+ */
+
+TEST(RelayWebsocket, ClientHandshakeValid)
+{
+ struct t_relay_http_request *request;
+ char *str;
+
+ LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (NULL));
+
+ request = relay_http_request_alloc ();
+ CHECK(request);
+
+ LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (NULL));
+
+ LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "upgrade", NULL);
+ LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "upgrade", "test");
+ LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "upgrade", "websocket");
+ LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "sec-websocket-key", NULL);
+ LONGS_EQUAL(-1, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "sec-websocket-key", "CI1sXhf/u2o34BfWK7NeIg==");
+ LONGS_EQUAL(0, relay_websocket_client_handshake_valid (request));
+
+ POINTERS_EQUAL(NULL, relay_websocket_build_handshake (NULL));
+
+ WEE_TEST_STR(
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
+ "\r\n",
+ relay_websocket_build_handshake (request));
+
+ config_file_option_set (relay_config_network_websocket_allowed_origins, "example.com", 1);
+ LONGS_EQUAL(-2, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "origin", NULL);
+ LONGS_EQUAL(-2, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "origin", "weechat.org");
+ LONGS_EQUAL(-2, relay_websocket_client_handshake_valid (request));
+ hashtable_set (request->headers, "origin", "example.com");
+ LONGS_EQUAL(0, relay_websocket_client_handshake_valid (request));
+
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits",
+ request->ws_deflate);
+ WEE_TEST_STR(
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
+ "Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15\r\n"
+ "\r\n",
+ relay_websocket_build_handshake (request));
+
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits = 12; server_no_context_takeover",
+ request->ws_deflate);
+ WEE_TEST_STR(
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
+ "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; server_max_window_bits=15; client_max_window_bits=12\r\n"
+ "\r\n",
+ relay_websocket_build_handshake (request));
+
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits = 12; server_max_window_bits=8; client_no_context_takeover; server_no_context_takeover",
+ request->ws_deflate);
+ WEE_TEST_STR(
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: fhLJYtv//ugX2vQXpifQgByRZ5Y=\r\n"
+ "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover; server_max_window_bits=8; client_max_window_bits=12\r\n"
+ "\r\n",
+ relay_websocket_build_handshake (request));
+
+ relay_http_request_free (request);
+}
+
+/*
+ * Tests functions:
+ * relay_websocket_parse_extensions
+ */
+
+TEST(RelayWebsocket, ParseExtensions)
+{
+ struct t_relay_websocket_deflate ws_deflate;
+
+ relay_websocket_parse_extensions (NULL, NULL);
+ relay_websocket_parse_extensions ("test", NULL);
+ relay_websocket_parse_extensions (NULL, &ws_deflate);
+
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions ("test", &ws_deflate);
+ LONGS_EQUAL(0, ws_deflate.enabled);
+
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions ("permessage-deflate", &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(1, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions ("permessage-deflate; client_max_window_bits",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(1, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ /* client_max_window_bits < 8 (min value) */
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits=4",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(1, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(8, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ /* client_max_window_bits > 15 (max value) */
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits=30",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(1, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ /* invalid value for client_max_window_bits */
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits=test",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(1, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(15, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits=9",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(1, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(15, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits=9; server_max_window_bits=10",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(1, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(10, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits=9; server_max_window_bits=10; "
+ "server_no_context_takeover",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(0, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(1, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(10, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+
+ memset (&ws_deflate, 0, sizeof (ws_deflate));
+ relay_websocket_parse_extensions (
+ "permessage-deflate; client_max_window_bits=9; server_max_window_bits=10; "
+ "server_no_context_takeover; client_no_context_takeover",
+ &ws_deflate);
+ LONGS_EQUAL(1, ws_deflate.enabled);
+ LONGS_EQUAL(0, ws_deflate.server_context_takeover);
+ LONGS_EQUAL(0, ws_deflate.client_context_takeover);
+ LONGS_EQUAL(10, ws_deflate.window_bits_deflate);
+ LONGS_EQUAL(9, ws_deflate.window_bits_inflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_deflate);
+ POINTERS_EQUAL(NULL, ws_deflate.strm_inflate);
+}
+
+/*
+ * Tests functions:
+ * relay_websocket_deflate
+ * relay_websocket_inflate
+ */
+
+TEST(RelayWebsocket, Inflate)
+{
+ struct t_relay_websocket_deflate *ws_deflate;
+ char payload[256], *payload_comp, *payload_decomp;
+ size_t size_comp, size_decomp;
+ int i;
+
+ ws_deflate = relay_websocket_deflate_alloc ();
+ CHECK(ws_deflate);
+
+ ws_deflate->window_bits_deflate = 15;
+ ws_deflate->window_bits_inflate = 15;
+
+ ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate));
+ CHECK(ws_deflate->strm_deflate);
+ LONGS_EQUAL(1, relay_websocket_deflate_init_stream_deflate (ws_deflate));
+
+ ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate));
+ CHECK(ws_deflate->strm_inflate);
+ LONGS_EQUAL(1, relay_websocket_deflate_init_stream_inflate (ws_deflate));
+
+ for (i = 0; i < (int)sizeof (payload); i++)
+ {
+ payload[i] = i % 64;
+ }
+
+ POINTERS_EQUAL(NULL, relay_websocket_deflate (NULL, 0, NULL, NULL));
+ POINTERS_EQUAL(NULL, relay_websocket_deflate (payload, 0, NULL, NULL));
+ POINTERS_EQUAL(NULL, relay_websocket_deflate (payload, sizeof (payload),
+ NULL, NULL));
+ POINTERS_EQUAL(NULL, relay_websocket_deflate (payload, sizeof (payload),
+ ws_deflate->strm_deflate, NULL));
+
+ payload_comp = (char *)relay_websocket_deflate (payload, sizeof (payload),
+ ws_deflate->strm_deflate, &size_comp);
+ CHECK(payload_comp);
+ CHECK((size_comp > 0) && (size_comp < (int)sizeof (payload)));
+
+ payload_decomp = (char *)relay_websocket_inflate (payload_comp, size_comp,
+ ws_deflate->strm_inflate, &size_decomp);
+ CHECK(payload_decomp);
+ LONGS_EQUAL(sizeof (payload), size_decomp);
+ MEMCMP_EQUAL(payload, payload_decomp, sizeof (payload));
+
+ free (payload_decomp);
+ free (payload_comp);
+
+ relay_websocket_deflate_free (ws_deflate);
+}
+
+/*
+ * Tests functions:
+ * relay_websocket_decode_frame
+ */
+
+TEST(RelayWebsocket, DecodeFrame)
+{
+ /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ * relay_websocket_encode_frame
+ */
+
+TEST(RelayWebsocket, EncodeFrame)
+{
+ /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ * relay_websocket_deflate_print_log
+ */
+
+TEST(RelayWebsocket, DeflatePrintLog)
+{
+ /* TODO: write tests */
+}