tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

request_handler.py (12195B)


      1 # Copyright 2020, 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 """Request Handler and Request/Connection classes for standalone server.
     30 """
     31 
     32 import os
     33 
     34 from six.moves import CGIHTTPServer
     35 from six.moves import http_client
     36 
     37 from mod_pywebsocket import common
     38 from mod_pywebsocket import dispatch
     39 from mod_pywebsocket import handshake
     40 from mod_pywebsocket import http_header_util
     41 from mod_pywebsocket import memorizingfile
     42 from mod_pywebsocket import util
     43 
     44 # 1024 is practically large enough to contain WebSocket handshake lines.
     45 _MAX_MEMORIZED_LINES = 1024
     46 
     47 
     48 class _StandaloneConnection(object):
     49    """Mimic mod_python mp_conn."""
     50    def __init__(self, request_handler):
     51        """Construct an instance.
     52 
     53        Args:
     54            request_handler: A WebSocketRequestHandler instance.
     55        """
     56 
     57        self._request_handler = request_handler
     58 
     59    def get_local_addr(self):
     60        """Getter to mimic mp_conn.local_addr."""
     61 
     62        return (self._request_handler.server.server_name,
     63                self._request_handler.server.server_port)
     64 
     65    local_addr = property(get_local_addr)
     66 
     67    def get_remote_addr(self):
     68        """Getter to mimic mp_conn.remote_addr.
     69 
     70        Setting the property in __init__ won't work because the request
     71        handler is not initialized yet there."""
     72 
     73        return self._request_handler.client_address
     74 
     75    remote_addr = property(get_remote_addr)
     76 
     77    def write(self, data):
     78        """Mimic mp_conn.write()."""
     79 
     80        return self._request_handler.wfile.write(data)
     81 
     82    def read(self, length):
     83        """Mimic mp_conn.read()."""
     84 
     85        return self._request_handler.rfile.read(length)
     86 
     87    def get_memorized_lines(self):
     88        """Get memorized lines."""
     89 
     90        return self._request_handler.rfile.get_memorized_lines()
     91 
     92 
     93 class _StandaloneRequest(object):
     94    """Mimic mod_python request."""
     95    def __init__(self, request_handler, use_tls):
     96        """Construct an instance.
     97 
     98        Args:
     99            request_handler: A WebSocketRequestHandler instance.
    100        """
    101 
    102        self._logger = util.get_class_logger(self)
    103 
    104        self._request_handler = request_handler
    105        self.connection = _StandaloneConnection(request_handler)
    106        self._use_tls = use_tls
    107        self.headers_in = request_handler.headers
    108 
    109    def get_uri(self):
    110        """Getter to mimic request.uri.
    111 
    112        This method returns the raw data at the Request-URI part of the
    113        Request-Line, while the uri method on the request object of mod_python
    114        returns the path portion after parsing the raw data. This behavior is
    115        kept for compatibility.
    116        """
    117 
    118        return self._request_handler.path
    119 
    120    uri = property(get_uri)
    121 
    122    def get_unparsed_uri(self):
    123        """Getter to mimic request.unparsed_uri."""
    124 
    125        return self._request_handler.path
    126 
    127    unparsed_uri = property(get_unparsed_uri)
    128 
    129    def get_method(self):
    130        """Getter to mimic request.method."""
    131 
    132        return self._request_handler.command
    133 
    134    method = property(get_method)
    135 
    136    def get_protocol(self):
    137        """Getter to mimic request.protocol."""
    138 
    139        return self._request_handler.request_version
    140 
    141    protocol = property(get_protocol)
    142 
    143    def is_https(self):
    144        """Mimic request.is_https()."""
    145 
    146        return self._use_tls
    147 
    148 
    149 class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
    150    """CGIHTTPRequestHandler specialized for WebSocket."""
    151 
    152    # Use httplib.HTTPMessage instead of mimetools.Message.
    153    MessageClass = http_client.HTTPMessage
    154 
    155    def setup(self):
    156        """Override SocketServer.StreamRequestHandler.setup to wrap rfile
    157        with MemorizingFile.
    158 
    159        This method will be called by BaseRequestHandler's constructor
    160        before calling BaseHTTPRequestHandler.handle.
    161        BaseHTTPRequestHandler.handle will call
    162        BaseHTTPRequestHandler.handle_one_request and it will call
    163        WebSocketRequestHandler.parse_request.
    164        """
    165 
    166        # Call superclass's setup to prepare rfile, wfile, etc. See setup
    167        # definition on the root class SocketServer.StreamRequestHandler to
    168        # understand what this does.
    169        CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
    170 
    171        self.rfile = memorizingfile.MemorizingFile(
    172            self.rfile, max_memorized_lines=_MAX_MEMORIZED_LINES)
    173 
    174    def __init__(self, request, client_address, server):
    175        self._logger = util.get_class_logger(self)
    176 
    177        self._options = server.websocket_server_options
    178 
    179        # Overrides CGIHTTPServerRequestHandler.cgi_directories.
    180        self.cgi_directories = self._options.cgi_directories
    181        # Replace CGIHTTPRequestHandler.is_executable method.
    182        if self._options.is_executable_method is not None:
    183            self.is_executable = self._options.is_executable_method
    184 
    185        # This actually calls BaseRequestHandler.__init__.
    186        CGIHTTPServer.CGIHTTPRequestHandler.__init__(self, request,
    187                                                     client_address, server)
    188 
    189    def parse_request(self):
    190        """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
    191 
    192        Return True to continue processing for HTTP(S), False otherwise.
    193 
    194        See BaseHTTPRequestHandler.handle_one_request method which calls
    195        this method to understand how the return value will be handled.
    196        """
    197 
    198        # We hook parse_request method, but also call the original
    199        # CGIHTTPRequestHandler.parse_request since when we return False,
    200        # CGIHTTPRequestHandler.handle_one_request continues processing and
    201        # it needs variables set by CGIHTTPRequestHandler.parse_request.
    202        #
    203        # Variables set by this method will be also used by WebSocket request
    204        # handling (self.path, self.command, self.requestline, etc. See also
    205        # how _StandaloneRequest's members are implemented using these
    206        # attributes).
    207        if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
    208            return False
    209 
    210        if self._options.use_basic_auth:
    211            auth = self.headers.get('Authorization')
    212            if auth != self._options.basic_auth_credential:
    213                self.send_response(401)
    214                self.send_header('WWW-Authenticate',
    215                                 'Basic realm="Pywebsocket"')
    216                self.end_headers()
    217                self._logger.info('Request basic authentication')
    218                return False
    219 
    220        host, port, resource = http_header_util.parse_uri(self.path)
    221        if resource is None:
    222            self._logger.info('Invalid URI: %r', self.path)
    223            self._logger.info('Fallback to CGIHTTPRequestHandler')
    224            return True
    225        server_options = self.server.websocket_server_options
    226        if host is not None:
    227            validation_host = server_options.validation_host
    228            if validation_host is not None and host != validation_host:
    229                self._logger.info('Invalid host: %r (expected: %r)', host,
    230                                  validation_host)
    231                self._logger.info('Fallback to CGIHTTPRequestHandler')
    232                return True
    233        if port is not None:
    234            validation_port = server_options.validation_port
    235            if validation_port is not None and port != validation_port:
    236                self._logger.info('Invalid port: %r (expected: %r)', port,
    237                                  validation_port)
    238                self._logger.info('Fallback to CGIHTTPRequestHandler')
    239                return True
    240        self.path = resource
    241 
    242        request = _StandaloneRequest(self, self._options.use_tls)
    243 
    244        try:
    245            # Fallback to default http handler for request paths for which
    246            # we don't have request handlers.
    247            if not self._options.dispatcher.get_handler_suite(self.path):
    248                self._logger.info('No handler for resource: %r', self.path)
    249                self._logger.info('Fallback to CGIHTTPRequestHandler')
    250                return True
    251        except dispatch.DispatchException as e:
    252            self._logger.info('Dispatch failed for error: %s', e)
    253            self.send_error(e.status)
    254            return False
    255 
    256        # If any Exceptions without except clause setup (including
    257        # DispatchException) is raised below this point, it will be caught
    258        # and logged by WebSocketServer.
    259 
    260        try:
    261            try:
    262                handshake.do_handshake(request, self._options.dispatcher)
    263            except handshake.VersionException as e:
    264                self._logger.info('Handshake failed for version error: %s', e)
    265                self.send_response(common.HTTP_STATUS_BAD_REQUEST)
    266                self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER,
    267                                 e.supported_versions)
    268                self.end_headers()
    269                return False
    270            except handshake.HandshakeException as e:
    271                # Handshake for ws(s) failed.
    272                self._logger.info('Handshake failed for error: %s', e)
    273                self.send_error(e.status)
    274                return False
    275 
    276            request._dispatcher = self._options.dispatcher
    277            self._options.dispatcher.transfer_data(request)
    278        except handshake.AbortedByUserException as e:
    279            self._logger.info('Aborted: %s', e)
    280        return False
    281 
    282    def log_request(self, code='-', size='-'):
    283        """Override BaseHTTPServer.log_request."""
    284 
    285        self._logger.info('"%s" %s %s', self.requestline, str(code), str(size))
    286 
    287    def log_error(self, *args):
    288        """Override BaseHTTPServer.log_error."""
    289 
    290        # Despite the name, this method is for warnings than for errors.
    291        # For example, HTTP status code is logged by this method.
    292        self._logger.warning('%s - %s', self.address_string(),
    293                             args[0] % args[1:])
    294 
    295    def is_cgi(self):
    296        """Test whether self.path corresponds to a CGI script.
    297 
    298        Add extra check that self.path doesn't contains ..
    299        Also check if the file is a executable file or not.
    300        If the file is not executable, it is handled as static file or dir
    301        rather than a CGI script.
    302        """
    303 
    304        if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
    305            if '..' in self.path:
    306                return False
    307            # strip query parameter from request path
    308            resource_name = self.path.split('?', 2)[0]
    309            # convert resource_name into real path name in filesystem.
    310            scriptfile = self.translate_path(resource_name)
    311            if not os.path.isfile(scriptfile):
    312                return False
    313            if not self.is_executable(scriptfile):
    314                return False
    315            return True
    316        return False
    317 
    318 
    319 # vi:sts=4 sw=4 et