tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

common.py (9342B)


      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 
     32 from __future__ import absolute_import
     33 from mod_pywebsocket import http_header_util
     34 
     35 # Additional log level definitions.
     36 LOGLEVEL_FINE = 9
     37 
     38 # Constants indicating WebSocket protocol version.
     39 VERSION_HYBI13 = 13
     40 VERSION_HYBI14 = 13
     41 VERSION_HYBI15 = 13
     42 VERSION_HYBI16 = 13
     43 VERSION_HYBI17 = 13
     44 
     45 # Constants indicating WebSocket protocol latest version.
     46 VERSION_HYBI_LATEST = VERSION_HYBI13
     47 
     48 # Port numbers
     49 DEFAULT_WEB_SOCKET_PORT = 80
     50 DEFAULT_WEB_SOCKET_SECURE_PORT = 443
     51 
     52 # Schemes
     53 WEB_SOCKET_SCHEME = 'ws'
     54 WEB_SOCKET_SECURE_SCHEME = 'wss'
     55 
     56 # Frame opcodes defined in the spec.
     57 OPCODE_CONTINUATION = 0x0
     58 OPCODE_TEXT = 0x1
     59 OPCODE_BINARY = 0x2
     60 OPCODE_CLOSE = 0x8
     61 OPCODE_PING = 0x9
     62 OPCODE_PONG = 0xa
     63 
     64 # UUID for the opening handshake and frame masking.
     65 WEBSOCKET_ACCEPT_UUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
     66 
     67 # Opening handshake header names and expected values.
     68 UPGRADE_HEADER = 'Upgrade'
     69 WEBSOCKET_UPGRADE_TYPE = 'websocket'
     70 CONNECTION_HEADER = 'Connection'
     71 UPGRADE_CONNECTION_TYPE = 'Upgrade'
     72 HOST_HEADER = 'Host'
     73 ORIGIN_HEADER = 'Origin'
     74 SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key'
     75 SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept'
     76 SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version'
     77 SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol'
     78 SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions'
     79 
     80 # Extensions
     81 PERMESSAGE_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.
     92 STATUS_NORMAL_CLOSURE = 1000
     93 STATUS_GOING_AWAY = 1001
     94 STATUS_PROTOCOL_ERROR = 1002
     95 STATUS_UNSUPPORTED_DATA = 1003
     96 STATUS_NO_STATUS_RECEIVED = 1005
     97 STATUS_ABNORMAL_CLOSURE = 1006
     98 STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007
     99 STATUS_POLICY_VIOLATION = 1008
    100 STATUS_MESSAGE_TOO_BIG = 1009
    101 STATUS_MANDATORY_EXTENSION = 1010
    102 STATUS_INTERNAL_ENDPOINT_ERROR = 1011
    103 STATUS_TLS_HANDSHAKE = 1015
    104 STATUS_USER_REGISTERED_BASE = 3000
    105 STATUS_USER_REGISTERED_MAX = 3999
    106 STATUS_USER_PRIVATE_BASE = 4000
    107 STATUS_USER_PRIVATE_MAX = 4999
    108 # Following definitions are aliases to keep compatibility. Applications must
    109 # not use these obsoleted definitions anymore.
    110 STATUS_NORMAL = STATUS_NORMAL_CLOSURE
    111 STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA
    112 STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED
    113 STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE
    114 STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA
    115 STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION
    116 
    117 # HTTP status codes
    118 HTTP_STATUS_BAD_REQUEST = 400
    119 HTTP_STATUS_FORBIDDEN = 403
    120 HTTP_STATUS_NOT_FOUND = 404
    121 
    122 
    123 def is_control_opcode(opcode):
    124    return (opcode >> 3) == 1
    125 
    126 
    127 class 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 
    167 class ExtensionParsingException(Exception):
    168    """Exception to handle errors in extension parsing."""
    169    def __init__(self, name):
    170        super(ExtensionParsingException, self).__init__(name)
    171 
    172 
    173 def _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 
    197 def _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 
    221 def 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 
    253 def 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 
    265 def 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