• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_STREAM_EXTENSION = 'deflate-stream'
105DEFLATE_FRAME_EXTENSION = 'deflate-frame'
106PERFRAME_COMPRESSION_EXTENSION = 'perframe-compress'
107PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress'
108X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame'
109MUX_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.
120STATUS_NORMAL_CLOSURE = 1000
121STATUS_GOING_AWAY = 1001
122STATUS_PROTOCOL_ERROR = 1002
123STATUS_UNSUPPORTED_DATA = 1003
124STATUS_NO_STATUS_RECEIVED = 1005
125STATUS_ABNORMAL_CLOSURE = 1006
126STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
127STATUS_POLICY_VIOLATION = 1008
128STATUS_MESSAGE_TOO_BIG = 1009
129STATUS_MANDATORY_EXTENSION = 1010
130STATUS_INTERNAL_SERVER_ERROR = 1011
131STATUS_TLS_HANDSHAKE = 1015
132STATUS_USER_REGISTERED_BASE = 3000
133STATUS_USER_REGISTERED_MAX = 3999
134STATUS_USER_PRIVATE_BASE = 4000
135STATUS_USER_PRIVATE_MAX = 4999
136# Following definitions are aliases to keep compatibility. Applications must
137# not use these obsoleted definitions anymore.
138STATUS_NORMAL = STATUS_NORMAL_CLOSURE
139STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
140STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
141STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
142STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
143STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
144
145# HTTP status codes
146HTTP_STATUS_BAD_REQUEST = 400
147HTTP_STATUS_FORBIDDEN = 403
148HTTP_STATUS_NOT_FOUND = 404
149
150
151def is_control_opcode(opcode):
152    return (opcode >> 3) == 1
153
154
155class 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
192class ExtensionParsingException(Exception):
193    def __init__(self, name):
194        super(ExtensionParsingException, self).__init__(name)
195
196
197def _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
224def _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
249def 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
284def 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
297def 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