Skip to content

Commit

Permalink
Fix #6700
Browse files Browse the repository at this point in the history
  • Loading branch information
drew2a committed Jan 20, 2022
1 parent 09df98c commit 2183121
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,37 +39,33 @@ async def test_get_torrentinfo(mock_dlmgr, tmp_path, rest_api, endpoint):
"""
Testing whether the API returns a correct dictionary with torrent info.
"""

def _path(file):
return f'file:{TESTS_DATA_DIR / file}'

endpoint.download_manager = mock_dlmgr

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))

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)
url = 'torrentinfo'
await do_request(rest_api, url, expected_code=400)
await do_request(rest_api, url, params={'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"

Expand All @@ -79,7 +74,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')
Expand Down Expand Up @@ -146,6 +141,7 @@ async def test_on_got_invalid_metainfo(mock_dlmgr, rest_api):
"""
Test whether the right operations happen when we receive an invalid metainfo object
"""

def get_metainfo(*_, **__):
return succeed("abcd")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from marshmallow.fields import String

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
Expand All @@ -26,6 +25,10 @@
from tribler_core.utilities.unicode import hexlify, recursive_unicode
from tribler_core.utilities.utilities import bdecode_compat, froze_it, parse_magnetlink

MAGNET = 'magnet'
HTTP = 'http'
FILE = 'file:'


async def query_http_uri(uri: str) -> bytes:
# This is moved to a separate method to be able to patch it separately,
Expand Down Expand Up @@ -68,29 +71,29 @@ def setup_routes(self):
}
)
async def get_torrent_info(self, request):
args = request.query
params = request.query
hops = params.get('hops', None)
uri = params.get('uri')

hops = None
if 'hops' in args:
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):
file = uri[len(FILE):]
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):
try:
response = await query_http_uri(uri)
except (ServerConnectionError, ClientResponseError) as e:
Expand All @@ -102,7 +105,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):
infohash = parse_magnetlink(uri)[1]
if infohash is None:
return RESTResponse({"error": "missing infohash"}, status=HTTP_BAD_REQUEST)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from json import JSONDecodeError
from typing import Dict, Optional

from aiohttp import ClientSession

Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion src/tribler-core/tribler_core/tests/tools/common.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/tribler-gui/tribler_gui/dialogs/dialogcontainer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from PyQt5.QtCore import QPoint
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QStyle, QStyleOption, QWidget
Expand All @@ -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, _):
Expand Down
21 changes: 12 additions & 9 deletions src/tribler-gui/tribler_gui/dialogs/startdownloaddialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
get_image_path,
get_ui_file_path,
is_dir_writable,
quote_plus_unicode,
tr,
)
from tribler_gui.widgets.torrentfiletreewidget import TORRENT_FILES_TREE_STYLESHEET

MAGNET = 'magnet:'

FILE = 'file:'


class StartDownloadDialog(DialogContainer):

Expand All @@ -36,9 +39,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):
torrent_name = torrent_name[len(FILE):]
elif torrent_name.startswith(MAGNET):
torrent_name = unquote_plus(torrent_name)

self.download_uri = download_uri
Expand Down Expand Up @@ -142,10 +145,11 @@ 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")
Expand All @@ -169,7 +173,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.
Expand Down
4 changes: 3 additions & 1 deletion src/tribler-gui/tribler_gui/start_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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}")
Expand Down

0 comments on commit 2183121

Please sign in to comment.