| # Copyright 2012, Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| |
| """Common functions and exceptions used by WebSocket opening handshake |
| processors. |
| """ |
| |
| |
| from mod_pywebsocket import common |
| from mod_pywebsocket import http_header_util |
| |
| |
| class AbortedByUserException(Exception): |
| """Exception for aborting a connection intentionally. |
| |
| If this exception is raised in do_extra_handshake handler, the connection |
| will be abandoned. No other WebSocket or HTTP(S) handler will be invoked. |
| |
| If this exception is raised in transfer_data_handler, the connection will |
| be closed without closing handshake. No other WebSocket or HTTP(S) handler |
| will be invoked. |
| """ |
| |
| pass |
| |
| |
| class HandshakeException(Exception): |
| """This exception will be raised when an error occurred while processing |
| WebSocket initial handshake. |
| """ |
| |
| def __init__(self, name, status=None): |
| super(HandshakeException, self).__init__(name) |
| self.status = status |
| |
| |
| class VersionException(Exception): |
| """This exception will be raised when a version of client request does not |
| match with version the server supports. |
| """ |
| |
| def __init__(self, name, supported_versions=''): |
| """Construct an instance. |
| |
| Args: |
| supported_version: a str object to show supported hybi versions. |
| (e.g. '8, 13') |
| """ |
| super(VersionException, self).__init__(name) |
| self.supported_versions = supported_versions |
| |
| |
| def get_default_port(is_secure): |
| if is_secure: |
| return common.DEFAULT_WEB_SOCKET_SECURE_PORT |
| else: |
| return common.DEFAULT_WEB_SOCKET_PORT |
| |
| |
| def validate_subprotocol(subprotocol): |
| """Validate a value in the Sec-WebSocket-Protocol field. |
| |
| See the Section 4.1., 4.2.2., and 4.3. of RFC 6455. |
| """ |
| |
| if not subprotocol: |
| raise HandshakeException('Invalid subprotocol name: empty') |
| |
| # Parameter should be encoded HTTP token. |
| state = http_header_util.ParsingState(subprotocol) |
| token = http_header_util.consume_token(state) |
| rest = http_header_util.peek(state) |
| # If |rest| is not None, |subprotocol| is not one token or invalid. If |
| # |rest| is None, |token| must not be None because |subprotocol| is |
| # concatenation of |token| and |rest| and is not None. |
| if rest is not None: |
| raise HandshakeException('Invalid non-token string in subprotocol ' |
| 'name: %r' % rest) |
| |
| |
| def parse_host_header(request): |
| fields = request.headers_in[common.HOST_HEADER].split(':', 1) |
| if len(fields) == 1: |
| return fields[0], get_default_port(request.is_https()) |
| try: |
| return fields[0], int(fields[1]) |
| except ValueError as e: |
| raise HandshakeException('Invalid port number format: %r' % e) |
| |
| |
| def format_header(name, value): |
| return '%s: %s\r\n' % (name, value) |
| |
| |
| def get_mandatory_header(request, key): |
| value = request.headers_in.get(key) |
| if value is None: |
| raise HandshakeException('Header %s is not defined' % key) |
| return value |
| |
| |
| def validate_mandatory_header(request, key, expected_value, fail_status=None): |
| value = get_mandatory_header(request, key) |
| |
| if value.lower() != expected_value.lower(): |
| raise HandshakeException( |
| 'Expected %r for header %s but found %r (case-insensitive)' % |
| (expected_value, key, value), status=fail_status) |
| |
| |
| def check_request_line(request): |
| # 5.1 1. The three character UTF-8 string "GET". |
| # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte). |
| if request.method != 'GET': |
| raise HandshakeException('Method is not GET: %r' % request.method) |
| |
| if request.protocol != 'HTTP/1.1': |
| raise HandshakeException('Version is not HTTP/1.1: %r' % |
| request.protocol) |
| |
| |
| def parse_token_list(data): |
| """Parses a header value which follows 1#token and returns parsed elements |
| as a list of strings. |
| |
| Leading LWSes must be trimmed. |
| """ |
| |
| state = http_header_util.ParsingState(data) |
| |
| token_list = [] |
| |
| while True: |
| token = http_header_util.consume_token(state) |
| if token is not None: |
| token_list.append(token) |
| |
| http_header_util.consume_lwses(state) |
| |
| if http_header_util.peek(state) is None: |
| break |
| |
| if not http_header_util.consume_string(state, ','): |
| raise HandshakeException( |
| 'Expected a comma but found %r' % http_header_util.peek(state)) |
| |
| http_header_util.consume_lwses(state) |
| |
| if len(token_list) == 0: |
| raise HandshakeException('No valid token found') |
| |
| return token_list |
| |
| |
| # vi:sts=4 sw=4 et |