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