2010-10-16 01:55:56 +04:00
|
|
|
/*
|
|
|
|
* Bittorrent Client using Qt4 and libtorrent.
|
|
|
|
* Copyright (C) 2006 Christophe Dumez
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Contact : chris@qbittorrent.org
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <QHttpRequestHeader>
|
|
|
|
#include <QTcpSocket>
|
2011-09-25 18:26:02 +04:00
|
|
|
#include <QUrl>
|
2010-10-16 01:55:56 +04:00
|
|
|
|
|
|
|
#include <libtorrent/bencode.hpp>
|
|
|
|
#include <libtorrent/entry.hpp>
|
|
|
|
|
|
|
|
#include "qtracker.h"
|
|
|
|
#include "preferences.h"
|
|
|
|
|
|
|
|
using namespace libtorrent;
|
|
|
|
|
|
|
|
QTracker::QTracker(QObject *parent) :
|
|
|
|
QTcpServer(parent)
|
|
|
|
{
|
2010-11-16 23:34:31 +03:00
|
|
|
Q_ASSERT(Preferences().isTrackerEnabled());
|
2010-10-16 01:55:56 +04:00
|
|
|
connect(this, SIGNAL(newConnection()), this, SLOT(handlePeerConnection()));
|
|
|
|
}
|
|
|
|
|
|
|
|
QTracker::~QTracker() {
|
2012-02-20 21:30:53 +04:00
|
|
|
if (isListening()) {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("Shutting down the embedded tracker...");
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
// TODO: Store the torrent list
|
|
|
|
}
|
|
|
|
|
|
|
|
void QTracker::handlePeerConnection()
|
|
|
|
{
|
|
|
|
QTcpSocket *socket;
|
|
|
|
while((socket = nextPendingConnection()))
|
|
|
|
{
|
|
|
|
qDebug("QTracker: New peer connection");
|
|
|
|
connect(socket, SIGNAL(readyRead()), SLOT(readRequest()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QTracker::start()
|
|
|
|
{
|
2010-11-16 23:34:31 +03:00
|
|
|
const int listen_port = Preferences().getTrackerPort();
|
2010-10-16 01:55:56 +04:00
|
|
|
//
|
2012-02-20 21:30:53 +04:00
|
|
|
if (isListening()) {
|
|
|
|
if (serverPort() == listen_port) {
|
2010-10-16 01:55:56 +04:00
|
|
|
// Already listening on the right port, just return
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// Wrong port, closing the server
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
qDebug("Starting the embedded tracker...");
|
|
|
|
// Listen on the predefined port
|
|
|
|
return listen(QHostAddress::Any, listen_port);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QTracker::readRequest()
|
|
|
|
{
|
|
|
|
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
|
|
|
|
QByteArray input = socket->readAll();
|
|
|
|
//qDebug("QTracker: Raw request:\n%s", input.data());
|
2011-09-25 18:26:02 +04:00
|
|
|
QHttpRequestHeader http_request(input);
|
2012-02-20 21:30:53 +04:00
|
|
|
if (!http_request.isValid()) {
|
2011-09-25 18:26:02 +04:00
|
|
|
qDebug("QTracker: Invalid HTTP Request:\n %s", qPrintable(http_request.toString()));
|
2010-10-16 01:55:56 +04:00
|
|
|
respondInvalidRequest(socket, 100, "Invalid request type");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString()));
|
|
|
|
// Request is correct, is it a GET request?
|
2012-02-20 21:30:53 +04:00
|
|
|
if (http_request.method() != "GET") {
|
2011-09-25 18:26:02 +04:00
|
|
|
qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(http_request.method()));
|
2010-10-16 01:55:56 +04:00
|
|
|
respondInvalidRequest(socket, 100, "Invalid request type");
|
|
|
|
return;
|
|
|
|
}
|
2012-02-20 21:30:53 +04:00
|
|
|
if (!http_request.path().startsWith("/announce", Qt::CaseInsensitive)) {
|
2011-09-25 18:26:02 +04:00
|
|
|
qDebug("QTracker: Unrecognized path: %s", qPrintable(http_request.path()));
|
2010-10-16 01:55:56 +04:00
|
|
|
respondInvalidRequest(socket, 100, "Invalid request type");
|
|
|
|
return;
|
|
|
|
}
|
2011-09-25 18:26:02 +04:00
|
|
|
|
2010-10-16 01:55:56 +04:00
|
|
|
// OK, this is a GET request
|
2011-09-25 18:26:02 +04:00
|
|
|
// Parse GET parameters
|
|
|
|
QHash<QString, QString> get_parameters;
|
|
|
|
QUrl url = QUrl::fromEncoded(http_request.path().toAscii());
|
|
|
|
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
|
|
|
while (i.hasNext()) {
|
|
|
|
QPair<QString, QString> pair = i.next();
|
|
|
|
get_parameters[pair.first] = pair.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
respondToAnnounceRequest(socket, get_parameters);
|
2010-10-16 01:55:56 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void QTracker::respondInvalidRequest(QTcpSocket *socket, int code, QString msg)
|
|
|
|
{
|
|
|
|
QHttpResponseHeader response;
|
|
|
|
response.setStatusLine(code, msg);
|
|
|
|
socket->write(response.toString().toLocal8Bit());
|
|
|
|
socket->disconnectFromHost();
|
|
|
|
}
|
|
|
|
|
2011-09-25 18:26:02 +04:00
|
|
|
void QTracker::respondToAnnounceRequest(QTcpSocket *socket,
|
|
|
|
const QHash<QString, QString>& get_parameters)
|
2010-10-16 01:55:56 +04:00
|
|
|
{
|
|
|
|
TrackerAnnounceRequest annonce_req;
|
|
|
|
// IP
|
|
|
|
annonce_req.peer.ip = socket->peerAddress().toString();
|
|
|
|
// 1. Get info_hash
|
2012-02-20 21:30:53 +04:00
|
|
|
if (!get_parameters.contains("info_hash")) {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: Missing info_hash");
|
|
|
|
respondInvalidRequest(socket, 101, "Missing info_hash");
|
|
|
|
return;
|
|
|
|
}
|
2011-09-25 18:26:02 +04:00
|
|
|
annonce_req.info_hash = get_parameters.value("info_hash");
|
2010-10-16 01:55:56 +04:00
|
|
|
// info_hash cannot be longer than 20 bytes
|
2013-09-21 11:59:58 +04:00
|
|
|
/*if (annonce_req.info_hash.toLatin1().length() > 20) {
|
|
|
|
qDebug("QTracker: Info_hash is not 20 byte long: %s (%d)", qPrintable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length());
|
2010-10-16 01:55:56 +04:00
|
|
|
respondInvalidRequest(socket, 150, "Invalid infohash");
|
|
|
|
return;
|
2010-10-16 19:30:44 +04:00
|
|
|
}*/
|
2010-10-16 01:55:56 +04:00
|
|
|
// 2. Get peer ID
|
2012-02-20 21:30:53 +04:00
|
|
|
if (!get_parameters.contains("peer_id")) {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: Missing peer_id");
|
|
|
|
respondInvalidRequest(socket, 102, "Missing peer_id");
|
|
|
|
return;
|
|
|
|
}
|
2011-09-25 18:26:02 +04:00
|
|
|
annonce_req.peer.peer_id = get_parameters.value("peer_id");
|
2010-10-16 01:55:56 +04:00
|
|
|
// peer_id cannot be longer than 20 bytes
|
2012-02-20 21:30:53 +04:00
|
|
|
/*if (annonce_req.peer.peer_id.length() > 20) {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id));
|
|
|
|
respondInvalidRequest(socket, 151, "Invalid peerid");
|
|
|
|
return;
|
2010-10-16 19:30:44 +04:00
|
|
|
}*/
|
2010-10-16 01:55:56 +04:00
|
|
|
// 3. Get port
|
2012-02-20 21:30:53 +04:00
|
|
|
if (!get_parameters.contains("port")) {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: Missing port");
|
|
|
|
respondInvalidRequest(socket, 103, "Missing port");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
bool ok = false;
|
2011-09-25 18:26:02 +04:00
|
|
|
annonce_req.peer.port = get_parameters.value("port").toInt(&ok);
|
2012-02-20 21:30:53 +04:00
|
|
|
if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port);
|
|
|
|
respondInvalidRequest(socket, 103, "Missing port");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// 4. Get event
|
|
|
|
annonce_req.event = "";
|
2012-02-20 21:30:53 +04:00
|
|
|
if (get_parameters.contains("event")) {
|
2011-09-25 18:26:02 +04:00
|
|
|
annonce_req.event = get_parameters.value("event");
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: event is %s", qPrintable(annonce_req.event));
|
|
|
|
}
|
|
|
|
// 5. Get numwant
|
|
|
|
annonce_req.numwant = 50;
|
2012-02-20 21:30:53 +04:00
|
|
|
if (get_parameters.contains("numwant")) {
|
2011-09-25 18:26:02 +04:00
|
|
|
int tmp = get_parameters.value("numwant").toInt();
|
2012-02-20 21:30:53 +04:00
|
|
|
if (tmp > 0) {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: numwant=%d", tmp);
|
|
|
|
annonce_req.numwant = tmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 6. no_peer_id (extension)
|
|
|
|
annonce_req.no_peer_id = false;
|
2012-02-20 21:30:53 +04:00
|
|
|
if (get_parameters.contains("no_peer_id")) {
|
2010-10-16 01:55:56 +04:00
|
|
|
annonce_req.no_peer_id = true;
|
|
|
|
}
|
|
|
|
// 7. TODO: support "compact" extension
|
|
|
|
// Done parsing, now let's reply
|
2012-02-20 21:30:53 +04:00
|
|
|
if (m_torrents.contains(annonce_req.info_hash)) {
|
|
|
|
if (annonce_req.event == "stopped") {
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: Peer stopped downloading, deleting it from the list");
|
|
|
|
m_torrents[annonce_req.info_hash].remove(annonce_req.peer.qhash());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Unknown torrent
|
2012-02-20 21:30:53 +04:00
|
|
|
if (m_torrents.size() == MAX_TORRENTS) {
|
2010-10-16 01:55:56 +04:00
|
|
|
// Reached max size, remove a random torrent
|
|
|
|
m_torrents.erase(m_torrents.begin());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Register the user
|
|
|
|
PeerList peers = m_torrents.value(annonce_req.info_hash);
|
2012-02-20 21:30:53 +04:00
|
|
|
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
2010-10-16 01:55:56 +04:00
|
|
|
// Too many peers, remove a random one
|
|
|
|
peers.erase(peers.begin());
|
|
|
|
}
|
|
|
|
peers[annonce_req.peer.qhash()] = annonce_req.peer;
|
|
|
|
m_torrents[annonce_req.info_hash] = peers;
|
|
|
|
// Reply
|
|
|
|
ReplyWithPeerList(socket, annonce_req);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QTracker::ReplyWithPeerList(QTcpSocket *socket, const TrackerAnnounceRequest &annonce_req)
|
|
|
|
{
|
|
|
|
// Prepare the entry for bencoding
|
|
|
|
entry::dictionary_type reply_dict;
|
|
|
|
reply_dict["interval"] = entry(ANNOUNCE_INTERVAL);
|
|
|
|
QList<QPeer> peers = m_torrents.value(annonce_req.info_hash).values();
|
|
|
|
entry::list_type peer_list;
|
2012-02-20 21:30:53 +04:00
|
|
|
foreach (const QPeer & p, peers) {
|
|
|
|
//if (p != annonce_req.peer)
|
2010-10-16 01:55:56 +04:00
|
|
|
peer_list.push_back(p.toEntry(annonce_req.no_peer_id));
|
|
|
|
}
|
|
|
|
reply_dict["peers"] = entry(peer_list);
|
|
|
|
entry reply_entry(reply_dict);
|
|
|
|
// bencode
|
|
|
|
std::vector<char> buf;
|
|
|
|
bencode(std::back_inserter(buf), reply_entry);
|
2010-11-01 21:05:54 +03:00
|
|
|
QByteArray reply(&buf[0], buf.size());
|
2010-10-16 01:55:56 +04:00
|
|
|
qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData());
|
|
|
|
// HTTP reply
|
|
|
|
QHttpResponseHeader response;
|
|
|
|
response.setStatusLine(200, "OK");
|
|
|
|
socket->write(response.toString().toLocal8Bit() + reply);
|
|
|
|
socket->disconnectFromHost();
|
|
|
|
}
|
|
|
|
|
|
|
|
|