common.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. # Copyright 2012, Google Inc.
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above
  11. # copyright notice, this list of conditions and the following disclaimer
  12. # in the documentation and/or other materials provided with the
  13. # distribution.
  14. # * Neither the name of Google Inc. nor the names of its
  15. # contributors may be used to endorse or promote products derived from
  16. # this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. """This file must not depend on any module specific to the WebSocket protocol.
  30. """
  31. from mod_pywebsocket import http_header_util
  32. # Additional log level definitions.
  33. LOGLEVEL_FINE = 9
  34. # Constants indicating WebSocket protocol version.
  35. VERSION_HIXIE75 = -1
  36. VERSION_HYBI00 = 0
  37. VERSION_HYBI01 = 1
  38. VERSION_HYBI02 = 2
  39. VERSION_HYBI03 = 2
  40. VERSION_HYBI04 = 4
  41. VERSION_HYBI05 = 5
  42. VERSION_HYBI06 = 6
  43. VERSION_HYBI07 = 7
  44. VERSION_HYBI08 = 8
  45. VERSION_HYBI09 = 8
  46. VERSION_HYBI10 = 8
  47. VERSION_HYBI11 = 8
  48. VERSION_HYBI12 = 8
  49. VERSION_HYBI13 = 13
  50. VERSION_HYBI14 = 13
  51. VERSION_HYBI15 = 13
  52. VERSION_HYBI16 = 13
  53. VERSION_HYBI17 = 13
  54. # Constants indicating WebSocket protocol latest version.
  55. VERSION_HYBI_LATEST = VERSION_HYBI13
  56. # Port numbers
  57. DEFAULT_WEB_SOCKET_PORT = 80
  58. DEFAULT_WEB_SOCKET_SECURE_PORT = 443
  59. # Schemes
  60. WEB_SOCKET_SCHEME = 'ws'
  61. WEB_SOCKET_SECURE_SCHEME = 'wss'
  62. # Frame opcodes defined in the spec.
  63. OPCODE_CONTINUATION = 0x0
  64. OPCODE_TEXT = 0x1
  65. OPCODE_BINARY = 0x2
  66. OPCODE_CLOSE = 0x8
  67. OPCODE_PING = 0x9
  68. OPCODE_PONG = 0xa
  69. # UUIDs used by HyBi 04 and later opening handshake and frame masking.
  70. WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
  71. # Opening handshake header names and expected values.
  72. UPGRADE_HEADER = 'Upgrade'
  73. WEBSOCKET_UPGRADE_TYPE = 'websocket'
  74. WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket'
  75. CONNECTION_HEADER = 'Connection'
  76. UPGRADE_CONNECTION_TYPE = 'Upgrade'
  77. HOST_HEADER = 'Host'
  78. ORIGIN_HEADER = 'Origin'
  79. SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin'
  80. SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key'
  81. SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept'
  82. SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version'
  83. SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol'
  84. SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions'
  85. SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft'
  86. SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1'
  87. SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2'
  88. SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location'
  89. # Extensions
  90. DEFLATE_FRAME_EXTENSION = 'deflate-frame'
  91. PERMESSAGE_DEFLATE_EXTENSION = 'permessage-deflate'
  92. X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
  93. MUX_EXTENSION = 'mux_DO_NOT_USE'
  94. # Status codes
  95. # Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and
  96. # STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases.
  97. # Could not be used for codes in actual closing frames.
  98. # Application level errors must use codes in the range
  99. # STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the
  100. # range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed
  101. # by IANA. Usually application must define user protocol level errors in the
  102. # range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX.
  103. STATUS_NORMAL_CLOSURE = 1000
  104. STATUS_GOING_AWAY = 1001
  105. STATUS_PROTOCOL_ERROR = 1002
  106. STATUS_UNSUPPORTED_DATA = 1003
  107. STATUS_NO_STATUS_RECEIVED = 1005
  108. STATUS_ABNORMAL_CLOSURE = 1006
  109. STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
  110. STATUS_POLICY_VIOLATION = 1008
  111. STATUS_MESSAGE_TOO_BIG = 1009
  112. STATUS_MANDATORY_EXTENSION = 1010
  113. STATUS_INTERNAL_ENDPOINT_ERROR = 1011
  114. STATUS_TLS_HANDSHAKE = 1015
  115. STATUS_USER_REGISTERED_BASE = 3000
  116. STATUS_USER_REGISTERED_MAX = 3999
  117. STATUS_USER_PRIVATE_BASE = 4000
  118. STATUS_USER_PRIVATE_MAX = 4999
  119. # Following definitions are aliases to keep compatibility. Applications must
  120. # not use these obsoleted definitions anymore.
  121. STATUS_NORMAL = STATUS_NORMAL_CLOSURE
  122. STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
  123. STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
  124. STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
  125. STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
  126. STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
  127. # HTTP status codes
  128. HTTP_STATUS_BAD_REQUEST = 400
  129. HTTP_STATUS_FORBIDDEN = 403
  130. HTTP_STATUS_NOT_FOUND = 404
  131. def is_control_opcode(opcode):
  132. return (opcode >> 3) == 1
  133. class ExtensionParameter(object):
  134. """Holds information about an extension which is exchanged on extension
  135. negotiation in opening handshake.
  136. """
  137. def __init__(self, name):
  138. self._name = name
  139. # TODO(tyoshino): Change the data structure to more efficient one such
  140. # as dict when the spec changes to say like
  141. # - Parameter names must be unique
  142. # - The order of parameters is not significant
  143. self._parameters = []
  144. def name(self):
  145. return self._name
  146. def add_parameter(self, name, value):
  147. self._parameters.append((name, value))
  148. def get_parameters(self):
  149. return self._parameters
  150. def get_parameter_names(self):
  151. return [name for name, unused_value in self._parameters]
  152. def has_parameter(self, name):
  153. for param_name, param_value in self._parameters:
  154. if param_name == name:
  155. return True
  156. return False
  157. def get_parameter_value(self, name):
  158. for param_name, param_value in self._parameters:
  159. if param_name == name:
  160. return param_value
  161. class ExtensionParsingException(Exception):
  162. def __init__(self, name):
  163. super(ExtensionParsingException, self).__init__(name)
  164. def _parse_extension_param(state, definition):
  165. param_name = http_header_util.consume_token(state)
  166. if param_name is None:
  167. raise ExtensionParsingException('No valid parameter name found')
  168. http_header_util.consume_lwses(state)
  169. if not http_header_util.consume_string(state, '='):
  170. definition.add_parameter(param_name, None)
  171. return
  172. http_header_util.consume_lwses(state)
  173. # TODO(tyoshino): Add code to validate that parsed param_value is token
  174. param_value = http_header_util.consume_token_or_quoted_string(state)
  175. if param_value is None:
  176. raise ExtensionParsingException(
  177. 'No valid parameter value found on the right-hand side of '
  178. 'parameter %r' % param_name)
  179. definition.add_parameter(param_name, param_value)
  180. def _parse_extension(state):
  181. extension_token = http_header_util.consume_token(state)
  182. if extension_token is None:
  183. return None
  184. extension = ExtensionParameter(extension_token)
  185. while True:
  186. http_header_util.consume_lwses(state)
  187. if not http_header_util.consume_string(state, ';'):
  188. break
  189. http_header_util.consume_lwses(state)
  190. try:
  191. _parse_extension_param(state, extension)
  192. except ExtensionParsingException, e:
  193. raise ExtensionParsingException(
  194. 'Failed to parse parameter for %r (%r)' %
  195. (extension_token, e))
  196. return extension
  197. def parse_extensions(data):
  198. """Parses Sec-WebSocket-Extensions header value returns a list of
  199. ExtensionParameter objects.
  200. Leading LWSes must be trimmed.
  201. """
  202. state = http_header_util.ParsingState(data)
  203. extension_list = []
  204. while True:
  205. extension = _parse_extension(state)
  206. if extension is not None:
  207. extension_list.append(extension)
  208. http_header_util.consume_lwses(state)
  209. if http_header_util.peek(state) is None:
  210. break
  211. if not http_header_util.consume_string(state, ','):
  212. raise ExtensionParsingException(
  213. 'Failed to parse Sec-WebSocket-Extensions header: '
  214. 'Expected a comma but found %r' %
  215. http_header_util.peek(state))
  216. http_header_util.consume_lwses(state)
  217. if len(extension_list) == 0:
  218. raise ExtensionParsingException(
  219. 'No valid extension entry found')
  220. return extension_list
  221. def format_extension(extension):
  222. """Formats an ExtensionParameter object."""
  223. formatted_params = [extension.name()]
  224. for param_name, param_value in extension.get_parameters():
  225. if param_value is None:
  226. formatted_params.append(param_name)
  227. else:
  228. quoted_value = http_header_util.quote_if_necessary(param_value)
  229. formatted_params.append('%s=%s' % (param_name, quoted_value))
  230. return '; '.join(formatted_params)
  231. def format_extensions(extension_list):
  232. """Formats a list of ExtensionParameter objects."""
  233. formatted_extension_list = []
  234. for extension in extension_list:
  235. formatted_extension_list.append(format_extension(extension))
  236. return ', '.join(formatted_extension_list)
  237. # vi:sts=4 sw=4 et