extension.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import zlib
  2. class Extension(object):
  3. name = ''
  4. rsv1 = False
  5. rsv2 = False
  6. rsv3 = False
  7. opcodes = []
  8. defaults = {}
  9. request = {}
  10. def __init__(self, defaults={}, request={}):
  11. for param in defaults.keys() + request.keys():
  12. if param not in self.defaults:
  13. raise KeyError('unrecognized parameter "%s"' % param)
  14. # Copy dict first to avoid duplicate references to the same object
  15. self.defaults = dict(self.__class__.defaults)
  16. self.defaults.update(defaults)
  17. self.request = dict(self.__class__.request)
  18. self.request.update(request)
  19. def __str__(self, frame):
  20. return '<Extension "%s" defaults=%s request=%s>' \
  21. % (self.name, self.defaults, self.request)
  22. def create_hook(self, **kwargs):
  23. params = {}
  24. params.update(self.defaults)
  25. params.update(kwargs)
  26. return self.Hook(**params)
  27. class Hook:
  28. def __init__(self, **kwargs):
  29. for param, value in kwargs.iteritems():
  30. setattr(self, param, value)
  31. def send(self, frame):
  32. return frame
  33. def recv(self, frame):
  34. return frame
  35. class DeflateFrame(Extension):
  36. """
  37. This is an implementation of the "deflate-frame" extension, as defined by
  38. http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06.
  39. Supported parameters are:
  40. - max_window_size: maximum size for the LZ77 sliding window.
  41. - no_context_takeover: disallows usage of LZ77 sliding window from
  42. previously built frames for the current frame.
  43. Note that the deflate and inflate hooks modify the RSV1 bit and payload of
  44. existing `Frame` objects.
  45. """
  46. name = 'deflate-frame'
  47. rsv1 = True
  48. # FIXME: is 32768 (below) correct?
  49. defaults = {'max_window_bits': 15, 'no_context_takeover': True}
  50. def __init__(self, defaults={}, request={}):
  51. Extension.__init__(self, defaults, request)
  52. mwb = self.defaults['max_window_bits']
  53. cto = self.defaults['no_context_takeover']
  54. if not isinstance(mwb, int):
  55. raise ValueError('"max_window_bits" must be an integer')
  56. elif mwb > 32768:
  57. raise ValueError('"max_window_bits" may not be larger than 32768')
  58. if cto is not False and cto is not True:
  59. raise ValueError('"no_context_takeover" must have no value')
  60. class Hook(Extension.Hook):
  61. def __init__(self, **kwargs):
  62. Extension.Hook.__init__(**kwargs)
  63. other_wbits = self.request.get('max_window_bits', 15)
  64. # Don't request default value of max_window_bits
  65. if 'max_window_bits' in self.request and other_wbits == 15:
  66. del self.request['max_window_bits']
  67. if not self.no_context_takeover:
  68. self.com = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  69. zlib.DEFLATED,
  70. self.max_window_bits)
  71. self.dec = zlib.decompressobj(other_wbits)
  72. def send(self, frame):
  73. if not frame.rsv1:
  74. frame.rsv1 = True
  75. frame.payload = self.deflate(frame.payload)
  76. return frame
  77. def recv(self, frame):
  78. if frame.rsv1:
  79. frame.rsv1 = False
  80. frame.payload = self.inflate(frame.payload)
  81. return frame
  82. def deflate(self, data):
  83. if self.no_context_takeover:
  84. if self.max_window_bits == 15:
  85. return zlib.compress(data)
  86. self.com = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  87. zlib.DEFLATED,
  88. self.max_window_bits)
  89. return self.com.compress(data)
  90. def inflate(self, data):
  91. return self.dec.decompress(data)
  92. class Multiplex(Extension):
  93. """
  94. This is an implementation of the "mux" extension, as defined by
  95. http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-11.
  96. Supported parameters are:
  97. - quota: TODO
  98. """
  99. name = 'mux'
  100. rsv1 = True # FIXME
  101. rsv2 = True # FIXME
  102. rsv3 = True # FIXME
  103. defaults = {'quota': None}
  104. def __init__(self, defaults={}, request={}):
  105. Extension.__init__(self, defaults, request)
  106. # TODO: check "quota" value
  107. class Hook(Extension.Hook):
  108. def send(self, frame):
  109. raise NotImplementedError # TODO
  110. def recv(self, frame):
  111. raise NotImplementedError # TODO
  112. def filter_extensions(extensions):
  113. """
  114. Remove extensions that use conflicting rsv bits and/or opcodes, with the
  115. first options being the most preferable.
  116. """
  117. rsv1_reserved = False
  118. rsv2_reserved = False
  119. rsv3_reserved = False
  120. opcodes_reserved = []
  121. compat = []
  122. for ext in extensions:
  123. if ext.rsv1 and rsv1_reserved \
  124. or ext.rsv2 and rsv2_reserved \
  125. or ext.rsv3 and rsv3_reserved \
  126. or len(set(ext.opcodes) & set(opcodes_reserved)):
  127. continue
  128. rsv1_reserved |= ext.rsv1
  129. rsv2_reserved |= ext.rsv2
  130. rsv3_reserved |= ext.rsv3
  131. opcodes_reserved.extend(ext.opcodes)
  132. compat.append(ext)
  133. return compat