permessage_deflate.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. from __future__ import annotations
  2. import zlib
  3. from collections.abc import Sequence
  4. from typing import Any, Literal
  5. from .. import frames
  6. from ..exceptions import (
  7. DuplicateParameter,
  8. InvalidParameterName,
  9. InvalidParameterValue,
  10. NegotiationError,
  11. PayloadTooBig,
  12. ProtocolError,
  13. )
  14. from ..typing import ExtensionName, ExtensionParameter
  15. from .base import ClientExtensionFactory, Extension, ServerExtensionFactory
  16. __all__ = [
  17. "PerMessageDeflate",
  18. "ClientPerMessageDeflateFactory",
  19. "enable_client_permessage_deflate",
  20. "ServerPerMessageDeflateFactory",
  21. "enable_server_permessage_deflate",
  22. ]
  23. _EMPTY_UNCOMPRESSED_BLOCK = b"\x00\x00\xff\xff"
  24. _MAX_WINDOW_BITS_VALUES = [str(bits) for bits in range(8, 16)]
  25. class PerMessageDeflate(Extension):
  26. """
  27. Per-Message Deflate extension.
  28. """
  29. name = ExtensionName("permessage-deflate")
  30. def __init__(
  31. self,
  32. remote_no_context_takeover: bool,
  33. local_no_context_takeover: bool,
  34. remote_max_window_bits: int,
  35. local_max_window_bits: int,
  36. compress_settings: dict[Any, Any] | None = None,
  37. ) -> None:
  38. """
  39. Configure the Per-Message Deflate extension.
  40. """
  41. if compress_settings is None:
  42. compress_settings = {}
  43. assert remote_no_context_takeover in [False, True]
  44. assert local_no_context_takeover in [False, True]
  45. assert 8 <= remote_max_window_bits <= 15
  46. assert 8 <= local_max_window_bits <= 15
  47. assert "wbits" not in compress_settings
  48. self.remote_no_context_takeover = remote_no_context_takeover
  49. self.local_no_context_takeover = local_no_context_takeover
  50. self.remote_max_window_bits = remote_max_window_bits
  51. self.local_max_window_bits = local_max_window_bits
  52. self.compress_settings = compress_settings
  53. if not self.remote_no_context_takeover:
  54. self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits)
  55. if not self.local_no_context_takeover:
  56. self.encoder = zlib.compressobj(
  57. wbits=-self.local_max_window_bits,
  58. **self.compress_settings,
  59. )
  60. # To handle continuation frames properly, we must keep track of
  61. # whether that initial frame was encoded.
  62. self.decode_cont_data = False
  63. # There's no need for self.encode_cont_data because we always encode
  64. # outgoing frames, so it would always be True.
  65. def __repr__(self) -> str:
  66. return (
  67. f"PerMessageDeflate("
  68. f"remote_no_context_takeover={self.remote_no_context_takeover}, "
  69. f"local_no_context_takeover={self.local_no_context_takeover}, "
  70. f"remote_max_window_bits={self.remote_max_window_bits}, "
  71. f"local_max_window_bits={self.local_max_window_bits})"
  72. )
  73. def decode(
  74. self,
  75. frame: frames.Frame,
  76. *,
  77. max_size: int | None = None,
  78. ) -> frames.Frame:
  79. """
  80. Decode an incoming frame.
  81. """
  82. # Skip control frames.
  83. if frame.opcode in frames.CTRL_OPCODES:
  84. return frame
  85. # Handle continuation data frames:
  86. # - skip if the message isn't encoded
  87. # - reset "decode continuation data" flag if it's a final frame
  88. if frame.opcode is frames.OP_CONT:
  89. if not self.decode_cont_data:
  90. return frame
  91. if frame.fin:
  92. self.decode_cont_data = False
  93. # Handle text and binary data frames:
  94. # - skip if the message isn't encoded
  95. # - unset the rsv1 flag on the first frame of a compressed message
  96. # - set "decode continuation data" flag if it's a non-final frame
  97. else:
  98. if not frame.rsv1:
  99. return frame
  100. if not frame.fin:
  101. self.decode_cont_data = True
  102. # Re-initialize per-message decoder.
  103. if self.remote_no_context_takeover:
  104. self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits)
  105. # Uncompress data. Protect against zip bombs by preventing zlib from
  106. # decompressing more than max_length bytes (except when the limit is
  107. # disabled with max_size = None).
  108. if frame.fin and len(frame.data) < 2044:
  109. # Profiling shows that appending four bytes, which makes a copy, is
  110. # faster than calling decompress() again when data is less than 2kB.
  111. data = bytes(frame.data) + _EMPTY_UNCOMPRESSED_BLOCK
  112. else:
  113. data = frame.data
  114. max_length = 0 if max_size is None else max_size
  115. try:
  116. data = self.decoder.decompress(data, max_length)
  117. if self.decoder.unconsumed_tail:
  118. assert max_size is not None # help mypy
  119. raise PayloadTooBig(None, max_size)
  120. if frame.fin and len(frame.data) >= 2044:
  121. # This cannot generate additional data.
  122. self.decoder.decompress(_EMPTY_UNCOMPRESSED_BLOCK)
  123. except zlib.error as exc:
  124. raise ProtocolError("decompression failed") from exc
  125. # Allow garbage collection of the decoder if it won't be reused.
  126. if frame.fin and self.remote_no_context_takeover:
  127. del self.decoder
  128. return frames.Frame(
  129. frame.opcode,
  130. data,
  131. frame.fin,
  132. # Unset the rsv1 flag on the first frame of a compressed message.
  133. False,
  134. frame.rsv2,
  135. frame.rsv3,
  136. )
  137. def encode(self, frame: frames.Frame) -> frames.Frame:
  138. """
  139. Encode an outgoing frame.
  140. """
  141. # Skip control frames.
  142. if frame.opcode in frames.CTRL_OPCODES:
  143. return frame
  144. # Since we always encode messages, there's no "encode continuation
  145. # data" flag similar to "decode continuation data" at this time.
  146. if frame.opcode is not frames.OP_CONT:
  147. # Re-initialize per-message decoder.
  148. if self.local_no_context_takeover:
  149. self.encoder = zlib.compressobj(
  150. wbits=-self.local_max_window_bits,
  151. **self.compress_settings,
  152. )
  153. # Compress data.
  154. data = self.encoder.compress(frame.data) + self.encoder.flush(zlib.Z_SYNC_FLUSH)
  155. if frame.fin:
  156. # Sync flush generates between 5 or 6 bytes, ending with the bytes
  157. # 0x00 0x00 0xff 0xff, which must be removed.
  158. assert data[-4:] == _EMPTY_UNCOMPRESSED_BLOCK
  159. # Making a copy is faster than memoryview(a)[:-4] until 2kB.
  160. if len(data) < 2048:
  161. data = data[:-4]
  162. else:
  163. data = memoryview(data)[:-4]
  164. # Allow garbage collection of the encoder if it won't be reused.
  165. if frame.fin and self.local_no_context_takeover:
  166. del self.encoder
  167. return frames.Frame(
  168. frame.opcode,
  169. data,
  170. frame.fin,
  171. # Set the rsv1 flag on the first frame of a compressed message.
  172. frame.opcode is not frames.OP_CONT,
  173. frame.rsv2,
  174. frame.rsv3,
  175. )
  176. def _build_parameters(
  177. server_no_context_takeover: bool,
  178. client_no_context_takeover: bool,
  179. server_max_window_bits: int | None,
  180. client_max_window_bits: int | Literal[True] | None,
  181. ) -> list[ExtensionParameter]:
  182. """
  183. Build a list of ``(name, value)`` pairs for some compression parameters.
  184. """
  185. params: list[ExtensionParameter] = []
  186. if server_no_context_takeover:
  187. params.append(("server_no_context_takeover", None))
  188. if client_no_context_takeover:
  189. params.append(("client_no_context_takeover", None))
  190. if server_max_window_bits:
  191. params.append(("server_max_window_bits", str(server_max_window_bits)))
  192. if client_max_window_bits is True: # only in handshake requests
  193. params.append(("client_max_window_bits", None))
  194. elif client_max_window_bits:
  195. params.append(("client_max_window_bits", str(client_max_window_bits)))
  196. return params
  197. def _extract_parameters(
  198. params: Sequence[ExtensionParameter], *, is_server: bool
  199. ) -> tuple[bool, bool, int | None, int | Literal[True] | None]:
  200. """
  201. Extract compression parameters from a list of ``(name, value)`` pairs.
  202. If ``is_server`` is :obj:`True`, ``client_max_window_bits`` may be
  203. provided without a value. This is only allowed in handshake requests.
  204. """
  205. server_no_context_takeover: bool = False
  206. client_no_context_takeover: bool = False
  207. server_max_window_bits: int | None = None
  208. client_max_window_bits: int | Literal[True] | None = None
  209. for name, value in params:
  210. if name == "server_no_context_takeover":
  211. if server_no_context_takeover:
  212. raise DuplicateParameter(name)
  213. if value is None:
  214. server_no_context_takeover = True
  215. else:
  216. raise InvalidParameterValue(name, value)
  217. elif name == "client_no_context_takeover":
  218. if client_no_context_takeover:
  219. raise DuplicateParameter(name)
  220. if value is None:
  221. client_no_context_takeover = True
  222. else:
  223. raise InvalidParameterValue(name, value)
  224. elif name == "server_max_window_bits":
  225. if server_max_window_bits is not None:
  226. raise DuplicateParameter(name)
  227. if value in _MAX_WINDOW_BITS_VALUES:
  228. server_max_window_bits = int(value)
  229. else:
  230. raise InvalidParameterValue(name, value)
  231. elif name == "client_max_window_bits":
  232. if client_max_window_bits is not None:
  233. raise DuplicateParameter(name)
  234. if is_server and value is None: # only in handshake requests
  235. client_max_window_bits = True
  236. elif value in _MAX_WINDOW_BITS_VALUES:
  237. client_max_window_bits = int(value)
  238. else:
  239. raise InvalidParameterValue(name, value)
  240. else:
  241. raise InvalidParameterName(name)
  242. return (
  243. server_no_context_takeover,
  244. client_no_context_takeover,
  245. server_max_window_bits,
  246. client_max_window_bits,
  247. )
  248. class ClientPerMessageDeflateFactory(ClientExtensionFactory):
  249. """
  250. Client-side extension factory for the Per-Message Deflate extension.
  251. Parameters behave as described in `section 7.1 of RFC 7692`_.
  252. .. _section 7.1 of RFC 7692: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1
  253. Set them to :obj:`True` to include them in the negotiation offer without a
  254. value or to an integer value to include them with this value.
  255. Args:
  256. server_no_context_takeover: Prevent server from using context takeover.
  257. client_no_context_takeover: Prevent client from using context takeover.
  258. server_max_window_bits: Maximum size of the server's LZ77 sliding window
  259. in bits, between 8 and 15.
  260. client_max_window_bits: Maximum size of the client's LZ77 sliding window
  261. in bits, between 8 and 15, or :obj:`True` to indicate support without
  262. setting a limit.
  263. compress_settings: Additional keyword arguments for :func:`zlib.compressobj`,
  264. excluding ``wbits``.
  265. """
  266. name = ExtensionName("permessage-deflate")
  267. def __init__(
  268. self,
  269. server_no_context_takeover: bool = False,
  270. client_no_context_takeover: bool = False,
  271. server_max_window_bits: int | None = None,
  272. client_max_window_bits: int | Literal[True] | None = True,
  273. compress_settings: dict[str, Any] | None = None,
  274. ) -> None:
  275. """
  276. Configure the Per-Message Deflate extension factory.
  277. """
  278. if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15):
  279. raise ValueError("server_max_window_bits must be between 8 and 15")
  280. if not (
  281. client_max_window_bits is None
  282. or client_max_window_bits is True
  283. or 8 <= client_max_window_bits <= 15
  284. ):
  285. raise ValueError("client_max_window_bits must be between 8 and 15")
  286. if compress_settings is not None and "wbits" in compress_settings:
  287. raise ValueError(
  288. "compress_settings must not include wbits, "
  289. "set client_max_window_bits instead"
  290. )
  291. self.server_no_context_takeover = server_no_context_takeover
  292. self.client_no_context_takeover = client_no_context_takeover
  293. self.server_max_window_bits = server_max_window_bits
  294. self.client_max_window_bits = client_max_window_bits
  295. self.compress_settings = compress_settings
  296. def get_request_params(self) -> Sequence[ExtensionParameter]:
  297. """
  298. Build request parameters.
  299. """
  300. return _build_parameters(
  301. self.server_no_context_takeover,
  302. self.client_no_context_takeover,
  303. self.server_max_window_bits,
  304. self.client_max_window_bits,
  305. )
  306. def process_response_params(
  307. self,
  308. params: Sequence[ExtensionParameter],
  309. accepted_extensions: Sequence[Extension],
  310. ) -> PerMessageDeflate:
  311. """
  312. Process response parameters.
  313. Return an extension instance.
  314. """
  315. if any(other.name == self.name for other in accepted_extensions):
  316. raise NegotiationError(f"received duplicate {self.name}")
  317. # Request parameters are available in instance variables.
  318. # Load response parameters in local variables.
  319. (
  320. server_no_context_takeover,
  321. client_no_context_takeover,
  322. server_max_window_bits,
  323. client_max_window_bits,
  324. ) = _extract_parameters(params, is_server=False)
  325. # After comparing the request and the response, the final
  326. # configuration must be available in the local variables.
  327. # server_no_context_takeover
  328. #
  329. # Req. Resp. Result
  330. # ------ ------ --------------------------------------------------
  331. # False False False
  332. # False True True
  333. # True False Error!
  334. # True True True
  335. if self.server_no_context_takeover:
  336. if not server_no_context_takeover:
  337. raise NegotiationError("expected server_no_context_takeover")
  338. # client_no_context_takeover
  339. #
  340. # Req. Resp. Result
  341. # ------ ------ --------------------------------------------------
  342. # False False False
  343. # False True True
  344. # True False True - must change value
  345. # True True True
  346. if self.client_no_context_takeover:
  347. if not client_no_context_takeover:
  348. client_no_context_takeover = True
  349. # server_max_window_bits
  350. # Req. Resp. Result
  351. # ------ ------ --------------------------------------------------
  352. # None None None
  353. # None 8≤M≤15 M
  354. # 8≤N≤15 None Error!
  355. # 8≤N≤15 8≤M≤N M
  356. # 8≤N≤15 N<M≤15 Error!
  357. if self.server_max_window_bits is None:
  358. pass
  359. else:
  360. if server_max_window_bits is None:
  361. raise NegotiationError("expected server_max_window_bits")
  362. elif server_max_window_bits > self.server_max_window_bits:
  363. raise NegotiationError("unsupported server_max_window_bits")
  364. # client_max_window_bits
  365. # Req. Resp. Result
  366. # ------ ------ --------------------------------------------------
  367. # None None None
  368. # None 8≤M≤15 Error!
  369. # True None None
  370. # True 8≤M≤15 M
  371. # 8≤N≤15 None N - must change value
  372. # 8≤N≤15 8≤M≤N M
  373. # 8≤N≤15 N<M≤15 Error!
  374. if self.client_max_window_bits is None:
  375. if client_max_window_bits is not None:
  376. raise NegotiationError("unexpected client_max_window_bits")
  377. elif self.client_max_window_bits is True:
  378. pass
  379. else:
  380. if client_max_window_bits is None:
  381. client_max_window_bits = self.client_max_window_bits
  382. elif client_max_window_bits > self.client_max_window_bits:
  383. raise NegotiationError("unsupported client_max_window_bits")
  384. return PerMessageDeflate(
  385. server_no_context_takeover, # remote_no_context_takeover
  386. client_no_context_takeover, # local_no_context_takeover
  387. server_max_window_bits or 15, # remote_max_window_bits
  388. client_max_window_bits or 15, # local_max_window_bits
  389. self.compress_settings,
  390. )
  391. def enable_client_permessage_deflate(
  392. extensions: Sequence[ClientExtensionFactory] | None,
  393. ) -> Sequence[ClientExtensionFactory]:
  394. """
  395. Enable Per-Message Deflate with default settings in client extensions.
  396. If the extension is already present, perhaps with non-default settings,
  397. the configuration isn't changed.
  398. """
  399. if extensions is None:
  400. extensions = []
  401. if not any(
  402. extension_factory.name == ClientPerMessageDeflateFactory.name
  403. for extension_factory in extensions
  404. ):
  405. extensions = list(extensions) + [
  406. ClientPerMessageDeflateFactory(
  407. compress_settings={"memLevel": 5},
  408. )
  409. ]
  410. return extensions
  411. class ServerPerMessageDeflateFactory(ServerExtensionFactory):
  412. """
  413. Server-side extension factory for the Per-Message Deflate extension.
  414. Parameters behave as described in `section 7.1 of RFC 7692`_.
  415. .. _section 7.1 of RFC 7692: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1
  416. Set them to :obj:`True` to include them in the negotiation offer without a
  417. value or to an integer value to include them with this value.
  418. Args:
  419. server_no_context_takeover: Prevent server from using context takeover.
  420. client_no_context_takeover: Prevent client from using context takeover.
  421. server_max_window_bits: Maximum size of the server's LZ77 sliding window
  422. in bits, between 8 and 15.
  423. client_max_window_bits: Maximum size of the client's LZ77 sliding window
  424. in bits, between 8 and 15.
  425. compress_settings: Additional keyword arguments for :func:`zlib.compressobj`,
  426. excluding ``wbits``.
  427. require_client_max_window_bits: Do not enable compression at all if
  428. client doesn't advertise support for ``client_max_window_bits``;
  429. the default behavior is to enable compression without enforcing
  430. ``client_max_window_bits``.
  431. """
  432. name = ExtensionName("permessage-deflate")
  433. def __init__(
  434. self,
  435. server_no_context_takeover: bool = False,
  436. client_no_context_takeover: bool = False,
  437. server_max_window_bits: int | None = None,
  438. client_max_window_bits: int | None = None,
  439. compress_settings: dict[str, Any] | None = None,
  440. require_client_max_window_bits: bool = False,
  441. ) -> None:
  442. """
  443. Configure the Per-Message Deflate extension factory.
  444. """
  445. if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15):
  446. raise ValueError("server_max_window_bits must be between 8 and 15")
  447. if not (client_max_window_bits is None or 8 <= client_max_window_bits <= 15):
  448. raise ValueError("client_max_window_bits must be between 8 and 15")
  449. if compress_settings is not None and "wbits" in compress_settings:
  450. raise ValueError(
  451. "compress_settings must not include wbits, "
  452. "set server_max_window_bits instead"
  453. )
  454. if client_max_window_bits is None and require_client_max_window_bits:
  455. raise ValueError(
  456. "require_client_max_window_bits is enabled, "
  457. "but client_max_window_bits isn't configured"
  458. )
  459. self.server_no_context_takeover = server_no_context_takeover
  460. self.client_no_context_takeover = client_no_context_takeover
  461. self.server_max_window_bits = server_max_window_bits
  462. self.client_max_window_bits = client_max_window_bits
  463. self.compress_settings = compress_settings
  464. self.require_client_max_window_bits = require_client_max_window_bits
  465. def process_request_params(
  466. self,
  467. params: Sequence[ExtensionParameter],
  468. accepted_extensions: Sequence[Extension],
  469. ) -> tuple[list[ExtensionParameter], PerMessageDeflate]:
  470. """
  471. Process request parameters.
  472. Return response params and an extension instance.
  473. """
  474. if any(other.name == self.name for other in accepted_extensions):
  475. raise NegotiationError(f"skipped duplicate {self.name}")
  476. # Load request parameters in local variables.
  477. (
  478. server_no_context_takeover,
  479. client_no_context_takeover,
  480. server_max_window_bits,
  481. client_max_window_bits,
  482. ) = _extract_parameters(params, is_server=True)
  483. # Configuration parameters are available in instance variables.
  484. # After comparing the request and the configuration, the response must
  485. # be available in the local variables.
  486. # server_no_context_takeover
  487. #
  488. # Config Req. Resp.
  489. # ------ ------ --------------------------------------------------
  490. # False False False
  491. # False True True
  492. # True False True - must change value to True
  493. # True True True
  494. if self.server_no_context_takeover:
  495. if not server_no_context_takeover:
  496. server_no_context_takeover = True
  497. # client_no_context_takeover
  498. #
  499. # Config Req. Resp.
  500. # ------ ------ --------------------------------------------------
  501. # False False False
  502. # False True True (or False)
  503. # True False True - must change value to True
  504. # True True True (or False)
  505. if self.client_no_context_takeover:
  506. if not client_no_context_takeover:
  507. client_no_context_takeover = True
  508. # server_max_window_bits
  509. # Config Req. Resp.
  510. # ------ ------ --------------------------------------------------
  511. # None None None
  512. # None 8≤M≤15 M
  513. # 8≤N≤15 None N - must change value
  514. # 8≤N≤15 8≤M≤N M
  515. # 8≤N≤15 N<M≤15 N - must change value
  516. if self.server_max_window_bits is None:
  517. pass
  518. else:
  519. if server_max_window_bits is None:
  520. server_max_window_bits = self.server_max_window_bits
  521. elif server_max_window_bits > self.server_max_window_bits:
  522. server_max_window_bits = self.server_max_window_bits
  523. # client_max_window_bits
  524. # Config Req. Resp.
  525. # ------ ------ --------------------------------------------------
  526. # None None None
  527. # None True None - must change value
  528. # None 8≤M≤15 M (or None)
  529. # 8≤N≤15 None None or Error!
  530. # 8≤N≤15 True N - must change value
  531. # 8≤N≤15 8≤M≤N M (or None)
  532. # 8≤N≤15 N<M≤15 N
  533. if self.client_max_window_bits is None:
  534. if client_max_window_bits is True:
  535. client_max_window_bits = self.client_max_window_bits
  536. else:
  537. if client_max_window_bits is None:
  538. if self.require_client_max_window_bits:
  539. raise NegotiationError("required client_max_window_bits")
  540. elif client_max_window_bits is True:
  541. client_max_window_bits = self.client_max_window_bits
  542. elif self.client_max_window_bits < client_max_window_bits:
  543. client_max_window_bits = self.client_max_window_bits
  544. return (
  545. _build_parameters(
  546. server_no_context_takeover,
  547. client_no_context_takeover,
  548. server_max_window_bits,
  549. client_max_window_bits,
  550. ),
  551. PerMessageDeflate(
  552. client_no_context_takeover, # remote_no_context_takeover
  553. server_no_context_takeover, # local_no_context_takeover
  554. client_max_window_bits or 15, # remote_max_window_bits
  555. server_max_window_bits or 15, # local_max_window_bits
  556. self.compress_settings,
  557. ),
  558. )
  559. def enable_server_permessage_deflate(
  560. extensions: Sequence[ServerExtensionFactory] | None,
  561. ) -> Sequence[ServerExtensionFactory]:
  562. """
  563. Enable Per-Message Deflate with default settings in server extensions.
  564. If the extension is already present, perhaps with non-default settings,
  565. the configuration isn't changed.
  566. """
  567. if extensions is None:
  568. extensions = []
  569. if not any(
  570. ext_factory.name == ServerPerMessageDeflateFactory.name
  571. for ext_factory in extensions
  572. ):
  573. extensions = list(extensions) + [
  574. ServerPerMessageDeflateFactory(
  575. server_max_window_bits=12,
  576. client_max_window_bits=12,
  577. compress_settings={"memLevel": 5},
  578. )
  579. ]
  580. return extensions