From 4b6ac1f4c4dd9c9965b78e58681ff3a9e5c0ff04 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Thu, 2 Mar 2023 21:53:27 +0000 Subject: [PATCH] [WebUI] Fix setting base path Simplify the code for setting the base path, both via headers and config. Replaced putchild since it is not recommended for dynamic paths and overriding getChildWithDefault provides a proper solution. This also fixes recursive base path problem with previous code where appending base paths to URL would still return Deluge web e.g. http://localhost:8112/deluge/deluge/deluge Removed getChild override by consolidating empty path conditional to getChildWithDefault. This simplifies and combines the returning of TopLevel resource for root path or base path. Added workaround for test logfile error with forwardslash in filename --- deluge/tests/common.py | 2 +- deluge/tests/test_webserver.py | 54 ++++++++++++++++++++++++++++++++++ deluge/ui/web/server.py | 20 ++++++------- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/deluge/tests/common.py b/deluge/tests/common.py index b5941568d..0e82bf12b 100644 --- a/deluge/tests/common.py +++ b/deluge/tests/common.py @@ -113,7 +113,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol): self.shutdown_func = shutdown_func self.log_output = '' self.stderr_out = '' - self.logfile = logfile + self.logfile = logfile.replace('/', '_') if logfile else None self.print_stdout = print_stdout self.print_stderr = print_stderr self.quit_d = None diff --git a/deluge/tests/test_webserver.py b/deluge/tests/test_webserver.py index 69a424e77..683ab97a1 100644 --- a/deluge/tests/test_webserver.py +++ b/deluge/tests/test_webserver.py @@ -9,6 +9,7 @@ import json as json_lib from io import BytesIO +import pytest import pytest_twisted import twisted.web.client from twisted.internet import reactor @@ -57,3 +58,56 @@ class TestWebServer(WebServerTestBase, WebServerMockBase): print('aoeu') assert json['error'] is None assert 'torrent_filehash' == json['result']['name'] + + @pytest.mark.parametrize('base', ['', '/', 'deluge']) + @pytest_twisted.ensureDeferred + async def test_base_with_config(self, base): + agent = Agent(reactor) + root_url = f'http://127.0.0.1:{self.deluge_web.port}' + base_url = f'{root_url}/{base}' + + self.deluge_web.base = base + + response = await agent.request(b'GET', root_url.encode()) + assert response.code == 200 + body = await twisted.web.client.readBody(response) + assert 'Deluge WebUI' in body.decode() + + response = await agent.request(b'GET', base_url.encode()) + assert response.code == 200 + + @pytest.mark.parametrize('base', ['/', 'deluge']) + @pytest_twisted.ensureDeferred + async def test_base_with_config_recurring_basepath(self, base): + agent = Agent(reactor) + base_url = f'http://127.0.0.1:{self.deluge_web.port}/{base}' + + self.deluge_web.base = base + + response = await agent.request(b'GET', base_url.encode()) + assert response.code == 200 + + recursive_url = f'{base_url}/{base}' + response = await agent.request(b'GET', recursive_url.encode()) + assert response.code == 404 if base.strip('/') else 200 + + recursive_url = f'{recursive_url}/{base}' + response = await agent.request(b'GET', recursive_url.encode()) + assert response.code == 404 if base.strip('/') else 200 + + @pytest_twisted.ensureDeferred + async def test_base_with_deluge_header(self): + """Ensure base path is set and HTML contains path""" + agent = Agent(reactor) + base = 'deluge' + url = f'http://127.0.0.1:{self.deluge_web.port}' + headers = Headers({'X-Deluge-Base': [base]}) + + response = await agent.request(b'GET', url.encode(), headers) + body = await twisted.web.client.readBody(response) + assert f'href="/{base}/' in body.decode() + + # Header only changes HTML base path so ensure no resource at server path + url = f'{url}/{base}' + response = await agent.request(b'GET', url.encode(), headers) + assert response.code == 404 diff --git a/deluge/ui/web/server.py b/deluge/ui/web/server.py index 06b25923b..fe563f130 100644 --- a/deluge/ui/web/server.py +++ b/deluge/ui/web/server.py @@ -569,18 +569,20 @@ class TopLevel(resource.Resource): self.__scripts.remove(script) self.__debug_scripts.remove(script) - def getChild(self, path, request): # NOQA: N802 - if not path: - return self - else: - return super().getChild(path, request) - def getChildWithDefault(self, path, request): # NOQA: N802 # Calculate the request base header = request.getHeader('x-deluge-base') - base = header if header else component.get('DelugeWeb').base + config_base = component.get('DelugeWeb').base + base = header if header else config_base + + first_request = not hasattr(request, 'base') request.base = absolute_base_url(base).encode() + base_resource = first_request and path.decode() == config_base.strip('/') + + if not path or base_resource: + return self + return super().getChildWithDefault(path, request) def render(self, request): @@ -680,10 +682,6 @@ class DelugeWeb(component.Component): elif options.no_ssl: self.https = False - if self.base != '/': - # Strip away slashes and serve on the base path as well as root path - self.top_level.putChild(self.base.strip('/').encode(), self.top_level) - setup_translation() # Remove twisted version number from 'server' http-header for security reasons