1
# Copyright 2012, Google Inc.
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are
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
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.
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.
31
"""This file must not depend on any module specific to the WebSocket protocol.
35
from mod_pywebsocket import http_header_util
38
# Additional log level definitions.
41
# Constants indicating WebSocket protocol version.
62
# Constants indicating WebSocket protocol latest version.
63
VERSION_HYBI_LATEST = VERSION_HYBI13
66
DEFAULT_WEB_SOCKET_PORT = 80
67
DEFAULT_WEB_SOCKET_SECURE_PORT = 443
70
WEB_SOCKET_SCHEME = 'ws'
71
WEB_SOCKET_SECURE_SCHEME = 'wss'
73
# Frame opcodes defined in the spec.
74
OPCODE_CONTINUATION = 0x0
81
# UUIDs used by HyBi 04 and later opening handshake and frame masking.
82
WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
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'
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'
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
X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress'
110
MUX_EXTENSION = 'mux_DO_NOT_USE'
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.
121
STATUS_NORMAL_CLOSURE = 1000
122
STATUS_GOING_AWAY = 1001
123
STATUS_PROTOCOL_ERROR = 1002
124
STATUS_UNSUPPORTED_DATA = 1003
125
STATUS_NO_STATUS_RECEIVED = 1005
126
STATUS_ABNORMAL_CLOSURE = 1006
127
STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
128
STATUS_POLICY_VIOLATION = 1008
129
STATUS_MESSAGE_TOO_BIG = 1009
130
STATUS_MANDATORY_EXTENSION = 1010
131
STATUS_INTERNAL_ENDPOINT_ERROR = 1011
132
STATUS_TLS_HANDSHAKE = 1015
133
STATUS_USER_REGISTERED_BASE = 3000
134
STATUS_USER_REGISTERED_MAX = 3999
135
STATUS_USER_PRIVATE_BASE = 4000
136
STATUS_USER_PRIVATE_MAX = 4999
137
# Following definitions are aliases to keep compatibility. Applications must
138
# not use these obsoleted definitions anymore.
139
STATUS_NORMAL = STATUS_NORMAL_CLOSURE
140
STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
141
STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
142
STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
143
STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
144
STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
147
HTTP_STATUS_BAD_REQUEST = 400
148
HTTP_STATUS_FORBIDDEN = 403
149
HTTP_STATUS_NOT_FOUND = 404
152
def is_control_opcode(opcode):
153
return (opcode >> 3) == 1
156
class ExtensionParameter(object):
157
"""Holds information about an extension which is exchanged on extension
158
negotiation in opening handshake.
161
def __init__(self, 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 = []
172
def add_parameter(self, name, value):
173
self._parameters.append((name, value))
175
def get_parameters(self):
176
return self._parameters
178
def get_parameter_names(self):
179
return [name for name, unused_value in self._parameters]
181
def has_parameter(self, name):
182
for param_name, param_value in self._parameters:
183
if param_name == name:
187
def get_parameter_value(self, name):
188
for param_name, param_value in self._parameters:
189
if param_name == name:
193
class ExtensionParsingException(Exception):
194
def __init__(self, name):
195
super(ExtensionParsingException, self).__init__(name)
198
def _parse_extension_param(state, definition, allow_quoted_string):
199
param_name = http_header_util.consume_token(state)
201
if param_name is None:
202
raise ExtensionParsingException('No valid parameter name found')
204
http_header_util.consume_lwses(state)
206
if not http_header_util.consume_string(state, '='):
207
definition.add_parameter(param_name, None)
210
http_header_util.consume_lwses(state)
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)
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)
222
definition.add_parameter(param_name, param_value)
225
def _parse_extension(state, allow_quoted_string):
226
extension_token = http_header_util.consume_token(state)
227
if extension_token is None:
230
extension = ExtensionParameter(extension_token)
233
http_header_util.consume_lwses(state)
235
if not http_header_util.consume_string(state, ';'):
238
http_header_util.consume_lwses(state)
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))
250
def parse_extensions(data, allow_quoted_string=False):
251
"""Parses Sec-WebSocket-Extensions header value returns a list of
252
ExtensionParameter objects.
254
Leading LWSes must be trimmed.
257
state = http_header_util.ParsingState(data)
261
extension = _parse_extension(state, allow_quoted_string)
262
if extension is not None:
263
extension_list.append(extension)
265
http_header_util.consume_lwses(state)
267
if http_header_util.peek(state) is None:
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))
276
http_header_util.consume_lwses(state)
278
if len(extension_list) == 0:
279
raise ExtensionParsingException(
280
'No valid extension entry found')
282
return extension_list
285
def format_extension(extension):
286
"""Formats an ExtensionParameter object."""
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)
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)
298
def format_extensions(extension_list):
299
"""Formats a list of ExtensionParameter objects."""
301
formatted_extension_list = []
302
for extension in extension_list:
303
formatted_extension_list.append(format_extension(extension))
304
return ', '.join(formatted_extension_list)