testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,323 @@
     1.4 +# Copyright 2011, Google Inc.
     1.5 +# All rights reserved.
     1.6 +#
     1.7 +# Redistribution and use in source and binary forms, with or without
     1.8 +# modification, are permitted provided that the following conditions are
     1.9 +# met:
    1.10 +#
    1.11 +#     * Redistributions of source code must retain the above copyright
    1.12 +# notice, this list of conditions and the following disclaimer.
    1.13 +#     * Redistributions in binary form must reproduce the above
    1.14 +# copyright notice, this list of conditions and the following disclaimer
    1.15 +# in the documentation and/or other materials provided with the
    1.16 +# distribution.
    1.17 +#     * Neither the name of Google Inc. nor the names of its
    1.18 +# contributors may be used to endorse or promote products derived from
    1.19 +# this software without specific prior written permission.
    1.20 +#
    1.21 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1.22 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1.23 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1.24 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    1.25 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1.26 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    1.27 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    1.28 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    1.29 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    1.30 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    1.31 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.32 +
    1.33 +
    1.34 +"""Common functions and exceptions used by WebSocket opening handshake
    1.35 +processors.
    1.36 +"""
    1.37 +
    1.38 +
    1.39 +from mod_pywebsocket import common
    1.40 +from mod_pywebsocket import http_header_util
    1.41 +
    1.42 +
    1.43 +class AbortedByUserException(Exception):
    1.44 +    """Exception for aborting a connection intentionally.
    1.45 +
    1.46 +    If this exception is raised in do_extra_handshake handler, the connection
    1.47 +    will be abandoned. No other WebSocket or HTTP(S) handler will be invoked.
    1.48 +
    1.49 +    If this exception is raised in transfer_data_handler, the connection will
    1.50 +    be closed without closing handshake. No other WebSocket or HTTP(S) handler
    1.51 +    will be invoked.
    1.52 +    """
    1.53 +
    1.54 +    pass
    1.55 +
    1.56 +
    1.57 +class HandshakeException(Exception):
    1.58 +    """This exception will be raised when an error occurred while processing
    1.59 +    WebSocket initial handshake.
    1.60 +    """
    1.61 +
    1.62 +    def __init__(self, name, status=None):
    1.63 +        super(HandshakeException, self).__init__(name)
    1.64 +        self.status = status
    1.65 +
    1.66 +
    1.67 +class VersionException(Exception):
    1.68 +    """This exception will be raised when a version of client request does not
    1.69 +    match with version the server supports.
    1.70 +    """
    1.71 +
    1.72 +    def __init__(self, name, supported_versions=''):
    1.73 +        """Construct an instance.
    1.74 +
    1.75 +        Args:
    1.76 +            supported_version: a str object to show supported hybi versions.
    1.77 +                               (e.g. '8, 13')
    1.78 +        """
    1.79 +        super(VersionException, self).__init__(name)
    1.80 +        self.supported_versions = supported_versions
    1.81 +
    1.82 +
    1.83 +def get_default_port(is_secure):
    1.84 +    if is_secure:
    1.85 +        return common.DEFAULT_WEB_SOCKET_SECURE_PORT
    1.86 +    else:
    1.87 +        return common.DEFAULT_WEB_SOCKET_PORT
    1.88 +
    1.89 +
    1.90 +def validate_subprotocol(subprotocol, hixie):
    1.91 +    """Validate a value in subprotocol fields such as WebSocket-Protocol,
    1.92 +    Sec-WebSocket-Protocol.
    1.93 +
    1.94 +    See
    1.95 +    - RFC 6455: Section 4.1., 4.2.2., and 4.3.
    1.96 +    - HyBi 00: Section 4.1. Opening handshake
    1.97 +    - Hixie 75: Section 4.1. Handshake
    1.98 +    """
    1.99 +
   1.100 +    if not subprotocol:
   1.101 +        raise HandshakeException('Invalid subprotocol name: empty')
   1.102 +    if hixie:
   1.103 +        # Parameter should be in the range U+0020 to U+007E.
   1.104 +        for c in subprotocol:
   1.105 +            if not 0x20 <= ord(c) <= 0x7e:
   1.106 +                raise HandshakeException(
   1.107 +                    'Illegal character in subprotocol name: %r' % c)
   1.108 +    else:
   1.109 +        # Parameter should be encoded HTTP token.
   1.110 +        state = http_header_util.ParsingState(subprotocol)
   1.111 +        token = http_header_util.consume_token(state)
   1.112 +        rest = http_header_util.peek(state)
   1.113 +        # If |rest| is not None, |subprotocol| is not one token or invalid. If
   1.114 +        # |rest| is None, |token| must not be None because |subprotocol| is
   1.115 +        # concatenation of |token| and |rest| and is not None.
   1.116 +        if rest is not None:
   1.117 +            raise HandshakeException('Invalid non-token string in subprotocol '
   1.118 +                                     'name: %r' % rest)
   1.119 +
   1.120 +
   1.121 +def parse_host_header(request):
   1.122 +    fields = request.headers_in['Host'].split(':', 1)
   1.123 +    if len(fields) == 1:
   1.124 +        return fields[0], get_default_port(request.is_https())
   1.125 +    try:
   1.126 +        return fields[0], int(fields[1])
   1.127 +    except ValueError, e:
   1.128 +        raise HandshakeException('Invalid port number format: %r' % e)
   1.129 +
   1.130 +
   1.131 +def format_header(name, value):
   1.132 +    return '%s: %s\r\n' % (name, value)
   1.133 +
   1.134 +
   1.135 +def build_location(request):
   1.136 +    """Build WebSocket location for request."""
   1.137 +    location_parts = []
   1.138 +    if request.is_https():
   1.139 +        location_parts.append(common.WEB_SOCKET_SECURE_SCHEME)
   1.140 +    else:
   1.141 +        location_parts.append(common.WEB_SOCKET_SCHEME)
   1.142 +    location_parts.append('://')
   1.143 +    host, port = parse_host_header(request)
   1.144 +    connection_port = request.connection.local_addr[1]
   1.145 +    if port != connection_port:
   1.146 +        raise HandshakeException('Header/connection port mismatch: %d/%d' %
   1.147 +                                 (port, connection_port))
   1.148 +    location_parts.append(host)
   1.149 +    if (port != get_default_port(request.is_https())):
   1.150 +        location_parts.append(':')
   1.151 +        location_parts.append(str(port))
   1.152 +    location_parts.append(request.uri)
   1.153 +    return ''.join(location_parts)
   1.154 +
   1.155 +
   1.156 +def get_mandatory_header(request, key):
   1.157 +    value = request.headers_in.get(key)
   1.158 +    if value is None:
   1.159 +        raise HandshakeException('Header %s is not defined' % key)
   1.160 +    return value
   1.161 +
   1.162 +
   1.163 +def validate_mandatory_header(request, key, expected_value, fail_status=None):
   1.164 +    value = get_mandatory_header(request, key)
   1.165 +
   1.166 +    if value.lower() != expected_value.lower():
   1.167 +        raise HandshakeException(
   1.168 +            'Expected %r for header %s but found %r (case-insensitive)' %
   1.169 +            (expected_value, key, value), status=fail_status)
   1.170 +
   1.171 +
   1.172 +def check_request_line(request):
   1.173 +    # 5.1 1. The three character UTF-8 string "GET".
   1.174 +    # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte).
   1.175 +    if request.method != 'GET':
   1.176 +        raise HandshakeException('Method is not GET')
   1.177 +
   1.178 +
   1.179 +def check_header_lines(request, mandatory_headers):
   1.180 +    check_request_line(request)
   1.181 +
   1.182 +    # The expected field names, and the meaning of their corresponding
   1.183 +    # values, are as follows.
   1.184 +    #  |Upgrade| and |Connection|
   1.185 +    for key, expected_value in mandatory_headers:
   1.186 +        validate_mandatory_header(request, key, expected_value)
   1.187 +
   1.188 +
   1.189 +def parse_token_list(data):
   1.190 +    """Parses a header value which follows 1#token and returns parsed elements
   1.191 +    as a list of strings.
   1.192 +
   1.193 +    Leading LWSes must be trimmed.
   1.194 +    """
   1.195 +
   1.196 +    state = http_header_util.ParsingState(data)
   1.197 +
   1.198 +    token_list = []
   1.199 +
   1.200 +    while True:
   1.201 +        token = http_header_util.consume_token(state)
   1.202 +        if token is not None:
   1.203 +            token_list.append(token)
   1.204 +
   1.205 +        http_header_util.consume_lwses(state)
   1.206 +
   1.207 +        if http_header_util.peek(state) is None:
   1.208 +            break
   1.209 +
   1.210 +        if not http_header_util.consume_string(state, ','):
   1.211 +            raise HandshakeException(
   1.212 +                'Expected a comma but found %r' % http_header_util.peek(state))
   1.213 +
   1.214 +        http_header_util.consume_lwses(state)
   1.215 +
   1.216 +    if len(token_list) == 0:
   1.217 +        raise HandshakeException('No valid token found')
   1.218 +
   1.219 +    return token_list
   1.220 +
   1.221 +
   1.222 +def _parse_extension_param(state, definition, allow_quoted_string):
   1.223 +    param_name = http_header_util.consume_token(state)
   1.224 +
   1.225 +    if param_name is None:
   1.226 +        raise HandshakeException('No valid parameter name found')
   1.227 +
   1.228 +    http_header_util.consume_lwses(state)
   1.229 +
   1.230 +    if not http_header_util.consume_string(state, '='):
   1.231 +        definition.add_parameter(param_name, None)
   1.232 +        return
   1.233 +
   1.234 +    http_header_util.consume_lwses(state)
   1.235 +
   1.236 +    if allow_quoted_string:
   1.237 +        # TODO(toyoshim): Add code to validate that parsed param_value is token
   1.238 +        param_value = http_header_util.consume_token_or_quoted_string(state)
   1.239 +    else:
   1.240 +        param_value = http_header_util.consume_token(state)
   1.241 +    if param_value is None:
   1.242 +        raise HandshakeException(
   1.243 +            'No valid parameter value found on the right-hand side of '
   1.244 +            'parameter %r' % param_name)
   1.245 +
   1.246 +    definition.add_parameter(param_name, param_value)
   1.247 +
   1.248 +
   1.249 +def _parse_extension(state, allow_quoted_string):
   1.250 +    extension_token = http_header_util.consume_token(state)
   1.251 +    if extension_token is None:
   1.252 +        return None
   1.253 +
   1.254 +    extension = common.ExtensionParameter(extension_token)
   1.255 +
   1.256 +    while True:
   1.257 +        http_header_util.consume_lwses(state)
   1.258 +
   1.259 +        if not http_header_util.consume_string(state, ';'):
   1.260 +            break
   1.261 +
   1.262 +        http_header_util.consume_lwses(state)
   1.263 +
   1.264 +        try:
   1.265 +            _parse_extension_param(state, extension, allow_quoted_string)
   1.266 +        except HandshakeException, e:
   1.267 +            raise HandshakeException(
   1.268 +                'Failed to parse Sec-WebSocket-Extensions header: '
   1.269 +                'Failed to parse parameter for %r (%r)' %
   1.270 +                (extension_token, e))
   1.271 +
   1.272 +    return extension
   1.273 +
   1.274 +
   1.275 +def parse_extensions(data, allow_quoted_string=False):
   1.276 +    """Parses Sec-WebSocket-Extensions header value returns a list of
   1.277 +    common.ExtensionParameter objects.
   1.278 +
   1.279 +    Leading LWSes must be trimmed.
   1.280 +    """
   1.281 +
   1.282 +    state = http_header_util.ParsingState(data)
   1.283 +
   1.284 +    extension_list = []
   1.285 +    while True:
   1.286 +        extension = _parse_extension(state, allow_quoted_string)
   1.287 +        if extension is not None:
   1.288 +            extension_list.append(extension)
   1.289 +
   1.290 +        http_header_util.consume_lwses(state)
   1.291 +
   1.292 +        if http_header_util.peek(state) is None:
   1.293 +            break
   1.294 +
   1.295 +        if not http_header_util.consume_string(state, ','):
   1.296 +            raise HandshakeException(
   1.297 +                'Failed to parse Sec-WebSocket-Extensions header: '
   1.298 +                'Expected a comma but found %r' %
   1.299 +                http_header_util.peek(state))
   1.300 +
   1.301 +        http_header_util.consume_lwses(state)
   1.302 +
   1.303 +    if len(extension_list) == 0:
   1.304 +        raise HandshakeException(
   1.305 +            'Sec-WebSocket-Extensions header contains no valid extension')
   1.306 +
   1.307 +    return extension_list
   1.308 +
   1.309 +
   1.310 +def format_extensions(extension_list):
   1.311 +    formatted_extension_list = []
   1.312 +    for extension in extension_list:
   1.313 +        formatted_params = [extension.name()]
   1.314 +        for param_name, param_value in extension.get_parameters():
   1.315 +            if param_value is None:
   1.316 +                formatted_params.append(param_name)
   1.317 +            else:
   1.318 +                quoted_value = http_header_util.quote_if_necessary(param_value)
   1.319 +                formatted_params.append('%s=%s' % (param_name, quoted_value))
   1.320 +
   1.321 +        formatted_extension_list.append('; '.join(formatted_params))
   1.322 +
   1.323 +    return ', '.join(formatted_extension_list)
   1.324 +
   1.325 +
   1.326 +# vi:sts=4 sw=4 et

mercurial