permessage_deflate.py 25 KB

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