telnetlib3 3.0.0__tar.gz → 3.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/PKG-INFO +50 -6
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/README.rst +49 -5
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/conf.py +5 -3
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/history.rst +14 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/rfcs.rst +8 -1
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/pyproject.toml +1 -1
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/_base.py +9 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/accessories.py +1 -1
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/client.py +54 -17
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/client_base.py +91 -1
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/fingerprinting.py +4 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/server.py +87 -5
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/server_base.py +32 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/stream_writer.py +111 -5
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/telopt.py +3 -1
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_charset.py +69 -5
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_client_unit.py +3 -3
- telnetlib3-3.0.2/telnetlib3/tests/test_mccp.py +683 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_stream_writer_full.py +17 -5
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/.gitignore +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/CONTRIBUTING.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/LICENSE.txt +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/Makefile +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/accessories.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/client.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/client_base.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/client_shell.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/color_filter.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/fingerprinting.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/guard_shells.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/mud.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/server.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/server_base.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/server_pty_shell.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/server_shell.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/session_context.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/slc.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/stream_reader.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/stream_writer.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/sync.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/telnetlib.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api/telopt.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/api.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/contributing.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/guidebook.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/index.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/intro.rst +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/make.bat +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/docs/sphinxext/github.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/requirements-analysis.txt +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/requirements-docs.txt +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/requirements-tests.txt +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/requirements.txt +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/.gitignore +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/__init__.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/_paths.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/_session_context.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/_types.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/client_shell.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/color_filter.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/encodings/__init__.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/encodings/atarist.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/encodings/atascii.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/encodings/petscii.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/fingerprinting_display.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/guard_shells.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/mud.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/py.typed +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/relay_server.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/server_fingerprinting.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/server_pty_shell.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/server_shell.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/slc.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/stream_reader.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/sync.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/telnetlib.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/accessories.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/conftest.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/pty_helper.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_accessories.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_accessories_extra.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_atascii_codec.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_benchmarks.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_client_shell.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_color_filter.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_core.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_encoding.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_environ.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_fingerprinting.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_guard_integration.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_linemode.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_mud.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_mud_negotiation.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_naws.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_petscii_codec.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_platform.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_pty_shell.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_reader.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_relay_server.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_server.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_server_api.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_server_cli.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_server_fingerprinting.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_server_mud.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_server_shell_unit.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_shell.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_slc.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_status_logger.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_stream_reader_extra.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_sync.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_telnetlib.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_timeout.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_tls.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_tspeed.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_ttype.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_uvloop_integration.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_writer.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/telnetlib3/tests/test_xdisploc.py +0 -0
- {telnetlib3-3.0.0 → telnetlib3-3.0.2}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: telnetlib3
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: Python Telnet server and client CLI and Protocol library
|
|
5
5
|
Project-URL: Homepage, https://github.com/jquast/telnetlib3
|
|
6
6
|
Project-URL: Documentation, https://telnetlib3.readthedocs.io
|
|
@@ -83,8 +83,8 @@ The CLI utility ``telnetlib3-client`` is provided for connecting to servers and
|
|
|
83
83
|
``telnetlib3-server`` for hosting a server.
|
|
84
84
|
|
|
85
85
|
Both tools accept the argument ``--shell=my_module.fn_shell`` describing a python module path to a
|
|
86
|
-
function of signature ``async def shell(reader, writer)``. The server also provides
|
|
87
|
-
argument to
|
|
86
|
+
function of signature ``async def shell(reader, writer)``. The server also provides a
|
|
87
|
+
``--pty-exec`` argument allowing it to act as a telnet server for any CLI/TUI programs.
|
|
88
88
|
|
|
89
89
|
::
|
|
90
90
|
|
|
@@ -97,7 +97,7 @@ argument to host stand-alone programs.
|
|
|
97
97
|
# automatic script communicates with a server
|
|
98
98
|
telnetlib3-client --shell bin.client_wargame.shell 1984.ws 666
|
|
99
99
|
|
|
100
|
-
# run a
|
|
100
|
+
# run a default shell server bound to 127.0.0.1 6023
|
|
101
101
|
telnetlib3-server
|
|
102
102
|
|
|
103
103
|
# or custom ip, port and shell
|
|
@@ -150,6 +150,48 @@ connected to a TCP socket without any telnet negotiation may require "raw" mode
|
|
|
150
150
|
|
|
151
151
|
telnetlib3-client --raw-mode area52.tk 5200 --encoding=atascii
|
|
152
152
|
|
|
153
|
+
Go-Ahead (GA)
|
|
154
|
+
~~~~~~~~~~~~~
|
|
155
|
+
|
|
156
|
+
When a client does not negotiate Suppress Go-Ahead (SGA), the server sends
|
|
157
|
+
``IAC GA`` after output to signal that the client may transmit. This is
|
|
158
|
+
correct behavior for MUD clients like Mudlet that expect prompt detection
|
|
159
|
+
via GA.
|
|
160
|
+
|
|
161
|
+
If GA causes unwanted output for your use case, disable it::
|
|
162
|
+
|
|
163
|
+
telnetlib3-server --never-send-ga
|
|
164
|
+
|
|
165
|
+
For PTY shells, GA is sent after 500ms of output idle time to avoid
|
|
166
|
+
injecting GA in the middle of streaming output.
|
|
167
|
+
|
|
168
|
+
Compression (MCCP)
|
|
169
|
+
~~~~~~~~~~~~~~~~~~
|
|
170
|
+
|
|
171
|
+
MCCP2 (server-to-client) and MCCP3 (client-to-server) zlib compression are
|
|
172
|
+
supported, widely used by MUD servers to reduce bandwidth::
|
|
173
|
+
|
|
174
|
+
# connect to a MUD that offers MCCP compression
|
|
175
|
+
telnetlib3-client dunemud.net 6789
|
|
176
|
+
|
|
177
|
+
# or with TLS (compression auto-disabled over TLS, CRIME/BREACH mitigation)
|
|
178
|
+
telnetlib3-client --ssl dunemud.net 6788
|
|
179
|
+
|
|
180
|
+
# actively request compression from a server
|
|
181
|
+
telnetlib3-client --compression dunemud.net 6789
|
|
182
|
+
|
|
183
|
+
# reject compression even if the server offers it
|
|
184
|
+
telnetlib3-client --no-compression dunemud.net 6789
|
|
185
|
+
|
|
186
|
+
# host a MUD server that advertises MCCP2/MCCP3
|
|
187
|
+
telnetlib3-server --compression --shell=my_mud.shell
|
|
188
|
+
|
|
189
|
+
By default (without ``--compression`` or ``--no-compression``), the client
|
|
190
|
+
passively accepts compression when offered by the server, and the server does
|
|
191
|
+
not advertise compression. Compression is automatically disabled over TLS
|
|
192
|
+
connections to avoid CRIME/BREACH attacks.
|
|
193
|
+
|
|
194
|
+
|
|
153
195
|
Asyncio Protocol
|
|
154
196
|
----------------
|
|
155
197
|
|
|
@@ -177,8 +219,10 @@ To migrate code, change import statements:
|
|
|
177
219
|
# NEW imports:
|
|
178
220
|
import telnetlib3
|
|
179
221
|
|
|
180
|
-
|
|
181
|
-
|
|
222
|
+
``telnetlib3`` did not provide server support, while this library also provides
|
|
223
|
+
both client and server support through a similar Blocking API interface.
|
|
224
|
+
|
|
225
|
+
See `sync API documentation`_ for details.
|
|
182
226
|
|
|
183
227
|
Quick Example
|
|
184
228
|
=============
|
|
@@ -45,8 +45,8 @@ The CLI utility ``telnetlib3-client`` is provided for connecting to servers and
|
|
|
45
45
|
``telnetlib3-server`` for hosting a server.
|
|
46
46
|
|
|
47
47
|
Both tools accept the argument ``--shell=my_module.fn_shell`` describing a python module path to a
|
|
48
|
-
function of signature ``async def shell(reader, writer)``. The server also provides
|
|
49
|
-
argument to
|
|
48
|
+
function of signature ``async def shell(reader, writer)``. The server also provides a
|
|
49
|
+
``--pty-exec`` argument allowing it to act as a telnet server for any CLI/TUI programs.
|
|
50
50
|
|
|
51
51
|
::
|
|
52
52
|
|
|
@@ -59,7 +59,7 @@ argument to host stand-alone programs.
|
|
|
59
59
|
# automatic script communicates with a server
|
|
60
60
|
telnetlib3-client --shell bin.client_wargame.shell 1984.ws 666
|
|
61
61
|
|
|
62
|
-
# run a
|
|
62
|
+
# run a default shell server bound to 127.0.0.1 6023
|
|
63
63
|
telnetlib3-server
|
|
64
64
|
|
|
65
65
|
# or custom ip, port and shell
|
|
@@ -112,6 +112,48 @@ connected to a TCP socket without any telnet negotiation may require "raw" mode
|
|
|
112
112
|
|
|
113
113
|
telnetlib3-client --raw-mode area52.tk 5200 --encoding=atascii
|
|
114
114
|
|
|
115
|
+
Go-Ahead (GA)
|
|
116
|
+
~~~~~~~~~~~~~
|
|
117
|
+
|
|
118
|
+
When a client does not negotiate Suppress Go-Ahead (SGA), the server sends
|
|
119
|
+
``IAC GA`` after output to signal that the client may transmit. This is
|
|
120
|
+
correct behavior for MUD clients like Mudlet that expect prompt detection
|
|
121
|
+
via GA.
|
|
122
|
+
|
|
123
|
+
If GA causes unwanted output for your use case, disable it::
|
|
124
|
+
|
|
125
|
+
telnetlib3-server --never-send-ga
|
|
126
|
+
|
|
127
|
+
For PTY shells, GA is sent after 500ms of output idle time to avoid
|
|
128
|
+
injecting GA in the middle of streaming output.
|
|
129
|
+
|
|
130
|
+
Compression (MCCP)
|
|
131
|
+
~~~~~~~~~~~~~~~~~~
|
|
132
|
+
|
|
133
|
+
MCCP2 (server-to-client) and MCCP3 (client-to-server) zlib compression are
|
|
134
|
+
supported, widely used by MUD servers to reduce bandwidth::
|
|
135
|
+
|
|
136
|
+
# connect to a MUD that offers MCCP compression
|
|
137
|
+
telnetlib3-client dunemud.net 6789
|
|
138
|
+
|
|
139
|
+
# or with TLS (compression auto-disabled over TLS, CRIME/BREACH mitigation)
|
|
140
|
+
telnetlib3-client --ssl dunemud.net 6788
|
|
141
|
+
|
|
142
|
+
# actively request compression from a server
|
|
143
|
+
telnetlib3-client --compression dunemud.net 6789
|
|
144
|
+
|
|
145
|
+
# reject compression even if the server offers it
|
|
146
|
+
telnetlib3-client --no-compression dunemud.net 6789
|
|
147
|
+
|
|
148
|
+
# host a MUD server that advertises MCCP2/MCCP3
|
|
149
|
+
telnetlib3-server --compression --shell=my_mud.shell
|
|
150
|
+
|
|
151
|
+
By default (without ``--compression`` or ``--no-compression``), the client
|
|
152
|
+
passively accepts compression when offered by the server, and the server does
|
|
153
|
+
not advertise compression. Compression is automatically disabled over TLS
|
|
154
|
+
connections to avoid CRIME/BREACH attacks.
|
|
155
|
+
|
|
156
|
+
|
|
115
157
|
Asyncio Protocol
|
|
116
158
|
----------------
|
|
117
159
|
|
|
@@ -139,8 +181,10 @@ To migrate code, change import statements:
|
|
|
139
181
|
# NEW imports:
|
|
140
182
|
import telnetlib3
|
|
141
183
|
|
|
142
|
-
|
|
143
|
-
|
|
184
|
+
``telnetlib3`` did not provide server support, while this library also provides
|
|
185
|
+
both client and server support through a similar Blocking API interface.
|
|
186
|
+
|
|
187
|
+
See `sync API documentation`_ for details.
|
|
144
188
|
|
|
145
189
|
Quick Example
|
|
146
190
|
=============
|
|
@@ -22,7 +22,7 @@ sys.path.insert(
|
|
|
22
22
|
0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
suppress_warnings = ["image.nonlocal_uri"]
|
|
25
|
+
suppress_warnings = ["image.nonlocal_uri", "ref.class"]
|
|
26
26
|
|
|
27
27
|
autodoc_default_flags = [
|
|
28
28
|
"members",
|
|
@@ -68,10 +68,12 @@ copyright = f"2013-{datetime.datetime.now().year} Jeff Quast"
|
|
|
68
68
|
# built documents.
|
|
69
69
|
#
|
|
70
70
|
# The short X.Y version.
|
|
71
|
-
|
|
71
|
+
import telnetlib3.accessories
|
|
72
|
+
|
|
73
|
+
version = release = telnetlib3.accessories.get_version()
|
|
72
74
|
|
|
73
75
|
# The full version, including alpha/beta/rc tags.
|
|
74
|
-
release = "3.0.
|
|
76
|
+
#release = "3.0.1" # keep in sync with pyproject.toml and telnetlib3/accessories.py !!
|
|
75
77
|
|
|
76
78
|
# The language for content auto-generated by Sphinx. Refer to documentation
|
|
77
79
|
# for a list of supported languages.
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
History
|
|
2
2
|
=======
|
|
3
|
+
3.0.2
|
|
4
|
+
* bugfix: :meth:`~telnetlib3.stream_writer.TelnetWriter.request_charset` raised :exc:`TypeError`,
|
|
5
|
+
:ghissue:`128`. Offer callbacks (no-arg, returning a list of items to propose) are now
|
|
6
|
+
separated from send callbacks (which respond to received requests) via new
|
|
7
|
+
:meth:`~telnetlib3.stream_writer.TelnetWriter.set_ext_offer_callback` method.
|
|
8
|
+
|
|
9
|
+
3.0.1
|
|
10
|
+
* change: Unused client argument ``gmcp_log`` removed.
|
|
11
|
+
* new: MCCP2 and MCCP3. Both client and server ends passively support if requested, and request
|
|
12
|
+
support by --compression or deny support by --no-compression.
|
|
13
|
+
* new: :meth:`~telnetlib3.client.TelnetClient.on_request_charset` and
|
|
14
|
+
:meth:`~telnetlib3.client.TelnetClient.on_request_environ` offer callbacks
|
|
15
|
+
on the client, symmetric with the existing server-side callbacks.
|
|
16
|
+
|
|
3
17
|
3.0.0
|
|
4
18
|
* change: :attr:`~telnetlib3.client_base.BaseClient.connect_minwait` default
|
|
5
19
|
now 0 (was 1.0 seconds in library API).
|
|
@@ -81,16 +81,23 @@ Dungeon) servers and clients.
|
|
|
81
81
|
* `MSSP`_ (MUD Server Status Protocol, option 70). Server metadata protocol
|
|
82
82
|
for MUD crawlers and directories, providing server name, player count,
|
|
83
83
|
codebase, and other listing information.
|
|
84
|
+
* `MCCP2`_ (MUD Client Compression Protocol v2, option 86). Server-to-client
|
|
85
|
+
zlib compression, reducing bandwidth for output-heavy sessions. Activated
|
|
86
|
+
via ``IAC SB MCCP2 IAC SE``; all subsequent server output is compressed.
|
|
87
|
+
* `MCCP3`_ (MUD Client Compression Protocol v3, option 87). Client-to-server
|
|
88
|
+
zlib compression, the reverse direction of MCCP2.
|
|
84
89
|
|
|
85
90
|
.. _GMCP: https://www.gammon.com.au/gmcp
|
|
86
91
|
.. _MSDP: https://tintin.mudhalla.net/protocols/msdp/
|
|
87
92
|
.. _MSSP: https://tintin.mudhalla.net/protocols/mssp/
|
|
93
|
+
.. _MCCP2: https://tintin.mudhalla.net/protocols/mccp/
|
|
94
|
+
.. _MCCP3: https://tintin.mudhalla.net/protocols/mccp/
|
|
88
95
|
|
|
89
96
|
MUDs Not Implemented
|
|
90
97
|
--------------------
|
|
91
98
|
|
|
92
99
|
Constants are also defined for the following MUD options, though their handlers
|
|
93
|
-
are not implemented: MCCP
|
|
100
|
+
are not implemented: MCCP (85, legacy compression), MXP (91, markup), ZMP
|
|
94
101
|
(93, messaging), MSP (90, sound), and ATCP (200, Achaea-specific).
|
|
95
102
|
|
|
96
103
|
Additional Resources
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "telnetlib3"
|
|
7
|
-
version = "3.0.
|
|
7
|
+
version = "3.0.2" # Keep in sync with telnetlib3/accessories.py::get_version !
|
|
8
8
|
description = " Python Telnet server and client CLI and Protocol library"
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
license = "ISC"
|
|
@@ -45,6 +45,9 @@ def _process_data_chunk(
|
|
|
45
45
|
or ``None`` when only IAC (255) is special.
|
|
46
46
|
:param log_fn: Callable for logging exceptions (e.g. ``logger.warning``).
|
|
47
47
|
:returns: ``True`` if any IAC/SB command was observed.
|
|
48
|
+
|
|
49
|
+
When MCCP2 is activated mid-chunk, the remaining compressed bytes are
|
|
50
|
+
stored in ``writer._compressed_remainder`` for the caller to consume.
|
|
48
51
|
"""
|
|
49
52
|
cmd_received = False
|
|
50
53
|
n = len(data)
|
|
@@ -84,6 +87,12 @@ def _process_data_chunk(
|
|
|
84
87
|
out_start = i
|
|
85
88
|
feeding_oob = bool(writer.is_oob)
|
|
86
89
|
|
|
90
|
+
if writer._mccp2_activated:
|
|
91
|
+
writer._mccp2_activated = False
|
|
92
|
+
writer.mccp2_active = True
|
|
93
|
+
writer._compressed_remainder = data[i:] if i < n else b""
|
|
94
|
+
return True
|
|
95
|
+
|
|
87
96
|
return cmd_received
|
|
88
97
|
|
|
89
98
|
|
|
@@ -42,7 +42,7 @@ PATIENCE_MESSAGES = [
|
|
|
42
42
|
|
|
43
43
|
def get_version() -> str:
|
|
44
44
|
"""Return the current version of telnetlib3."""
|
|
45
|
-
return "3.0.
|
|
45
|
+
return "3.0.2" # keep in sync with pyproject.toml !
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def encoding_from_lang(lang: str) -> Optional[str]:
|
|
@@ -23,6 +23,8 @@ from telnetlib3.stream_writer import TelnetWriter, TelnetWriterUnicode
|
|
|
23
23
|
__all__ = ("TelnetClient", "TelnetTerminalClient", "open_connection")
|
|
24
24
|
|
|
25
25
|
#: Default GMCP modules requested via ``Core.Supports.Set``.
|
|
26
|
+
#: Sub-modules are listed explicitly because not all servers treat
|
|
27
|
+
#: top-level subscriptions as wildcards.
|
|
26
28
|
_DEFAULT_GMCP_MODULES = [
|
|
27
29
|
"Char 1",
|
|
28
30
|
"Char.Vitals 1",
|
|
@@ -31,6 +33,7 @@ _DEFAULT_GMCP_MODULES = [
|
|
|
31
33
|
"Room.Info 1",
|
|
32
34
|
"Comm 1",
|
|
33
35
|
"Comm.Channel 1",
|
|
36
|
+
"Group 1",
|
|
34
37
|
]
|
|
35
38
|
|
|
36
39
|
|
|
@@ -65,13 +68,14 @@ class TelnetClient(client_base.BaseClient):
|
|
|
65
68
|
force_binary: bool = False,
|
|
66
69
|
connect_minwait: float = 0,
|
|
67
70
|
connect_maxwait: float = 4.0,
|
|
71
|
+
compression: Optional[bool] = None,
|
|
68
72
|
limit: Optional[int] = None,
|
|
69
73
|
waiter_closed: Optional[asyncio.Future[None]] = None,
|
|
70
74
|
_waiter_connected: Optional[asyncio.Future[None]] = None,
|
|
71
75
|
gmcp_modules: Optional[List[str]] = None,
|
|
72
|
-
gmcp_log: bool = False,
|
|
73
76
|
) -> None:
|
|
74
77
|
"""Initialize TelnetClient with terminal parameters."""
|
|
78
|
+
self._compression = compression
|
|
75
79
|
super().__init__(
|
|
76
80
|
shell=shell,
|
|
77
81
|
encoding=encoding,
|
|
@@ -84,7 +88,6 @@ class TelnetClient(client_base.BaseClient):
|
|
|
84
88
|
_waiter_connected=_waiter_connected,
|
|
85
89
|
)
|
|
86
90
|
self._gmcp_modules = gmcp_modules or list(_DEFAULT_GMCP_MODULES)
|
|
87
|
-
self._gmcp_log = gmcp_log
|
|
88
91
|
self._gmcp_hello_sent = False
|
|
89
92
|
self._send_environ = set(send_environ or self.DEFAULT_SEND_ENVIRON)
|
|
90
93
|
self._extra.update(
|
|
@@ -118,6 +121,9 @@ class TelnetClient(client_base.BaseClient):
|
|
|
118
121
|
|
|
119
122
|
super().connection_made(transport)
|
|
120
123
|
|
|
124
|
+
# Set compression policy on writer
|
|
125
|
+
self.writer.compression = self._compression
|
|
126
|
+
|
|
121
127
|
# Wire extended rfc callbacks for requests of
|
|
122
128
|
# terminal attributes, environment values, etc.
|
|
123
129
|
for opt, func in (
|
|
@@ -130,6 +136,14 @@ class TelnetClient(client_base.BaseClient):
|
|
|
130
136
|
):
|
|
131
137
|
self.writer.set_ext_send_callback(opt, func)
|
|
132
138
|
|
|
139
|
+
# Offer callbacks define what to include in outgoing requests
|
|
140
|
+
# (e.g. what charsets to offer in SB CHARSET REQUEST).
|
|
141
|
+
for opt, offer_func in (
|
|
142
|
+
(CHARSET, self.on_request_charset),
|
|
143
|
+
(NEW_ENVIRON, self.on_request_environ),
|
|
144
|
+
):
|
|
145
|
+
self.writer.set_ext_offer_callback(opt, offer_func)
|
|
146
|
+
|
|
133
147
|
# Override the default handle_will method to detect when both sides support CHARSET
|
|
134
148
|
# Store the original only on first connection to prevent chain growth on reconnect.
|
|
135
149
|
if not hasattr(self.writer, "_original_handle_will"):
|
|
@@ -161,7 +175,7 @@ class TelnetClient(client_base.BaseClient):
|
|
|
161
175
|
"""Wire GMCP callback and WILL-detection for Core.Hello handshake."""
|
|
162
176
|
from telnetlib3.telopt import GMCP
|
|
163
177
|
|
|
164
|
-
self.writer.set_ext_callback(GMCP, self.
|
|
178
|
+
self.writer.set_ext_callback(GMCP, self.on_gmcp)
|
|
165
179
|
|
|
166
180
|
# Capture current handle_will (already includes CHARSET wrapper).
|
|
167
181
|
# On reconnect, _original_handle_will was already restored in connection_made,
|
|
@@ -186,17 +200,14 @@ class TelnetClient(client_base.BaseClient):
|
|
|
186
200
|
self.writer.send_gmcp("Core.Supports.Set", self._gmcp_modules)
|
|
187
201
|
self.log.info("GMCP handshake: Core.Hello + Core.Supports.Set %s", self._gmcp_modules)
|
|
188
202
|
|
|
189
|
-
def
|
|
203
|
+
def on_gmcp(self, package: str, data: Any) -> None:
|
|
190
204
|
"""Store incoming GMCP data on ``writer.ctx``, merging dict updates."""
|
|
191
205
|
gmcp = self.writer.ctx.gmcp_data
|
|
192
206
|
if isinstance(data, dict) and isinstance(gmcp.get(package), dict):
|
|
193
207
|
gmcp[package].update(data)
|
|
194
208
|
else:
|
|
195
209
|
gmcp[package] = data
|
|
196
|
-
|
|
197
|
-
self.log.info("GMCP: %s %r", package, data)
|
|
198
|
-
else:
|
|
199
|
-
self.log.debug("GMCP: %s %r", package, data)
|
|
210
|
+
self.log.debug("GMCP: %s %r", package, data)
|
|
200
211
|
|
|
201
212
|
def send_ttype(self) -> str:
|
|
202
213
|
"""Callback for responding to TTYPE requests."""
|
|
@@ -369,6 +380,28 @@ class TelnetClient(client_base.BaseClient):
|
|
|
369
380
|
self.log.warning("No suitable encoding offered by server: %s", offered)
|
|
370
381
|
return ""
|
|
371
382
|
|
|
383
|
+
def on_request_charset(self) -> List[str]:
|
|
384
|
+
"""
|
|
385
|
+
Offer callback for client-initiated CHARSET REQUEST, :rfc:`2066`.
|
|
386
|
+
|
|
387
|
+
Called by :meth:`~.TelnetWriter.request_charset` to determine which
|
|
388
|
+
character sets the client offers to the server.
|
|
389
|
+
|
|
390
|
+
:returns: List of charset name strings to offer.
|
|
391
|
+
"""
|
|
392
|
+
return ["UTF-8", "LATIN1", "US-ASCII"]
|
|
393
|
+
|
|
394
|
+
def on_request_environ(self) -> List[str]:
|
|
395
|
+
"""
|
|
396
|
+
Offer callback for client-initiated NEW_ENVIRON SEND, :rfc:`1572`.
|
|
397
|
+
|
|
398
|
+
Called by :meth:`~.TelnetWriter.request_environ` to determine which
|
|
399
|
+
environment variable names the client requests from the server.
|
|
400
|
+
|
|
401
|
+
:returns: List of environment variable names to request.
|
|
402
|
+
"""
|
|
403
|
+
return []
|
|
404
|
+
|
|
372
405
|
def send_naws(self) -> Tuple[int, int]:
|
|
373
406
|
"""
|
|
374
407
|
Callback for responding to NAWS requests.
|
|
@@ -472,6 +505,7 @@ async def open_connection(
|
|
|
472
505
|
connect_minwait: float = 0,
|
|
473
506
|
connect_maxwait: float = 3.0,
|
|
474
507
|
connect_timeout: Optional[float] = None,
|
|
508
|
+
compression: Optional[bool] = None,
|
|
475
509
|
waiter_closed: Optional[asyncio.Future[None]] = None,
|
|
476
510
|
_waiter_connected: Optional[asyncio.Future[None]] = None,
|
|
477
511
|
limit: Optional[int] = None,
|
|
@@ -531,6 +565,9 @@ async def open_connection(
|
|
|
531
565
|
connection attempt may block indefinitely. When specified, a
|
|
532
566
|
:exc:`ConnectionError` is raised if the connection is not established
|
|
533
567
|
within the given time.
|
|
568
|
+
:param compression: MCCP compression policy. ``None`` (default) passively
|
|
569
|
+
accepts compression when offered by the server. ``True`` actively
|
|
570
|
+
requests MCCP2/MCCP3. ``False`` rejects all compression offers.
|
|
534
571
|
|
|
535
572
|
:param force_binary: When ``True``, the encoding is used regardless
|
|
536
573
|
of BINARY mode negotiation.
|
|
@@ -565,6 +602,7 @@ async def open_connection(
|
|
|
565
602
|
shell=shell,
|
|
566
603
|
connect_minwait=connect_minwait,
|
|
567
604
|
connect_maxwait=connect_maxwait,
|
|
605
|
+
compression=compression,
|
|
568
606
|
waiter_closed=waiter_closed,
|
|
569
607
|
_waiter_connected=_waiter_connected,
|
|
570
608
|
limit=limit,
|
|
@@ -618,12 +656,10 @@ async def run_client() -> None:
|
|
|
618
656
|
# flags before negotiation starts.
|
|
619
657
|
encoding_explicit = args["encoding"] not in ("utf8", "utf-8", False)
|
|
620
658
|
gmcp_modules: Optional[List[str]] = args.get("gmcp_modules")
|
|
621
|
-
gmcp_log: bool = args.get("gmcp_log", False)
|
|
622
659
|
|
|
623
660
|
def _client_factory(**kwargs: Any) -> client_base.BaseClient:
|
|
624
661
|
client: TelnetClient
|
|
625
662
|
kwargs["gmcp_modules"] = gmcp_modules
|
|
626
|
-
kwargs["gmcp_log"] = gmcp_log
|
|
627
663
|
if sys.platform != "win32" and sys.stdin.isatty():
|
|
628
664
|
client = TelnetTerminalClient(**kwargs)
|
|
629
665
|
else:
|
|
@@ -897,6 +933,13 @@ def _get_argument_parser() -> argparse.ArgumentParser:
|
|
|
897
933
|
"keys instead of encoding-specific control codes. Use for "
|
|
898
934
|
"BBSes that expect ANSI cursor sequences.",
|
|
899
935
|
)
|
|
936
|
+
parser.add_argument(
|
|
937
|
+
"--compression",
|
|
938
|
+
action=argparse.BooleanOptionalAction,
|
|
939
|
+
default=None,
|
|
940
|
+
help="MCCP compression: --compression to request, --no-compression to reject, "
|
|
941
|
+
"omit to passively accept (default)",
|
|
942
|
+
)
|
|
900
943
|
parser.add_argument(
|
|
901
944
|
"--ssl", action="store_true", default=False, help="connect using TLS (TELNETS)"
|
|
902
945
|
)
|
|
@@ -923,12 +966,6 @@ def _get_argument_parser() -> argparse.ArgumentParser:
|
|
|
923
966
|
'(e.g. "Char 1,Room 1,IRE.Rift 1"). '
|
|
924
967
|
"When provided, replaces the built-in defaults.",
|
|
925
968
|
)
|
|
926
|
-
parser.add_argument(
|
|
927
|
-
"--gmcp-log",
|
|
928
|
-
action="store_true",
|
|
929
|
-
default=False,
|
|
930
|
-
help="log all incoming GMCP messages at INFO level " "(default: DEBUG only)",
|
|
931
|
-
)
|
|
932
969
|
parser.add_argument(
|
|
933
970
|
"--typescript",
|
|
934
971
|
default=None,
|
|
@@ -1029,7 +1066,7 @@ def _transform_args(args: argparse.Namespace) -> Dict[str, Any]:
|
|
|
1029
1066
|
if args.gmcp_modules
|
|
1030
1067
|
else None
|
|
1031
1068
|
),
|
|
1032
|
-
"
|
|
1069
|
+
"compression": args.compression,
|
|
1033
1070
|
"typescript": args.typescript,
|
|
1034
1071
|
}
|
|
1035
1072
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
# std imports
|
|
6
|
+
import zlib
|
|
6
7
|
import asyncio
|
|
7
8
|
import logging
|
|
8
9
|
import weakref
|
|
@@ -66,6 +67,12 @@ class BaseClient(TelnetProtocolBase, asyncio.streams.FlowControlMixin, asyncio.P
|
|
|
66
67
|
self.writer: Optional[Union[TelnetWriter, TelnetWriterUnicode]] = None
|
|
67
68
|
self._limit = limit
|
|
68
69
|
|
|
70
|
+
# MCCP2: server→client decompression
|
|
71
|
+
self._mccp2_decompressor: Optional[zlib._Decompress] = None
|
|
72
|
+
# MCCP3: client→server compression
|
|
73
|
+
self._mccp3_compressor: Optional[zlib._Compress] = None
|
|
74
|
+
self._mccp3_orig_write: Any = None
|
|
75
|
+
|
|
69
76
|
# High-throughput receive pipeline
|
|
70
77
|
self._rx_queue: collections.deque[bytes] = collections.deque()
|
|
71
78
|
self._rx_bytes = 0
|
|
@@ -93,6 +100,11 @@ class BaseClient(TelnetProtocolBase, asyncio.streams.FlowControlMixin, asyncio.P
|
|
|
93
100
|
return
|
|
94
101
|
self._closing = True
|
|
95
102
|
|
|
103
|
+
# Clean up MCCP compressors/decompressors
|
|
104
|
+
self._mccp2_decompressor = None
|
|
105
|
+
self._mccp3_compressor = None
|
|
106
|
+
self._mccp3_orig_write = None
|
|
107
|
+
|
|
96
108
|
# Drain any pending rx data before signalling EOF to prevent
|
|
97
109
|
# _process_rx from calling feed_data() after feed_eof().
|
|
98
110
|
self._rx_queue.clear()
|
|
@@ -343,6 +355,26 @@ class BaseClient(TelnetProtocolBase, asyncio.streams.FlowControlMixin, asyncio.P
|
|
|
343
355
|
"""Process a chunk of received bytes; return True if any IAC/SB cmd observed."""
|
|
344
356
|
self._last_received = datetime.datetime.now()
|
|
345
357
|
|
|
358
|
+
# MCCP2: decompress server→client data when active
|
|
359
|
+
if self._mccp2_decompressor is not None:
|
|
360
|
+
try:
|
|
361
|
+
data = self._mccp2_decompressor.decompress(data)
|
|
362
|
+
except zlib.error:
|
|
363
|
+
self.log.warning("MCCP2 decompression error, disabling")
|
|
364
|
+
self._mccp2_end()
|
|
365
|
+
return False
|
|
366
|
+
if self._mccp2_decompressor.eof:
|
|
367
|
+
unused = self._mccp2_decompressor.unused_data
|
|
368
|
+
self._mccp2_end()
|
|
369
|
+
cmd = self._process_chunk_inner(data)
|
|
370
|
+
if unused:
|
|
371
|
+
cmd = self._process_chunk(unused) or cmd
|
|
372
|
+
return cmd
|
|
373
|
+
|
|
374
|
+
return self._process_chunk_inner(data)
|
|
375
|
+
|
|
376
|
+
def _process_chunk_inner(self, data: bytes) -> bool:
|
|
377
|
+
"""Inner chunk processing with IAC interpretation and mid-chunk MCCP2 detection."""
|
|
346
378
|
try:
|
|
347
379
|
mode = self.writer.mode
|
|
348
380
|
except Exception:
|
|
@@ -355,7 +387,22 @@ class BaseClient(TelnetProtocolBase, asyncio.streams.FlowControlMixin, asyncio.P
|
|
|
355
387
|
else:
|
|
356
388
|
slc_special = None
|
|
357
389
|
|
|
358
|
-
|
|
390
|
+
cmd_received = _process_data_chunk(
|
|
391
|
+
data, self.writer, self.reader, slc_special, self.log.warning
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if self.writer._compressed_remainder is not None:
|
|
395
|
+
remainder = self.writer._compressed_remainder
|
|
396
|
+
self.writer._compressed_remainder = None
|
|
397
|
+
self._mccp2_start()
|
|
398
|
+
if remainder:
|
|
399
|
+
cmd_received = self._process_chunk(remainder) or cmd_received
|
|
400
|
+
|
|
401
|
+
# MCCP3: start compressor when writer signals activation
|
|
402
|
+
if self.writer.mccp3_active and self._mccp3_compressor is None:
|
|
403
|
+
self._mccp3_start()
|
|
404
|
+
|
|
405
|
+
return cmd_received
|
|
359
406
|
|
|
360
407
|
async def _process_rx(self) -> None:
|
|
361
408
|
"""Async processor for receive queue that yields control and applies backpressure."""
|
|
@@ -395,6 +442,49 @@ class BaseClient(TelnetProtocolBase, asyncio.streams.FlowControlMixin, asyncio.P
|
|
|
395
442
|
if any_cmd and not self._waiter_connected.done():
|
|
396
443
|
self._check_negotiation_timer()
|
|
397
444
|
|
|
445
|
+
def _mccp2_start(self) -> None:
|
|
446
|
+
"""Start MCCP2 decompression of server→client data."""
|
|
447
|
+
self._mccp2_decompressor = zlib.decompressobj()
|
|
448
|
+
self.log.debug("MCCP2 decompression started (server→client)")
|
|
449
|
+
|
|
450
|
+
def _mccp2_end(self) -> None:
|
|
451
|
+
"""Stop MCCP2 decompression."""
|
|
452
|
+
self._mccp2_decompressor = None
|
|
453
|
+
self.writer.mccp2_active = False
|
|
454
|
+
self.log.debug("MCCP2 decompression ended (server→client)")
|
|
455
|
+
|
|
456
|
+
def _mccp3_start(self) -> None:
|
|
457
|
+
"""Start MCCP3 compression of client→server data."""
|
|
458
|
+
self._mccp3_compressor = zlib.compressobj(
|
|
459
|
+
zlib.Z_BEST_COMPRESSION, zlib.DEFLATED, 12, 5, zlib.Z_DEFAULT_STRATEGY
|
|
460
|
+
)
|
|
461
|
+
# Wrap transport.write so all outbound bytes are compressed
|
|
462
|
+
transport = self.writer._transport
|
|
463
|
+
orig_write = transport.write
|
|
464
|
+
|
|
465
|
+
def compressed_write(data: bytes) -> None:
|
|
466
|
+
if self._mccp3_compressor is not None:
|
|
467
|
+
compressed = self._mccp3_compressor.compress(data)
|
|
468
|
+
compressed += self._mccp3_compressor.flush(zlib.Z_SYNC_FLUSH)
|
|
469
|
+
orig_write(compressed)
|
|
470
|
+
else:
|
|
471
|
+
orig_write(data)
|
|
472
|
+
|
|
473
|
+
transport.write = compressed_write # type: ignore[method-assign]
|
|
474
|
+
self._mccp3_orig_write = orig_write
|
|
475
|
+
self.log.debug("MCCP3 compression started (client→server)")
|
|
476
|
+
|
|
477
|
+
def _mccp3_end(self) -> None:
|
|
478
|
+
"""Stop MCCP3 compression, flush Z_FINISH."""
|
|
479
|
+
if self._mccp3_compressor is not None:
|
|
480
|
+
if not self.writer.is_closing():
|
|
481
|
+
self._mccp3_orig_write(self._mccp3_compressor.flush(zlib.Z_FINISH))
|
|
482
|
+
self._mccp3_compressor = None
|
|
483
|
+
# Restore original transport.write
|
|
484
|
+
self.writer._transport.write = self._mccp3_orig_write # type: ignore[method-assign]
|
|
485
|
+
self.writer.mccp3_active = False
|
|
486
|
+
self.log.debug("MCCP3 compression ended (client→server)")
|
|
487
|
+
|
|
398
488
|
def _check_negotiation_timer(self) -> None:
|
|
399
489
|
self._check_later.cancel()
|
|
400
490
|
self._tasks.remove(self._check_later)
|
|
@@ -83,6 +83,8 @@ from .telopt import (
|
|
|
83
83
|
SUPDUPOUTPUT,
|
|
84
84
|
VT3270REGIME,
|
|
85
85
|
AUTHENTICATION,
|
|
86
|
+
MCCP2_COMPRESS,
|
|
87
|
+
MCCP3_COMPRESS,
|
|
86
88
|
COM_PORT_OPTION,
|
|
87
89
|
PRAGMA_HEARTBEAT,
|
|
88
90
|
SUPPRESS_LOCAL_ECHO,
|
|
@@ -260,6 +262,8 @@ MUD_OPTIONS = [(COM_PORT_OPTION, "COM_PORT", "Serial port control (RFC 2217)")]
|
|
|
260
262
|
# returning a hard error for anything else. GMCP-capable MUD clients
|
|
261
263
|
# typically self-announce via IAC WILL GMCP, so probing is unnecessary.
|
|
262
264
|
EXTENDED_OPTIONS = [
|
|
265
|
+
(MCCP2_COMPRESS, "MCCP2", "MUD Client Compression Protocol v2"),
|
|
266
|
+
(MCCP3_COMPRESS, "MCCP3", "MUD Client Compression Protocol v3"),
|
|
263
267
|
(GMCP, "GMCP", "Generic MUD Communication Protocol"),
|
|
264
268
|
(MSDP, "MSDP", "MUD Server Data Protocol"),
|
|
265
269
|
(MSSP, "MSSP", "MUD Server Status Protocol"),
|