deflate_frame.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import zlib
  2. from frame import ControlFrame
  3. from errors import SocketClosed
  4. from extension import Extension
  5. class DeflateFrame(Extension):
  6. """
  7. This is an implementation of the "deflate-frame" extension, as defined by
  8. http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06.
  9. Supported parameters are:
  10. - max_window_size: maximum size for the LZ77 sliding window.
  11. - no_context_takeover: disallows usage of LZ77 sliding window from
  12. previously built frames for the current frame.
  13. Note that the deflate and inflate hooks modify the RSV1 bit and payload of
  14. existing `Frame` objects.
  15. """
  16. name = 'deflate-frame'
  17. rsv1 = True
  18. # FIXME: is 32768 (below) correct?
  19. defaults = {'max_window_bits': 15, 'no_context_takeover': False}
  20. def __init__(self, defaults={}, request={}):
  21. Extension.__init__(self, defaults, request)
  22. mwb = self.defaults['max_window_bits']
  23. cto = self.defaults['no_context_takeover']
  24. if not isinstance(mwb, int):
  25. raise ValueError('"max_window_bits" must be an integer')
  26. elif mwb > 32768:
  27. raise ValueError('"max_window_bits" may not be larger than 32768')
  28. if cto is not False and cto is not True:
  29. raise ValueError('"no_context_takeover" must have no value')
  30. class Hook(Extension.Hook):
  31. def __init__(self, extension, **kwargs):
  32. Extension.Hook.__init__(self, extension, **kwargs)
  33. if not self.no_context_takeover:
  34. self.defl = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  35. zlib.DEFLATED,
  36. -self.max_window_bits)
  37. other_wbits = self.extension.request.get('max_window_bits', 15)
  38. self.dec = zlib.decompressobj(-other_wbits)
  39. def send(self, frame):
  40. if not frame.rsv1 and not isinstance(frame, ControlFrame):
  41. frame.rsv1 = True
  42. frame.payload = self.deflate(frame.payload)
  43. return frame
  44. def recv(self, frame):
  45. if frame.rsv1:
  46. if isinstance(frame, ControlFrame):
  47. raise SocketClosed('received compressed control frame')
  48. frame.rsv1 = False
  49. frame.payload = self.inflate(frame.payload)
  50. return frame
  51. def deflate(self, data):
  52. if self.no_context_takeover:
  53. defl = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  54. zlib.DEFLATED, -self.max_window_bits)
  55. # FIXME: why the '\x00' below? This was borrowed from
  56. # https://github.com/fancycode/tornado/blob/bc317b6dcf63608ff004ff1f57073be0504b6550/tornado/websocket.py#L91
  57. return defl.compress(data) + defl.flush(zlib.Z_FINISH) + '\x00'
  58. compressed = self.defl.compress(data)
  59. compressed += self.defl.flush(zlib.Z_SYNC_FLUSH)
  60. assert compressed[-4:] == '\x00\x00\xff\xff'
  61. return compressed[:-4]
  62. def inflate(self, data):
  63. data = self.dec.decompress(str(data + '\x00\x00\xff\xff'))
  64. assert not self.dec.unused_data
  65. return data
  66. class WebkitDeflateFrame(DeflateFrame):
  67. name = 'x-webkit-deflate-frame'