File indexing completed on 2025-02-16 04:37:35

0001 /*
0002     SPDX-FileCopyrightText: 2009 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "magnetlink.h"
0008 #include <QRegularExpression>
0009 #include <QStringList>
0010 #include <QUrlQuery>
0011 #include <util/error.h>
0012 #include <util/log.h>
0013 
0014 namespace bt
0015 {
0016 MagnetLink::MagnetLink()
0017 {
0018 }
0019 
0020 MagnetLink::MagnetLink(const MagnetLink &mlink)
0021     : magnet_string(mlink.magnet_string)
0022     , info_hash(mlink.info_hash)
0023     , torrent_url(mlink.torrent_url)
0024     , tracker_urls(mlink.tracker_urls)
0025     , path(mlink.path)
0026     , name(mlink.name)
0027 {
0028 }
0029 
0030 MagnetLink::MagnetLink(const QUrl &mlink)
0031 {
0032     parse(mlink);
0033 }
0034 
0035 MagnetLink::MagnetLink(const QString &mlink)
0036 {
0037     parse(QUrl(mlink));
0038 }
0039 
0040 MagnetLink::~MagnetLink()
0041 {
0042 }
0043 
0044 MagnetLink &MagnetLink::operator=(const bt::MagnetLink &mlink)
0045 {
0046     magnet_string = mlink.magnet_string;
0047     info_hash = mlink.info_hash;
0048     tracker_urls = mlink.tracker_urls;
0049     torrent_url = mlink.torrent_url;
0050     path = mlink.path;
0051     name = mlink.name;
0052     return *this;
0053 }
0054 
0055 bool MagnetLink::operator==(const bt::MagnetLink &mlink) const
0056 {
0057     return info_hash == mlink.infoHash();
0058 }
0059 
0060 static QList<QUrl> GetTrackers(const QUrl &url)
0061 {
0062     QList<QUrl> result;
0063     for (QString tr : QUrlQuery(url).allQueryItemValues("tr", QUrl::FullyDecoded))
0064         result << QUrl(tr.replace(QLatin1Char('+'), QLatin1Char(' ')));
0065     return result;
0066 }
0067 
0068 void MagnetLink::parse(const QUrl &url)
0069 {
0070     if (url.scheme() != QLatin1String("magnet")) {
0071         Out(SYS_GEN | LOG_NOTICE) << "Invalid protocol of magnet link " << url << endl;
0072         return;
0073     }
0074 
0075     torrent_url = QUrlQuery(url).queryItemValue(QStringLiteral("to"), QUrl::FullyDecoded);
0076     // magnet://description-of-content.btih.HASH(-HASH)*.dht/path/file?x.pt=&x.to=
0077 
0078     // TODO automatically select these files and prefetches from here
0079     path = QUrlQuery(url).queryItemValue(QStringLiteral("pt"));
0080     if (path.isEmpty() && url.path() != QLatin1String("/")) {
0081         // TODO find out why RemoveTrailingSlash does not work
0082         path = url.adjusted(QUrl::StripTrailingSlash).path().remove(QRegularExpression(QLatin1String("^/")));
0083     }
0084 
0085     QString xt = QUrlQuery(url).queryItemValue(QLatin1String("xt"));
0086     if (xt.isEmpty() || !xt.startsWith(QLatin1String("urn:btih:"))) {
0087         static QRegularExpression btihHash(QLatin1String("([^\\.]+).btih"));
0088 
0089         const QRegularExpressionMatch match = btihHash.match(url.host());
0090 
0091         if (match.hasMatch()) {
0092             QString primaryHash = match.captured(1).split('-')[0];
0093             xt = QLatin1String("urn:btih:") + primaryHash;
0094         } else {
0095             Out(SYS_GEN | LOG_NOTICE) << "No hash found in magnet link " << url << endl;
0096             return;
0097         }
0098     }
0099 
0100     QString ih = xt.mid(9);
0101     if (ih.length() != 40 && ih.length() != 32) {
0102         Out(SYS_GEN | LOG_NOTICE) << "Hash has not valid length in magnet link " << url << endl;
0103         return;
0104     }
0105 
0106     try {
0107         if (ih.length() == 32)
0108             ih = base32ToHexString(ih);
0109 
0110         Uint8 hash[20];
0111         memset(hash, 0, 20);
0112         for (int i = 0; i < 20; i++) {
0113             Uint8 low = charToHex(ih[2 * i + 1]);
0114             Uint8 high = charToHex(ih[2 * i]);
0115             hash[i] = (high << 4) | low;
0116         }
0117 
0118         info_hash = SHA1Hash(hash);
0119         tracker_urls = GetTrackers(url);
0120         name = QUrlQuery(url).queryItemValue(QLatin1String("dn")).replace('+', ' ');
0121         magnet_string = url.toString();
0122     } catch (...) {
0123         Out(SYS_GEN | LOG_NOTICE) << "Invalid magnet link " << url << endl;
0124     }
0125 }
0126 
0127 Uint8 MagnetLink::charToHex(const QChar &ch)
0128 {
0129     if (ch.isDigit())
0130         return ch.digitValue();
0131 
0132     if (!ch.isLetter())
0133         throw bt::Error("Invalid char");
0134 
0135     if (ch.isLower())
0136         return 10 + ch.toLatin1() - 'a';
0137     else
0138         return 10 + ch.toLatin1() - 'A';
0139 }
0140 
0141 QString MagnetLink::base32ToHexString(const QString &s)
0142 {
0143     Uint32 part;
0144     Uint32 tmp;
0145     QString ret;
0146     QChar ch;
0147     QString str = s.toUpper();
0148     // 32 base32 chars -> 40 hex chars
0149     // 4 base32 chars -> 5 hex chars
0150     for (int i = 0; i < 8; i++) {
0151         part = 0;
0152         for (int j = 0; j < 4; j++) {
0153             ch = str[i * 4 + j];
0154             if (ch.isDigit() && (ch.digitValue() < 2 || ch.digitValue() > 7))
0155                 throw bt::Error("Invalid char");
0156 
0157             if (ch.isDigit())
0158                 tmp = ch.digitValue() + 24;
0159             else
0160                 tmp = ch.toLatin1() - 'A';
0161             part = part + (tmp << 5 * (3 - j));
0162         }
0163 
0164         // part is a Uint32 with 20 bits (5 hex)
0165         for (int j = 0; j < 5; j++) {
0166             tmp = (part >> 4 * (4 - j)) & 0xf;
0167             if (tmp >= 10)
0168                 ret.append(QChar((tmp - 10) + 'a'));
0169             else
0170                 ret.append(QChar(tmp + '0'));
0171         }
0172     }
0173     return ret;
0174 }
0175 
0176 }