testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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.
    31 """Dispatch WebSocket request.
    32 """
    35 import logging
    36 import os
    37 import re
    39 from mod_pywebsocket import common
    40 from mod_pywebsocket import handshake
    41 from mod_pywebsocket import msgutil
    42 from mod_pywebsocket import stream
    43 from mod_pywebsocket import util
    46 _SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$')
    47 _SOURCE_SUFFIX = '_wsh.py'
    48 _DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake'
    49 _TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
    50 _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = (
    51     'web_socket_passive_closing_handshake')
    54 class DispatchException(Exception):
    55     """Exception in dispatching WebSocket request."""
    57     def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND):
    58         super(DispatchException, self).__init__(name)
    59         self.status = status
    62 def _default_passive_closing_handshake_handler(request):
    63     """Default web_socket_passive_closing_handshake handler."""
    65     return common.STATUS_NORMAL_CLOSURE, ''
    68 def _normalize_path(path):
    69     """Normalize path.
    71     Args:
    72         path: the path to normalize.
    74     Path is converted to the absolute path.
    75     The input path can use either '\\' or '/' as the separator.
    76     The normalized path always uses '/' regardless of the platform.
    77     """
    79     path = path.replace('\\', os.path.sep)
    80     path = os.path.realpath(path)
    81     path = path.replace('\\', '/')
    82     return path
    85 def _create_path_to_resource_converter(base_dir):
    86     """Returns a function that converts the path of a WebSocket handler source
    87     file to a resource string by removing the path to the base directory from
    88     its head, removing _SOURCE_SUFFIX from its tail, and replacing path
    89     separators in it with '/'.
    91     Args:
    92         base_dir: the path to the base directory.
    93     """
    95     base_dir = _normalize_path(base_dir)
    97     base_len = len(base_dir)
    98     suffix_len = len(_SOURCE_SUFFIX)
   100     def converter(path):
   101         if not path.endswith(_SOURCE_SUFFIX):
   102             return None
   103         # _normalize_path must not be used because resolving symlink breaks
   104         # following path check.
   105         path = path.replace('\\', '/')
   106         if not path.startswith(base_dir):
   107             return None
   108         return path[base_len:-suffix_len]
   110     return converter
   113 def _enumerate_handler_file_paths(directory):
   114     """Returns a generator that enumerates WebSocket Handler source file names
   115     in the given directory.
   116     """
   118     for root, unused_dirs, files in os.walk(directory):
   119         for base in files:
   120             path = os.path.join(root, base)
   121             if _SOURCE_PATH_PATTERN.search(path):
   122                 yield path
   125 class _HandlerSuite(object):
   126     """A handler suite holder class."""
   128     def __init__(self, do_extra_handshake, transfer_data,
   129                  passive_closing_handshake):
   130         self.do_extra_handshake = do_extra_handshake
   131         self.transfer_data = transfer_data
   132         self.passive_closing_handshake = passive_closing_handshake
   135 def _source_handler_file(handler_definition):
   136     """Source a handler definition string.
   138     Args:
   139         handler_definition: a string containing Python statements that define
   140                             handler functions.
   141     """
   143     global_dic = {}
   144     try:
   145         exec handler_definition in global_dic
   146     except Exception:
   147         raise DispatchException('Error in sourcing handler:' +
   148                                 util.get_stack_trace())
   149     passive_closing_handshake_handler = None
   150     try:
   151         passive_closing_handshake_handler = _extract_handler(
   152             global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME)
   153     except Exception:
   154         passive_closing_handshake_handler = (
   155             _default_passive_closing_handshake_handler)
   156     return _HandlerSuite(
   157         _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
   158         _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME),
   159         passive_closing_handshake_handler)
   162 def _extract_handler(dic, name):
   163     """Extracts a callable with the specified name from the given dictionary
   164     dic.
   165     """
   167     if name not in dic:
   168         raise DispatchException('%s is not defined.' % name)
   169     handler = dic[name]
   170     if not callable(handler):
   171         raise DispatchException('%s is not callable.' % name)
   172     return handler
   175 class Dispatcher(object):
   176     """Dispatches WebSocket requests.
   178     This class maintains a map from resource name to handlers.
   179     """
   181     def __init__(
   182         self, root_dir, scan_dir=None,
   183         allow_handlers_outside_root_dir=True):
   184         """Construct an instance.
   186         Args:
   187             root_dir: The directory where handler definition files are
   188                       placed.
   189             scan_dir: The directory where handler definition files are
   190                       searched. scan_dir must be a directory under root_dir,
   191                       including root_dir itself.  If scan_dir is None,
   192                       root_dir is used as scan_dir. scan_dir can be useful
   193                       in saving scan time when root_dir contains many
   194                       subdirectories.
   195             allow_handlers_outside_root_dir: Scans handler files even if their
   196                       canonical path is not under root_dir.
   197         """
   199         self._logger = util.get_class_logger(self)
   201         self._handler_suite_map = {}
   202         self._source_warnings = []
   203         if scan_dir is None:
   204             scan_dir = root_dir
   205         if not os.path.realpath(scan_dir).startswith(
   206                 os.path.realpath(root_dir)):
   207             raise DispatchException('scan_dir:%s must be a directory under '
   208                                     'root_dir:%s.' % (scan_dir, root_dir))
   209         self._source_handler_files_in_dir(
   210             root_dir, scan_dir, allow_handlers_outside_root_dir)
   212     def add_resource_path_alias(self,
   213                                 alias_resource_path, existing_resource_path):
   214         """Add resource path alias.
   216         Once added, request to alias_resource_path would be handled by
   217         handler registered for existing_resource_path.
   219         Args:
   220             alias_resource_path: alias resource path
   221             existing_resource_path: existing resource path
   222         """
   223         try:
   224             handler_suite = self._handler_suite_map[existing_resource_path]
   225             self._handler_suite_map[alias_resource_path] = handler_suite
   226         except KeyError:
   227             raise DispatchException('No handler for: %r' %
   228                                     existing_resource_path)
   230     def source_warnings(self):
   231         """Return warnings in sourcing handlers."""
   233         return self._source_warnings
   235     def do_extra_handshake(self, request):
   236         """Do extra checking in WebSocket handshake.
   238         Select a handler based on request.uri and call its
   239         web_socket_do_extra_handshake function.
   241         Args:
   242             request: mod_python request.
   244         Raises:
   245             DispatchException: when handler was not found
   246             AbortedByUserException: when user handler abort connection
   247             HandshakeException: when opening handshake failed
   248         """
   250         handler_suite = self.get_handler_suite(request.ws_resource)
   251         if handler_suite is None:
   252             raise DispatchException('No handler for: %r' % request.ws_resource)
   253         do_extra_handshake_ = handler_suite.do_extra_handshake
   254         try:
   255             do_extra_handshake_(request)
   256         except handshake.AbortedByUserException, e:
   257             raise
   258         except Exception, e:
   259             util.prepend_message_to_exception(
   260                     '%s raised exception for %s: ' % (
   261                             _DO_EXTRA_HANDSHAKE_HANDLER_NAME,
   262                             request.ws_resource),
   263                     e)
   264             raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN)
   266     def transfer_data(self, request):
   267         """Let a handler transfer_data with a WebSocket client.
   269         Select a handler based on request.ws_resource and call its
   270         web_socket_transfer_data function.
   272         Args:
   273             request: mod_python request.
   275         Raises:
   276             DispatchException: when handler was not found
   277             AbortedByUserException: when user handler abort connection
   278         """
   280         handler_suite = self.get_handler_suite(request.ws_resource)
   281         if handler_suite is None:
   282             raise DispatchException('No handler for: %r' % request.ws_resource)
   283         transfer_data_ = handler_suite.transfer_data
   284         # TODO(tyoshino): Terminate underlying TCP connection if possible.
   285         try:
   286             transfer_data_(request)
   287             if not request.server_terminated:
   288                 request.ws_stream.close_connection()
   289         # Catch non-critical exceptions the handler didn't handle.
   290         except handshake.AbortedByUserException, e:
   291             self._logger.debug('%s', e)
   292             raise
   293         except msgutil.BadOperationException, e:
   294             self._logger.debug('%s', e)
   295             request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE)
   296         except msgutil.InvalidFrameException, e:
   297             # InvalidFrameException must be caught before
   298             # ConnectionTerminatedException that catches InvalidFrameException.
   299             self._logger.debug('%s', e)
   300             request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
   301         except msgutil.UnsupportedFrameException, e:
   302             self._logger.debug('%s', e)
   303             request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA)
   304         except stream.InvalidUTF8Exception, e:
   305             self._logger.debug('%s', e)
   306             request.ws_stream.close_connection(
   307                 common.STATUS_INVALID_FRAME_PAYLOAD_DATA)
   308         except msgutil.ConnectionTerminatedException, e:
   309             self._logger.debug('%s', e)
   310         except Exception, e:
   311             util.prepend_message_to_exception(
   312                 '%s raised exception for %s: ' % (
   313                     _TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
   314                 e)
   315             raise
   317     def passive_closing_handshake(self, request):
   318         """Prepare code and reason for responding client initiated closing
   319         handshake.
   320         """
   322         handler_suite = self.get_handler_suite(request.ws_resource)
   323         if handler_suite is None:
   324             return _default_passive_closing_handshake_handler(request)
   325         return handler_suite.passive_closing_handshake(request)
   327     def get_handler_suite(self, resource):
   328         """Retrieves two handlers (one for extra handshake processing, and one
   329         for data transfer) for the given request as a HandlerSuite object.
   330         """
   332         fragment = None
   333         if '#' in resource:
   334             resource, fragment = resource.split('#', 1)
   335         if '?' in resource:
   336             resource = resource.split('?', 1)[0]
   337         handler_suite = self._handler_suite_map.get(resource)
   338         if handler_suite and fragment:
   339             raise DispatchException('Fragment identifiers MUST NOT be used on '
   340                                     'WebSocket URIs',
   341                                     common.HTTP_STATUS_BAD_REQUEST)
   342         return handler_suite
   344     def _source_handler_files_in_dir(
   345         self, root_dir, scan_dir, allow_handlers_outside_root_dir):
   346         """Source all the handler source files in the scan_dir directory.
   348         The resource path is determined relative to root_dir.
   349         """
   351         # We build a map from resource to handler code assuming that there's
   352         # only one path from root_dir to scan_dir and it can be obtained by
   353         # comparing realpath of them.
   355         # Here we cannot use abspath. See
   356         # https://bugs.webkit.org/show_bug.cgi?id=31603
   358         convert = _create_path_to_resource_converter(root_dir)
   359         scan_realpath = os.path.realpath(scan_dir)
   360         root_realpath = os.path.realpath(root_dir)
   361         for path in _enumerate_handler_file_paths(scan_realpath):
   362             if (not allow_handlers_outside_root_dir and
   363                 (not os.path.realpath(path).startswith(root_realpath))):
   364                 self._logger.debug(
   365                     'Canonical path of %s is not under root directory' %
   366                     path)
   367                 continue
   368             try:
   369                 handler_suite = _source_handler_file(open(path).read())
   370             except DispatchException, e:
   371                 self._source_warnings.append('%s: %s' % (path, e))
   372                 continue
   373             resource = convert(path)
   374             if resource is None:
   375                 self._logger.debug(
   376                     'Path to resource conversion on %s failed' % path)
   377             else:
   378                 self._handler_suite_map[convert(path)] = handler_suite
   381 # vi:sts=4 sw=4 et

mercurial