deflate_frame.py 3.2 KB

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