forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			321 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright 2020, Google Inc.
 | |
| # All rights reserved.
 | |
| #
 | |
| # Redistribution and use in source and binary forms, with or without
 | |
| # modification, are permitted provided that the following conditions are
 | |
| # met:
 | |
| #
 | |
| #     * Redistributions of source code must retain the above copyright
 | |
| # notice, this list of conditions and the following disclaimer.
 | |
| #     * Redistributions in binary form must reproduce the above
 | |
| # copyright notice, this list of conditions and the following disclaimer
 | |
| # in the documentation and/or other materials provided with the
 | |
| # distribution.
 | |
| #     * Neither the name of Google Inc. nor the names of its
 | |
| # contributors may be used to endorse or promote products derived from
 | |
| # this software without specific prior written permission.
 | |
| #
 | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
| """Request Handler and Request/Connection classes for standalone server.
 | |
| """
 | |
| 
 | |
| from __future__ import absolute_import
 | |
| 
 | |
| import os
 | |
| 
 | |
| from six.moves import CGIHTTPServer
 | |
| from six.moves import http_client
 | |
| 
 | |
| from mod_pywebsocket import common
 | |
| from mod_pywebsocket import dispatch
 | |
| from mod_pywebsocket import handshake
 | |
| from mod_pywebsocket import http_header_util
 | |
| from mod_pywebsocket import memorizingfile
 | |
| from mod_pywebsocket import util
 | |
| 
 | |
| # 1024 is practically large enough to contain WebSocket handshake lines.
 | |
| _MAX_MEMORIZED_LINES = 1024
 | |
| 
 | |
| 
 | |
| class _StandaloneConnection(object):
 | |
|     """Mimic mod_python mp_conn."""
 | |
|     def __init__(self, request_handler):
 | |
|         """Construct an instance.
 | |
| 
 | |
|         Args:
 | |
|             request_handler: A WebSocketRequestHandler instance.
 | |
|         """
 | |
| 
 | |
|         self._request_handler = request_handler
 | |
| 
 | |
|     def get_local_addr(self):
 | |
|         """Getter to mimic mp_conn.local_addr."""
 | |
| 
 | |
|         return (self._request_handler.server.server_name,
 | |
|                 self._request_handler.server.server_port)
 | |
| 
 | |
|     local_addr = property(get_local_addr)
 | |
| 
 | |
|     def get_remote_addr(self):
 | |
|         """Getter to mimic mp_conn.remote_addr.
 | |
| 
 | |
|         Setting the property in __init__ won't work because the request
 | |
|         handler is not initialized yet there."""
 | |
| 
 | |
|         return self._request_handler.client_address
 | |
| 
 | |
|     remote_addr = property(get_remote_addr)
 | |
| 
 | |
|     def write(self, data):
 | |
|         """Mimic mp_conn.write()."""
 | |
| 
 | |
|         return self._request_handler.wfile.write(data)
 | |
| 
 | |
|     def read(self, length):
 | |
|         """Mimic mp_conn.read()."""
 | |
| 
 | |
|         return self._request_handler.rfile.read(length)
 | |
| 
 | |
|     def get_memorized_lines(self):
 | |
|         """Get memorized lines."""
 | |
| 
 | |
|         return self._request_handler.rfile.get_memorized_lines()
 | |
| 
 | |
| 
 | |
| class _StandaloneRequest(object):
 | |
|     """Mimic mod_python request."""
 | |
|     def __init__(self, request_handler, use_tls):
 | |
|         """Construct an instance.
 | |
| 
 | |
|         Args:
 | |
|             request_handler: A WebSocketRequestHandler instance.
 | |
|         """
 | |
| 
 | |
|         self._logger = util.get_class_logger(self)
 | |
| 
 | |
|         self._request_handler = request_handler
 | |
|         self.connection = _StandaloneConnection(request_handler)
 | |
|         self._use_tls = use_tls
 | |
|         self.headers_in = request_handler.headers
 | |
| 
 | |
|     def get_uri(self):
 | |
|         """Getter to mimic request.uri.
 | |
| 
 | |
|         This method returns the raw data at the Request-URI part of the
 | |
|         Request-Line, while the uri method on the request object of mod_python
 | |
|         returns the path portion after parsing the raw data. This behavior is
 | |
|         kept for compatibility.
 | |
|         """
 | |
| 
 | |
|         return self._request_handler.path
 | |
| 
 | |
|     uri = property(get_uri)
 | |
| 
 | |
|     def get_unparsed_uri(self):
 | |
|         """Getter to mimic request.unparsed_uri."""
 | |
| 
 | |
|         return self._request_handler.path
 | |
| 
 | |
|     unparsed_uri = property(get_unparsed_uri)
 | |
| 
 | |
|     def get_method(self):
 | |
|         """Getter to mimic request.method."""
 | |
| 
 | |
|         return self._request_handler.command
 | |
| 
 | |
|     method = property(get_method)
 | |
| 
 | |
|     def get_protocol(self):
 | |
|         """Getter to mimic request.protocol."""
 | |
| 
 | |
|         return self._request_handler.request_version
 | |
| 
 | |
|     protocol = property(get_protocol)
 | |
| 
 | |
|     def is_https(self):
 | |
|         """Mimic request.is_https()."""
 | |
| 
 | |
|         return self._use_tls
 | |
| 
 | |
| 
 | |
| class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
 | |
|     """CGIHTTPRequestHandler specialized for WebSocket."""
 | |
| 
 | |
|     # Use httplib.HTTPMessage instead of mimetools.Message.
 | |
|     MessageClass = http_client.HTTPMessage
 | |
| 
 | |
|     def setup(self):
 | |
|         """Override SocketServer.StreamRequestHandler.setup to wrap rfile
 | |
|         with MemorizingFile.
 | |
| 
 | |
|         This method will be called by BaseRequestHandler's constructor
 | |
|         before calling BaseHTTPRequestHandler.handle.
 | |
|         BaseHTTPRequestHandler.handle will call
 | |
|         BaseHTTPRequestHandler.handle_one_request and it will call
 | |
|         WebSocketRequestHandler.parse_request.
 | |
|         """
 | |
| 
 | |
|         # Call superclass's setup to prepare rfile, wfile, etc. See setup
 | |
|         # definition on the root class SocketServer.StreamRequestHandler to
 | |
|         # understand what this does.
 | |
|         CGIHTTPServer.CGIHTTPRequestHandler.setup(self)
 | |
| 
 | |
|         self.rfile = memorizingfile.MemorizingFile(
 | |
|             self.rfile, max_memorized_lines=_MAX_MEMORIZED_LINES)
 | |
| 
 | |
|     def __init__(self, request, client_address, server):
 | |
|         self._logger = util.get_class_logger(self)
 | |
| 
 | |
|         self._options = server.websocket_server_options
 | |
| 
 | |
|         # Overrides CGIHTTPServerRequestHandler.cgi_directories.
 | |
|         self.cgi_directories = self._options.cgi_directories
 | |
|         # Replace CGIHTTPRequestHandler.is_executable method.
 | |
|         if self._options.is_executable_method is not None:
 | |
|             self.is_executable = self._options.is_executable_method
 | |
| 
 | |
|         # This actually calls BaseRequestHandler.__init__.
 | |
|         CGIHTTPServer.CGIHTTPRequestHandler.__init__(self, request,
 | |
|                                                      client_address, server)
 | |
| 
 | |
|     def parse_request(self):
 | |
|         """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
 | |
| 
 | |
|         Return True to continue processing for HTTP(S), False otherwise.
 | |
| 
 | |
|         See BaseHTTPRequestHandler.handle_one_request method which calls
 | |
|         this method to understand how the return value will be handled.
 | |
|         """
 | |
| 
 | |
|         # We hook parse_request method, but also call the original
 | |
|         # CGIHTTPRequestHandler.parse_request since when we return False,
 | |
|         # CGIHTTPRequestHandler.handle_one_request continues processing and
 | |
|         # it needs variables set by CGIHTTPRequestHandler.parse_request.
 | |
|         #
 | |
|         # Variables set by this method will be also used by WebSocket request
 | |
|         # handling (self.path, self.command, self.requestline, etc. See also
 | |
|         # how _StandaloneRequest's members are implemented using these
 | |
|         # attributes).
 | |
|         if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self):
 | |
|             return False
 | |
| 
 | |
|         if self._options.use_basic_auth:
 | |
|             auth = self.headers.get('Authorization')
 | |
|             if auth != self._options.basic_auth_credential:
 | |
|                 self.send_response(401)
 | |
|                 self.send_header('WWW-Authenticate',
 | |
|                                  'Basic realm="Pywebsocket"')
 | |
|                 self.end_headers()
 | |
|                 self._logger.info('Request basic authentication')
 | |
|                 return False
 | |
| 
 | |
|         host, port, resource = http_header_util.parse_uri(self.path)
 | |
|         if resource is None:
 | |
|             self._logger.info('Invalid URI: %r', self.path)
 | |
|             self._logger.info('Fallback to CGIHTTPRequestHandler')
 | |
|             return True
 | |
|         server_options = self.server.websocket_server_options
 | |
|         if host is not None:
 | |
|             validation_host = server_options.validation_host
 | |
|             if validation_host is not None and host != validation_host:
 | |
|                 self._logger.info('Invalid host: %r (expected: %r)', host,
 | |
|                                   validation_host)
 | |
|                 self._logger.info('Fallback to CGIHTTPRequestHandler')
 | |
|                 return True
 | |
|         if port is not None:
 | |
|             validation_port = server_options.validation_port
 | |
|             if validation_port is not None and port != validation_port:
 | |
|                 self._logger.info('Invalid port: %r (expected: %r)', port,
 | |
|                                   validation_port)
 | |
|                 self._logger.info('Fallback to CGIHTTPRequestHandler')
 | |
|                 return True
 | |
|         self.path = resource
 | |
| 
 | |
|         request = _StandaloneRequest(self, self._options.use_tls)
 | |
| 
 | |
|         try:
 | |
|             # Fallback to default http handler for request paths for which
 | |
|             # we don't have request handlers.
 | |
|             if not self._options.dispatcher.get_handler_suite(self.path):
 | |
|                 self._logger.info('No handler for resource: %r', self.path)
 | |
|                 self._logger.info('Fallback to CGIHTTPRequestHandler')
 | |
|                 return True
 | |
|         except dispatch.DispatchException as e:
 | |
|             self._logger.info('Dispatch failed for error: %s', e)
 | |
|             self.send_error(e.status)
 | |
|             return False
 | |
| 
 | |
|         # If any Exceptions without except clause setup (including
 | |
|         # DispatchException) is raised below this point, it will be caught
 | |
|         # and logged by WebSocketServer.
 | |
| 
 | |
|         try:
 | |
|             try:
 | |
|                 handshake.do_handshake(request, self._options.dispatcher)
 | |
|             except handshake.VersionException as e:
 | |
|                 self._logger.info('Handshake failed for version error: %s', e)
 | |
|                 self.send_response(common.HTTP_STATUS_BAD_REQUEST)
 | |
|                 self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER,
 | |
|                                  e.supported_versions)
 | |
|                 self.end_headers()
 | |
|                 return False
 | |
|             except handshake.HandshakeException as e:
 | |
|                 # Handshake for ws(s) failed.
 | |
|                 self._logger.info('Handshake failed for error: %s', e)
 | |
|                 self.send_error(e.status)
 | |
|                 return False
 | |
| 
 | |
|             request._dispatcher = self._options.dispatcher
 | |
|             self._options.dispatcher.transfer_data(request)
 | |
|         except handshake.AbortedByUserException as e:
 | |
|             self._logger.info('Aborted: %s', e)
 | |
|         return False
 | |
| 
 | |
|     def log_request(self, code='-', size='-'):
 | |
|         """Override BaseHTTPServer.log_request."""
 | |
| 
 | |
|         self._logger.info('"%s" %s %s', self.requestline, str(code), str(size))
 | |
| 
 | |
|     def log_error(self, *args):
 | |
|         """Override BaseHTTPServer.log_error."""
 | |
| 
 | |
|         # Despite the name, this method is for warnings than for errors.
 | |
|         # For example, HTTP status code is logged by this method.
 | |
|         self._logger.warning('%s - %s', self.address_string(),
 | |
|                              args[0] % args[1:])
 | |
| 
 | |
|     def is_cgi(self):
 | |
|         """Test whether self.path corresponds to a CGI script.
 | |
| 
 | |
|         Add extra check that self.path doesn't contains ..
 | |
|         Also check if the file is a executable file or not.
 | |
|         If the file is not executable, it is handled as static file or dir
 | |
|         rather than a CGI script.
 | |
|         """
 | |
| 
 | |
|         if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
 | |
|             if '..' in self.path:
 | |
|                 return False
 | |
|             # strip query parameter from request path
 | |
|             resource_name = self.path.split('?', 2)[0]
 | |
|             # convert resource_name into real path name in filesystem.
 | |
|             scriptfile = self.translate_path(resource_name)
 | |
|             if not os.path.isfile(scriptfile):
 | |
|                 return False
 | |
|             if not self.is_executable(scriptfile):
 | |
|                 return False
 | |
|             return True
 | |
|         return False
 | |
| 
 | |
| 
 | |
| # vi:sts=4 sw=4 et
 | 
