From 660059682693ac0e5a3d9a556f0836349ffec265 Mon Sep 17 00:00:00 2001 From: drew2a Date: Thu, 20 Jan 2022 12:21:35 +0100 Subject: [PATCH 1/5] Revert an ability to run Tribler with torrent argument --- src/run_tribler.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/run_tribler.py b/src/run_tribler.py index bdaf61421f5..8532acee94a 100644 --- a/src/run_tribler.py +++ b/src/run_tribler.py @@ -25,6 +25,7 @@ class RunTriblerArgsParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): kwargs['description'] = 'Run Tribler BitTorrent client' super().__init__(*args, **kwargs) + self.add_argument('torrent', help='torrent file to download', default='', nargs='?') self.add_argument('--core', action="store_true") self.add_argument('--gui-test-mode', action="store_true") @@ -65,6 +66,7 @@ def init_boot_logger(): init_sentry_reporter() parsed_args = RunTriblerArgsParser().parse_args() + logger.info(f'Run Tribler: {parsed_args}') # Get root state directory (e.g. from environment variable or from system default) from tribler_common.osutils import get_root_state_directory @@ -144,12 +146,12 @@ def init_boot_logger(): app.installTranslator(translator) if app.is_running(): - logger.info('Application is running') - for arg in sys.argv[1:]: - if os.path.exists(arg) and arg.endswith(".torrent"): - app.send_message(f"file:{arg}") - elif arg.startswith('magnet'): - app.send_message(arg) + logger.info('GUI Application is running') + if torrent := parsed_args.torrent: + if os.path.exists(torrent) and torrent.endswith(".torrent"): + app.send_message(f"file:{torrent}") + elif torrent.startswith('magnet'): + app.send_message(torrent) sys.exit(1) From 3294ec3dcbf5a7648289163acc9a56f6c9a06b24 Mon Sep 17 00:00:00 2001 From: drew2a Date: Thu, 20 Jan 2022 13:06:45 +0100 Subject: [PATCH 2/5] Extract start_gui and start_core from run_tribler --- src/run_tribler.py | 117 ++---------------- src/tribler-core/tribler_core/start_core.py | 31 ++++- .../tribler_core/tests/test_start_core.py | 4 +- src/tribler-gui/tribler_gui/start_gui.py | 90 ++++++++++++++ 4 files changed, 129 insertions(+), 113 deletions(-) create mode 100644 src/tribler-gui/tribler_gui/start_gui.py diff --git a/src/run_tribler.py b/src/run_tribler.py index 8532acee94a..d32ac06e170 100644 --- a/src/run_tribler.py +++ b/src/run_tribler.py @@ -2,17 +2,15 @@ # A fix for "LookupError: unknown encoding: idna" error. # Adding encodings.idna to hiddenimports is not enough. # https://github.com/pyinstaller/pyinstaller/issues/1113 +# noinspection PyUnresolvedReferences +import encodings.idna # pylint: disable=unused-import import logging.config import os import sys -from tribler_common.logger import load_logger_config -from tribler_common.process_checker import ProcessChecker from tribler_common.sentry_reporter.sentry_reporter import SentryStrategy from tribler_common.sentry_reporter.sentry_scrubber import SentryScrubber -from tribler_common.version_manager import VersionHistory -from tribler_core import start_core from tribler_core.components.reporter.exception_handler import default_core_exception_handler logger = logging.getLogger(__name__) @@ -79,109 +77,10 @@ def init_boot_logger(): # Check whether we need to start the core or the user interface if parsed_args.core: - from tribler_core.check_os import should_kill_other_tribler_instances - - should_kill_other_tribler_instances(root_state_dir) - logger.info('Running Core' + ' in gui_test_mode' if parsed_args.gui_test_mode else '') - load_logger_config('tribler-core', root_state_dir) - - # Check if we are already running a Tribler instance - process_checker = ProcessChecker(root_state_dir) - if process_checker.already_running: - logger.info('Core is already running, exiting') - sys.exit(1) - process_checker.create_lock_file() - version_history = VersionHistory(root_state_dir) - state_dir = version_history.code_version.directory - try: - start_core.run_tribler_core(api_port, api_key, state_dir, gui_test_mode=parsed_args.gui_test_mode) - finally: - logger.info('Remove lock file') - process_checker.remove_lock_file() + from tribler_core.start_core import run_core - else: - from PyQt5.QtCore import QSettings - from tribler_gui.utilities import get_translator - - logger.info('Running GUI' + ' in gui_test_mode' if parsed_args.gui_test_mode else '') - - # Workaround for macOS Big Sur, see https://github.com/Tribler/tribler/issues/5728 - if sys.platform == "darwin": - logger.info('Enabling a workaround for macOS Big Sur') - os.environ["QT_MAC_WANTS_LAYER"] = "1" - - # Set up logging - load_logger_config('tribler-gui', root_state_dir) - - from tribler_core.check_os import ( - check_and_enable_code_tracing, - check_environment, - check_free_space, - enable_fault_handler, - error_and_exit, - ) - from tribler_core.exceptions import TriblerException - - try: - # Enable tracer using commandline args: --trace-debug or --trace-exceptions - trace_logger = check_and_enable_code_tracing('gui', root_state_dir) - - enable_fault_handler(root_state_dir) - - # Exit if we cant read/write files, etc. - check_environment() - - check_free_space() - - from tribler_gui.tribler_app import TriblerApplication - from tribler_gui.tribler_window import TriblerWindow - - app_name = os.environ.get('TRIBLER_APP_NAME', 'triblerapp') - app = TriblerApplication(app_name, sys.argv) - - # ACHTUNG! translator MUST BE created and assigned to a separate variable - # BEFORE calling installTranslator on app. Otherwise, it won't work for some reason - settings = QSettings('nl.tudelft.tribler') - translator = get_translator(settings.value('translation', None)) - app.installTranslator(translator) - - if app.is_running(): - logger.info('GUI Application is running') - if torrent := parsed_args.torrent: - if os.path.exists(torrent) and torrent.endswith(".torrent"): - app.send_message(f"file:{torrent}") - elif torrent.startswith('magnet'): - app.send_message(torrent) - - sys.exit(1) - - logger.info('Start Tribler Window') - window = TriblerWindow(settings, - root_state_dir, - api_port=api_port, - api_key=api_key, - run_core=True) - window.setWindowTitle("Tribler") - app.set_activation_window(window) - app.parse_sys_args(sys.argv) - sys.exit(app.exec_()) - - except ImportError as ie: - logger.exception(ie) - error_and_exit("Import Error", f"Import error: {ie}") - - except TriblerException as te: - logger.exception(te) - error_and_exit("Tribler Exception", f"{te}") - - except SystemExit: - logger.info("Shutting down Tribler") - if trace_logger: - trace_logger.close() - - # Flush all the logs to make sure it is written to file before it exits - for handler in logging.getLogger().handlers: - handler.flush() - - default_core_exception_handler.sentry_reporter.global_strategy = SentryStrategy.SEND_SUPPRESSED - raise + run_core(api_port, api_key, root_state_dir, parsed_args) + else: # GUI + from tribler_gui.start_gui import run_gui + + run_gui(api_port, api_key, root_state_dir, parsed_args) diff --git a/src/tribler-core/tribler_core/start_core.py b/src/tribler-core/tribler_core/start_core.py index b1641665a2c..168fed60ad6 100644 --- a/src/tribler-core/tribler_core/start_core.py +++ b/src/tribler-core/tribler_core/start_core.py @@ -6,10 +6,17 @@ import sys from typing import List +from tribler_common.logger import load_logger_config +from tribler_common.process_checker import ProcessChecker from tribler_common.sentry_reporter.sentry_reporter import SentryReporter, SentryStrategy from tribler_common.simpledefs import NTFY +from tribler_common.version_manager import VersionHistory -from tribler_core.check_os import check_and_enable_code_tracing, set_process_priority +from tribler_core.check_os import ( + check_and_enable_code_tracing, + set_process_priority, + should_kill_other_tribler_instances, +) from tribler_core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent from tribler_core.components.base import Component, Session from tribler_core.components.gigachannel.gigachannel_component import GigaChannelComponent @@ -101,7 +108,7 @@ async def core_session(config: TriblerConfig, components: List[Component]): config.write() -def run_tribler_core(api_port, api_key, state_dir, gui_test_mode=False): +def run_tribler_core_session(api_port, api_key, state_dir, gui_test_mode=False): """ This method will start a new Tribler session. Note that there is no direct communication between the GUI process and the core: all communication is performed @@ -154,3 +161,23 @@ def run_tribler_core(api_port, api_key, state_dir, gui_test_mode=False): # Flush the logs to the file before exiting for handler in logging.getLogger().handlers: handler.flush() + + +def run_core(api_port, api_key, root_state_dir, parsed_args): + should_kill_other_tribler_instances(root_state_dir) + logger.info('Running Core' + ' in gui_test_mode' if parsed_args.gui_test_mode else '') + load_logger_config('tribler-core', root_state_dir) + + # Check if we are already running a Tribler instance + process_checker = ProcessChecker(root_state_dir) + if process_checker.already_running: + logger.info('Core is already running, exiting') + sys.exit(1) + process_checker.create_lock_file() + version_history = VersionHistory(root_state_dir) + state_dir = version_history.code_version.directory + try: + run_tribler_core_session(api_port, api_key, state_dir, gui_test_mode=parsed_args.gui_test_mode) + finally: + logger.info('Remove lock file') + process_checker.remove_lock_file() diff --git a/src/tribler-core/tribler_core/tests/test_start_core.py b/src/tribler-core/tribler_core/tests/test_start_core.py index e09d4160668..1e5a20b6674 100644 --- a/src/tribler-core/tribler_core/tests/test_start_core.py +++ b/src/tribler-core/tribler_core/tests/test_start_core.py @@ -1,6 +1,6 @@ from unittest.mock import MagicMock, patch -from tribler_core.start_core import run_tribler_core +from tribler_core.start_core import run_tribler_core_session from tribler_core.utilities.path_util import Path # pylint: disable= @@ -15,5 +15,5 @@ @patch('tribler_core.start_core.core_session') def test_start_tribler_core_no_exceptions(mocked_core_session): # test that base logic of tribler core runs without exceptions - run_tribler_core(1, 'key', Path('.'), False) + run_tribler_core_session(1, 'key', Path('.'), False) mocked_core_session.assert_called_once() diff --git a/src/tribler-gui/tribler_gui/start_gui.py b/src/tribler-gui/tribler_gui/start_gui.py new file mode 100644 index 00000000000..056caf7a564 --- /dev/null +++ b/src/tribler-gui/tribler_gui/start_gui.py @@ -0,0 +1,90 @@ +import logging +import os +import sys + +from PyQt5.QtCore import QSettings + +from tribler_common.logger import load_logger_config +from tribler_common.sentry_reporter.sentry_reporter import SentryStrategy +from tribler_core.check_os import ( + check_and_enable_code_tracing, + check_environment, + check_free_space, + enable_fault_handler, + error_and_exit, +) +from tribler_core.exceptions import TriblerException +from tribler_gui import gui_sentry_reporter +from tribler_gui.tribler_app import TriblerApplication +from tribler_gui.tribler_window import TriblerWindow +from tribler_gui.utilities import get_translator + +logger = logging.getLogger(__name__) + + +def run_gui(api_port, api_key, root_state_dir, parsed_args): + logger.info('Running GUI' + ' in gui_test_mode' if parsed_args.gui_test_mode else '') + + # Workaround for macOS Big Sur, see https://github.com/Tribler/tribler/issues/5728 + if sys.platform == "darwin": + logger.info('Enabling a workaround for macOS Big Sur') + os.environ["QT_MAC_WANTS_LAYER"] = "1" + + # Set up logging + load_logger_config('tribler-gui', root_state_dir) + + # Enable tracer using commandline args: --trace-debug or --trace-exceptions + trace_logger = check_and_enable_code_tracing('gui', root_state_dir) + try: + enable_fault_handler(root_state_dir) + # Exit if we cant read/write files, etc. + check_environment() + check_free_space() + + app_name = os.environ.get('TRIBLER_APP_NAME', 'triblerapp') + app = TriblerApplication(app_name, sys.argv) + + # Note (@ichorid): translator MUST BE created and assigned to a separate variable + # before calling installTranslator on app. Otherwise, it won't work for some reason + settings = QSettings('nl.tudelft.tribler') + translator = get_translator(settings.value('translation', None)) + app.installTranslator(translator) + + if app.is_running(): + # if an application is already running, then send the command line + # argument to it and close the current instance + logger.info(f'GUI Application already running. Passing a torrent file path to it.') + for arg in sys.argv[1:]: + if os.path.exists(arg) and arg.endswith(".torrent"): + app.send_message(f"file:{arg}") + elif arg.startswith('magnet'): + app.send_message(arg) + logger.info('Close the current application.') + sys.exit(1) + + logger.info('Start Tribler Window') + window = TriblerWindow(settings, root_state_dir, api_port=api_port, api_key=api_key) + window.setWindowTitle("Tribler") + app.set_activation_window(window) + app.parse_sys_args(sys.argv) + sys.exit(app.exec_()) + + except ImportError as ie: + logger.exception(ie) + error_and_exit("Import Error", f"Import error: {ie}") + + except TriblerException as te: + logger.exception(te) + error_and_exit("Tribler Exception", f"{te}") + + except SystemExit: + logger.info("Shutting down Tribler") + if trace_logger: + trace_logger.close() + + # Flush all the logs to make sure it is written to file before it exits + for handler in logging.getLogger().handlers: + handler.flush() + + gui_sentry_reporter.global_strategy = SentryStrategy.SEND_SUPPRESSED + raise From 156d738cb25ee9a3ca5a369a38c1648aee15fb39 Mon Sep 17 00:00:00 2001 From: drew2a Date: Fri, 21 Jan 2022 21:38:04 +0100 Subject: [PATCH 3/5] Fix quote/unquote issue --- .../tribler_common/rest_constants.py | 3 + .../download_manager/download_manager.py | 12 +-- .../libtorrent/restapi/downloads_endpoint.py | 16 +-- .../tests/test_torrentinfo_endpoint.py | 101 ++++++++++-------- .../restapi/torrentinfo_endpoint.py | 34 +++--- .../libtorrent/tests/test_download_api.py | 11 +- .../components/restapi/rest/base_api_test.py | 6 +- .../tribler_core/requirements.txt | 3 +- .../tribler_core/tests/tools/common.py | 3 +- .../tribler_gui/dialogs/dialogcontainer.py | 3 + .../dialogs/startdownloaddialog.py | 21 ++-- src/tribler-gui/tribler_gui/start_gui.py | 4 +- 12 files changed, 127 insertions(+), 90 deletions(-) create mode 100644 src/tribler-common/tribler_common/rest_constants.py diff --git a/src/tribler-common/tribler_common/rest_constants.py b/src/tribler-common/tribler_common/rest_constants.py new file mode 100644 index 00000000000..0944191557d --- /dev/null +++ b/src/tribler-common/tribler_common/rest_constants.py @@ -0,0 +1,3 @@ +MAGNET_PREFIX = 'magnet' +HTTP_PREFIX = 'http' +FILE_PREFIX = 'file' diff --git a/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py b/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py index 7b8a15b4dbe..42eb6bdb25b 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py +++ b/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py @@ -16,8 +16,8 @@ from ipv8.taskmanager import TaskManager, task from tribler_common.network_utils import default_network_utils +from tribler_common.rest_constants import FILE_PREFIX, HTTP_PREFIX, MAGNET_PREFIX from tribler_common.simpledefs import DLSTATUS_SEEDING, MAX_LIBTORRENT_RATE_LIMIT, NTFY, STATEDIR_CHECKPOINT_DIR -from tribler_common.utilities import uri_to_path from tribler_core.components.libtorrent.download_manager.dht_health_manager import DHTHealthManager from tribler_core.components.libtorrent.download_manager.download import Download @@ -505,10 +505,10 @@ def _map_call_on_ltsessions(self, hops, funcname, *args, **kwargs): getattr(self.get_session(hops), funcname)(*args, **kwargs) async def start_download_from_uri(self, uri, config=None): - if uri.startswith("http"): + if uri.startswith(HTTP_PREFIX): tdef = await TorrentDef.load_from_url(uri) return self.start_download(tdef=tdef, config=config) - if uri.startswith("magnet:"): + if uri.startswith(MAGNET_PREFIX): name, infohash, _ = parse_magnetlink(uri) if infohash is None: raise RuntimeError("Missing infohash") @@ -517,9 +517,9 @@ async def start_download_from_uri(self, uri, config=None): else: tdef = TorrentDefNoMetainfo(infohash, "Unknown name" if name is None else name, url=uri) return self.start_download(tdef=tdef, config=config) - if uri.startswith("file:"): - argument = uri_to_path(uri) - return self.start_download(torrent_file=argument, config=config) + if uri.startswith(FILE_PREFIX): + file = uri[len(FILE_PREFIX) + 1:] + return self.start_download(torrent_file=file, config=config) raise Exception("invalid uri") def start_download(self, torrent_file=None, tdef=None, config=None, checkpoint_disabled=False, hidden=False): diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py index eebadf063ab..2bbc7ee284d 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py @@ -233,9 +233,10 @@ def get_files_info_json(download): "and should only be used in situations where this data is required. " ) async def get_downloads(self, request): - get_peers = request.query.get('get_peers', '0') == '1' - get_pieces = request.query.get('get_pieces', '0') == '1' - get_files = request.query.get('get_files', '0') == '1' + params = request.query + get_peers = params.get('get_peers', '0') == '1' + get_pieces = params.get('get_pieces', '0') == '1' + get_files = params.get('get_files', '0') == '1' downloads_json = [] downloads = self.download_manager.get_downloads() @@ -373,16 +374,17 @@ async def get_downloads(self, request): 'location, a magnet link or a HTTP(S) url.'), })) async def add_download(self, request): - parameters = await request.json() - if not parameters.get('uri'): + params = await request.json() + uri = params.get('uri') + if not uri: return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST) - download_config, error = DownloadsEndpoint.create_dconfig_from_params(parameters) + download_config, error = DownloadsEndpoint.create_dconfig_from_params(params) if error: return RESTResponse({"error": error}, status=HTTP_BAD_REQUEST) try: - download = await self.download_manager.start_download_from_uri(parameters['uri'], config=download_config) + download = await self.download_manager.start_download_from_uri(uri, config=download_config) except Exception as e: return RESTResponse({"error": str(e)}, status=HTTP_INTERNAL_SERVER_ERROR) diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py index 99dce69fc51..3e6dfc20718 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py @@ -1,6 +1,5 @@ import json import shutil -import urllib from binascii import unhexlify from unittest.mock import Mock, patch from urllib.parse import quote_plus, unquote_plus @@ -11,9 +10,11 @@ import pytest +from tribler_common.rest_constants import FILE_PREFIX from tribler_common.simpledefs import NTFY from tribler_core.components.libtorrent.restapi.torrentinfo_endpoint import TorrentInfoEndpoint +from tribler_core.components.libtorrent.settings import LibtorrentSettings from tribler_core.components.libtorrent.torrentdef import TorrentDef from tribler_core.components.metadata_store.db.orm_bindings.torrent_metadata import tdef_to_metadata_dict from tribler_core.components.restapi.rest.base_api_test import do_request @@ -24,9 +25,30 @@ SAMPLE_CHANNEL_FILES_DIR = TESTS_DIR / "data" / "sample_channel" +# pylint: disable=redefined-outer-name + + +@pytest.fixture +def download_manager(state_dir): + dlmgr = Mock() + dlmgr.config = LibtorrentSettings() + dlmgr.shutdown = lambda: succeed(None) + checkpoints_dir = state_dir / 'dlcheckpoints' + checkpoints_dir.mkdir() + dlmgr.get_checkpoint_dir = lambda: checkpoints_dir + dlmgr.state_dir = state_dir + dlmgr.get_downloads = lambda: [] + dlmgr.downloads = {} + dlmgr.metainfo_requests = {} + dlmgr.get_channel_downloads = lambda: [] + dlmgr.shutdown = lambda: succeed(None) + dlmgr.notifier = Mock() + return dlmgr + + @pytest.fixture -def endpoint(mock_dlmgr): - return TorrentInfoEndpoint(mock_dlmgr) +def endpoint(download_manager): + return TorrentInfoEndpoint(download_manager) @pytest.fixture @@ -36,41 +58,41 @@ def rest_api(loop, aiohttp_client, endpoint): # pylint: disable=unused-argument return loop.run_until_complete(aiohttp_client(app)) -async def test_get_torrentinfo(mock_dlmgr, tmp_path, rest_api, endpoint): +async def test_get_torrentinfo_escaped_characters(tmp_path, rest_api): + # test for the bug fix: https://github.com/Tribler/tribler/issues/6700 + source = TORRENT_UBUNTU_FILE + destination = tmp_path / 'ubuntu%20%21 15.04.torrent' + shutil.copyfile(source, destination) + + response = await do_request(rest_api, url='torrentinfo', params={'uri': f'{FILE_PREFIX}:{destination}'}, + expected_code=200) + + assert 'metainfo' in response + + +async def test_get_torrentinfo(tmp_path, rest_api, endpoint: TorrentInfoEndpoint): """ Testing whether the API returns a correct dictionary with torrent info. """ - endpoint.download_manager = mock_dlmgr + + def _path(file): + return f'{FILE_PREFIX}:{TESTS_DATA_DIR / file}' shutil.copyfile(TORRENT_UBUNTU_FILE, tmp_path / 'ubuntu.torrent') def verify_valid_dict(json_data): metainfo_dict = json.loads(unhexlify(json_data['metainfo'])) - # FIXME: This check is commented out because json.dump garbles pieces binary data during transfer. - # To fix it, we must switch to some encoding scheme that is able to encode and decode raw binary - # fields in the dicts. - # However, for this works fine at the moment because we never use pieces data in the GUI. - # assert TorrentDef.load_from_dict(metainfo_dict) assert 'info' in metainfo_dict - def path_to_url(path): - return urllib.request.pathname2url(str(path)) + url = 'torrentinfo' + await do_request(rest_api, url, expected_code=400) + await do_request(rest_api, url, params={'uri': 'def'}, expected_code=400) - mock_dlmgr.downloads = {} - mock_dlmgr.metainfo_requests = {} - mock_dlmgr.get_channel_downloads = lambda: [] - mock_dlmgr.shutdown = lambda: succeed(None) - mock_dlmgr.notifier = Mock() - - await do_request(rest_api, 'torrentinfo', expected_code=400) - await do_request(rest_api, 'torrentinfo?uri=def', expected_code=400) - - path = "file:" + path_to_url(TESTS_DATA_DIR / "bak_single.torrent") - verify_valid_dict(await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=200)) + response = await do_request(rest_api, url, params={'uri': _path('bak_single.torrent')}, expected_code=200) + verify_valid_dict(response) # Corrupt file - path = "file:" + path_to_url(TESTS_DATA_DIR / "test_rss.xml") - await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=500) + await do_request(rest_api, url, params={'uri': _path('test_rss.xml')}, expected_code=500) path = "http://localhost:1234/ubuntu.torrent" @@ -79,7 +101,7 @@ async def mock_http_query(*_): return f.read() with patch("tribler_core.components.libtorrent.restapi.torrentinfo_endpoint.query_http_uri", new=mock_http_query): - verify_valid_dict(await do_request(rest_api, f'torrentinfo?uri={quote_plus(path)}', expected_code=200)) + verify_valid_dict(await do_request(rest_api, url, params={'uri': quote_plus(path)}, expected_code=200)) path = quote_plus(f'magnet:?xt=urn:btih:{hexlify(UBUNTU_1504_INFOHASH)}' f'&dn=test torrent&tr=http://ubuntu.org/ann') @@ -98,20 +120,20 @@ def get_metainfo(infohash, timeout=20, hops=None, url=None): assert url == unquote_plus(path) return succeed(tdef.get_metainfo()) - mock_dlmgr.get_metainfo = get_metainfo + endpoint.download_manager.get_metainfo = get_metainfo verify_valid_dict(await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=200)) path = 'magnet:?xt=urn:ed2k:354B15E68FB8F36D7CD88FF94116CDC1' # No infohash await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=400) path = quote_plus(f"magnet:?xt=urn:btih:{'a' * 40}&dn=test torrent") - mock_dlmgr.get_metainfo = lambda *_, **__: succeed(None) + endpoint.download_manager.get_metainfo = lambda *_, **__: succeed(None) await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=500) # Ensure that correct torrent metadata was sent through notifier (to MetadataStore) - mock_dlmgr.notifier.notify.assert_called_with(NTFY.TORRENT_METADATA_ADDED.value, metainfo_dict) + endpoint.download_manager.notifier.notify.assert_called_with(NTFY.TORRENT_METADATA_ADDED.value, metainfo_dict) - mock_dlmgr.get_metainfo = get_metainfo + endpoint.download_manager.get_metainfo = get_metainfo verify_valid_dict(await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=200)) await do_request(rest_api, f'torrentinfo?uri={path}&hops=0', expected_code=200) @@ -124,36 +146,29 @@ def get_metainfo(infohash, timeout=20, hops=None, url=None): mock_download = Mock() path = quote_plus(f'magnet:?xt=urn:btih:{hexlify(UBUNTU_1504_INFOHASH)}&dn=test torrent') - mock_dlmgr.downloads = {UBUNTU_1504_INFOHASH: mock_download} + endpoint.download_manager.downloads = {UBUNTU_1504_INFOHASH: mock_download} result = await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=200) assert result["download_exists"] # Check that we do not return "downloads_exists" if the download is metainfo only download - mock_dlmgr.downloads = {UBUNTU_1504_INFOHASH: mock_download} - mock_dlmgr.metainfo_requests = {UBUNTU_1504_INFOHASH: [mock_download]} + endpoint.download_manager.downloads = {UBUNTU_1504_INFOHASH: mock_download} + endpoint.download_manager.metainfo_requests = {UBUNTU_1504_INFOHASH: [mock_download]} result = await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=200) assert not result["download_exists"] # Check that we return "downloads_exists" if there is a metainfo download for the infohash, # but there is also a regular download for the same infohash - mock_dlmgr.downloads = {UBUNTU_1504_INFOHASH: mock_download} - mock_dlmgr.metainfo_requests = {UBUNTU_1504_INFOHASH: [Mock()]} + endpoint.download_manager.downloads = {UBUNTU_1504_INFOHASH: mock_download} + endpoint.download_manager.metainfo_requests = {UBUNTU_1504_INFOHASH: [Mock()]} result = await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=200) assert result["download_exists"] -async def test_on_got_invalid_metainfo(mock_dlmgr, rest_api): +async def test_on_got_invalid_metainfo(rest_api): """ Test whether the right operations happen when we receive an invalid metainfo object """ - def get_metainfo(*_, **__): - return succeed("abcd") - mock_dlmgr.get_metainfo = get_metainfo - mock_dlmgr.shutdown = lambda: succeed(None) - mock_dlmgr.shutdown_downloads = lambda: succeed(None) - mock_dlmgr.checkpoint_downloads = lambda: succeed(None) path = f"magnet:?xt=urn:btih:{hexlify(UBUNTU_1504_INFOHASH)}&dn={quote_plus('test torrent')}" - res = await do_request(rest_api, f'torrentinfo?uri={path}', expected_code=500) assert "error" in res diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py index fa298b3ca26..e6a4a7a32f1 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py @@ -10,8 +10,8 @@ from marshmallow.fields import String +from tribler_common.rest_constants import FILE_PREFIX, HTTP_PREFIX, MAGNET_PREFIX from tribler_common.simpledefs import NTFY -from tribler_common.utilities import uri_to_path from tribler_core.components.libtorrent.download_manager.download_manager import DownloadManager from tribler_core.components.libtorrent.torrentdef import TorrentDef @@ -68,29 +68,29 @@ def setup_routes(self): } ) async def get_torrent_info(self, request): - args = request.query - - hops = None - if 'hops' in args: + params = request.query + hops = params.get('hops') + uri = params.get('uri') + self._logger.info(f'URI: {uri}') + if hops: try: - hops = int(args['hops']) + hops = int(hops) except ValueError: - return RESTResponse({"error": f"wrong value of 'hops' parameter: {repr(args['hops'])}"}, - status=HTTP_BAD_REQUEST) + return RESTResponse({"error": f"wrong value of 'hops' parameter: {hops}"}, status=HTTP_BAD_REQUEST) - if 'uri' not in args or not args['uri']: + if not uri: return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST) - uri = args['uri'] metainfo = None - if uri.startswith('file:'): + if uri.startswith(FILE_PREFIX): + file = uri[len(FILE_PREFIX) + 1:] try: - filename = uri_to_path(uri) - tdef = TorrentDef.load(filename) - metainfo = tdef.get_metainfo() + tdef = TorrentDef.load(file) + metainfo = tdef.metainfo except (TypeError, RuntimeError): - return RESTResponse({"error": "error while decoding torrent file"}, status=HTTP_INTERNAL_SERVER_ERROR) - elif uri.startswith('http'): + return RESTResponse({"error": f"error while decoding torrent file: {file}"}, + status=HTTP_INTERNAL_SERVER_ERROR) + elif uri.startswith(HTTP_PREFIX): try: response = await query_http_uri(uri) except (ServerConnectionError, ClientResponseError) as e: @@ -102,7 +102,7 @@ async def get_torrent_info(self, request): metainfo = await self.download_manager.get_metainfo(infohash, timeout=60, hops=hops, url=response) else: metainfo = bdecode_compat(response) - elif uri.startswith('magnet'): + elif uri.startswith(MAGNET_PREFIX): infohash = parse_magnetlink(uri)[1] if infohash is None: return RESTResponse({"error": "missing infohash"}, status=HTTP_BAD_REQUEST) diff --git a/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py b/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py index 8f223c947eb..b058bc85295 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py +++ b/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py @@ -2,6 +2,7 @@ import pytest +from tribler_common.rest_constants import FILE_PREFIX from tribler_common.simpledefs import DLSTATUS_DOWNLOADING from tribler_core.tests.tools.common import TORRENT_UBUNTU_FILE @@ -10,7 +11,6 @@ @pytest.mark.asyncio @pytest.mark.timeout(10) async def test_download_torrent_from_url(tmp_path, file_server, download_manager): - # Setup file server to serve torrent file shutil.copyfile(TORRENT_UBUNTU_FILE, tmp_path / "ubuntu.torrent") download = await download_manager.start_download_from_uri(f'http://localhost:{file_server}/ubuntu.torrent') @@ -22,3 +22,12 @@ async def test_download_torrent_from_url(tmp_path, file_server, download_manager async def test_download_torrent_from_file(download_manager): d = await download_manager.start_download_from_uri(TORRENT_UBUNTU_FILE.as_uri()) await d.wait_for_status(DLSTATUS_DOWNLOADING) + + +@pytest.mark.asyncio +@pytest.mark.timeout(10) +async def test_download_torrent_from_file_with_escaped_characters(download_manager, tmp_path): + destination = tmp_path / 'ubuntu%20%21 15.04.torrent' + shutil.copyfile(TORRENT_UBUNTU_FILE, destination) + d = await download_manager.start_download_from_uri(f'{FILE_PREFIX}:{destination}') + await d.wait_for_status(DLSTATUS_DOWNLOADING) diff --git a/src/tribler-core/tribler_core/components/restapi/rest/base_api_test.py b/src/tribler-core/tribler_core/components/restapi/rest/base_api_test.py index a5d2edba174..0174076aef2 100644 --- a/src/tribler-core/tribler_core/components/restapi/rest/base_api_test.py +++ b/src/tribler-core/tribler_core/components/restapi/rest/base_api_test.py @@ -1,5 +1,6 @@ import json from json import JSONDecodeError +from typing import Dict, Optional from aiohttp import ClientSession @@ -37,12 +38,13 @@ async def do_real_request(port, endpoint, expected_code=200, expected_json=None, async def do_request(test_client, url, expected_code=200, expected_json=None, - request_type='GET', post_data=None, headers=None, json_response=True): + request_type='GET', post_data=None, headers=None, json_response=True, + params: Optional[Dict] = None): post_data = post_data or {} data = json.dumps(path_to_str(post_data)) if isinstance(post_data, (dict, list)) else post_data headers = headers or {'User-Agent': 'Tribler ' + version_id} - async with test_client.request(request_type, url, data=data, headers=headers, ssl=False) as response: + async with test_client.request(request_type, url, data=data, headers=headers, ssl=False, params=params) as response: status = response.status try: response = (await response.json(content_type=None) diff --git a/src/tribler-core/tribler_core/requirements.txt b/src/tribler-core/tribler_core/requirements.txt index 261af0977cf..d9b67a1134d 100644 --- a/src/tribler-core/tribler_core/requirements.txt +++ b/src/tribler-core/tribler_core/requirements.txt @@ -19,4 +19,5 @@ PyOpenSSL==21.0.0 pyyaml==6.0 sentry-sdk==1.5.0 service-identity==21.1.0 -yappi==1.3.3 \ No newline at end of file +yappi==1.3.3 +yarl==1.7.0 \ No newline at end of file diff --git a/src/tribler-core/tribler_core/tests/tools/common.py b/src/tribler-core/tribler_core/tests/tools/common.py index 26714c22711..13e9f074d8f 100644 --- a/src/tribler-core/tribler_core/tests/tools/common.py +++ b/src/tribler-core/tribler_core/tests/tools/common.py @@ -1,8 +1,9 @@ import binascii -from pathlib import Path import tribler_common +from tribler_core.utilities.path_util import Path + UBUNTU_1504_INFOHASH = binascii.unhexlify('FC8A15A2FAF2734DBB1DC5F7AFDC5C9BEAEB1F59') TESTS_DIR = Path(__file__).parent diff --git a/src/tribler-gui/tribler_gui/dialogs/dialogcontainer.py b/src/tribler-gui/tribler_gui/dialogs/dialogcontainer.py index af7fd4551b4..80454e7eaad 100644 --- a/src/tribler-gui/tribler_gui/dialogs/dialogcontainer.py +++ b/src/tribler-gui/tribler_gui/dialogs/dialogcontainer.py @@ -1,3 +1,5 @@ +import logging + from PyQt5.QtCore import QPoint from PyQt5.QtGui import QPainter from PyQt5.QtWidgets import QStyle, QStyleOption, QWidget @@ -15,6 +17,7 @@ def __init__(self, parent, left_right_margin=100): self.dialog_widget = QWidget(self) self.left_right_margin = left_right_margin # The margin at the left and right of the dialog window self.closed = False + self.logger = logging.getLogger(self.__class__.__name__) connect(self.window().resize_event, self.on_main_window_resize) def paintEvent(self, _): diff --git a/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py b/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py index 848fb8606ab..b703b416386 100644 --- a/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py +++ b/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py @@ -8,7 +8,7 @@ from PyQt5.QtCore import QTimer, pyqtSignal from PyQt5.QtWidgets import QFileDialog, QSizePolicy -from tribler_common.utilities import uri_to_path +from tribler_common.rest_constants import FILE_PREFIX, MAGNET_PREFIX from tribler_gui.defs import METAINFO_MAX_RETRIES, METAINFO_TIMEOUT from tribler_gui.dialogs.confirmationdialog import ConfirmationDialog @@ -21,14 +21,12 @@ get_image_path, get_ui_file_path, is_dir_writable, - quote_plus_unicode, tr, ) from tribler_gui.widgets.torrentfiletreewidget import TORRENT_FILES_TREE_STYLESHEET class StartDownloadDialog(DialogContainer): - button_clicked = pyqtSignal(int) received_metainfo = pyqtSignal(dict) @@ -36,9 +34,9 @@ def __init__(self, parent, download_uri): DialogContainer.__init__(self, parent) torrent_name = download_uri - if torrent_name.startswith('file:'): - torrent_name = uri_to_path(torrent_name).stem - elif torrent_name.startswith('magnet:'): + if torrent_name.startswith(FILE_PREFIX): + torrent_name = torrent_name[len(FILE_PREFIX) + 1 :] + elif torrent_name.startswith(MAGNET_PREFIX): torrent_name = unquote_plus(torrent_name) self.download_uri = download_uri @@ -142,10 +140,12 @@ def perform_files_request(self): return direct = not self.dialog_widget.anon_download_checkbox.isChecked() - request = f"torrentinfo?uri={quote_plus_unicode(self.download_uri)}" - if direct is True: - request = request + "&hops=0" - self.rest_request = TriblerNetworkRequest(request, self.on_received_metainfo, capture_core_errors=False) + params = {'uri': self.download_uri} + if direct: + params['hops'] = 0 + self.rest_request = TriblerNetworkRequest( + 'torrentinfo', self.on_received_metainfo, capture_core_errors=False, url_params=params + ) if self.metainfo_retries <= METAINFO_MAX_RETRIES: fetch_mode = tr("directly") if direct else tr("anonymously") @@ -169,7 +169,6 @@ def perform_files_request(self): def on_received_metainfo(self, response): if not response or not self or self.closed or self.has_metainfo: return - if 'error' in response: if response['error'] == 'metainfo error': # If it failed to load metainfo for max number of times, show an error message in red. diff --git a/src/tribler-gui/tribler_gui/start_gui.py b/src/tribler-gui/tribler_gui/start_gui.py index 056caf7a564..4588c11f8b8 100644 --- a/src/tribler-gui/tribler_gui/start_gui.py +++ b/src/tribler-gui/tribler_gui/start_gui.py @@ -6,6 +6,7 @@ from tribler_common.logger import load_logger_config from tribler_common.sentry_reporter.sentry_reporter import SentryStrategy + from tribler_core.check_os import ( check_and_enable_code_tracing, check_environment, @@ -14,6 +15,7 @@ error_and_exit, ) from tribler_core.exceptions import TriblerException + from tribler_gui import gui_sentry_reporter from tribler_gui.tribler_app import TriblerApplication from tribler_gui.tribler_window import TriblerWindow @@ -53,7 +55,7 @@ def run_gui(api_port, api_key, root_state_dir, parsed_args): if app.is_running(): # if an application is already running, then send the command line # argument to it and close the current instance - logger.info(f'GUI Application already running. Passing a torrent file path to it.') + logger.info('GUI Application is already running. Passing a torrent file path to it.') for arg in sys.argv[1:]: if os.path.exists(arg) and arg.endswith(".torrent"): app.send_message(f"file:{arg}") From 496c07e19c966ea99134705214456f8ee1958f62 Mon Sep 17 00:00:00 2001 From: drew2a Date: Mon, 24 Jan 2022 14:59:36 +0100 Subject: [PATCH 4/5] Replace file URI creating by yarl's `URL()` --- .../tribler_common/rest_constants.py | 3 -- .../tribler_common/rest_utils.py | 33 ++++++++++++++ .../tribler_common/tests/test_rest_utils.py | 43 +++++++++++++++++++ .../tribler_common/tests/test_utils.py | 8 +--- .../tribler_common/utilities.py | 11 ----- .../download_manager/download_manager.py | 10 ++--- .../libtorrent/libtorrent_component.py | 4 +- .../restapi/tests/test_downloads_endpoint.py | 12 +++--- .../tests/test_torrentinfo_endpoint.py | 9 ++-- .../restapi/torrentinfo_endpoint.py | 10 ++--- .../libtorrent/tests/test_download_api.py | 8 ++-- .../tribler_core/requirements.txt | 2 +- .../dialogs/startdownloaddialog.py | 8 ++-- src/tribler-gui/tribler_gui/start_gui.py | 3 +- src/tribler-gui/tribler_gui/tests/test_gui.py | 5 ++- src/tribler-gui/tribler_gui/tribler_app.py | 9 +++- src/tribler-gui/tribler_gui/tribler_window.py | 16 ++++--- 17 files changed, 131 insertions(+), 63 deletions(-) delete mode 100644 src/tribler-common/tribler_common/rest_constants.py create mode 100644 src/tribler-common/tribler_common/rest_utils.py create mode 100644 src/tribler-common/tribler_common/tests/test_rest_utils.py diff --git a/src/tribler-common/tribler_common/rest_constants.py b/src/tribler-common/tribler_common/rest_constants.py deleted file mode 100644 index 0944191557d..00000000000 --- a/src/tribler-common/tribler_common/rest_constants.py +++ /dev/null @@ -1,3 +0,0 @@ -MAGNET_PREFIX = 'magnet' -HTTP_PREFIX = 'http' -FILE_PREFIX = 'file' diff --git a/src/tribler-common/tribler_common/rest_utils.py b/src/tribler-common/tribler_common/rest_utils.py new file mode 100644 index 00000000000..0b3a84a007e --- /dev/null +++ b/src/tribler-common/tribler_common/rest_utils.py @@ -0,0 +1,33 @@ +import os +from typing import Any, Union + +from yarl import URL + +MAGNET_SCHEME = 'magnet' +HTTP_SCHEME = 'http' +FILE_SCHEME = 'file' + + +def path_to_uri(file_path: Union[str, Any]) -> str: + """Convert path to url + + Example: + '/path/to/file' -> 'file:///path/to/file' + """ + if not isinstance(file_path, str): + file_path = str(file_path) + return str(URL().build(scheme=FILE_SCHEME, path=file_path)) + + +def uri_to_path(file_uri: str) -> str: + """Convert uri to path + + Example: + 'file:///path/to/file' -> '/path/to/file' + """ + path = URL(file_uri).path + if os.name == 'nt': + # Removes first slash for win OS + # see https://github.com/aio-libs/yarl/issues/674 + return path.lstrip('/') + return path diff --git a/src/tribler-common/tribler_common/tests/test_rest_utils.py b/src/tribler-common/tribler_common/tests/test_rest_utils.py new file mode 100644 index 00000000000..509a056b4a4 --- /dev/null +++ b/src/tribler-common/tribler_common/tests/test_rest_utils.py @@ -0,0 +1,43 @@ +from unittest.mock import patch + +import pytest + +from tribler_common.rest_utils import path_to_uri, uri_to_path + +NIX_PATHS = [ + ('/path/to/file', 'file:///path/to/file'), + ('/path/to/file with space', 'file:///path/to/file%20with%20space'), + ('/path/to/%20%21file', 'file:///path/to/%2520%2521file'), # See: https://github.com/Tribler/tribler/issues/6700 +] + +WIN_PATHS = [ + ('C:\\path\\to\\file', 'file:///C:%5Cpath%5Cto%5Cfile'), + ('C:\\path\\to\\file with space', 'file:///C:%5Cpath%5Cto%5Cfile%20with%20space'), + ('C:\\path\\to\\%20%21file', 'file:///C:%5Cpath%5Cto%5C%2520%2521file'), +] + + +# posix +@pytest.mark.parametrize('path,uri', NIX_PATHS) +@patch('os.name', 'posix') +def test_path_to_uri(path, uri): + assert path_to_uri(path) == uri + + +@pytest.mark.parametrize('path,uri', NIX_PATHS) +@patch('os.name', 'posix') +def test_uri_to_path(path, uri): + assert uri_to_path(uri) == path + + +# win +@pytest.mark.parametrize('path,uri', WIN_PATHS) +@patch('os.name', 'nt') +def test_path_to_uri_win(path, uri): + assert path_to_uri(path) == uri + + +@pytest.mark.parametrize('path,uri', WIN_PATHS) +@patch('os.name', 'nt') +def test_uri_to_path_win(path, uri): + assert uri_to_path(uri) == path diff --git a/src/tribler-common/tribler_common/tests/test_utils.py b/src/tribler-common/tribler_common/tests/test_utils.py index 4267db80573..319ca9c0dd6 100644 --- a/src/tribler-common/tribler_common/tests/test_utils.py +++ b/src/tribler-common/tribler_common/tests/test_utils.py @@ -1,17 +1,11 @@ -from pathlib import Path from unittest.mock import MagicMock, patch from tribler_common.patch_import import patch_import -from tribler_common.utilities import Query, extract_tags, parse_query, show_system_popup, to_fts_query, uri_to_path +from tribler_common.utilities import Query, extract_tags, parse_query, show_system_popup, to_fts_query # pylint: disable=import-outside-toplevel, import-error # fmt: off -def test_uri_to_path(): - path = Path(__file__).parent / "bla%20foo.bar" - uri = path.as_uri() - assert uri_to_path(uri) == path - def test_to_fts_query(): assert to_fts_query(None) is None diff --git a/src/tribler-common/tribler_common/utilities.py b/src/tribler-common/tribler_common/utilities.py index 2e93f46cd28..06cc0968bbe 100644 --- a/src/tribler-common/tribler_common/utilities.py +++ b/src/tribler-common/tribler_common/utilities.py @@ -1,14 +1,9 @@ import itertools -import os import platform import re import sys from dataclasses import dataclass, field from typing import Set, Tuple -from urllib.parse import urlparse -from urllib.request import url2pathname - -from tribler_core.utilities.path_util import Path def is_frozen(): @@ -23,12 +18,6 @@ def is_frozen(): return True -def uri_to_path(uri): - parsed = urlparse(uri) - host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc) - return Path(host) / url2pathname(parsed.path) - - fts_query_re = re.compile(r'\w+', re.UNICODE) tags_re = re.compile(r'#[^\s^#]{3,50}(?=[#\s]|$)') diff --git a/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py b/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py index 42eb6bdb25b..fb7e63249dd 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py +++ b/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py @@ -16,7 +16,7 @@ from ipv8.taskmanager import TaskManager, task from tribler_common.network_utils import default_network_utils -from tribler_common.rest_constants import FILE_PREFIX, HTTP_PREFIX, MAGNET_PREFIX +from tribler_common.rest_utils import FILE_SCHEME, HTTP_SCHEME, MAGNET_SCHEME, uri_to_path from tribler_common.simpledefs import DLSTATUS_SEEDING, MAX_LIBTORRENT_RATE_LIMIT, NTFY, STATEDIR_CHECKPOINT_DIR from tribler_core.components.libtorrent.download_manager.dht_health_manager import DHTHealthManager @@ -505,10 +505,10 @@ def _map_call_on_ltsessions(self, hops, funcname, *args, **kwargs): getattr(self.get_session(hops), funcname)(*args, **kwargs) async def start_download_from_uri(self, uri, config=None): - if uri.startswith(HTTP_PREFIX): + if uri.startswith(HTTP_SCHEME): tdef = await TorrentDef.load_from_url(uri) return self.start_download(tdef=tdef, config=config) - if uri.startswith(MAGNET_PREFIX): + if uri.startswith(MAGNET_SCHEME): name, infohash, _ = parse_magnetlink(uri) if infohash is None: raise RuntimeError("Missing infohash") @@ -517,8 +517,8 @@ async def start_download_from_uri(self, uri, config=None): else: tdef = TorrentDefNoMetainfo(infohash, "Unknown name" if name is None else name, url=uri) return self.start_download(tdef=tdef, config=config) - if uri.startswith(FILE_PREFIX): - file = uri[len(FILE_PREFIX) + 1:] + if uri.startswith(FILE_SCHEME): + file = uri_to_path(uri) return self.start_download(torrent_file=file, config=config) raise Exception("invalid uri") diff --git a/src/tribler-core/tribler_core/components/libtorrent/libtorrent_component.py b/src/tribler-core/tribler_core/components/libtorrent/libtorrent_component.py index 3735a57a83a..3b85e65cea1 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/libtorrent_component.py +++ b/src/tribler-core/tribler_core/components/libtorrent/libtorrent_component.py @@ -1,3 +1,5 @@ +from tribler_common.rest_utils import path_to_uri + from tribler_core.components.base import Component from tribler_core.components.key.key_component import KeyComponent from tribler_core.components.libtorrent.download_manager.download_manager import DownloadManager @@ -33,7 +35,7 @@ async def run(self): if config.gui_test_mode: from tribler_core.tests.tools.common import TORRENT_WITH_DIRS # pylint: disable=import-outside-toplevel - uri = f"file:{TORRENT_WITH_DIRS}" + uri = path_to_uri(TORRENT_WITH_DIRS) await self.download_manager.start_download_from_uri(uri) async def shutdown(self): diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py index ef0fbe30906..5e814595a06 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py @@ -8,6 +8,7 @@ import pytest +from tribler_common.rest_utils import HTTP_SCHEME, path_to_uri from tribler_common.simpledefs import DLSTATUS_CIRCUITS, DLSTATUS_DOWNLOADING, DLSTATUS_EXIT_NODES, DLSTATUS_STOPPED from tribler_core.components.libtorrent.download_manager.download_state import DownloadState @@ -184,11 +185,10 @@ async def test_start_download_from_file(test_download, mock_dlmgr, rest_api): Testing whether we can start a download from a file """ mock_dlmgr.start_download_from_uri = lambda *_, **__: succeed(test_download) - - post_data = {'uri': f"file:{TESTS_DATA_DIR / 'video.avi.torrent'}"} + uri = path_to_uri(TESTS_DATA_DIR / 'video.avi.torrent') expected_json = {'started': True, 'infohash': 'c9a19e7fe5d9a6c106d6ea3c01746ac88ca3c7a5'} await do_request(rest_api, 'downloads', expected_code=200, request_type='PUT', - post_data=post_data, expected_json=expected_json) + post_data={'uri': uri}, expected_json=expected_json) async def test_start_download_with_selected_files(test_download, mock_dlmgr, rest_api): @@ -200,8 +200,8 @@ def mocked_start_download(*_, config=None): return succeed(test_download) mock_dlmgr.start_download_from_uri = mocked_start_download - - post_data = {'uri': f"file:{TESTS_DATA_DIR / 'video.avi.torrent'}", 'selected_files': [0]} + uri = path_to_uri(TESTS_DATA_DIR / 'video.avi.torrent') + post_data = {'uri': uri, 'selected_files': [0]} expected_json = {'started': True, 'infohash': 'c9a19e7fe5d9a6c106d6ea3c01746ac88ca3c7a5'} await do_request(rest_api, 'downloads', expected_code=200, request_type='PUT', post_data=post_data, expected_json=expected_json) @@ -249,7 +249,7 @@ def mocked_start_download(*_, **__): mock_dlmgr.start_download_from_uri = mocked_start_download - post_data = {'uri': 'http://localhost:1234/test.torrent'} + post_data = {'uri': f'{HTTP_SCHEME}://localhost:1234/test.torrent'} result = await do_request(rest_api, 'downloads', expected_code=500, request_type='PUT', post_data=post_data) assert result["error"] == "test" diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py index 3e6dfc20718..73479943cfc 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py @@ -10,7 +10,7 @@ import pytest -from tribler_common.rest_constants import FILE_PREFIX +from tribler_common.rest_utils import path_to_uri from tribler_common.simpledefs import NTFY from tribler_core.components.libtorrent.restapi.torrentinfo_endpoint import TorrentInfoEndpoint @@ -63,9 +63,8 @@ async def test_get_torrentinfo_escaped_characters(tmp_path, rest_api): source = TORRENT_UBUNTU_FILE destination = tmp_path / 'ubuntu%20%21 15.04.torrent' shutil.copyfile(source, destination) - - response = await do_request(rest_api, url='torrentinfo', params={'uri': f'{FILE_PREFIX}:{destination}'}, - expected_code=200) + uri = path_to_uri(destination) + response = await do_request(rest_api, url='torrentinfo', params={'uri': uri}, expected_code=200) assert 'metainfo' in response @@ -76,7 +75,7 @@ async def test_get_torrentinfo(tmp_path, rest_api, endpoint: TorrentInfoEndpoint """ def _path(file): - return f'{FILE_PREFIX}:{TESTS_DATA_DIR / file}' + return path_to_uri(TESTS_DATA_DIR / file) shutil.copyfile(TORRENT_UBUNTU_FILE, tmp_path / 'ubuntu.torrent') diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py index e6a4a7a32f1..64fc98fcab7 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py @@ -10,7 +10,7 @@ from marshmallow.fields import String -from tribler_common.rest_constants import FILE_PREFIX, HTTP_PREFIX, MAGNET_PREFIX +from tribler_common.rest_utils import FILE_SCHEME, HTTP_SCHEME, MAGNET_SCHEME, uri_to_path from tribler_common.simpledefs import NTFY from tribler_core.components.libtorrent.download_manager.download_manager import DownloadManager @@ -82,15 +82,15 @@ async def get_torrent_info(self, request): return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST) metainfo = None - if uri.startswith(FILE_PREFIX): - file = uri[len(FILE_PREFIX) + 1:] + if uri.startswith(FILE_SCHEME): + file = uri_to_path(uri) try: tdef = TorrentDef.load(file) metainfo = tdef.metainfo except (TypeError, RuntimeError): return RESTResponse({"error": f"error while decoding torrent file: {file}"}, status=HTTP_INTERNAL_SERVER_ERROR) - elif uri.startswith(HTTP_PREFIX): + elif uri.startswith(HTTP_SCHEME): try: response = await query_http_uri(uri) except (ServerConnectionError, ClientResponseError) as e: @@ -102,7 +102,7 @@ async def get_torrent_info(self, request): metainfo = await self.download_manager.get_metainfo(infohash, timeout=60, hops=hops, url=response) else: metainfo = bdecode_compat(response) - elif uri.startswith(MAGNET_PREFIX): + elif uri.startswith(MAGNET_SCHEME): infohash = parse_magnetlink(uri)[1] if infohash is None: return RESTResponse({"error": "missing infohash"}, status=HTTP_BAD_REQUEST) diff --git a/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py b/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py index b058bc85295..21086bd0684 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py +++ b/src/tribler-core/tribler_core/components/libtorrent/tests/test_download_api.py @@ -2,7 +2,7 @@ import pytest -from tribler_common.rest_constants import FILE_PREFIX +from tribler_common.rest_utils import path_to_uri from tribler_common.simpledefs import DLSTATUS_DOWNLOADING from tribler_core.tests.tools.common import TORRENT_UBUNTU_FILE @@ -20,7 +20,8 @@ async def test_download_torrent_from_url(tmp_path, file_server, download_manager @pytest.mark.asyncio @pytest.mark.timeout(10) async def test_download_torrent_from_file(download_manager): - d = await download_manager.start_download_from_uri(TORRENT_UBUNTU_FILE.as_uri()) + uri = path_to_uri(TORRENT_UBUNTU_FILE) + d = await download_manager.start_download_from_uri(uri) await d.wait_for_status(DLSTATUS_DOWNLOADING) @@ -29,5 +30,6 @@ async def test_download_torrent_from_file(download_manager): async def test_download_torrent_from_file_with_escaped_characters(download_manager, tmp_path): destination = tmp_path / 'ubuntu%20%21 15.04.torrent' shutil.copyfile(TORRENT_UBUNTU_FILE, destination) - d = await download_manager.start_download_from_uri(f'{FILE_PREFIX}:{destination}') + uri = path_to_uri(destination) + d = await download_manager.start_download_from_uri(uri) await d.wait_for_status(DLSTATUS_DOWNLOADING) diff --git a/src/tribler-core/tribler_core/requirements.txt b/src/tribler-core/tribler_core/requirements.txt index d9b67a1134d..f043d25a03e 100644 --- a/src/tribler-core/tribler_core/requirements.txt +++ b/src/tribler-core/tribler_core/requirements.txt @@ -20,4 +20,4 @@ pyyaml==6.0 sentry-sdk==1.5.0 service-identity==21.1.0 yappi==1.3.3 -yarl==1.7.0 \ No newline at end of file +yarl==1.7.2 # keep this dependency higher than 1.6.3. See: https://github.com/aio-libs/yarl/issues/517 diff --git a/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py b/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py index b703b416386..0ab94c9e681 100644 --- a/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py +++ b/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py @@ -8,7 +8,7 @@ from PyQt5.QtCore import QTimer, pyqtSignal from PyQt5.QtWidgets import QFileDialog, QSizePolicy -from tribler_common.rest_constants import FILE_PREFIX, MAGNET_PREFIX +from tribler_common.rest_utils import FILE_SCHEME, MAGNET_SCHEME, uri_to_path from tribler_gui.defs import METAINFO_MAX_RETRIES, METAINFO_TIMEOUT from tribler_gui.dialogs.confirmationdialog import ConfirmationDialog @@ -34,9 +34,9 @@ def __init__(self, parent, download_uri): DialogContainer.__init__(self, parent) torrent_name = download_uri - if torrent_name.startswith(FILE_PREFIX): - torrent_name = torrent_name[len(FILE_PREFIX) + 1 :] - elif torrent_name.startswith(MAGNET_PREFIX): + if torrent_name.startswith(FILE_SCHEME): + torrent_name = uri_to_path(torrent_name) + elif torrent_name.startswith(MAGNET_SCHEME): torrent_name = unquote_plus(torrent_name) self.download_uri = download_uri diff --git a/src/tribler-gui/tribler_gui/start_gui.py b/src/tribler-gui/tribler_gui/start_gui.py index 4588c11f8b8..33ab278b6ac 100644 --- a/src/tribler-gui/tribler_gui/start_gui.py +++ b/src/tribler-gui/tribler_gui/start_gui.py @@ -5,6 +5,7 @@ from PyQt5.QtCore import QSettings from tribler_common.logger import load_logger_config +from tribler_common.rest_utils import path_to_uri from tribler_common.sentry_reporter.sentry_reporter import SentryStrategy from tribler_core.check_os import ( @@ -58,7 +59,7 @@ def run_gui(api_port, api_key, root_state_dir, parsed_args): logger.info('GUI Application is already running. Passing a torrent file path to it.') for arg in sys.argv[1:]: if os.path.exists(arg) and arg.endswith(".torrent"): - app.send_message(f"file:{arg}") + app.send_message(path_to_uri(arg)) elif arg.startswith('magnet'): app.send_message(arg) logger.info('Close the current application.') diff --git a/src/tribler-gui/tribler_gui/tests/test_gui.py b/src/tribler-gui/tribler_gui/tests/test_gui.py index e73b1bb9a2d..3153fd68757 100644 --- a/src/tribler-gui/tribler_gui/tests/test_gui.py +++ b/src/tribler-gui/tribler_gui/tests/test_gui.py @@ -12,6 +12,7 @@ import tribler_common from tribler_common.reported_error import ReportedError +from tribler_common.rest_utils import path_to_uri from tribler_common.sentry_reporter.sentry_reporter import SentryReporter from tribler_common.tag_constants import MIN_TAG_LENGTH @@ -393,8 +394,8 @@ def test_add_download_url(window): go_to_and_wait_for_downloads(window) window.on_add_torrent_from_url() screenshot(window, name="add_torrent_url_dialog") - - window.dialog.dialog_widget.dialog_input.setText("file:" + str(TORRENT_WITH_DIRS)) + uri = path_to_uri(TORRENT_WITH_DIRS) + window.dialog.dialog_widget.dialog_input.setText(uri) QTest.mouseClick(window.dialog.buttons[0], Qt.LeftButton) QTest.qWait(200) screenshot(window, name="add_torrent_url_startdownload_dialog") diff --git a/src/tribler-gui/tribler_gui/tribler_app.py b/src/tribler-gui/tribler_gui/tribler_app.py index 916f016b866..bb82efefa3d 100644 --- a/src/tribler-gui/tribler_gui/tribler_app.py +++ b/src/tribler-gui/tribler_gui/tribler_app.py @@ -4,6 +4,8 @@ from PyQt5.QtCore import QCoreApplication, QEvent, Qt +from tribler_common.rest_utils import path_to_uri + from tribler_core.utilities.unicode import ensure_unicode from tribler_gui.code_executor import CodeExecutor @@ -38,7 +40,9 @@ def handle_uri(self, uri): def parse_sys_args(self, args): for arg in args[1:]: if os.path.exists(arg): - self.handle_uri(f"file:{ensure_unicode(arg, 'utf8')}") + file_path = ensure_unicode(arg, 'utf8') + uri = path_to_uri(file_path) + self.handle_uri(uri) elif arg.startswith('magnet'): self.handle_uri(arg) @@ -60,5 +64,6 @@ def parse_sys_args(self, args): def event(self, event): if event.type() == QEvent.FileOpen and event.file().endswith(".torrent"): - self.handle_uri(f'file:{event.file()}') + uri = path_to_uri(event.file()) + self.handle_uri(uri) return QtSingleApplication.event(self, event) diff --git a/src/tribler-gui/tribler_gui/tribler_window.py b/src/tribler-gui/tribler_gui/tribler_window.py index 06c5109e306..f1ec285c70f 100644 --- a/src/tribler-gui/tribler_gui/tribler_window.py +++ b/src/tribler-gui/tribler_gui/tribler_window.py @@ -40,7 +40,8 @@ from tribler_common.network_utils import default_network_utils from tribler_common.process_checker import ProcessChecker -from tribler_common.utilities import parse_query, uri_to_path +from tribler_common.rest_utils import FILE_SCHEME, MAGNET_SCHEME, uri_to_path +from tribler_common.utilities import parse_query from tribler_common.version_manager import VersionHistory from tribler_core.utilities.unicode import hexlify @@ -632,10 +633,11 @@ def perform_start_download_request( def show_add_torrent_to_channel_dialog_from_uri(self, uri): def on_add_button_pressed(channel_id): post_data = {} - if uri.startswith("file:"): - with open(uri_to_path(uri), "rb") as torrent_file: + if uri.startswith(FILE_SCHEME): + file_path = uri_to_path(uri) + with open(file_path) as torrent_file: post_data['torrent'] = b64encode(torrent_file.read()).decode('utf8') - elif uri.startswith("magnet:"): + elif uri.startswith(MAGNET_SCHEME): post_data['uri'] = uri if post_data: @@ -1139,8 +1141,7 @@ def get_urls_from_dragndrop_list(cls, e): def dragEnterEvent(self, e): file_urls = self.get_urls_from_dragndrop_list(e) - - if any(uri_to_path(fu).is_file() for fu in file_urls): + if any(Path(uri_to_path(fu)).is_file() for fu in file_urls): e.accept() else: e.ignore() @@ -1149,7 +1150,8 @@ def dropEvent(self, e): file_urls = self.get_urls_from_dragndrop_list(e) for fu in file_urls: - if uri_to_path(fu).is_file(): + path = Path(uri_to_path(fu)) + if path.is_file(): self.start_download_from_uri(fu) e.accept() From be5b4d82be783c7c03b3b74935cfb58f5ce9bb2f Mon Sep 17 00:00:00 2001 From: drew2a Date: Mon, 24 Jan 2022 17:10:26 +0100 Subject: [PATCH 5/5] Add scheme_from_uri --- .../tribler_common/rest_utils.py | 14 ++++++++++++- .../tribler_common/tests/test_rest_utils.py | 20 ++++++++++++++----- .../download_manager/download_manager.py | 17 ++++++++++++---- .../tests/test_torrentinfo_endpoint.py | 2 +- .../restapi/torrentinfo_endpoint.py | 17 ++++++++++++---- .../dialogs/startdownloaddialog.py | 8 +++++--- src/tribler-gui/tribler_gui/tribler_window.py | 7 ++++--- 7 files changed, 64 insertions(+), 21 deletions(-) diff --git a/src/tribler-common/tribler_common/rest_utils.py b/src/tribler-common/tribler_common/rest_utils.py index 0b3a84a007e..2a495adfc7e 100644 --- a/src/tribler-common/tribler_common/rest_utils.py +++ b/src/tribler-common/tribler_common/rest_utils.py @@ -4,8 +4,9 @@ from yarl import URL MAGNET_SCHEME = 'magnet' -HTTP_SCHEME = 'http' FILE_SCHEME = 'file' +HTTP_SCHEME = 'http' +HTTPS_SCHEME = 'https' def path_to_uri(file_path: Union[str, Any]) -> str: @@ -31,3 +32,14 @@ def uri_to_path(file_uri: str) -> str: # see https://github.com/aio-libs/yarl/issues/674 return path.lstrip('/') return path + + +def scheme_from_uri(uri: str) -> str: + """Get scheme from URI + + Examples: + 'file:///some/file' -> 'file' + 'magnet:link' -> 'magnet' + 'http://en.wikipedia.org' -> 'http' + """ + return URL(uri).scheme diff --git a/src/tribler-common/tribler_common/tests/test_rest_utils.py b/src/tribler-common/tribler_common/tests/test_rest_utils.py index 509a056b4a4..9d75c23f1f1 100644 --- a/src/tribler-common/tribler_common/tests/test_rest_utils.py +++ b/src/tribler-common/tribler_common/tests/test_rest_utils.py @@ -2,7 +2,7 @@ import pytest -from tribler_common.rest_utils import path_to_uri, uri_to_path +from tribler_common.rest_utils import path_to_uri, scheme_from_uri, uri_to_path NIX_PATHS = [ ('/path/to/file', 'file:///path/to/file'), @@ -16,28 +16,38 @@ ('C:\\path\\to\\%20%21file', 'file:///C:%5Cpath%5Cto%5C%2520%2521file'), ] +SCHEMES = [ + ('file:///path/to/file', 'file'), + ('magnet:link', 'magnet'), + ('http://en.wikipedia.org', 'http'), +] # posix -@pytest.mark.parametrize('path,uri', NIX_PATHS) +@pytest.mark.parametrize('path, uri', NIX_PATHS) @patch('os.name', 'posix') def test_path_to_uri(path, uri): assert path_to_uri(path) == uri -@pytest.mark.parametrize('path,uri', NIX_PATHS) +@pytest.mark.parametrize('path, uri', NIX_PATHS) @patch('os.name', 'posix') def test_uri_to_path(path, uri): assert uri_to_path(uri) == path # win -@pytest.mark.parametrize('path,uri', WIN_PATHS) +@pytest.mark.parametrize('path, uri', WIN_PATHS) @patch('os.name', 'nt') def test_path_to_uri_win(path, uri): assert path_to_uri(path) == uri -@pytest.mark.parametrize('path,uri', WIN_PATHS) +@pytest.mark.parametrize('path, uri', WIN_PATHS) @patch('os.name', 'nt') def test_uri_to_path_win(path, uri): assert uri_to_path(uri) == path + + +@pytest.mark.parametrize('path, scheme', SCHEMES) +def test_scheme_from_uri(path, scheme): + assert scheme_from_uri(path) == scheme diff --git a/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py b/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py index fb7e63249dd..4dfad6740b3 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py +++ b/src/tribler-core/tribler_core/components/libtorrent/download_manager/download_manager.py @@ -16,7 +16,14 @@ from ipv8.taskmanager import TaskManager, task from tribler_common.network_utils import default_network_utils -from tribler_common.rest_utils import FILE_SCHEME, HTTP_SCHEME, MAGNET_SCHEME, uri_to_path +from tribler_common.rest_utils import ( + FILE_SCHEME, + HTTPS_SCHEME, + HTTP_SCHEME, + MAGNET_SCHEME, + scheme_from_uri, + uri_to_path, +) from tribler_common.simpledefs import DLSTATUS_SEEDING, MAX_LIBTORRENT_RATE_LIMIT, NTFY, STATEDIR_CHECKPOINT_DIR from tribler_core.components.libtorrent.download_manager.dht_health_manager import DHTHealthManager @@ -505,10 +512,12 @@ def _map_call_on_ltsessions(self, hops, funcname, *args, **kwargs): getattr(self.get_session(hops), funcname)(*args, **kwargs) async def start_download_from_uri(self, uri, config=None): - if uri.startswith(HTTP_SCHEME): + scheme = scheme_from_uri(uri) + + if scheme in (HTTP_SCHEME, HTTPS_SCHEME): tdef = await TorrentDef.load_from_url(uri) return self.start_download(tdef=tdef, config=config) - if uri.startswith(MAGNET_SCHEME): + if scheme == MAGNET_SCHEME: name, infohash, _ = parse_magnetlink(uri) if infohash is None: raise RuntimeError("Missing infohash") @@ -517,7 +526,7 @@ async def start_download_from_uri(self, uri, config=None): else: tdef = TorrentDefNoMetainfo(infohash, "Unknown name" if name is None else name, url=uri) return self.start_download(tdef=tdef, config=config) - if uri.startswith(FILE_SCHEME): + if scheme == FILE_SCHEME: file = uri_to_path(uri) return self.start_download(torrent_file=file, config=config) raise Exception("invalid uri") diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py index 73479943cfc..0a74430d847 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_torrentinfo_endpoint.py @@ -100,7 +100,7 @@ async def mock_http_query(*_): return f.read() with patch("tribler_core.components.libtorrent.restapi.torrentinfo_endpoint.query_http_uri", new=mock_http_query): - verify_valid_dict(await do_request(rest_api, url, params={'uri': quote_plus(path)}, expected_code=200)) + verify_valid_dict(await do_request(rest_api, url, params={'uri': path}, expected_code=200)) path = quote_plus(f'magnet:?xt=urn:btih:{hexlify(UBUNTU_1504_INFOHASH)}' f'&dn=test torrent&tr=http://ubuntu.org/ann') diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py index 64fc98fcab7..d568e9d22b8 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py @@ -10,7 +10,14 @@ from marshmallow.fields import String -from tribler_common.rest_utils import FILE_SCHEME, HTTP_SCHEME, MAGNET_SCHEME, uri_to_path +from tribler_common.rest_utils import ( + FILE_SCHEME, + HTTPS_SCHEME, + HTTP_SCHEME, + MAGNET_SCHEME, + scheme_from_uri, + uri_to_path, +) from tribler_common.simpledefs import NTFY from tribler_core.components.libtorrent.download_manager.download_manager import DownloadManager @@ -82,7 +89,9 @@ async def get_torrent_info(self, request): return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST) metainfo = None - if uri.startswith(FILE_SCHEME): + scheme = scheme_from_uri(uri) + + if scheme == FILE_SCHEME: file = uri_to_path(uri) try: tdef = TorrentDef.load(file) @@ -90,7 +99,7 @@ async def get_torrent_info(self, request): except (TypeError, RuntimeError): return RESTResponse({"error": f"error while decoding torrent file: {file}"}, status=HTTP_INTERNAL_SERVER_ERROR) - elif uri.startswith(HTTP_SCHEME): + elif scheme in (HTTP_SCHEME, HTTPS_SCHEME): try: response = await query_http_uri(uri) except (ServerConnectionError, ClientResponseError) as e: @@ -102,7 +111,7 @@ async def get_torrent_info(self, request): metainfo = await self.download_manager.get_metainfo(infohash, timeout=60, hops=hops, url=response) else: metainfo = bdecode_compat(response) - elif uri.startswith(MAGNET_SCHEME): + elif scheme == MAGNET_SCHEME: infohash = parse_magnetlink(uri)[1] if infohash is None: return RESTResponse({"error": "missing infohash"}, status=HTTP_BAD_REQUEST) diff --git a/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py b/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py index 0ab94c9e681..fbc8e2c9b2a 100644 --- a/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py +++ b/src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py @@ -8,7 +8,7 @@ from PyQt5.QtCore import QTimer, pyqtSignal from PyQt5.QtWidgets import QFileDialog, QSizePolicy -from tribler_common.rest_utils import FILE_SCHEME, MAGNET_SCHEME, uri_to_path +from tribler_common.rest_utils import FILE_SCHEME, MAGNET_SCHEME, scheme_from_uri, uri_to_path from tribler_gui.defs import METAINFO_MAX_RETRIES, METAINFO_TIMEOUT from tribler_gui.dialogs.confirmationdialog import ConfirmationDialog @@ -34,9 +34,11 @@ def __init__(self, parent, download_uri): DialogContainer.__init__(self, parent) torrent_name = download_uri - if torrent_name.startswith(FILE_SCHEME): + scheme = scheme_from_uri(download_uri) + + if scheme == FILE_SCHEME: torrent_name = uri_to_path(torrent_name) - elif torrent_name.startswith(MAGNET_SCHEME): + elif scheme == MAGNET_SCHEME: torrent_name = unquote_plus(torrent_name) self.download_uri = download_uri diff --git a/src/tribler-gui/tribler_gui/tribler_window.py b/src/tribler-gui/tribler_gui/tribler_window.py index f1ec285c70f..8c92ffa94d8 100644 --- a/src/tribler-gui/tribler_gui/tribler_window.py +++ b/src/tribler-gui/tribler_gui/tribler_window.py @@ -40,7 +40,7 @@ from tribler_common.network_utils import default_network_utils from tribler_common.process_checker import ProcessChecker -from tribler_common.rest_utils import FILE_SCHEME, MAGNET_SCHEME, uri_to_path +from tribler_common.rest_utils import FILE_SCHEME, MAGNET_SCHEME, scheme_from_uri, uri_to_path from tribler_common.utilities import parse_query from tribler_common.version_manager import VersionHistory @@ -633,11 +633,12 @@ def perform_start_download_request( def show_add_torrent_to_channel_dialog_from_uri(self, uri): def on_add_button_pressed(channel_id): post_data = {} - if uri.startswith(FILE_SCHEME): + scheme = scheme_from_uri(uri) + if scheme == FILE_SCHEME: file_path = uri_to_path(uri) with open(file_path) as torrent_file: post_data['torrent'] = b64encode(torrent_file.read()).decode('utf8') - elif uri.startswith(MAGNET_SCHEME): + elif scheme == MAGNET_SCHEME: post_data['uri'] = uri if post_data: