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