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 }