From 2a2765f7f3b74f8e0b21ce392a9d59e75168d4c5 Mon Sep 17 00:00:00 2001 From: drew2a Date: Fri, 9 Sep 2022 14:50:01 +0200 Subject: [PATCH] Fix UnicodeDecodeError --- .../libtorrent/download_manager/download.py | 16 ++++---- .../libtorrent/tests/test_download.py | 38 +++++++++++++++---- src/tribler/gui/tribler_request_manager.py | 1 + src/tribler/gui/tribler_window.py | 7 ++-- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/tribler/core/components/libtorrent/download_manager/download.py b/src/tribler/core/components/libtorrent/download_manager/download.py index 95ffa115740..50030abf081 100644 --- a/src/tribler/core/components/libtorrent/download_manager/download.py +++ b/src/tribler/core/components/libtorrent/download_manager/download.py @@ -24,7 +24,7 @@ from tribler.core.utilities.notifier import Notifier from tribler.core.utilities.osutils import fix_filebasename from tribler.core.utilities.path_util import Path -from tribler.core.utilities.simpledefs import DLSTATUS_SEEDING, DLSTATUS_STOPPED, DOWNLOAD, NTFY +from tribler.core.utilities.simpledefs import DLSTATUS_SEEDING, DLSTATUS_STOPPED, DOWNLOAD from tribler.core.utilities.unicode import ensure_unicode, hexlify from tribler.core.utilities.utilities import bdecode_compat @@ -269,7 +269,7 @@ def on_save_resume_data_alert(self, alert): Callback for the alert that contains the resume data of a specific download. This resume data will be written to a file on disk. """ - self._logger.debug(f'On save resume data alert: {alert}') + self._logger.debug('On save resume data alert: %s', alert) if self.checkpoint_disabled: return @@ -557,13 +557,13 @@ def get_torrent(self): @check_handle(default={}) def get_tracker_status(self): # Make sure all trackers are in the tracker_status dict - for announce_entry in self.handle.trackers(): - if announce_entry['url'] not in self.tracker_status: - try: - url = announce_entry['url'] + try: + for announce_entry in self.handle.trackers(): + url = announce_entry['url'] + if url not in self.tracker_status: self.tracker_status[url] = [0, 'Not contacted yet'] - except UnicodeDecodeError: - pass + except UnicodeDecodeError: + self._logger.warning('UnicodeDecodeError in get_tracker_status') # Count DHT and PeX peers dht_peers = pex_peers = 0 diff --git a/src/tribler/core/components/libtorrent/tests/test_download.py b/src/tribler/core/components/libtorrent/tests/test_download.py index 69799c91d2f..337972129f7 100644 --- a/src/tribler/core/components/libtorrent/tests/test_download.py +++ b/src/tribler/core/components/libtorrent/tests/test_download.py @@ -1,19 +1,18 @@ from asyncio import Future, sleep from pathlib import Path -from unittest.mock import Mock - -from ipv8.util import succeed +from unittest.mock import MagicMock, Mock import libtorrent as lt -from libtorrent import bencode - import pytest +from ipv8.util import succeed +from libtorrent import bencode -from tribler.core.exceptions import SaveResumeDataError +from tribler.core.components.libtorrent.download_manager.download import Download from tribler.core.components.libtorrent.download_manager.download_config import DownloadConfig +from tribler.core.components.libtorrent.utils.torrent_utils import get_info_from_handle +from tribler.core.exceptions import SaveResumeDataError from tribler.core.tests.tools.base_test import MockObject from tribler.core.tests.tools.common import TESTS_DATA_DIR -from tribler.core.components.libtorrent.utils.torrent_utils import get_info_from_handle from tribler.core.utilities.unicode import hexlify from tribler.core.utilities.utilities import bdecode_compat @@ -89,6 +88,7 @@ def test_selected_files(mock_handle, test_download): """ Test whether the selected files are set correctly """ + def mocked_set_file_prios(_): mocked_set_file_prios.called = True @@ -116,6 +116,7 @@ def test_selected_files_no_files(mock_handle, test_download): """ Test that no files are selected if torrent info is not available. """ + def mocked_set_file_prios(_): mocked_set_file_prios.called = True @@ -151,6 +152,7 @@ async def test_set_share_mode(mock_handle, test_download): """ Test whether we set the right share mode in Download """ + def mocked_set_share_mode(val): assert val mocked_set_share_mode.called = True @@ -165,11 +167,12 @@ def test_get_num_connected_seeds_peers(mock_handle, test_download): """ Test whether connected peers and seeds are correctly returned """ + def get_peer_info(seeders, leechers): peer_info = [] for _ in range(seeders): seeder = MockObject() - seeder.flags = 140347 # some value where seed flag(1024) is true + seeder.flags = 140347 # some value where seed flag(1024) is true seeder.seed = 1024 peer_info.append(seeder) for _ in range(leechers): @@ -193,6 +196,7 @@ async def test_set_priority(mock_handle, test_download): """ Test whether setting the priority calls the right methods in Download """ + def mocked_set_priority(prio): assert prio == 1234 mocked_set_priority.called = True @@ -207,6 +211,7 @@ def test_add_trackers(mock_handle, test_download): """ Testing whether trackers are added to the libtorrent handler in Download """ + def mocked_add_trackers(tracker_info): assert isinstance(tracker_info, dict) assert tracker_info['url'] == 'http://google.com' @@ -284,6 +289,7 @@ def test_metadata_received_invalid_info(mock_handle, test_download): """ Testing whether the right operations happen when we receive metadata but the torrent info is invalid """ + def mocked_checkpoint(): raise RuntimeError("This code should not be reached!") @@ -297,6 +303,7 @@ def test_metadata_received_invalid_torrent_with_value_error(mock_handle, test_do Testing whether the right operations happen when we receive metadata but the torrent info is invalid and throws Value Error """ + def mocked_checkpoint(): raise RuntimeError("This code should not be reached!") @@ -318,6 +325,7 @@ def test_torrent_checked_alert(mock_handle, test_download): """ Testing whether the right operations happen after a torrent checked alert is received """ + def mocked_pause_checkpoint(): mocked_pause_checkpoint.called = True return succeed(None) @@ -404,3 +412,17 @@ async def test_checkpoint_timeout(test_download): test_download.futures['save_resume_data'].pop(0) await sleep(0.2) assert task.done() + + +def test_get_tracker_status_unicode_decode_error(test_download: Download): + """ + Sometimes a tracker entry raises UnicodeDecodeError while accessing it's values. + The reason for this is unknown. + In this test we ensures that this types of bugs don't affect `get_tracker_status` method. + See: https://github.com/Tribler/tribler/issues/7036 + """ + + test_download.handle = MagicMock(trackers=MagicMock(side_effect=UnicodeDecodeError('', b'', 0, 0, ''))) + test_download.get_tracker_status() + + assert test_download.handle.trackers.called diff --git a/src/tribler/gui/tribler_request_manager.py b/src/tribler/gui/tribler_request_manager.py index b877e547262..452b6299097 100644 --- a/src/tribler/gui/tribler_request_manager.py +++ b/src/tribler/gui/tribler_request_manager.py @@ -222,6 +222,7 @@ def on_finished(self, request): and not TriblerRequestManager.window.core_manager.shutting_down ): # TODO: Report REST API errors to Sentry + logging.error(f'REST API error: {json_result}') request_manager.show_error(TriblerRequestManager.get_message_from_error(json_result)) else: self.received_json.emit(json_result) diff --git a/src/tribler/gui/tribler_window.py b/src/tribler/gui/tribler_window.py index ff414aa0fc5..80152df02b1 100644 --- a/src/tribler/gui/tribler_window.py +++ b/src/tribler/gui/tribler_window.py @@ -42,7 +42,6 @@ QSystemTrayIcon, QTreeWidget, ) - from psutil import LINUX from tribler.core.upgrade.version_manager import VersionHistory @@ -84,8 +83,8 @@ from tribler.gui.dialogs.new_channel_dialog import NewChannelDialog from tribler.gui.dialogs.startdownloaddialog import StartDownloadDialog from tribler.gui.error_handler import ErrorHandler -from tribler.gui.exceptions import TriblerGuiTestException from tribler.gui.event_request_manager import EventRequestManager +from tribler.gui.exceptions import TriblerGuiTestException from tribler.gui.tribler_action_menu import TriblerActionMenu from tribler.gui.tribler_request_manager import ( TriblerNetworkRequest, @@ -681,8 +680,8 @@ def on_add_button_pressed(channel_id): scheme = scheme_from_url(uri) if scheme == FILE_SCHEME: file_path = url_to_path(uri) - with open(file_path) as torrent_file: - post_data['torrent'] = b64encode(torrent_file.read()).decode('utf8') + content = Path(file_path).read_bytes() + post_data['torrent'] = b64encode(content).decode('ascii') elif scheme == MAGNET_SCHEME: post_data['uri'] = uri