From fadce7a2972e227208962b30b2f17d1734b95bbd Mon Sep 17 00:00:00 2001 From: Constantine Kovalensky <45331093+kovalensky@users.noreply.github.com> Date: Sun, 27 Aug 2023 15:33:38 +0400 Subject: [PATCH] =?UTF-8?q?BitTorrent=20v2=20support=20=F0=9F=90=B8=20(#86?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BitTorrent v2 support * Update TorrentFileList.php * BitTorrent v2 support Added support for BitTorrent v2 file hash displaying, magnet links * Updated * Updated * Update changes.txt * Update Torrent.php * Update Torrent.php * Updated * Added support in announcer * Update announce.php * Update scrape.php * Update scrape.php * Update scrape.php * Changed the condition for single files * Update scrape.php * Update displaying_torrent.php * Update displaying_torrent.php * Update displaying_torrent.php --------- Co-authored-by: Roman Kelesidis --- bt/announce.php | 17 ++++++-- bt/scrape.php | 23 +++++++++-- install/sql/mysql.sql | 1 + install/upgrade/changes.txt | 1 + library/attach_mod/displaying_torrent.php | 2 +- library/includes/functions.php | 5 ++- src/Legacy/Torrent.php | 25 ++++++++++-- src/Legacy/TorrentFileList.php | 49 ++++++++++++++++++----- tracker.php | 4 +- viewforum.php | 4 +- 10 files changed, 104 insertions(+), 27 deletions(-) diff --git a/bt/announce.php b/bt/announce.php index 8565c4aa3..ee70f9a2b 100644 --- a/bt/announce.php +++ b/bt/announce.php @@ -63,12 +63,22 @@ $passkey = ${$passkey_key} ?? null; // Verify request // Required params (info_hash, peer_id, port, uploaded, downloaded, left, passkey) -if (!isset($info_hash) || strlen($info_hash) != 20) { - msg_die('Invalid info_hash'); +if (!isset($info_hash)) { + msg_die('info_hash does not exists'); } if (!isset($peer_id) || strlen($peer_id) != 20) { msg_die('Invalid peer_id'); } + +// Check info_hash version +if (strlen($info_hash) == 32) { + $is_bt_v2 = true; +} elseif (strlen($info_hash) == 20) { + $is_bt_v2 = false; +} else { + msg_die('Invalid info_hash'); +} + if (!isset($port) || $port < 0 || $port > 0xFFFF) { msg_die('Invalid port'); } @@ -178,13 +188,14 @@ if ($lp_info) { } else { // Verify if torrent registered on tracker and user authorized $info_hash_sql = rtrim(DB()->escape($info_hash), ' '); + $info_hash_where = $is_bt_v2 ? "WHERE tor.info_hash_v2 = '$info_hash_sql'" : "WHERE tor.info_hash = '$info_hash_sql'"; $passkey_sql = DB()->escape($passkey); $sql = " SELECT tor.topic_id, tor.poster_id, tor.tor_type, u.* FROM " . BB_BT_TORRENTS . " tor LEFT JOIN " . BB_BT_USERS . " u ON u.auth_key = '$passkey_sql' - WHERE tor.info_hash = '$info_hash_sql' + $info_hash_where LIMIT 1 "; diff --git a/bt/scrape.php b/bt/scrape.php index 81e7cbbce..af6128941 100644 --- a/bt/scrape.php +++ b/bt/scrape.php @@ -22,11 +22,21 @@ if (isset($_GET['?info_hash']) && !isset($_GET['info_hash'])) { $_GET['info_hash'] = $_GET['?info_hash']; } -if (!isset($_GET['info_hash']) || strlen($_GET['info_hash']) != 20) { - msg_die('Invalid info_hash'); +$is_bt_v2 = null; +$info_hash = isset($_GET['info_hash']) ? (string)$_GET['info_hash'] : null; + +if (!isset($info_hash)) { + msg_die('info_hash does not exists'); } -$info_hash = $_GET['info_hash']; +// Check info_hash version +if (strlen($info_hash) == 32) { + $is_bt_v2 = true; +} elseif (strlen($info_hash) == 20) { + $is_bt_v2 = false; +} else { + msg_die('Invalid info_hash'); +} function msg_die($msg) { @@ -42,15 +52,20 @@ function msg_die($msg) require __DIR__ . '/includes/init_tr.php'; $info_hash_sql = rtrim(DB()->escape($info_hash), ' '); +$info_hash_where = $is_bt_v2 ? "WHERE tor.info_hash_v2 = '$info_hash_sql'" : "WHERE tor.info_hash = '$info_hash_sql'"; $row = DB()->fetch_row(" SELECT tor.complete_count, snap.seeders, snap.leechers FROM " . BB_BT_TORRENTS . " tor LEFT JOIN " . BB_BT_TRACKER_SNAP . " snap ON (snap.topic_id = tor.topic_id) - WHERE tor.info_hash = '$info_hash_sql' + $info_hash_where LIMIT 1 "); +if (!$row) { + msg_die('Torrent not registered, info_hash = ' . bin2hex($info_hash_sql)); +} + $output['files'][$info_hash] = [ 'complete' => (int)$row['seeders'], 'downloaded' => (int)$row['complete_count'], diff --git a/install/sql/mysql.sql b/install/sql/mysql.sql index cc4d91eea..d93b6dbab 100644 --- a/install/sql/mysql.sql +++ b/install/sql/mysql.sql @@ -274,6 +274,7 @@ DROP TABLE IF EXISTS `bb_bt_torrents`; CREATE TABLE IF NOT EXISTS `bb_bt_torrents` ( `info_hash` VARBINARY(20) NOT NULL DEFAULT '', + `info_hash_v2` VARBINARY(32) NOT NULL DEFAULT '', `post_id` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0', `poster_id` MEDIUMINT(9) NOT NULL DEFAULT '0', `topic_id` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0', diff --git a/install/upgrade/changes.txt b/install/upgrade/changes.txt index c7aa1c8e7..e4cf6d56a 100644 --- a/install/upgrade/changes.txt +++ b/install/upgrade/changes.txt @@ -67,3 +67,4 @@ INSERT INTO `bb_cron` (`cron_active`, `cron_title`, `cron_script`, `schedule`, ` `disable_board`, `run_counter`) VALUES ('1', 'PM cleanup', 'clean_pm.php', 'daily', '', '05:00:00', '70', '', '', '', '1', '', '0', '1', '0'); ALTER TABLE `bb_posts_text` CHANGE `post_text` `post_text` MEDIUMTEXT NOT NULL; ALTER TABLE `bb_privmsgs_text` CHANGE `privmsgs_text` `privmsgs_text` MEDIUMTEXT NOT NULL; +ALTER TABLE `bb_bt_torrents` ADD COLUMN `info_hash_v2` VARBINARY(32) NOT NULL DEFAULT ''; diff --git a/library/attach_mod/displaying_torrent.php b/library/attach_mod/displaying_torrent.php index ed08a4910..9ca1548dd 100644 --- a/library/attach_mod/displaying_torrent.php +++ b/library/attach_mod/displaying_torrent.php @@ -155,7 +155,7 @@ if ($tor_reged && $tor_info) { $tor_type = $tor_info['tor_type']; // Magnet link - $tor_magnet = create_magnet($tor_info['info_hash'], $user_passkey); + $tor_magnet = create_magnet($tor_info['info_hash'], $tor_info['info_hash_v2'], $user_passkey); // ratio limits $min_ratio_dl = $bb_cfg['bt_min_ratio_allow_dl_tor']; diff --git a/library/includes/functions.php b/library/includes/functions.php index 91dab9e6b..451c7cb56 100644 --- a/library/includes/functions.php +++ b/library/includes/functions.php @@ -1760,11 +1760,12 @@ function decode_text_match($txt) * Create magnet link * * @param string $infohash + * @param string $infohash_v2 * @param string|bool $auth_key * * @return string */ -function create_magnet($infohash, $auth_key): string +function create_magnet(string $infohash, string $infohash_v2, $auth_key): string { global $bb_cfg, $images; @@ -1778,7 +1779,7 @@ function create_magnet($infohash, $auth_key): string return false; } - return ''; + return ''; } function set_die_append_msg($forum_id = null, $topic_id = null, $group_id = null) diff --git a/src/Legacy/Torrent.php b/src/Legacy/Torrent.php index 43000b065..aeb956854 100644 --- a/src/Legacy/Torrent.php +++ b/src/Legacy/Torrent.php @@ -287,7 +287,8 @@ class Torrent $topic_id = $torrent['topic_id']; $forum_id = $torrent['forum_id']; $poster_id = $torrent['poster_id']; - $info_hash = null; + $info_hash = $info_hash_v2 = null; + $info_hash_sql = $info_hash_v2_sql = null; if ($torrent['extension'] !== TORRENT_EXT) { return self::torrent_error_exit($lang['NOT_TORRENT']); @@ -345,10 +346,23 @@ class Torrent return self::torrent_error_exit($lang['TORFILE_INVALID']); } + // Check if torrent contains info_hash v2 + $bt_v2 = false; + if (($info['meta version'] ?? null) == 2 && is_array($info['file tree'] ?? null)) { + $bt_v2 = true; + } + + // Getting info_hash v1 $info_hash = pack('H*', sha1(\SandFox\Bencode\Bencode::encode($info))); $info_hash_sql = rtrim(DB()->escape($info_hash), ' '); $info_hash_md5 = md5($info_hash); + // Getting info_hash v2 + if ($bt_v2) { + $info_hash_v2 = pack('H*', hash('sha256', \SandFox\Bencode\Bencode::encode($info))); + $info_hash_v2_sql = rtrim(DB()->escape($info_hash_v2), ' '); + } + // Ocelot if ($bb_cfg['ocelot']['enabled']) { self::ocelot_update_tracker('add_torrent', ['info_hash' => rawurlencode($info_hash), 'id' => $topic_id, 'freetorrent' => 0]); @@ -366,7 +380,10 @@ class Torrent $totallen = (float)$info['length']; } elseif (isset($info['files']) && \is_array($info['files'])) { foreach ($info['files'] as $fn => $f) { - $totallen += (float)$f['length']; + // Exclude padding files + if (($f['attr'] ?? null) !== 'p') { + $totallen += (float)$f['length']; + } } } else { return self::torrent_error_exit($lang['TORFILE_INVALID']); @@ -374,8 +391,8 @@ class Torrent $size = sprintf('%.0f', (float)$totallen); - $columns = ' info_hash, post_id, poster_id, topic_id, forum_id, attach_id, size, reg_time, tor_status'; - $values = "'$info_hash_sql', $post_id, $poster_id, $topic_id, $forum_id, $attach_id, '$size', $reg_time, $tor_status"; + $columns = 'info_hash, info_hash_v2, post_id, poster_id, topic_id, forum_id, attach_id, size, reg_time, tor_status'; + $values = "'$info_hash_sql', '$info_hash_v2_sql', $post_id, $poster_id, $topic_id, $forum_id, $attach_id, '$size', $reg_time, $tor_status"; $sql = "INSERT INTO " . BB_BT_TORRENTS . " ($columns) VALUES ($values)"; diff --git a/src/Legacy/TorrentFileList.php b/src/Legacy/TorrentFileList.php index c3b9d1bcc..b9bc54ac7 100644 --- a/src/Legacy/TorrentFileList.php +++ b/src/Legacy/TorrentFileList.php @@ -43,18 +43,45 @@ class TorrentFileList { global $html; - $this->build_filelist_array(); + if (($this->tor_decoded['info']['meta version'] ?? null) == 2 && is_array($this->tor_decoded['info']['file tree'] ?? null)) { + // v2 + function fileTree($array, $name = '') + { + $folders = []; + $rootFiles = []; - if ($this->multiple) { - if ($this->files_ary['/'] !== '') { - $this->files_ary = array_merge($this->files_ary, $this->files_ary['/']); - unset($this->files_ary['/']); + foreach ($array as $key => $value) { + if (is_array($value) && !isset($value[''])) { + $html_v2 = fileTree($value); + $folders[] = "
  • $key
  • "; + } else { + $length = $value['']['length']; + $root = bin2hex($value['']['pieces root'] ?? ''); + $rootFiles[] = "
  • $key$length $root
  • "; + } + } + + $allItems = array_merge($folders, $rootFiles); + + return '
    ' . (empty($folders) ? '' : $name) . '
    '; } - $filelist = $html->array2html($this->files_ary); - return "
    {$this->root_dir}
    $filelist"; - } - return implode('', $this->files_ary['/']); + return fileTree($this->tor_decoded['info']['file tree'], $this->tor_decoded['info']['name']); + } else { + // v1 + $this->build_filelist_array(); + + if ($this->multiple) { + if ($this->files_ary['/'] !== '') { + $this->files_ary = array_merge($this->files_ary, $this->files_ary['/']); + unset($this->files_ary['/']); + } + $filelist = $html->array2html($this->files_ary); + return "
    {$this->root_dir}
    $filelist"; + } + + return implode('', $this->files_ary['/']); + } } /** @@ -81,6 +108,10 @@ class TorrentFileList if (!isset($f['path']) || !\is_array($f['path'])) { continue; } + // Exclude padding files + if (($f['attr'] ?? null) === 'p') { + continue; + } array_deep($f['path'], 'clean_tor_dirname'); $length = isset($f['length']) ? (float)$f['length'] : 0; diff --git a/tracker.php b/tracker.php index b1a6aa981..9e18dc1d7 100644 --- a/tracker.php +++ b/tracker.php @@ -645,7 +645,7 @@ if ($allowed_forums) { $select = " SELECT tor.topic_id, tor.post_id, tor.attach_id, tor.size, tor.reg_time, tor.complete_count, tor.seeder_last_seen, tor.tor_status, tor.tor_type, - t.topic_title, t.topic_time, t.topic_replies, t.topic_views, sn.seeders, sn.leechers, tor.info_hash + t.topic_title, t.topic_time, t.topic_replies, t.topic_views, sn.seeders, sn.leechers, tor.info_hash, tor.info_hash_v2 "; $select .= (!$hide_speed) ? ", sn.speed_up, sn.speed_down" : ''; $select .= (!$hide_forum) ? ", tor.forum_id" : ''; @@ -702,7 +702,7 @@ if ($allowed_forums) { $s_last = $tor['seeder_last_seen']; $att_id = $tor['attach_id']; $size = $tor['size']; - $tor_magnet = create_magnet($tor['info_hash'], \TorrentPier\Legacy\Torrent::getPasskey($user_id)); + $tor_magnet = create_magnet($tor['info_hash'], $tor['info_hash_v2'], \TorrentPier\Legacy\Torrent::getPasskey($user_id)); $compl = $tor['complete_count']; $dl_sp = ($dl) ? humn_size($dl, 0, 'KB') . '/s' : '0 KB/s'; $ul_sp = ($ul) ? humn_size($ul, 0, 'KB') . '/s' : '0 KB/s'; diff --git a/viewforum.php b/viewforum.php index a1055c623..f4d7a8898 100644 --- a/viewforum.php +++ b/viewforum.php @@ -285,7 +285,7 @@ if ($forum_data['allow_reg_tracker']) { } $select_tor_sql = ', - bt.auth_key, tor.info_hash, tor.size AS tor_size, tor.reg_time, tor.complete_count, tor.seeder_last_seen, tor.attach_id, tor.tor_status, tor.tor_type, + bt.auth_key, tor.info_hash, tor.info_hash_v2, tor.size AS tor_size, tor.reg_time, tor.complete_count, tor.seeder_last_seen, tor.attach_id, tor.tor_status, tor.tor_type, sn.seeders, sn.leechers '; $select_tor_sql .= ($join_dl) ? ', dl.user_status AS dl_status' : ''; @@ -470,7 +470,7 @@ foreach ($topic_rowset as $topic) { )); if (isset($topic['tor_size'])) { - $tor_magnet = create_magnet($topic['info_hash'], ($topic['auth_key'] ?? false)); + $tor_magnet = create_magnet($topic['info_hash'], $topic['info_hash_v2'], ($topic['auth_key'] ?? false)); $template->assign_block_vars('t.tor', array( 'SEEDERS' => (int)$topic['seeders'],