diff options
-rw-r--r-- | ChangeLog.adoc | 1 | ||||
-rw-r--r-- | doc/en/weechat_dev.en.adoc | 169 | ||||
-rw-r--r-- | doc/fr/weechat_dev.fr.adoc | 169 | ||||
-rw-r--r-- | doc/ja/weechat_dev.ja.adoc | 170 | ||||
-rw-r--r-- | doc/sr/weechat_dev.sr.adoc | 170 | ||||
-rw-r--r-- | src/plugins/relay/api/relay-api-msg.c | 1 | ||||
-rw-r--r-- | src/plugins/relay/api/relay-api-protocol.c | 1 | ||||
-rw-r--r-- | src/plugins/relay/relay-client.c | 238 | ||||
-rw-r--r-- | src/plugins/relay/relay-client.h | 1 | ||||
-rw-r--r-- | src/plugins/relay/relay-http.c | 33 | ||||
-rw-r--r-- | src/plugins/relay/relay-http.h | 1 | ||||
-rw-r--r-- | src/plugins/relay/relay-websocket.c | 588 | ||||
-rw-r--r-- | src/plugins/relay/relay-websocket.h | 55 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/unit/plugins/relay/test-relay-http.cpp | 41 | ||||
-rw-r--r-- | tests/unit/plugins/relay/test-relay-websocket.cpp | 415 |
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 */ +} |