1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "bencodeparser.h"
52#include "connectionmanager.h"
53#include "torrentclient.h"
54#include "torrentserver.h"
55#include "trackerclient.h"
56
57#include <QtCore>
58#include <QNetworkRequest>
59
60TrackerClient::TrackerClient(TorrentClient *downloader, QObject *parent)
61 : QObject(parent), torrentDownloader(downloader)
62{
63 connect(sender: &http, signal: &QNetworkAccessManager::finished,
64 receiver: this, slot: &TrackerClient::httpRequestDone);
65}
66
67void TrackerClient::start(const MetaInfo &info)
68{
69 metaInfo = info;
70 QTimer::singleShot(msec: 0, receiver: this, SLOT(fetchPeerList()));
71
72 if (metaInfo.fileForm() == MetaInfo::SingleFileForm) {
73 length = metaInfo.singleFile().length;
74 } else {
75 QList<MetaInfoMultiFile> files = metaInfo.multiFiles();
76 for (int i = 0; i < files.size(); ++i)
77 length += files.at(i).length;
78 }
79}
80
81void TrackerClient::startSeeding()
82{
83 firstSeeding = true;
84 fetchPeerList();
85}
86
87void TrackerClient::stop()
88{
89 lastTrackerRequest = true;
90 fetchPeerList();
91}
92
93void TrackerClient::timerEvent(QTimerEvent *event)
94{
95 if (event->timerId() == requestIntervalTimer) {
96 fetchPeerList();
97 } else {
98 QObject::timerEvent(event);
99 }
100}
101
102void TrackerClient::fetchPeerList()
103{
104 QUrl url(metaInfo.announceUrl());
105
106 // Base the query on announce url to include a passkey (if any)
107 QUrlQuery query(url);
108
109 // Percent encode the hash
110 QByteArray infoHash = torrentDownloader->infoHash();
111 QByteArray encodedSum;
112 for (int i = 0; i < infoHash.size(); ++i) {
113 encodedSum += '%';
114 encodedSum += QByteArray::number(infoHash[i], base: 16).right(len: 2).rightJustified(width: 2, fill: '0');
115 }
116
117 bool seeding = (torrentDownloader->state() == TorrentClient::Seeding);
118
119 query.addQueryItem(key: "info_hash", value: encodedSum);
120 query.addQueryItem(key: "peer_id", value: ConnectionManager::instance()->clientId());
121 query.addQueryItem(key: "port", value: QByteArray::number(TorrentServer::instance()->serverPort()));
122 query.addQueryItem(key: "compact", value: "1");
123 query.addQueryItem(key: "uploaded", value: QByteArray::number(torrentDownloader->uploadedBytes()));
124
125 if (!firstSeeding) {
126 query.addQueryItem(key: "downloaded", value: "0");
127 query.addQueryItem(key: "left", value: "0");
128 } else {
129 query.addQueryItem(key: "downloaded",
130 value: QByteArray::number(torrentDownloader->downloadedBytes()));
131 int left = qMax<int>(a: 0, b: metaInfo.totalSize() - torrentDownloader->downloadedBytes());
132 query.addQueryItem(key: "left", value: QByteArray::number(seeding ? 0 : left));
133 }
134
135 if (seeding && firstSeeding) {
136 query.addQueryItem(key: "event", value: "completed");
137 firstSeeding = false;
138 } else if (firstTrackerRequest) {
139 firstTrackerRequest = false;
140 query.addQueryItem(key: "event", value: "started");
141 } else if(lastTrackerRequest) {
142 query.addQueryItem(key: "event", value: "stopped");
143 }
144
145 if (!trackerId.isEmpty())
146 query.addQueryItem(key: "trackerid", value: trackerId);
147
148 url.setQuery(query);
149
150 QNetworkRequest req(url);
151 if (!url.userName().isEmpty()) {
152 uname = url.userName();
153 pwd = url.password();
154 connect(sender: &http, signal: &QNetworkAccessManager::authenticationRequired,
155 receiver: this, slot: &TrackerClient::provideAuthentication);
156 }
157 http.get(request: req);
158}
159
160void TrackerClient::httpRequestDone(QNetworkReply *reply)
161{
162 reply->deleteLater();
163 if (lastTrackerRequest) {
164 emit stopped();
165 return;
166 }
167
168 if (reply->error() != QNetworkReply::NoError) {
169 emit connectionError(error: reply->error());
170 return;
171 }
172
173 QByteArray response = reply->readAll();
174 reply->abort();
175
176 BencodeParser parser;
177 if (!parser.parse(content: response)) {
178 qWarning(msg: "Error parsing bencode response from tracker: %s",
179 qPrintable(parser.errorString()));
180 return;
181 }
182
183 QMap<QByteArray, QVariant> dict = parser.dictionary();
184
185 if (dict.contains(akey: "failure reason")) {
186 // no other items are present
187 emit failure(reason: QString::fromUtf8(str: dict.value(akey: "failure reason").toByteArray()));
188 return;
189 }
190
191 if (dict.contains(akey: "warning message")) {
192 // continue processing
193 emit warning(message: QString::fromUtf8(str: dict.value(akey: "warning message").toByteArray()));
194 }
195
196 if (dict.contains(akey: "tracker id")) {
197 // store it
198 trackerId = dict.value(akey: "tracker id").toByteArray();
199 }
200
201 if (dict.contains(akey: "interval")) {
202 // Mandatory item
203 if (requestIntervalTimer != -1)
204 killTimer(id: requestIntervalTimer);
205 requestIntervalTimer = startTimer(interval: dict.value(akey: "interval").toInt() * 1000);
206 }
207
208 if (dict.contains(akey: "peers")) {
209 // store it
210 peers.clear();
211 QVariant peerEntry = dict.value(akey: "peers");
212 if (peerEntry.userType() == QMetaType::QVariantList) {
213 QList<QVariant> peerTmp = peerEntry.toList();
214 for (int i = 0; i < peerTmp.size(); ++i) {
215 TorrentPeer tmp;
216 QMap<QByteArray, QVariant> peer = qvariant_cast<QMap<QByteArray, QVariant> >(v: peerTmp.at(i));
217 tmp.id = QString::fromUtf8(str: peer.value(akey: "peer id").toByteArray());
218 tmp.address.setAddress(QString::fromUtf8(str: peer.value(akey: "ip").toByteArray()));
219 tmp.port = peer.value(akey: "port").toInt();
220 peers << tmp;
221 }
222 } else {
223 QByteArray peerTmp = peerEntry.toByteArray();
224 for (int i = 0; i < peerTmp.size(); i += 6) {
225 TorrentPeer tmp;
226 uchar *data = (uchar *)peerTmp.constData() + i;
227 tmp.port = (int(data[4]) << 8) + data[5];
228 uint ipAddress = 0;
229 ipAddress += uint(data[0]) << 24;
230 ipAddress += uint(data[1]) << 16;
231 ipAddress += uint(data[2]) << 8;
232 ipAddress += uint(data[3]);
233 tmp.address.setAddress(ipAddress);
234 peers << tmp;
235 }
236 }
237 emit peerListUpdated(peerList: peers);
238 }
239}
240
241void TrackerClient::provideAuthentication(QNetworkReply *reply, QAuthenticator *auth)
242{
243 Q_UNUSED(reply);
244 auth->setUser(uname);
245 auth->setPassword(pwd);
246}
247

source code of qtbase/examples/network/torrent/trackerclient.cpp