12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2012, Google Inc. 22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# All rights reserved. 32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Redistribution and use in source and binary forms, with or without 52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# modification, are permitted provided that the following conditions are 62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# met: 72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# * Redistributions of source code must retain the above copyright 92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# notice, this list of conditions and the following disclaimer. 102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# * Redistributions in binary form must reproduce the above 112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# copyright notice, this list of conditions and the following disclaimer 122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# in the documentation and/or other materials provided with the 132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# distribution. 142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# * Neither the name of Google Inc. nor the names of its 152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# contributors may be used to endorse or promote products derived from 162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# this software without specific prior written permission. 172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""Dispatch WebSocket request. 322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis""" 332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 352da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport logging 362da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport os 372da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport re 382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 392da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import common 402da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import handshake 412da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import msgutil 422da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import mux 432da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import stream 442da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import util 452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$') 482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis_SOURCE_SUFFIX = '_wsh.py' 492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake' 502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data' 512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis_PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = ( 522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'web_socket_passive_closing_handshake') 532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 552da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass DispatchException(Exception): 562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Exception in dispatching WebSocket request.""" 572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND): 592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis super(DispatchException, self).__init__(name) 602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.status = status 612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 632da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _default_passive_closing_handshake_handler(request): 642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Default web_socket_passive_closing_handshake handler.""" 652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return common.STATUS_NORMAL_CLOSURE, '' 672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 692da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _normalize_path(path): 702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Normalize path. 712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis path: the path to normalize. 742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Path is converted to the absolute path. 762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The input path can use either '\\' or '/' as the separator. 772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The normalized path always uses '/' regardless of the platform. 782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis path = path.replace('\\', os.path.sep) 812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis path = os.path.realpath(path) 822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis path = path.replace('\\', '/') 832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return path 842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 862da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _create_path_to_resource_converter(base_dir): 872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Returns a function that converts the path of a WebSocket handler source 882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis file to a resource string by removing the path to the base directory from 892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis its head, removing _SOURCE_SUFFIX from its tail, and replacing path 902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis separators in it with '/'. 912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis base_dir: the path to the base directory. 942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis base_dir = _normalize_path(base_dir) 972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis base_len = len(base_dir) 992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis suffix_len = len(_SOURCE_SUFFIX) 1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def converter(path): 1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not path.endswith(_SOURCE_SUFFIX): 1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return None 1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # _normalize_path must not be used because resolving symlink breaks 1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # following path check. 1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis path = path.replace('\\', '/') 1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not path.startswith(base_dir): 1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return None 1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return path[base_len:-suffix_len] 1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return converter 1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _enumerate_handler_file_paths(directory): 1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Returns a generator that enumerates WebSocket Handler source file names 1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis in the given directory. 1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for root, unused_dirs, files in os.walk(directory): 1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for base in files: 1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis path = os.path.join(root, base) 1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if _SOURCE_PATH_PATTERN.search(path): 1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis yield path 1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass _HandlerSuite(object): 1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """A handler suite holder class.""" 1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def __init__(self, do_extra_handshake, transfer_data, 1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis passive_closing_handshake): 1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.do_extra_handshake = do_extra_handshake 1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.transfer_data = transfer_data 1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self.passive_closing_handshake = passive_closing_handshake 1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _source_handler_file(handler_definition): 1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Source a handler definition string. 1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler_definition: a string containing Python statements that define 1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler functions. 1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis global_dic = {} 1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis try: 1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis exec handler_definition in global_dic 1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except Exception: 1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('Error in sourcing handler:' + 1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis util.get_stack_trace()) 1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis passive_closing_handshake_handler = None 1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis try: 1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis passive_closing_handshake_handler = _extract_handler( 1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME) 1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except Exception: 1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis passive_closing_handshake_handler = ( 1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _default_passive_closing_handshake_handler) 1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return _HandlerSuite( 1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME), 1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME), 1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis passive_closing_handshake_handler) 1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _extract_handler(dic, name): 1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Extracts a callable with the specified name from the given dictionary 1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis dic. 1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if name not in dic: 1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('%s is not defined.' % name) 1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler = dic[name] 1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not callable(handler): 1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('%s is not callable.' % name) 1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return handler 1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass Dispatcher(object): 1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Dispatches WebSocket requests. 1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis This class maintains a map from resource name to handlers. 1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def __init__( 1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self, root_dir, scan_dir=None, 1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis allow_handlers_outside_root_dir=True): 1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Construct an instance. 1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis root_dir: The directory where handler definition files are 1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis placed. 1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis scan_dir: The directory where handler definition files are 1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis searched. scan_dir must be a directory under root_dir, 1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis including root_dir itself. If scan_dir is None, 1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis root_dir is used as scan_dir. scan_dir can be useful 1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis in saving scan time when root_dir contains many 1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis subdirectories. 1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis allow_handlers_outside_root_dir: Scans handler files even if their 1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis canonical path is not under root_dir. 1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger = util.get_class_logger(self) 2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._handler_suite_map = {} 2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._source_warnings = [] 2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if scan_dir is None: 2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis scan_dir = root_dir 2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not os.path.realpath(scan_dir).startswith( 2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis os.path.realpath(root_dir)): 2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('scan_dir:%s must be a directory under ' 2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'root_dir:%s.' % (scan_dir, root_dir)) 2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._source_handler_files_in_dir( 2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis root_dir, scan_dir, allow_handlers_outside_root_dir) 2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def add_resource_path_alias(self, 2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis alias_resource_path, existing_resource_path): 2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Add resource path alias. 2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Once added, request to alias_resource_path would be handled by 2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler registered for existing_resource_path. 2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis alias_resource_path: alias resource path 2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis existing_resource_path: existing resource path 2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis try: 2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler_suite = self._handler_suite_map[existing_resource_path] 2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._handler_suite_map[alias_resource_path] = handler_suite 2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except KeyError: 2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('No handler for: %r' % 2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis existing_resource_path) 2302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def source_warnings(self): 2322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Return warnings in sourcing handlers.""" 2332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return self._source_warnings 2352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def do_extra_handshake(self, request): 2372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Do extra checking in WebSocket handshake. 2382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Select a handler based on request.uri and call its 2402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis web_socket_do_extra_handshake function. 2412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 2432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request: mod_python request. 2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Raises: 2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis DispatchException: when handler was not found 2472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis AbortedByUserException: when user handler abort connection 2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis HandshakeException: when opening handshake failed 2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler_suite = self.get_handler_suite(request.ws_resource) 2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if handler_suite is None: 2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('No handler for: %r' % request.ws_resource) 2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis do_extra_handshake_ = handler_suite.do_extra_handshake 2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis try: 2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis do_extra_handshake_(request) 2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except handshake.AbortedByUserException, e: 2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise 2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except Exception, e: 2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis util.prepend_message_to_exception( 2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis '%s raised exception for %s: ' % ( 2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _DO_EXTRA_HANDSHAKE_HANDLER_NAME, 2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request.ws_resource), 2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis e) 2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN) 2662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def transfer_data(self, request): 2682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Let a handler transfer_data with a WebSocket client. 2692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Select a handler based on request.ws_resource and call its 2712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis web_socket_transfer_data function. 2722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 2742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request: mod_python request. 2752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Raises: 2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis DispatchException: when handler was not found 2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis AbortedByUserException: when user handler abort connection 2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # TODO(tyoshino): Terminate underlying TCP connection if possible. 2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis try: 2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if mux.use_mux(request): 2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis mux.start(request, self) 2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler_suite = self.get_handler_suite(request.ws_resource) 2872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if handler_suite is None: 2882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('No handler for: %r' % 2892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request.ws_resource) 2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis transfer_data_ = handler_suite.transfer_data 2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis transfer_data_(request) 2922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not request.server_terminated: 2942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request.ws_stream.close_connection() 2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Catch non-critical exceptions the handler didn't handle. 2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except handshake.AbortedByUserException, e: 2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('%s', e) 2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise 2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except msgutil.BadOperationException, e: 3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('%s', e) 3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE) 3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except msgutil.InvalidFrameException, e: 3032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # InvalidFrameException must be caught before 3042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # ConnectionTerminatedException that catches InvalidFrameException. 3052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('%s', e) 3062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR) 3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except msgutil.UnsupportedFrameException, e: 3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('%s', e) 3092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA) 3102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except stream.InvalidUTF8Exception, e: 3112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('%s', e) 3122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request.ws_stream.close_connection( 3132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis common.STATUS_INVALID_FRAME_PAYLOAD_DATA) 3142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except msgutil.ConnectionTerminatedException, e: 3152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('%s', e) 3162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except Exception, e: 3172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis util.prepend_message_to_exception( 3182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis '%s raised exception for %s: ' % ( 3192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _TRANSFER_DATA_HANDLER_NAME, request.ws_resource), 3202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis e) 3212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise 3222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def passive_closing_handshake(self, request): 3242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Prepare code and reason for responding client initiated closing 3252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handshake. 3262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 3272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler_suite = self.get_handler_suite(request.ws_resource) 3292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if handler_suite is None: 3302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return _default_passive_closing_handshake_handler(request) 3312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return handler_suite.passive_closing_handshake(request) 3322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def get_handler_suite(self, resource): 3342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Retrieves two handlers (one for extra handshake processing, and one 3352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for data transfer) for the given request as a HandlerSuite object. 3362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 3372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis fragment = None 3392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if '#' in resource: 3402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis resource, fragment = resource.split('#', 1) 3412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if '?' in resource: 3422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis resource = resource.split('?', 1)[0] 3432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler_suite = self._handler_suite_map.get(resource) 3442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if handler_suite and fragment: 3452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise DispatchException('Fragment identifiers MUST NOT be used on ' 3462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'WebSocket URIs', 3472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis common.HTTP_STATUS_BAD_REQUEST) 3482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return handler_suite 3492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _source_handler_files_in_dir( 3512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self, root_dir, scan_dir, allow_handlers_outside_root_dir): 3522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Source all the handler source files in the scan_dir directory. 3532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis The resource path is determined relative to root_dir. 3552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 3562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # We build a map from resource to handler code assuming that there's 3582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # only one path from root_dir to scan_dir and it can be obtained by 3592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # comparing realpath of them. 3602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Here we cannot use abspath. See 3622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # https://bugs.webkit.org/show_bug.cgi?id=31603 3632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis convert = _create_path_to_resource_converter(root_dir) 3652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis scan_realpath = os.path.realpath(scan_dir) 3662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis root_realpath = os.path.realpath(root_dir) 3672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis for path in _enumerate_handler_file_paths(scan_realpath): 3682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (not allow_handlers_outside_root_dir and 3692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis (not os.path.realpath(path).startswith(root_realpath))): 3702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug( 3712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Canonical path of %s is not under root directory' % 3722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis path) 3732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis continue 3742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis try: 3752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handler_suite = _source_handler_file(open(path).read()) 3762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis except DispatchException, e: 3772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._source_warnings.append('%s: %s' % (path, e)) 3782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis continue 3792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis resource = convert(path) 3802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if resource is None: 3812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug( 3822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Path to resource conversion on %s failed' % path) 3832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 3842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._handler_suite_map[convert(path)] = handler_suite 3852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 3872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# vi:sts=4 sw=4 et 388