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