def test_man_in_the_middle_plugin(self) -> None: request = build_http_request(b'GET', b'/', headers={ b'Host': b'uni.corn', }) self.client_ssl_connection.recv.return_value = request # Client read self.protocol_handler.run_once() self.server.queue.assert_called_once_with(request) # Server write self.protocol_handler.run_once() self.server.flush.assert_called_once() # Server read self.server.recv.return_value = \ build_http_response( httpStatusCodes.OK, reason=b'OK', body=b'Original Response From Upstream') self.protocol_handler.run_once() self.assertEqual( self.protocol_handler.client.buffer[0].tobytes(), build_http_response(httpStatusCodes.OK, reason=b'OK', body=b'Hello from man in the middle'))
def test_build_response(self) -> None: self.assertEqual( build_http_response(200, reason=b'OK', protocol_version=b'HTTP/1.1'), CRLF.join([b'HTTP/1.1 200 OK', CRLF])) self.assertEqual( build_http_response(200, reason=b'OK', protocol_version=b'HTTP/1.1', headers={b'key': b'value'}), CRLF.join([b'HTTP/1.1 200 OK', b'key: value', CRLF]))
def test_body_response(self) -> None: e = HttpRequestRejected( status_code=httpStatusCodes.NOT_FOUND, reason=b'NOT FOUND', body=b'Nothing here') self.assertEqual( e.response(self.request), build_http_response(httpStatusCodes.NOT_FOUND, reason=b'NOT FOUND', body=b'Nothing here'))
async def test_filter_by_upstream_host_plugin(self) -> None: request = build_http_request( b'GET', b'http://facebook.com/', headers={ b'Host': b'facebook.com', }, ) self._conn.recv.return_value = request self.mock_selector.return_value.select.side_effect = [ [( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], ] await self.protocol_handler._run_once() self.mock_server_conn.assert_not_called() self.assertEqual( self.protocol_handler.work.buffer[0], build_http_response( status_code=httpStatusCodes.I_AM_A_TEAPOT, reason=b'I\'m a tea pot', conn_close=True, ), )
async def test_shortlink_plugin(self) -> None: request = build_http_request( b'GET', b'http://t/', headers={ b'Host': b't', }, ) self._conn.recv.return_value = request self.mock_selector.return_value.select.side_effect = [ [( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], ] await self.protocol_handler._run_once() self.assertEqual( self.protocol_handler.work.buffer[0].tobytes(), build_http_response( status_code=httpStatusCodes.SEE_OTHER, reason=b'See Other', headers={ b'Location': b'http://twitter.com/', }, conn_close=True, ), )
def test_proposed_rest_api_plugin(self, mock_server_conn: mock.Mock) -> None: path = b'/v1/users/' self._conn.recv.return_value = build_http_request( b'GET', b'http://%s%s' % (ProposedRestApiPlugin.API_SERVER, path), headers={ b'Host': ProposedRestApiPlugin.API_SERVER, }) self.mock_selector.return_value.select.side_effect = [ [(selectors.SelectorKey(fileobj=self._conn, fd=self._conn.fileno, events=selectors.EVENT_READ, data=None), selectors.EVENT_READ)], ] self.protocol_handler.run_once() mock_server_conn.assert_not_called() self.assertEqual( self.protocol_handler.client.buffer[0].tobytes(), build_http_response( httpStatusCodes.OK, reason=b'OK', headers={b'Content-Type': b'application/json'}, body=bytes_( json.dumps(ProposedRestApiPlugin.REST_API_SPEC[path]))))
async def test_filter_by_url_regex_plugin(self) -> None: request = build_http_request( b'GET', b'http://www.facebook.com/tr/', headers={ b'Host': b'www.facebook.com', }, ) self._conn.recv.return_value = request self.mock_selector.return_value.select.side_effect = [ [( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], ] await self.protocol_handler._run_once() self.assertEqual( self.protocol_handler.work.buffer[0], build_http_response( status_code=httpStatusCodes.NOT_FOUND, reason=b'Blocked', conn_close=True, ), )
def handle_mc_listmaps(self): # A list of maps has been requested, return an empty list self.client.queue( memoryview( build_http_response(status_code=200, body=b'-;-;-;-;-', headers={b'Connection': b'close'})))
def test_static_web_server_serves(self, mock_fromfd: mock.Mock, mock_selector: mock.Mock) -> None: # Setup a static directory static_server_dir = os.path.join(tempfile.gettempdir(), 'static') index_file_path = os.path.join(static_server_dir, 'index.html') html_file_content = b'''<html><head></head><body><h1>Proxy.py Testing</h1></body></html>''' os.makedirs(static_server_dir, exist_ok=True) with open(index_file_path, 'wb') as f: f.write(html_file_content) self._conn = mock_fromfd.return_value self._conn.recv.return_value = build_http_request( b'GET', b'/index.html') mock_selector.return_value.select.side_effect = [ [(selectors.SelectorKey(fileobj=self._conn, fd=self._conn.fileno, events=selectors.EVENT_READ, data=None), selectors.EVENT_READ)], [(selectors.SelectorKey(fileobj=self._conn, fd=self._conn.fileno, events=selectors.EVENT_WRITE, data=None), selectors.EVENT_WRITE)], ] flags = Flags(enable_static_server=True, static_server_dir=static_server_dir) flags.plugins = Flags.load_plugins( b'proxy.http.proxy.HttpProxyPlugin,proxy.http.server.HttpWebServerPlugin' ) self.protocol_handler = HttpProtocolHandler(TcpClientConnection( self._conn, self._addr), flags=flags) self.protocol_handler.initialize() self.protocol_handler.run_once() self.protocol_handler.run_once() self.assertEqual(mock_selector.return_value.select.call_count, 2) self.assertEqual(self._conn.send.call_count, 1) encoded_html_file_content = gzip.compress(html_file_content) self.assertEqual( self._conn.send.call_args[0][0], build_http_response(200, reason=b'OK', headers={ b'Content-Type': b'text/html', b'Cache-Control': b'max-age=86400', b'Content-Encoding': b'gzip', b'Connection': b'close', b'Content-Length': bytes_(len(encoded_html_file_content)), }, body=encoded_html_file_content))
def test_pipelined_response_parse(self) -> None: response = build_http_response( httpStatusCodes.OK, reason=b'OK', headers={b'Content-Length': b'15'}, body=b'{"key":"value"}', ) self.assert_pipeline_response(response)
def handle_mc_auth(self): # Old Minecraft wants to authenticate, it is enough to reply with a 0 print('Authentication requested') self.client.queue( memoryview( build_http_response(status_code=200, body=b'0', headers={b'Connection': b'close'})))
def handle_client_request(self, request: HttpParser) -> Optional[HttpParser]: if request.host != self.API_SERVER: return request assert request.path if request.path in self.REST_API_SPEC: self.client.queue(memoryview(build_http_response( httpStatusCodes.OK, reason=b'OK', headers={b'Content-Type': b'application/json'}, body=bytes_(json.dumps( self.REST_API_SPEC[request.path])) ))) else: self.client.queue(memoryview(build_http_response( httpStatusCodes.NOT_FOUND, reason=b'NOT FOUND', body=b'Not Found' ))) return None
def test_pipelined_chunked_response_parse(self) -> None: response = build_http_response( httpStatusCodes.OK, reason=b'OK', headers={ b'Transfer-Encoding': b'chunked', b'Content-Type': b'application/json', }, body=b'f\r\n{"key":"value"}\r\n0\r\n\r\n') self.assert_pipeline_response(response)
class HttpsConnectTunnelHandler(BaseTcpTunnelHandler): """A https CONNECT tunnel.""" PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT = memoryview( build_http_response(httpStatusCodes.OK, reason=b'Connection established')) PROXY_TUNNEL_UNSUPPORTED_SCHEME = memoryview( build_http_response(httpStatusCodes.BAD_REQUEST, headers={b'Connection': b'close'}, reason=b'Unsupported protocol scheme')) def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) def handle_data(self, data: memoryview) -> Optional[bool]: # Queue for upstream if connection has been established if self.upstream and self.upstream._conn is not None: self.upstream.queue(data) return None # Parse client request self.request.parse(data) # Drop the request if not a CONNECT request if self.request.method != httpMethods.CONNECT: self.client.queue( HttpsConnectTunnelHandler.PROXY_TUNNEL_UNSUPPORTED_SCHEME) return True # CONNECT requests are short and we need not worry about # receiving partial request bodies here. assert self.request.state == httpParserStates.COMPLETE # Establish connection with upstream self.connect_upstream() # Queue tunnel established response to client self.client.queue( HttpsConnectTunnelHandler.PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT) return None
def test_build_response_adds_content_length_header(self) -> None: body = b'Hello world!!!' self.assertEqual( build_http_response(200, reason=b'OK', protocol_version=b'HTTP/1.1', headers={b'key': b'value'}, body=body), CRLF.join([ b'HTTP/1.1 200 OK', b'key: value', b'Content-Length: ' + bytes_(len(body)), CRLF ]) + body)
def test_pac_file_served_from_buffer( self, mock_fromfd: mock.Mock, mock_selector: mock.Mock) -> None: self._conn = mock_fromfd.return_value self.mock_selector_for_client_read(mock_selector) pac_file_content = b'function FindProxyForURL(url, host) { return "PROXY localhost:8899; DIRECT"; }' self.init_and_make_pac_file_request(text_(pac_file_content)) self.protocol_handler.run_once() self.assertEqual( self.protocol_handler.request.state, httpParserStates.COMPLETE) self._conn.send.called_once_with(build_http_response( 200, reason=b'OK', headers={ b'Content-Type': b'application/x-ns-proxy-autoconfig', b'Connection': b'close' }, body=pac_file_content ))
async def test_pac_file_served_from_disk(self) -> None: await self.protocol_handler._run_once() self.assertEqual( self.protocol_handler.request.state, httpParserStates.COMPLETE, ) self._conn.send.called_once_with( build_http_response( 200, reason=b'OK', headers={ b'Content-Type': b'application/x-ns-proxy-autoconfig', }, body=self.expected_response, conn_close=True, ), )
def test_with_proxy(self) -> None: """Makes a HTTP request to in-build web server via proxy server.""" with socket_connection(('localhost', self.PROXY_PORT)) as conn: conn.send( build_http_request(httpMethods.GET, b'http://localhost:%d/' % self.PROXY_PORT, headers={ b'Host': b'localhost:%d' % self.PROXY_PORT, })) response = conn.recv(DEFAULT_CLIENT_RECVBUF_SIZE) self.assertEqual( response, build_http_response(httpStatusCodes.NOT_FOUND, reason=b'NOT FOUND', headers={ b'Server': PROXY_AGENT_HEADER_VALUE, b'Connection': b'close' }))
def test_pac_file_served_from_disk(self, mock_fromfd: mock.Mock, mock_selector: mock.Mock) -> None: pac_file = os.path.join(os.path.dirname(PROXY_PY_DIR), 'helper', 'proxy.pac') self._conn = mock_fromfd.return_value self.mock_selector_for_client_read(mock_selector) self.init_and_make_pac_file_request(pac_file) self.protocol_handler.run_once() self.assertEqual(self.protocol_handler.request.state, httpParserStates.COMPLETE) with open(pac_file, 'rb') as f: self._conn.send.called_once_with( build_http_response(200, reason=b'OK', headers={ b'Content-Type': b'application/x-ns-proxy-autoconfig', b'Connection': b'close' }, body=f.read()))
def handle_mc_res(self, request: HttpParser): # Resources have been requested print('Resources requested: {}'.format(request.path)) if request.path == b'/resources/': try: new_resources = get_mc_resources() resources_old_format = convert_mc_resources_to_old_format( new_resources) self.client.queue( memoryview( build_http_response(status_code=200, body=resources_old_format.encode(), headers={b'Connection': b'close'}))) except RuntimeError as e: print('RuntimeError while getting resources: {}'.format(e)) except Exception as e: print('Exception while getting resources: {}'.format(e)) else: print( 'Individual resource passthrough is not currently supported!')
def test_filter_by_url_regex_plugin(self, mock_server_conn: mock.Mock) -> None: request = build_http_request(b'GET', b'http://www.facebook.com/tr/', headers={ b'Host': b'www.facebook.com', }) self._conn.recv.return_value = request self.mock_selector.return_value.select.side_effect = [ [(selectors.SelectorKey(fileobj=self._conn, fd=self._conn.fileno, events=selectors.EVENT_READ, data=None), selectors.EVENT_READ)], ] self.protocol_handler.run_once() self.assertEqual( self.protocol_handler.client.buffer[0].tobytes(), build_http_response( status_code=httpStatusCodes.NOT_FOUND, reason=b'Blocked', headers={b'Connection': b'close'}, ))
def handle_mc_skin(self, request: HttpParser, cloak: bool, query: bool): # A skin has been requested, get the username from the URL path if not cloak: print('Skin requested: {}'.format(request.path.decode())) subtype = 'SKIN' else: print('Cloak requested: {}'.format(request.path.decode())) subtype = 'CAPE' # 'not-query' form is a static file (or an imitation of one) named after the user, 'query' form ends in ?user=<username> if not query: username_start = request.path.rfind(b'/') + 1 username_end = request.path.find(b'.') username = request.path[username_start:username_end] else: username_start = request.path.rfind(b'=') + 1 username = request.path[username_start:] username = username.decode() print('Got player: {}'.format(username)) try: # Try to get the player UUID from their username player_uuid = get_mc_uuid_from_username(username) print('Got player UUID: {}'.format(player_uuid)) # Then, we can grab their skin data and send it as the response skin = get_mc_player_skin_from_uuid(player_uuid, subtype) self.client.queue( memoryview( build_http_response(status_code=200, body=skin, headers={b'Connection': b'close'}))) except RuntimeError as e: print('RuntimeError while getting player skin: {}'.format(e)) except Exception as e: print('Exception while getting player skin: {}'.format(e))
def test_filter_by_upstream_host_plugin( self, mock_server_conn: mock.Mock) -> None: request = build_http_request(b'GET', b'http://google.com/', headers={ b'Host': b'google.com', }) self._conn.recv.return_value = request self.mock_selector.return_value.select.side_effect = [ [(selectors.SelectorKey(fileobj=self._conn, fd=self._conn.fileno, events=selectors.EVENT_READ, data=None), selectors.EVENT_READ)], ] self.protocol_handler.run_once() mock_server_conn.assert_not_called() self.assertEqual( self.protocol_handler.client.buffer[0].tobytes(), build_http_response( status_code=httpStatusCodes.I_AM_A_TEAPOT, reason=b'I\'m a tea pot', headers={b'Connection': b'close'}, ))
async def test_man_in_the_middle_plugin(self) -> None: request = build_http_request( b'GET', b'http://super.secure/', headers={ b'Host': b'super.secure', }, no_ua=True, ) self._conn.recv.return_value = request server = self.mock_server_conn.return_value server.connect.return_value = True def has_buffer() -> bool: return cast(bool, server.queue.called) def closed() -> bool: return not server.connect.called server.has_buffer.side_effect = has_buffer type(server).closed = mock.PropertyMock(side_effect=closed) self.mock_selector.return_value.select.side_effect = [ [( selectors.SelectorKey( fileobj=self._conn.fileno(), fd=self._conn.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], [( selectors.SelectorKey( fileobj=server.connection.fileno(), fd=server.connection.fileno(), events=selectors.EVENT_WRITE, data=None, ), selectors.EVENT_WRITE, )], [( selectors.SelectorKey( fileobj=server.connection.fileno(), fd=server.connection.fileno(), events=selectors.EVENT_READ, data=None, ), selectors.EVENT_READ, )], ] # Client read await self.protocol_handler._run_once() self.mock_server_conn.assert_called_with( 'super.secure', DEFAULT_HTTP_PORT, ) server.connect.assert_called_once() queued_request = \ build_http_request( b'GET', b'/', headers={ b'Host': b'super.secure', b'Via': b'1.1 %s' % PROXY_AGENT_HEADER_VALUE, }, no_ua=True, ) server.queue.assert_called_once() print(server.queue.call_args_list[0][0][0].tobytes()) print(queued_request) self.assertEqual(server.queue.call_args_list[0][0][0], queued_request) # Server write await self.protocol_handler._run_once() server.flush.assert_called_once() # Server read server.recv.return_value = \ build_http_response( httpStatusCodes.OK, reason=b'OK', body=b'Original Response From Upstream', ) await self.protocol_handler._run_once() response = HttpParser(httpParserTypes.RESPONSE_PARSER) response.parse(self.protocol_handler.work.buffer[0]) assert response.body self.assertEqual( gzip.decompress(response.body), b'Hello from man in the middle', )
def test_man_in_the_middle_plugin(self, mock_server_conn: mock.Mock) -> None: request = build_http_request(b'GET', b'http://super.secure/', headers={ b'Host': b'super.secure', }) self._conn.recv.return_value = request server = mock_server_conn.return_value server.connect.return_value = True def has_buffer() -> bool: return cast(bool, server.queue.called) def closed() -> bool: return not server.connect.called server.has_buffer.side_effect = has_buffer type(server).closed = mock.PropertyMock(side_effect=closed) self.mock_selector.return_value.select.side_effect = [ [(selectors.SelectorKey(fileobj=self._conn, fd=self._conn.fileno, events=selectors.EVENT_READ, data=None), selectors.EVENT_READ)], [(selectors.SelectorKey(fileobj=server.connection, fd=server.connection.fileno, events=selectors.EVENT_WRITE, data=None), selectors.EVENT_WRITE)], [(selectors.SelectorKey(fileobj=server.connection, fd=server.connection.fileno, events=selectors.EVENT_READ, data=None), selectors.EVENT_READ)], ] # Client read self.protocol_handler.run_once() mock_server_conn.assert_called_with('super.secure', DEFAULT_HTTP_PORT) server.connect.assert_called_once() queued_request = \ build_http_request( b'GET', b'/', headers={ b'Host': b'super.secure', b'Via': b'1.1 %s' % PROXY_AGENT_HEADER_VALUE } ) server.queue.assert_called_once_with(queued_request) # Server write self.protocol_handler.run_once() server.flush.assert_called_once() # Server read server.recv.return_value = \ build_http_response( httpStatusCodes.OK, reason=b'OK', body=b'Original Response From Upstream') self.protocol_handler.run_once() self.assertEqual( self.protocol_handler.client.buffer[0].tobytes(), build_http_response(httpStatusCodes.OK, reason=b'OK', body=b'Hello from man in the middle'))