/* * Bittorrent Client using Qt and libtorrent. * Copyright (C) 2015-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give permission to * link this program with the OpenSSL project's "OpenSSL" library (or with * modified versions of it that use the same license as the "OpenSSL" library), * and distribute the linked executables. You must obey the GNU General Public * License in all respects for all of the code used other than "OpenSSL". If you * modify file(s), you may extend this exception to your version of the file(s), * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. */ #include "torrentdescriptor.h" #include #include #include #include #include #include #include #include #include #include #include "base/global.h" #include "base/preferences.h" #include "base/utils/io.h" #include "infohash.h" #include "trackerentry.h" namespace { // BEP9 Extension for Peers to Send Metadata Files bool isV1Hash(const QString &string) { // There are 2 representations for BitTorrent v1 info hash: // 1. 40 chars hex-encoded string // == 20 (SHA-1 length in bytes) * 2 (each byte maps to 2 hex characters) // 2. 32 chars Base32 encoded string // == 20 (SHA-1 length in bytes) * 1.6 (the efficiency of Base32 encoding) const int V1_HEX_SIZE = SHA1Hash::length() * 2; const int V1_BASE32_SIZE = SHA1Hash::length() * 1.6; return ((((string.size() == V1_HEX_SIZE)) && !string.contains(QRegularExpression(u"[^0-9A-Fa-f]"_s))) || ((string.size() == V1_BASE32_SIZE) && !string.contains(QRegularExpression(u"[^2-7A-Za-z]"_s)))); } bool isV2Hash(const QString &string) { // There are 1 representation for BitTorrent v2 info hash: // 1. 64 chars hex-encoded string // == 32 (SHA-2 256 length in bytes) * 2 (each byte maps to 2 hex characters) const int V2_HEX_SIZE = SHA256Hash::length() * 2; return (string.size() == V2_HEX_SIZE) && !string.contains(QRegularExpression(u"[^0-9A-Fa-f]"_s)); } lt::load_torrent_limits loadTorrentLimits() { const auto *pref = Preferences::instance(); lt::load_torrent_limits limits; limits.max_buffer_size = static_cast(pref->getTorrentFileSizeLimit()); limits.max_decode_depth = pref->getBdecodeDepthLimit(); limits.max_decode_tokens = pref->getBdecodeTokenLimit(); return limits; } } const int TORRENTDESCRIPTOR_TYPEID = qRegisterMetaType(); nonstd::expected BitTorrent::TorrentDescriptor::load(const QByteArray &data) noexcept try { return TorrentDescriptor(lt::load_torrent_buffer(lt::span(data.data(), data.size()), loadTorrentLimits())); } catch (const lt::system_error &err) { return nonstd::make_unexpected(QString::fromLocal8Bit(err.what())); } nonstd::expected BitTorrent::TorrentDescriptor::loadFromFile(const Path &path) noexcept try { return TorrentDescriptor(lt::load_torrent_file(path.toString().toStdString(), loadTorrentLimits())); } catch (const lt::system_error &err) { return nonstd::make_unexpected(QString::fromLocal8Bit(err.what())); } nonstd::expected BitTorrent::TorrentDescriptor::parse(const QString &str) noexcept try { QString magnetURI = str; if (isV2Hash(str)) magnetURI = u"magnet:?xt=urn:btmh:1220" + str; // 0x12 0x20 is the "multihash format" tag for the SHA-256 hashing scheme. else if (isV1Hash(str)) magnetURI = u"magnet:?xt=urn:btih:" + str; return TorrentDescriptor(lt::parse_magnet_uri(magnetURI.toStdString())); } catch (const lt::system_error &err) { return nonstd::make_unexpected(QString::fromLocal8Bit(err.what())); } nonstd::expected BitTorrent::TorrentDescriptor::saveToFile(const Path &path) const try { const lt::entry torrentEntry = lt::write_torrent_file(m_ltAddTorrentParams); const nonstd::expected result = Utils::IO::saveToFile(path, torrentEntry); if (!result) return result.get_unexpected(); return {}; } catch (const lt::system_error &err) { return nonstd::make_unexpected(QString::fromLocal8Bit(err.what())); } BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTorrentParams) : m_ltAddTorrentParams {std::move(ltAddTorrentParams)} { if (m_ltAddTorrentParams.ti && m_ltAddTorrentParams.ti->is_valid()) m_info.emplace(*m_ltAddTorrentParams.ti); } BitTorrent::InfoHash BitTorrent::TorrentDescriptor::infoHash() const { #ifdef QBT_USES_LIBTORRENT2 return m_ltAddTorrentParams.info_hashes; #else return m_ltAddTorrentParams.info_hash; #endif } QString BitTorrent::TorrentDescriptor::name() const { return m_info ? m_info->name() : QString::fromStdString(m_ltAddTorrentParams.name); } QDateTime BitTorrent::TorrentDescriptor::creationDate() const { return ((m_ltAddTorrentParams.ti->creation_date() != 0) ? QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date()) : QDateTime()); } QString BitTorrent::TorrentDescriptor::creator() const { return QString::fromStdString(m_ltAddTorrentParams.ti->creator()); } QString BitTorrent::TorrentDescriptor::comment() const { return QString::fromStdString(m_ltAddTorrentParams.ti->comment()); } const std::optional &BitTorrent::TorrentDescriptor::info() const { return m_info; } void BitTorrent::TorrentDescriptor::setTorrentInfo(TorrentInfo torrentInfo) { if (!torrentInfo.isValid()) { m_info.reset(); m_ltAddTorrentParams.ti.reset(); } else { m_info = std::move(torrentInfo); m_ltAddTorrentParams.ti = m_info->nativeInfo(); #ifdef QBT_USES_LIBTORRENT2 m_ltAddTorrentParams.info_hashes = m_ltAddTorrentParams.ti->info_hashes(); #else m_ltAddTorrentParams.info_hash = m_ltAddTorrentParams.ti->info_hash(); #endif } } QVector BitTorrent::TorrentDescriptor::trackers() const { QVector ret; ret.reserve(static_cast(m_ltAddTorrentParams.trackers.size())); std::size_t i = 0; for (const std::string &tracker : m_ltAddTorrentParams.trackers) ret.append({QString::fromStdString(tracker), m_ltAddTorrentParams.tracker_tiers[i++]}); return ret; } QVector BitTorrent::TorrentDescriptor::urlSeeds() const { QVector urlSeeds; urlSeeds.reserve(static_cast(m_ltAddTorrentParams.url_seeds.size())); for (const std::string &nativeURLSeed : m_ltAddTorrentParams.url_seeds) urlSeeds.append(QUrl(QString::fromStdString(nativeURLSeed))); return urlSeeds; } const libtorrent::add_torrent_params &BitTorrent::TorrentDescriptor::ltAddTorrentParams() const { return m_ltAddTorrentParams; }