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 30 31 """This file must not depend on any module specific to the WebSocket protocol. 32 """ 33 34 35 from mod_pywebsocket import http_header_util 36 37 38 # Additional log level definitions. 39 LOGLEVEL_FINE = 9 40 41 # Constants indicating WebSocket protocol version. 42 VERSION_HIXIE75 = -1 43 VERSION_HYBI00 = 0 44 VERSION_HYBI01 = 1 45 VERSION_HYBI02 = 2 46 VERSION_HYBI03 = 2 47 VERSION_HYBI04 = 4 48 VERSION_HYBI05 = 5 49 VERSION_HYBI06 = 6 50 VERSION_HYBI07 = 7 51 VERSION_HYBI08 = 8 52 VERSION_HYBI09 = 8 53 VERSION_HYBI10 = 8 54 VERSION_HYBI11 = 8 55 VERSION_HYBI12 = 8 56 VERSION_HYBI13 = 13 57 VERSION_HYBI14 = 13 58 VERSION_HYBI15 = 13 59 VERSION_HYBI16 = 13 60 VERSION_HYBI17 = 13 61 62 # Constants indicating WebSocket protocol latest version. 63 VERSION_HYBI_LATEST = VERSION_HYBI13 64 65 # Port numbers 66 DEFAULT_WEB_SOCKET_PORT = 80 67 DEFAULT_WEB_SOCKET_SECURE_PORT = 443 68 69 # Schemes 70 WEB_SOCKET_SCHEME = 'ws' 71 WEB_SOCKET_SECURE_SCHEME = 'wss' 72 73 # Frame opcodes defined in the spec. 74 OPCODE_CONTINUATION = 0x0 75 OPCODE_TEXT = 0x1 76 OPCODE_BINARY = 0x2 77 OPCODE_CLOSE = 0x8 78 OPCODE_PING = 0x9 79 OPCODE_PONG = 0xa 80 81 # UUIDs used by HyBi 04 and later opening handshake and frame masking. 82 WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 83 84 # Opening handshake header names and expected values. 85 UPGRADE_HEADER = 'Upgrade' 86 WEBSOCKET_UPGRADE_TYPE = 'websocket' 87 WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket' 88 CONNECTION_HEADER = 'Connection' 89 UPGRADE_CONNECTION_TYPE = 'Upgrade' 90 HOST_HEADER = 'Host' 91 ORIGIN_HEADER = 'Origin' 92 SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin' 93 SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key' 94 SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept' 95 SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version' 96 SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol' 97 SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions' 98 SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft' 99 SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1' 100 SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2' 101 SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location' 102 103 # Extensions 104 DEFLATE_STREAM_EXTENSION = 'deflate-stream' 105 DEFLATE_FRAME_EXTENSION = 'deflate-frame' 106 PERFRAME_COMPRESSION_EXTENSION = 'perframe-compress' 107 PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress' 108 X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame' 109 MUX_EXTENSION = 'mux_DO_NOT_USE' 110 111 # Status codes 112 # Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and 113 # STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases. 114 # Could not be used for codes in actual closing frames. 115 # Application level errors must use codes in the range 116 # STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the 117 # range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed 118 # by IANA. Usually application must define user protocol level errors in the 119 # range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX. 120 STATUS_NORMAL_CLOSURE = 1000 121 STATUS_GOING_AWAY = 1001 122 STATUS_PROTOCOL_ERROR = 1002 123 STATUS_UNSUPPORTED_DATA = 1003 124 STATUS_NO_STATUS_RECEIVED = 1005 125 STATUS_ABNORMAL_CLOSURE = 1006 126 STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007 127 STATUS_POLICY_VIOLATION = 1008 128 STATUS_MESSAGE_TOO_BIG = 1009 129 STATUS_MANDATORY_EXTENSION = 1010 130 STATUS_INTERNAL_SERVER_ERROR = 1011 131 STATUS_TLS_HANDSHAKE = 1015 132 STATUS_USER_REGISTERED_BASE = 3000 133 STATUS_USER_REGISTERED_MAX = 3999 134 STATUS_USER_PRIVATE_BASE = 4000 135 STATUS_USER_PRIVATE_MAX = 4999 136 # Following definitions are aliases to keep compatibility. Applications must 137 # not use these obsoleted definitions anymore. 138 STATUS_NORMAL = STATUS_NORMAL_CLOSURE 139 STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA 140 STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED 141 STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE 142 STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA 143 STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION 144 145 # HTTP status codes 146 HTTP_STATUS_BAD_REQUEST = 400 147 HTTP_STATUS_FORBIDDEN = 403 148 HTTP_STATUS_NOT_FOUND = 404 149 150 151 def is_control_opcode(opcode): 152 return (opcode >> 3) == 1 153 154 155 class ExtensionParameter(object): 156 """Holds information about an extension which is exchanged on extension 157 negotiation in opening handshake. 158 """ 159 160 def __init__(self, name): 161 self._name = name 162 # TODO(tyoshino): Change the data structure to more efficient one such 163 # as dict when the spec changes to say like 164 # - Parameter names must be unique 165 # - The order of parameters is not significant 166 self._parameters = [] 167 168 def name(self): 169 return self._name 170 171 def add_parameter(self, name, value): 172 self._parameters.append((name, value)) 173 174 def get_parameters(self): 175 return self._parameters 176 177 def get_parameter_names(self): 178 return [name for name, unused_value in self._parameters] 179 180 def has_parameter(self, name): 181 for param_name, param_value in self._parameters: 182 if param_name == name: 183 return True 184 return False 185 186 def get_parameter_value(self, name): 187 for param_name, param_value in self._parameters: 188 if param_name == name: 189 return param_value 190 191 192 class ExtensionParsingException(Exception): 193 def __init__(self, name): 194 super(ExtensionParsingException, self).__init__(name) 195 196 197 def _parse_extension_param(state, definition, allow_quoted_string): 198 param_name = http_header_util.consume_token(state) 199 200 if param_name is None: 201 raise ExtensionParsingException('No valid parameter name found') 202 203 http_header_util.consume_lwses(state) 204 205 if not http_header_util.consume_string(state, '='): 206 definition.add_parameter(param_name, None) 207 return 208 209 http_header_util.consume_lwses(state) 210 211 if allow_quoted_string: 212 # TODO(toyoshim): Add code to validate that parsed param_value is token 213 param_value = http_header_util.consume_token_or_quoted_string(state) 214 else: 215 param_value = http_header_util.consume_token(state) 216 if param_value is None: 217 raise ExtensionParsingException( 218 'No valid parameter value found on the right-hand side of ' 219 'parameter %r' % param_name) 220 221 definition.add_parameter(param_name, param_value) 222 223 224 def _parse_extension(state, allow_quoted_string): 225 extension_token = http_header_util.consume_token(state) 226 if extension_token is None: 227 return None 228 229 extension = ExtensionParameter(extension_token) 230 231 while True: 232 http_header_util.consume_lwses(state) 233 234 if not http_header_util.consume_string(state, ';'): 235 break 236 237 http_header_util.consume_lwses(state) 238 239 try: 240 _parse_extension_param(state, extension, allow_quoted_string) 241 except ExtensionParsingException, e: 242 raise ExtensionParsingException( 243 'Failed to parse parameter for %r (%r)' % 244 (extension_token, e)) 245 246 return extension 247 248 249 def parse_extensions(data, allow_quoted_string=False): 250 """Parses Sec-WebSocket-Extensions header value returns a list of 251 ExtensionParameter objects. 252 253 Leading LWSes must be trimmed. 254 """ 255 256 state = http_header_util.ParsingState(data) 257 258 extension_list = [] 259 while True: 260 extension = _parse_extension(state, allow_quoted_string) 261 if extension is not None: 262 extension_list.append(extension) 263 264 http_header_util.consume_lwses(state) 265 266 if http_header_util.peek(state) is None: 267 break 268 269 if not http_header_util.consume_string(state, ','): 270 raise ExtensionParsingException( 271 'Failed to parse Sec-WebSocket-Extensions header: ' 272 'Expected a comma but found %r' % 273 http_header_util.peek(state)) 274 275 http_header_util.consume_lwses(state) 276 277 if len(extension_list) == 0: 278 raise ExtensionParsingException( 279 'No valid extension entry found') 280 281 return extension_list 282 283 284 def format_extension(extension): 285 """Formats an ExtensionParameter object.""" 286 287 formatted_params = [extension.name()] 288 for param_name, param_value in extension.get_parameters(): 289 if param_value is None: 290 formatted_params.append(param_name) 291 else: 292 quoted_value = http_header_util.quote_if_necessary(param_value) 293 formatted_params.append('%s=%s' % (param_name, quoted_value)) 294 return '; '.join(formatted_params) 295 296 297 def format_extensions(extension_list): 298 """Formats a list of ExtensionParameter objects.""" 299 300 formatted_extension_list = [] 301 for extension in extension_list: 302 formatted_extension_list.append(format_extension(extension)) 303 return ', '.join(formatted_extension_list) 304 305 306 # vi:sts=4 sw=4 et 307