File indexing completed on 2024-11-17 04:32:34

0001 /*
0002     SPDX-FileCopyrightText: 2008 Joris Guisson <joris.guisson@gmail.com>
0003     SPDX-FileCopyrightText: 2008 Ivan Vasic <ivasic@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "webseed.h"
0009 
0010 #include "httpconnection.h"
0011 #include <QTimer>
0012 #include <diskio/chunkmanager.h>
0013 #include <diskio/piecedata.h>
0014 #include <klocalizedstring.h>
0015 #include <net/socketmonitor.h>
0016 #include <peer/peermanager.h>
0017 #include <torrent/torrent.h>
0018 #include <util/log.h>
0019 
0020 #include <QNetworkProxyFactory>
0021 
0022 namespace bt
0023 {
0024 QString WebSeed::proxy_host;
0025 Uint16 WebSeed::proxy_port = 8080;
0026 bool WebSeed::proxy_enabled = false;
0027 
0028 const Uint32 RETRY_INTERVAL = 30;
0029 
0030 WebSeed::WebSeed(const QUrl &url, bool user, const Torrent &tor, ChunkManager &cman)
0031     : WebSeedInterface(url, user)
0032     , tor(tor)
0033     , cman(cman)
0034 {
0035     first_chunk = last_chunk = tor.getNumChunks() + 1;
0036     num_failures = 0;
0037     conn = nullptr;
0038     downloaded = 0;
0039     current = nullptr;
0040     status = i18n("Not connected");
0041     up_gid = down_gid = 0;
0042     cur_chunk = -1;
0043     connect(&retry_timer, &QTimer::timeout, this, &WebSeed::reset);
0044     retry_timer.setSingleShot(true);
0045 }
0046 
0047 WebSeed::~WebSeed()
0048 {
0049     delete conn;
0050     delete current;
0051 }
0052 
0053 void WebSeed::setGroupIDs(Uint32 up, Uint32 down)
0054 {
0055     up_gid = up;
0056     down_gid = down;
0057     if (conn)
0058         conn->setGroupIDs(up, down);
0059 }
0060 
0061 void WebSeed::setProxy(const QString &host, bt::Uint16 port)
0062 {
0063     proxy_port = port;
0064     proxy_host = host;
0065 }
0066 
0067 void WebSeed::setProxyEnabled(bool on)
0068 {
0069     proxy_enabled = on;
0070 }
0071 
0072 void WebSeed::reset()
0073 {
0074     retry_timer.stop();
0075     if (current)
0076         chunkStopped();
0077 
0078     if (conn) {
0079         conn->deleteLater();
0080         conn = nullptr;
0081     }
0082 
0083     first_chunk = last_chunk = tor.getNumChunks() + 1;
0084     num_failures = 0;
0085     status = i18n("Not connected");
0086 }
0087 
0088 void WebSeed::cancel()
0089 {
0090     reset();
0091 }
0092 
0093 void WebSeed::disable(const QString &reason)
0094 {
0095     setEnabled(false);
0096     status = reason;
0097     Out(SYS_CON | LOG_IMPORTANT) << "Auto disabled webseed " << url.toDisplayString() << endl;
0098 }
0099 
0100 bool WebSeed::busy() const
0101 {
0102     return first_chunk < tor.getNumChunks();
0103 }
0104 
0105 Uint32 WebSeed::getDownloadRate() const
0106 {
0107     if (conn)
0108         return conn->getDownloadRate();
0109     else
0110         return 0;
0111 }
0112 
0113 void WebSeed::connectToServer()
0114 {
0115     if (!token)
0116         token = PeerManager::connectionLimits().acquire(tor.getInfoHash());
0117 
0118     if (!token) {
0119         retryLater();
0120         return;
0121     }
0122 
0123     QUrl dst = url;
0124     if (redirected_url.isValid())
0125         dst = redirected_url;
0126 
0127     if (!proxy_enabled) {
0128         QList<QNetworkProxy> proxyList = QNetworkProxyFactory::proxyForQuery(QNetworkProxyQuery(dst));
0129 
0130         if (proxyList.isEmpty()) {
0131             conn->connectTo(dst); // direct connection
0132         } else {
0133             QNetworkProxy proxy = proxyList.first();
0134 
0135             if (proxy.type() == QNetworkProxy::NoProxy) {
0136                 conn->connectTo(dst); // direct connection
0137             } else {
0138                 conn->connectToProxy(proxy.hostName(), proxy.port());
0139             }
0140         }
0141     } else {
0142         if (proxy_host.isNull())
0143             conn->connectTo(dst); // direct connection
0144         else
0145             conn->connectToProxy(proxy_host, proxy_port); // via a proxy
0146     }
0147     status = conn->getStatusString();
0148 }
0149 
0150 void WebSeed::download(Uint32 first, Uint32 last)
0151 {
0152     if (!enabled)
0153         return;
0154 
0155     // Out(SYS_CON|LOG_DEBUG) << "WebSeed: downloading " << first << "-" << last << " from " << url.toDisplayString() << endl;
0156     // open connection and connect if needed
0157     if (!conn) {
0158         conn = new HttpConnection();
0159         conn->setGroupIDs(up_gid, down_gid);
0160     }
0161 
0162     if (!conn->connected()) {
0163         connectToServer();
0164     }
0165 
0166     if (first == cur_chunk && last == last_chunk && bytes_of_cur_chunk > 0) {
0167         // we already have some of the data, so reuse it
0168         continueCurChunk();
0169         return;
0170     }
0171 
0172     cur_piece = PieceData::Ptr(nullptr);
0173     first_chunk = first;
0174     last_chunk = last;
0175     cur_chunk = first;
0176     bytes_of_cur_chunk = 0;
0177 
0178     QString path = url.path();
0179     QString query = url.query();
0180     if (path.endsWith('/'))
0181         path += tor.getNameSuggestion();
0182 
0183     if (tor.isMultiFile()) {
0184         range_queue.clear();
0185         // make the list of ranges to download
0186         for (Uint32 i = first_chunk; i <= last_chunk; i++) {
0187             fillRangeList(i);
0188         }
0189 
0190         if (range_queue.count() > 0) {
0191             // send the first request
0192             Range r = range_queue[0];
0193             range_queue.pop_front();
0194             const TorrentFile &tf = tor.getFile(r.file);
0195             QString host = redirected_url.isValid() ? redirected_url.host() : url.host();
0196             conn->get(host, path + '/' + tf.getPath(), query, r.off, r.len);
0197         }
0198     } else {
0199         Uint64 len = (last_chunk - first_chunk) * tor.getChunkSize();
0200         // last chunk can have a different size
0201         if (last_chunk == tor.getNumChunks() - 1)
0202             len += tor.getLastChunkSize();
0203         else
0204             len += tor.getChunkSize();
0205 
0206         QString host = redirected_url.isValid() ? redirected_url.host() : url.host();
0207         conn->get(host, path, query, first_chunk * tor.getChunkSize(), len);
0208     }
0209 }
0210 
0211 void WebSeed::continueCurChunk()
0212 {
0213     QString path = url.path();
0214     QString query = url.query();
0215     if (path.endsWith('/') && !isUserCreated())
0216         path += tor.getNameSuggestion();
0217 
0218     first_chunk = cur_chunk;
0219     if (tor.isMultiFile()) {
0220         range_queue.clear();
0221         // make the list of ranges to download
0222         for (Uint32 i = first_chunk; i <= last_chunk; i++) {
0223             fillRangeList(i);
0224         }
0225 
0226         bt::Uint32 length = 0;
0227         while (range_queue.count() > 0) {
0228             // send the first request, but skip the data we already have
0229             Range r = range_queue[0];
0230             range_queue.pop_front();
0231             if (length >= bytes_of_cur_chunk) {
0232                 const TorrentFile &tf = tor.getFile(r.file);
0233                 QString host = redirected_url.isValid() ? redirected_url.host() : url.host();
0234                 conn->get(host, path + '/' + tf.getPath(), query, r.off, r.len);
0235                 break;
0236             }
0237             length += r.len;
0238         }
0239     } else {
0240         Uint64 len = (last_chunk - first_chunk) * tor.getChunkSize();
0241         // last chunk can have a different size
0242         if (last_chunk == tor.getNumChunks() - 1)
0243             len += tor.getLastChunkSize();
0244         else
0245             len += tor.getChunkSize();
0246 
0247         QString host = redirected_url.isValid() ? redirected_url.host() : url.host();
0248         conn->get(host, path, query, first_chunk * tor.getChunkSize() + bytes_of_cur_chunk, len - bytes_of_cur_chunk);
0249     }
0250     chunkStarted(cur_chunk);
0251 }
0252 
0253 void WebSeed::chunkStarted(Uint32 chunk)
0254 {
0255     Uint32 csize = cman.getChunk(chunk)->getSize();
0256     Uint32 pieces_count = csize / MAX_PIECE_LEN;
0257     if (csize % MAX_PIECE_LEN > 0)
0258         pieces_count++;
0259 
0260     if (!current) {
0261         current = new WebSeedChunkDownload(this, url.toDisplayString(), chunk, pieces_count);
0262         chunkDownloadStarted(current, chunk);
0263     } else if (current->chunk != chunk) {
0264         chunkStopped();
0265         current = new WebSeedChunkDownload(this, url.toDisplayString(), chunk, pieces_count);
0266         chunkDownloadStarted(current, chunk);
0267     }
0268 }
0269 
0270 void WebSeed::chunkStopped()
0271 {
0272     if (current) {
0273         chunkDownloadFinished(current, current->chunk);
0274         delete current;
0275         current = nullptr;
0276     }
0277 }
0278 
0279 Uint32 WebSeed::update()
0280 {
0281     try {
0282         if (!conn || !busy())
0283             return 0;
0284 
0285         if (!conn->ok()) {
0286             readData();
0287 
0288             Out(SYS_CON | LOG_DEBUG) << "WebSeed: connection not OK" << endl;
0289             // shit happened delete connection
0290             status = conn->getStatusString();
0291             if (conn->responseCode() == 404) {
0292                 // if not found then retire this webseed for now
0293                 retryLater();
0294             }
0295             delete conn;
0296             conn = nullptr;
0297             token.clear();
0298             chunkStopped();
0299             first_chunk = last_chunk = cur_chunk = tor.getNumChunks() + 1;
0300             num_failures++;
0301             if (num_failures == 3)
0302                 retryLater();
0303             return 0;
0304         } else if (conn->closed()) {
0305             // Make sure we handle all data
0306             readData();
0307 
0308             Out(SYS_CON | LOG_DEBUG) << "WebSeed: connection closed" << endl;
0309             delete conn;
0310             conn = nullptr;
0311             token.clear();
0312 
0313             status = i18n("Connection closed");
0314             chunkStopped();
0315             if (last_chunk < tor.getNumChunks()) {
0316                 // lets try this again if we have not yet got the full range
0317                 download(cur_chunk, last_chunk);
0318                 status = conn->getStatusString();
0319             }
0320         } else if (conn->isRedirected()) {
0321             // Make sure we handle all data
0322             readData();
0323             redirected(conn->redirectedUrl());
0324         } else {
0325             readData();
0326             if (range_queue.count() > 0 && conn->ready()) {
0327                 if (conn->closed()) {
0328                     // after a redirect it is possible that the connection is closed
0329                     // so we need to reconnect to the old url
0330                     conn->deleteLater();
0331                     conn = new HttpConnection();
0332                     conn->setGroupIDs(up_gid, down_gid);
0333                     connectToServer();
0334                 }
0335 
0336                 QString path = url.path();
0337                 QString query = url.query();
0338                 if (path.endsWith('/'))
0339                     path += tor.getNameSuggestion();
0340 
0341                 // ask for the next range
0342                 Range r = range_queue[0];
0343                 range_queue.pop_front();
0344                 const TorrentFile &tf = tor.getFile(r.file);
0345                 QString host = redirected_url.isValid() ? redirected_url.host() : url.host();
0346                 conn->get(host, path + '/' + tf.getPath(), query, r.off, r.len);
0347             }
0348             status = conn->getStatusString();
0349         }
0350     } catch (AutoDisabled &) {
0351     }
0352     Uint32 ret = downloaded;
0353     downloaded = 0;
0354     total_downloaded += ret;
0355     return ret;
0356 }
0357 
0358 void WebSeed::readData()
0359 {
0360     QByteArray tmp;
0361     while (conn->getData(tmp) && cur_chunk <= last_chunk) {
0362         // Out(SYS_CON|LOG_DEBUG) << "WebSeed: handleData " << tmp.size() << endl;
0363         if (!current)
0364             chunkStarted(cur_chunk);
0365         handleData(tmp);
0366         tmp.clear();
0367     }
0368 
0369     if (cur_chunk > last_chunk) {
0370         // if the current chunk moves past the last chunk, we are done
0371         first_chunk = last_chunk = tor.getNumChunks() + 1;
0372         num_failures = 0;
0373         finished();
0374     }
0375 }
0376 
0377 void WebSeed::handleData(const QByteArray &tmp)
0378 {
0379     Uint32 off = 0;
0380     while (off < (Uint32)tmp.size() && cur_chunk <= last_chunk) {
0381         Chunk *c = cman.getChunk(cur_chunk);
0382         Uint32 bl = c->getSize() - bytes_of_cur_chunk;
0383         if (bl > tmp.size() - off)
0384             bl = tmp.size() - off;
0385 
0386         // ignore data if we already have it
0387         if (c->getStatus() != Chunk::ON_DISK) {
0388             if (!cur_piece || cur_piece->parentChunk() != c)
0389                 cur_piece = c->getPiece(0, c->getSize(), false);
0390 
0391             if (cur_piece)
0392                 cur_piece->write((const Uint8 *)tmp.data() + off, bl, bytes_of_cur_chunk);
0393             downloaded += bl;
0394         }
0395         off += bl;
0396         bytes_of_cur_chunk += bl;
0397         current->pieces_downloaded = bytes_of_cur_chunk / MAX_PIECE_LEN;
0398 
0399         if (bytes_of_cur_chunk == c->getSize()) {
0400             // we have one ready
0401             bytes_of_cur_chunk = 0;
0402             cur_chunk++;
0403             if (c->getStatus() != Chunk::ON_DISK) {
0404                 chunkReady(c);
0405                 // It is possible that the webseed has been disabled due receiving a bad chunk
0406                 if (!isEnabled())
0407                     throw AutoDisabled();
0408             }
0409 
0410             chunkStopped();
0411             cur_piece = PieceData::Ptr(nullptr);
0412             if (cur_chunk <= last_chunk) {
0413                 c = cman.getChunk(cur_chunk);
0414                 cur_piece = c->getPiece(0, c->getSize(), false);
0415                 chunkStarted(cur_chunk);
0416             }
0417         }
0418     }
0419 }
0420 
0421 void WebSeed::fillRangeList(Uint32 chunk)
0422 {
0423     QList<Uint32> tflist;
0424     tor.calcChunkPos(chunk, tflist);
0425     Chunk *c = cman.getChunk(chunk);
0426 
0427     Uint64 passed = 0; // number of bytes of the chunk which we have passed
0428     for (int i = 0; i < tflist.count(); i++) {
0429         const TorrentFile &tf = tor.getFile(tflist[i]);
0430         Range r = {tflist[i], 0, 0};
0431         if (i == 0)
0432             r.off = tf.fileOffset(chunk, tor.getChunkSize());
0433 
0434         if (tflist.count() == 1)
0435             r.len = c->getSize();
0436         else if (i == 0)
0437             r.len = tf.getLastChunkSize();
0438         else if (i == tflist.count() - 1)
0439             r.len = c->getSize() - passed;
0440         else
0441             r.len = tf.getSize();
0442 
0443         // add the range
0444         if (range_queue.count() == 0)
0445             range_queue.append(r);
0446         else if (range_queue.back().file != r.file)
0447             range_queue.append(r);
0448         else {
0449             // the last range and this one are in the same file
0450             // so expand it
0451             Range &l = range_queue.back();
0452             l.len += r.len;
0453         }
0454 
0455         passed += r.len;
0456     }
0457 }
0458 
0459 void WebSeed::onExcluded(Uint32 from, Uint32 to)
0460 {
0461     if (from <= first_chunk && first_chunk <= to && from <= last_chunk && last_chunk <= to) {
0462         reset();
0463     }
0464 }
0465 
0466 void WebSeed::chunkDownloaded(Uint32 chunk)
0467 {
0468     // reset if chunk downloaded is in the range we are currently downloading
0469     if (chunk >= cur_chunk) {
0470         reset();
0471     }
0472 }
0473 
0474 void WebSeed::setEnabled(bool on)
0475 {
0476     WebSeedInterface::setEnabled(on);
0477     if (!on) {
0478         reset();
0479     }
0480 }
0481 
0482 void WebSeed::redirected(const QUrl &to_url)
0483 {
0484     delete conn;
0485     conn = nullptr;
0486     token.clear();
0487     if (to_url.isValid() && to_url.scheme() == QLatin1String("http")) {
0488         redirected_url = to_url;
0489         download(cur_chunk, last_chunk);
0490         status = conn->getStatusString();
0491     } else {
0492         retryLater();
0493         cur_chunk = last_chunk = first_chunk = tor.getNumChunks() + 1;
0494     }
0495 }
0496 
0497 void WebSeed::retryLater()
0498 {
0499     num_failures = 3;
0500     status = i18np("Unused for %1 second (Too many connection failures)", "Unused for %1 seconds (Too many connection failures)", RETRY_INTERVAL);
0501     retry_timer.start(RETRY_INTERVAL * 1000);
0502 }
0503 
0504 ////////////////////////////////////////////
0505 
0506 WebSeedChunkDownload::WebSeedChunkDownload(WebSeed *ws, const QString &url, Uint32 index, Uint32 total)
0507     : ws(ws)
0508     , url(url)
0509     , chunk(index)
0510     , total_pieces(total)
0511     , pieces_downloaded(0)
0512 {
0513 }
0514 
0515 WebSeedChunkDownload::~WebSeedChunkDownload()
0516 {
0517 }
0518 
0519 void WebSeedChunkDownload::getStats(Stats &s)
0520 {
0521     s.current_peer_id = url;
0522     s.chunk_index = chunk;
0523     s.num_downloaders = 1;
0524     s.download_speed = ws->getDownloadRate();
0525     s.pieces_downloaded = pieces_downloaded;
0526     s.total_pieces = total_pieces;
0527 }
0528 
0529 }
0530 
0531 #include "moc_webseed.cpp"