Home | History | Annotate | Download | only in mod_pywebsocket
      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