From bfc89d6f6ad760b367f39b2bec582b20575bd6f4 Mon Sep 17 00:00:00 2001 From: qstokkink Date: Tue, 1 Oct 2024 11:29:00 +0200 Subject: [PATCH] Fixed selected files overflowing the URL --- .../libtorrent/restapi/downloads_endpoint.py | 9 +++- .../ui/src/services/tribler.service.ts | 46 ++++++++++++++++--- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/tribler/core/libtorrent/restapi/downloads_endpoint.py b/src/tribler/core/libtorrent/restapi/downloads_endpoint.py index 133de85006..3b6813d9a8 100644 --- a/src/tribler/core/libtorrent/restapi/downloads_endpoint.py +++ b/src/tribler/core/libtorrent/restapi/downloads_endpoint.py @@ -5,7 +5,7 @@ from binascii import hexlify, unhexlify from contextlib import suppress from pathlib import Path, PurePosixPath -from typing import TYPE_CHECKING, TypedDict, cast +from typing import TYPE_CHECKING, Any, Optional, TypedDict, cast import libtorrent as lt from aiohttp import web @@ -411,7 +411,12 @@ async def add_download(self, request: Request) -> RESTResponse: params['anon_hops'] = int(params['anon_hops']) if 'safe_seeding' in params: params['safe_seeding'] = params['safe_seeding'] != 'false' - tdef = TorrentDef.load_from_memory(await request.read()) + body = await request.read() + metainfo = cast(dict[bytes, Any], lt.bdecode(body)) + packed_selected_files = cast(Optional[list[int]], metainfo.pop(b"selected_files", None)) + if packed_selected_files is not None: + params["selected_files"] = packed_selected_files + tdef = TorrentDef.load_from_dict(metainfo) else: params = await request.json() uri = params.get("uri") diff --git a/src/tribler/ui/src/services/tribler.service.ts b/src/tribler/ui/src/services/tribler.service.ts index f4b6c687ee..bb63f84a42 100644 --- a/src/tribler/ui/src/services/tribler.service.ts +++ b/src/tribler/ui/src/services/tribler.service.ts @@ -72,14 +72,48 @@ export class TriblerService { } } + async _mixSelectedIntoMetainfo(torrent: File, selected_files: number[] | undefined): Promise { + // Read the torrent data from file + const raw_bytes = new Uint8Array(await torrent.arrayBuffer()); + + // Create the new data blocks + const new_key = new Uint8Array([49, 52, 58, 115, 101, 108, 101, 99, 116, 101, 100, 95, 102, 105, 108, 101, 115]) // b"14:selected_files" + if ((selected_files !== undefined) && (selected_files.length > 0)){ + const str_selected = selected_files.join('ei'); + var new_list = new Uint8Array(4 + str_selected.length); // b"li" + str_selected + b"ee" + new_list[0] = 108; // b"l" + new_list[1] = 105; // b"i" + new_list.set(str_selected.split('').map((c) => { return c.charCodeAt(0); }), 2); + new_list[new_list.length - 2] = 101; // b"e" + new_list[new_list.length - 1] = 101; // b"e" + } else { + var new_list = new Uint8Array([108, 101]); // b"le" + } + + // Merge everything into the output buffer + const buf_len = raw_bytes.length + new_list.length + 17; // (raw_bytes.length - 1) + 17 + new_list.length + 1 + const modified_data = new Uint8Array(buf_len); + modified_data.set(raw_bytes, 0); + modified_data.set(new_key, raw_bytes.length - 1); // [!] this overwrites the last byte of raw_bytes + modified_data.set(new_list, raw_bytes.length + 16); // (raw_bytes.length - 1) + 17 + modified_data[buf_len - 1] = 101; // b"e" + + return modified_data; + } + async startDownloadFromFile(torrent: File, params: DownloadConfig = {}): Promise { try { - return (await this.http.put('/downloads', torrent, { - params: params, - headers: { - 'Content-Type': 'applications/x-bittorrent' - } - })).data.started; + // The way selected files are URL encoded leads to the pattern "&selected_files[]=<>", + // roughly 20 characters per file in a torrent. With the max of 8190 bytes for a URL, this will lead to + // HTTP 400 errors for torrents that go over +- 400 files. + return (await this.http.put('/downloads', + await this._mixSelectedIntoMetainfo(torrent, params.selected_files), { + params: {...params, "selected_files": []}, + headers: { + 'Content-Type': 'applications/x-bittorrent' + } + } + )).data.started; } catch (error) { return formatAxiosError(error as Error | AxiosError); }