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"""Common functions and exceptions used by WebSocket opening handshake 32processors. 33""" 34 35 36from mod_pywebsocket import common 37from mod_pywebsocket import http_header_util 38 39 40class AbortedByUserException(Exception): 41 """Exception for aborting a connection intentionally. 42 43 If this exception is raised in do_extra_handshake handler, the connection 44 will be abandoned. No other WebSocket or HTTP(S) handler will be invoked. 45 46 If this exception is raised in transfer_data_handler, the connection will 47 be closed without closing handshake. No other WebSocket or HTTP(S) handler 48 will be invoked. 49 """ 50 51 pass 52 53 54class HandshakeException(Exception): 55 """This exception will be raised when an error occurred while processing 56 WebSocket initial handshake. 57 """ 58 59 def __init__(self, name, status=None): 60 super(HandshakeException, self).__init__(name) 61 self.status = status 62 63 64class VersionException(Exception): 65 """This exception will be raised when a version of client request does not 66 match with version the server supports. 67 """ 68 69 def __init__(self, name, supported_versions=''): 70 """Construct an instance. 71 72 Args: 73 supported_version: a str object to show supported hybi versions. 74 (e.g. '8, 13') 75 """ 76 super(VersionException, self).__init__(name) 77 self.supported_versions = supported_versions 78 79 80def get_default_port(is_secure): 81 if is_secure: 82 return common.DEFAULT_WEB_SOCKET_SECURE_PORT 83 else: 84 return common.DEFAULT_WEB_SOCKET_PORT 85 86 87def validate_subprotocol(subprotocol): 88 """Validate a value in the Sec-WebSocket-Protocol field. 89 90 See the Section 4.1., 4.2.2., and 4.3. of RFC 6455. 91 """ 92 93 if not subprotocol: 94 raise HandshakeException('Invalid subprotocol name: empty') 95 96 # Parameter should be encoded HTTP token. 97 state = http_header_util.ParsingState(subprotocol) 98 token = http_header_util.consume_token(state) 99 rest = http_header_util.peek(state) 100 # If |rest| is not None, |subprotocol| is not one token or invalid. If 101 # |rest| is None, |token| must not be None because |subprotocol| is 102 # concatenation of |token| and |rest| and is not None. 103 if rest is not None: 104 raise HandshakeException('Invalid non-token string in subprotocol ' 105 'name: %r' % rest) 106 107 108def parse_host_header(request): 109 fields = request.headers_in[common.HOST_HEADER].split(':', 1) 110 if len(fields) == 1: 111 return fields[0], get_default_port(request.is_https()) 112 try: 113 return fields[0], int(fields[1]) 114 except ValueError, e: 115 raise HandshakeException('Invalid port number format: %r' % e) 116 117 118def format_header(name, value): 119 return '%s: %s\r\n' % (name, value) 120 121 122def get_mandatory_header(request, key): 123 value = request.headers_in.get(key) 124 if value is None: 125 raise HandshakeException('Header %s is not defined' % key) 126 return value 127 128 129def validate_mandatory_header(request, key, expected_value, fail_status=None): 130 value = get_mandatory_header(request, key) 131 132 if value.lower() != expected_value.lower(): 133 raise HandshakeException( 134 'Expected %r for header %s but found %r (case-insensitive)' % 135 (expected_value, key, value), status=fail_status) 136 137 138def check_request_line(request): 139 # 5.1 1. The three character UTF-8 string "GET". 140 # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte). 141 if request.method != 'GET': 142 raise HandshakeException('Method is not GET: %r' % request.method) 143 144 if request.protocol != 'HTTP/1.1': 145 raise HandshakeException('Version is not HTTP/1.1: %r' % 146 request.protocol) 147 148 149def parse_token_list(data): 150 """Parses a header value which follows 1#token and returns parsed elements 151 as a list of strings. 152 153 Leading LWSes must be trimmed. 154 """ 155 156 state = http_header_util.ParsingState(data) 157 158 token_list = [] 159 160 while True: 161 token = http_header_util.consume_token(state) 162 if token is not None: 163 token_list.append(token) 164 165 http_header_util.consume_lwses(state) 166 167 if http_header_util.peek(state) is None: 168 break 169 170 if not http_header_util.consume_string(state, ','): 171 raise HandshakeException( 172 'Expected a comma but found %r' % http_header_util.peek(state)) 173 174 http_header_util.consume_lwses(state) 175 176 if len(token_list) == 0: 177 raise HandshakeException('No valid token found') 178 179 return token_list 180 181 182# vi:sts=4 sw=4 et 183