mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-02-05 19:34:17 +00:00
Follow project coding style (Issue #2192).
This commit is contained in:
parent
2707f5205f
commit
898d454b78
@ -40,13 +40,13 @@
|
|||||||
using namespace Http;
|
using namespace Http;
|
||||||
|
|
||||||
Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent)
|
Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_socket(socket)
|
, m_socket(socket)
|
||||||
, m_requestHandler(requestHandler)
|
, m_requestHandler(requestHandler)
|
||||||
{
|
{
|
||||||
m_socket->setParent(this);
|
m_socket->setParent(this);
|
||||||
connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
|
connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
|
||||||
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
|
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection::~Connection()
|
Connection::~Connection()
|
||||||
@ -55,57 +55,56 @@ Connection::~Connection()
|
|||||||
|
|
||||||
void Connection::read()
|
void Connection::read()
|
||||||
{
|
{
|
||||||
m_receivedData.append(m_socket->readAll());
|
m_receivedData.append(m_socket->readAll());
|
||||||
|
|
||||||
Request request;
|
Request request;
|
||||||
RequestParser::ErrorCode err = RequestParser::parse(m_receivedData, request);
|
RequestParser::ErrorCode err = RequestParser::parse(m_receivedData, request);
|
||||||
switch (err)
|
switch (err) {
|
||||||
{
|
case RequestParser::IncompleteRequest:
|
||||||
case RequestParser::IncompleteRequest:
|
// Partial request waiting for the rest
|
||||||
// Partial request waiting for the rest
|
break;
|
||||||
break;
|
case RequestParser::BadRequest:
|
||||||
case RequestParser::BadRequest:
|
sendResponse(Response(400, "Bad Request"));
|
||||||
sendResponse(Response(400, "Bad Request"));
|
break;
|
||||||
break;
|
case RequestParser::NoError:
|
||||||
case RequestParser::NoError:
|
Environment env;
|
||||||
Environment env;
|
env.clientAddress = m_socket->peerAddress();
|
||||||
env.clientAddress = m_socket->peerAddress();
|
Response response = m_requestHandler->processRequest(request, env);
|
||||||
Response response = m_requestHandler->processRequest(request, env);
|
if (acceptsGzipEncoding(request.headers["accept-encoding"]))
|
||||||
if (acceptsGzipEncoding(request.headers["accept-encoding"]))
|
response.headers[HEADER_CONTENT_ENCODING] = "gzip";
|
||||||
response.headers[HEADER_CONTENT_ENCODING] = "gzip";
|
sendResponse(response);
|
||||||
sendResponse(response);
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::sendResponse(const Response &response)
|
void Connection::sendResponse(const Response &response)
|
||||||
{
|
{
|
||||||
m_socket->write(ResponseGenerator::generate(response));
|
m_socket->write(ResponseGenerator::generate(response));
|
||||||
m_socket->disconnectFromHost();
|
m_socket->disconnectFromHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Connection::acceptsGzipEncoding(const QString &encoding)
|
bool Connection::acceptsGzipEncoding(const QString &encoding)
|
||||||
{
|
{
|
||||||
int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive);
|
int pos = encoding.indexOf("gzip", 0, Qt::CaseInsensitive);
|
||||||
if (pos == -1)
|
if (pos == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Let's see if there's a qvalue of 0.0 following
|
||||||
|
if (encoding[pos + 4] != ';') //there isn't, so it accepts gzip anyway
|
||||||
|
return true;
|
||||||
|
|
||||||
|
//So let's find = and the next comma
|
||||||
|
pos = encoding.indexOf("=", pos + 4, Qt::CaseInsensitive);
|
||||||
|
int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive);
|
||||||
|
|
||||||
|
QString value;
|
||||||
|
if (comma_pos == -1)
|
||||||
|
value = encoding.mid(pos + 1, comma_pos);
|
||||||
|
else
|
||||||
|
value = encoding.mid(pos + 1, comma_pos - (pos + 1));
|
||||||
|
|
||||||
|
if (value.toDouble() == 0.0)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Let's see if there's a qvalue of 0.0 following
|
|
||||||
if (encoding[pos + 4] != ';') //there isn't, so it accepts gzip anyway
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
//So let's find = and the next comma
|
|
||||||
pos = encoding.indexOf("=", pos + 4, Qt::CaseInsensitive);
|
|
||||||
int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive);
|
|
||||||
|
|
||||||
QString value;
|
|
||||||
if (comma_pos == -1)
|
|
||||||
value = encoding.mid(pos + 1, comma_pos);
|
|
||||||
else
|
|
||||||
value = encoding.mid(pos + 1, comma_pos - (pos + 1));
|
|
||||||
|
|
||||||
if (value.toDouble() == 0.0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
@ -47,23 +47,23 @@ class IRequestHandler;
|
|||||||
|
|
||||||
class Connection : public QObject
|
class Connection : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Connection)
|
Q_DISABLE_COPY(Connection)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
|
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
|
||||||
~Connection();
|
~Connection();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void read();
|
void read();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool acceptsGzipEncoding(const QString &encoding);
|
static bool acceptsGzipEncoding(const QString &encoding);
|
||||||
void sendResponse(const Response &response);
|
void sendResponse(const Response &response);
|
||||||
|
|
||||||
QTcpSocket *m_socket;
|
QTcpSocket *m_socket;
|
||||||
IRequestHandler *m_requestHandler;
|
IRequestHandler *m_requestHandler;
|
||||||
QByteArray m_receivedData;
|
QByteArray m_receivedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
//#include <QVariant>
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
#endif
|
#endif
|
||||||
@ -45,214 +44,196 @@ const QByteArray EOH("\r\n\r\n");
|
|||||||
|
|
||||||
inline QString unquoted(const QString& str)
|
inline QString unquoted(const QString& str)
|
||||||
{
|
{
|
||||||
if ((str[0] == '\"') && (str[str.length() - 1] == '\"'))
|
if ((str[0] == '\"') && (str[str.length() - 1] == '\"'))
|
||||||
return str.mid(1, str.length() - 2);
|
return str.mid(1, str.length() - 2);
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace Http;
|
using namespace Http;
|
||||||
|
|
||||||
RequestParser::ErrorCode RequestParser::parse(const QByteArray& data, Request& request, uint maxContentLength)
|
RequestParser::ErrorCode RequestParser::parse(const QByteArray& data, Request& request, uint maxContentLength)
|
||||||
{
|
{
|
||||||
return RequestParser(maxContentLength).parseHttpRequest(data, request);
|
return RequestParser(maxContentLength).parseHttpRequest(data, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestParser::RequestParser(uint maxContentLength)
|
RequestParser::RequestParser(uint maxContentLength)
|
||||||
: m_maxContentLength(maxContentLength)
|
: m_maxContentLength(maxContentLength)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestParser::ErrorCode RequestParser::parseHttpRequest(const QByteArray& data, Request& request)
|
RequestParser::ErrorCode RequestParser::parseHttpRequest(const QByteArray& data, Request& request)
|
||||||
{
|
{
|
||||||
m_request = Request();
|
m_request = Request();
|
||||||
|
|
||||||
// Parse HTTP request header
|
// Parse HTTP request header
|
||||||
const int header_end = data.indexOf(EOH);
|
const int header_end = data.indexOf(EOH);
|
||||||
if (header_end < 0)
|
if (header_end < 0) {
|
||||||
{
|
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
return IncompleteRequest;
|
||||||
return IncompleteRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parseHttpHeader(data.left(header_end)))
|
|
||||||
{
|
|
||||||
qWarning() << Q_FUNC_INFO << "header parsing error";
|
|
||||||
return BadRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse HTTP request message
|
|
||||||
int content_length = 0;
|
|
||||||
if (m_request.headers.contains("content-length"))
|
|
||||||
{
|
|
||||||
content_length = m_request.headers["content-length"].toInt();
|
|
||||||
if (content_length > static_cast<int>(m_maxContentLength))
|
|
||||||
{
|
|
||||||
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
|
||||||
return BadRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray content = data.mid(header_end + EOH.length(), content_length);
|
if (!parseHttpHeader(data.left(header_end))) {
|
||||||
if (content.length() < content_length)
|
qWarning() << Q_FUNC_INFO << "header parsing error";
|
||||||
{
|
return BadRequest;
|
||||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
|
||||||
return IncompleteRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parseContent(content))
|
// Parse HTTP request message
|
||||||
{
|
int content_length = 0;
|
||||||
qWarning() << Q_FUNC_INFO << "message parsing error";
|
if (m_request.headers.contains("content-length")) {
|
||||||
return BadRequest;
|
content_length = m_request.headers["content-length"].toInt();
|
||||||
}
|
if (content_length > static_cast<int>(m_maxContentLength)) {
|
||||||
}
|
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
||||||
|
return BadRequest;
|
||||||
// qDebug() << Q_FUNC_INFO;
|
}
|
||||||
// qDebug() << "HTTP Request header:";
|
|
||||||
// qDebug() << data.left(header_end) << "\n";
|
|
||||||
|
|
||||||
request = m_request;
|
QByteArray content = data.mid(header_end + EOH.length(), content_length);
|
||||||
return NoError;
|
if (content.length() < content_length) {
|
||||||
|
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||||
|
return IncompleteRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parseContent(content)) {
|
||||||
|
qWarning() << Q_FUNC_INFO << "message parsing error";
|
||||||
|
return BadRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qDebug() << Q_FUNC_INFO;
|
||||||
|
// qDebug() << "HTTP Request header:";
|
||||||
|
// qDebug() << data.left(header_end) << "\n";
|
||||||
|
|
||||||
|
request = m_request;
|
||||||
|
return NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseStartingLine(const QString &line)
|
bool RequestParser::parseStartingLine(const QString &line)
|
||||||
{
|
{
|
||||||
const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$");
|
const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$");
|
||||||
|
|
||||||
if (rx.indexIn(line.trimmed()) >= 0)
|
if (rx.indexIn(line.trimmed()) >= 0) {
|
||||||
{
|
m_request.method = rx.cap(1);
|
||||||
m_request.method = rx.cap(1);
|
|
||||||
|
|
||||||
QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1());
|
QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1());
|
||||||
m_request.path = url.path(); // Path
|
m_request.path = url.path(); // Path
|
||||||
|
|
||||||
// Parse GET parameters
|
// Parse GET parameters
|
||||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||||
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
||||||
#else
|
#else
|
||||||
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems());
|
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems());
|
||||||
#endif
|
#endif
|
||||||
while (i.hasNext())
|
while (i.hasNext()) {
|
||||||
{
|
QPair<QString, QString> pair = i.next();
|
||||||
QPair<QString, QString> pair = i.next();
|
m_request.gets[pair.first] = pair.second;
|
||||||
m_request.gets[pair.first] = pair.second;
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||||
}
|
return false;
|
||||||
|
|
||||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseHeaderLine(const QString &line, QPair<QString, QString>& out)
|
bool RequestParser::parseHeaderLine(const QString &line, QPair<QString, QString>& out)
|
||||||
{
|
{
|
||||||
int i = line.indexOf(QLatin1Char(':'));
|
int i = line.indexOf(QLatin1Char(':'));
|
||||||
if (i == -1)
|
if (i == -1) {
|
||||||
{
|
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
out = qMakePair(line.left(i).trimmed().toLower(), line.mid(i + 1).trimmed());
|
out = qMakePair(line.left(i).trimmed().toLower(), line.mid(i + 1).trimmed());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseHttpHeader(const QByteArray &data)
|
bool RequestParser::parseHttpHeader(const QByteArray &data)
|
||||||
{
|
{
|
||||||
QString str = QString::fromUtf8(data);
|
QString str = QString::fromUtf8(data);
|
||||||
QStringList lines = str.trimmed().split(EOL);
|
QStringList lines = str.trimmed().split(EOL);
|
||||||
|
|
||||||
QStringList headerLines;
|
QStringList headerLines;
|
||||||
foreach (const QString& line, lines)
|
foreach (const QString& line, lines) {
|
||||||
{
|
if (line[0].isSpace()) { // header line continuation
|
||||||
if (line[0].isSpace()) // header line continuation
|
if (!headerLines.isEmpty()) { // really continuation
|
||||||
{
|
headerLines.last() += QLatin1Char(' ');
|
||||||
if (!headerLines.isEmpty()) // really continuation
|
headerLines.last() += line.trimmed();
|
||||||
{
|
}
|
||||||
headerLines.last() += QLatin1Char(' ');
|
}
|
||||||
headerLines.last() += line.trimmed();
|
else {
|
||||||
}
|
headerLines.append(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
if (headerLines.isEmpty())
|
||||||
headerLines.append(line);
|
return false; // Empty header
|
||||||
|
|
||||||
|
QStringList::Iterator it = headerLines.begin();
|
||||||
|
if (!parseStartingLine(*it))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
++it;
|
||||||
|
for (; it != headerLines.end(); ++it) {
|
||||||
|
QPair<QString, QString> header;
|
||||||
|
if (!parseHeaderLine(*it, header))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_request.headers[header.first] = header.second;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (headerLines.isEmpty())
|
return true;
|
||||||
return false; // Empty header
|
|
||||||
|
|
||||||
QStringList::Iterator it = headerLines.begin();
|
|
||||||
if (!parseStartingLine(*it))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
++it;
|
|
||||||
for (; it != headerLines.end(); ++it)
|
|
||||||
{
|
|
||||||
QPair<QString, QString> header;
|
|
||||||
if (!parseHeaderLine(*it, header))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_request.headers[header.first] = header.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QByteArray> RequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary)
|
QList<QByteArray> RequestParser::splitMultipartData(const QByteArray& data, const QByteArray& boundary)
|
||||||
{
|
{
|
||||||
QList<QByteArray> ret;
|
QList<QByteArray> ret;
|
||||||
QByteArray sep = boundary + EOL;
|
QByteArray sep = boundary + EOL;
|
||||||
const int sepLength = sep.size();
|
const int sepLength = sep.size();
|
||||||
|
|
||||||
int start = 0, end = 0;
|
int start = 0, end = 0;
|
||||||
if ((end = data.indexOf(sep, start)) >= 0)
|
if ((end = data.indexOf(sep, start)) >= 0) {
|
||||||
{
|
start = end + sepLength; // skip first boundary
|
||||||
start = end + sepLength; // skip first boundary
|
|
||||||
|
|
||||||
while ((end = data.indexOf(sep, start)) >= 0)
|
while ((end = data.indexOf(sep, start)) >= 0) {
|
||||||
{
|
ret << data.mid(start, end - start);
|
||||||
ret << data.mid(start, end - start);
|
start = end + sepLength;
|
||||||
start = end + sepLength;
|
}
|
||||||
|
|
||||||
|
// last or single part
|
||||||
|
sep = boundary + "--" + EOL;
|
||||||
|
if ((end = data.indexOf(sep, start)) >= 0)
|
||||||
|
ret << data.mid(start, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last or single part
|
return ret;
|
||||||
sep = boundary + "--" + EOL;
|
|
||||||
if ((end = data.indexOf(sep, start)) >= 0)
|
|
||||||
ret << data.mid(start, end - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseContent(const QByteArray& data)
|
bool RequestParser::parseContent(const QByteArray& data)
|
||||||
{
|
{
|
||||||
// Parse message content
|
// Parse message content
|
||||||
qDebug() << Q_FUNC_INFO << "Content-Length: " << m_request.headers["content-length"];
|
qDebug() << Q_FUNC_INFO << "Content-Length: " << m_request.headers["content-length"];
|
||||||
qDebug() << Q_FUNC_INFO << "data.size(): " << data.size();
|
qDebug() << Q_FUNC_INFO << "data.size(): " << data.size();
|
||||||
|
|
||||||
// Parse url-encoded POST data
|
// Parse url-encoded POST data
|
||||||
if (m_request.headers["content-type"].startsWith("application/x-www-form-urlencoded"))
|
if (m_request.headers["content-type"].startsWith("application/x-www-form-urlencoded")) {
|
||||||
{
|
QUrl url;
|
||||||
QUrl url;
|
|
||||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||||
url.setEncodedQuery(data);
|
url.setEncodedQuery(data);
|
||||||
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
QListIterator<QPair<QString, QString> > i(url.queryItems());
|
||||||
#else
|
#else
|
||||||
url.setQuery(data);
|
url.setQuery(data);
|
||||||
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded));
|
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded));
|
||||||
#endif
|
#endif
|
||||||
while (i.hasNext())
|
while (i.hasNext()) {
|
||||||
{
|
QPair<QString, QString> pair = i.next();
|
||||||
QPair<QString, QString> pair = i.next();
|
m_request.posts[pair.first.toLower()] = pair.second;
|
||||||
m_request.posts[pair.first.toLower()] = pair.second;
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
// Parse multipart/form data (torrent file)
|
||||||
}
|
/**
|
||||||
|
|
||||||
// Parse multipart/form data (torrent file)
|
|
||||||
/**
|
|
||||||
data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5")
|
data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5")
|
||||||
|
|
||||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||||
@ -270,108 +251,96 @@ Content-Disposition: form-data; name=\"Upload\"
|
|||||||
Submit Query
|
Submit Query
|
||||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5--
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5--
|
||||||
**/
|
**/
|
||||||
QString content_type = m_request.headers["content-type"];
|
QString content_type = m_request.headers["content-type"];
|
||||||
if (content_type.startsWith("multipart/form-data"))
|
if (content_type.startsWith("multipart/form-data")) {
|
||||||
{
|
const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\"");
|
||||||
const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\"");
|
const QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)");
|
||||||
const QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)");
|
|
||||||
|
|
||||||
QByteArray boundary;
|
QByteArray boundary;
|
||||||
if (boundaryRegexQuoted.indexIn(content_type) < 0)
|
if (boundaryRegexQuoted.indexIn(content_type) < 0) {
|
||||||
{
|
if (boundaryRegexNotQuoted.indexIn(content_type) < 0) {
|
||||||
if (boundaryRegexNotQuoted.indexIn(content_type) < 0)
|
qWarning() << "Could not find boundary in multipart/form-data header!";
|
||||||
{
|
return false;
|
||||||
qWarning() << "Could not find boundary in multipart/form-data header!";
|
}
|
||||||
return false;
|
else {
|
||||||
}
|
boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1();
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1();
|
else {
|
||||||
}
|
boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
qDebug() << "Boundary is " << boundary;
|
||||||
boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1();
|
QList<QByteArray> parts = splitMultipartData(data, boundary);
|
||||||
|
qDebug() << parts.size() << "parts in data";
|
||||||
|
|
||||||
|
foreach (const QByteArray& part, parts) {
|
||||||
|
if (!parseFormData(part))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Boundary is " << boundary;
|
qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type);
|
||||||
QList<QByteArray> parts = splitMultipartData(data, boundary);
|
return false;
|
||||||
qDebug() << parts.size() << "parts in data";
|
|
||||||
|
|
||||||
foreach (const QByteArray& part, parts)
|
|
||||||
{
|
|
||||||
if (!parseFormData(part))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseFormData(const QByteArray& data)
|
bool RequestParser::parseFormData(const QByteArray& data)
|
||||||
{
|
{
|
||||||
// Parse form data header
|
// Parse form data header
|
||||||
const int header_end = data.indexOf(EOH);
|
const int header_end = data.indexOf(EOH);
|
||||||
if (header_end < 0)
|
if (header_end < 0) {
|
||||||
{
|
qDebug() << "Invalid form data: \n" << data;
|
||||||
qDebug() << "Invalid form data: \n" << data;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QString header_str = QString::fromUtf8(data.left(header_end));
|
QString header_str = QString::fromUtf8(data.left(header_end));
|
||||||
QStringList lines = header_str.trimmed().split(EOL);
|
QStringList lines = header_str.trimmed().split(EOL);
|
||||||
QStringMap headers;
|
QStringMap headers;
|
||||||
foreach (const QString& line, lines)
|
foreach (const QString& line, lines) {
|
||||||
{
|
QPair<QString, QString> header;
|
||||||
QPair<QString, QString> header;
|
if (!parseHeaderLine(line, header))
|
||||||
if (!parseHeaderLine(line, header))
|
return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
headers[header.first] = header.second;
|
headers[header.first] = header.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringMap disposition;
|
QStringMap disposition;
|
||||||
if (!headers.contains("content-disposition") ||
|
if (!headers.contains("content-disposition")
|
||||||
!parseHeaderValue(headers["content-disposition"], disposition) ||
|
|| !parseHeaderValue(headers["content-disposition"], disposition)
|
||||||
!disposition.contains("name"))
|
|| !disposition.contains("name")) {
|
||||||
{
|
qDebug() << "Invalid form data header: \n" << header_str;
|
||||||
qDebug() << "Invalid form data header: \n" << header_str;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (disposition.contains("filename"))
|
if (disposition.contains("filename")) {
|
||||||
{
|
UploadedFile ufile;
|
||||||
UploadedFile ufile;
|
ufile.filename = disposition["filename"];
|
||||||
ufile.filename = disposition["filename"];
|
ufile.type = disposition["content-type"];
|
||||||
ufile.type = disposition["content-type"];
|
ufile.data = data.mid(header_end + EOH.length());
|
||||||
ufile.data = data.mid(header_end + EOH.length());
|
|
||||||
|
|
||||||
m_request.files[disposition["name"]] = ufile;
|
m_request.files[disposition["name"]] = ufile;
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length()));
|
||||||
m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length()));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseHeaderValue(const QString& value, QStringMap& out)
|
bool RequestParser::parseHeaderValue(const QString& value, QStringMap& out)
|
||||||
{
|
{
|
||||||
QStringList items = value.split(QLatin1Char(';'));
|
QStringList items = value.split(QLatin1Char(';'));
|
||||||
out[""] = items[0];
|
out[""] = items[0];
|
||||||
|
|
||||||
for (QStringList::size_type i = 1; i < items.size(); ++i)
|
for (QStringList::size_type i = 1; i < items.size(); ++i) {
|
||||||
{
|
int pos = items[i].indexOf("=");
|
||||||
int pos = items[i].indexOf("=");
|
if (pos < 0)
|
||||||
if (pos < 0)
|
return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
out[items[i].left(pos).trimmed()] = unquoted(items[i].mid(pos + 1).trimmed());
|
out[items[i].left(pos).trimmed()] = unquoted(items[i].mid(pos + 1).trimmed());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -40,28 +40,33 @@ namespace Http
|
|||||||
class RequestParser
|
class RequestParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum ErrorCode { NoError = 0, IncompleteRequest, BadRequest };
|
enum ErrorCode
|
||||||
|
{
|
||||||
|
NoError = 0,
|
||||||
|
IncompleteRequest,
|
||||||
|
BadRequest
|
||||||
|
};
|
||||||
|
|
||||||
// when result != NoError parsed request is undefined
|
// when result != NoError parsed request is undefined
|
||||||
// Warning! Header names are converted to lower-case.
|
// Warning! Header names are converted to lower-case.
|
||||||
static ErrorCode parse(const QByteArray& data, Request& request, uint maxContentLength = 10000000 /* ~10MB */);
|
static ErrorCode parse(const QByteArray &data, Request &request, uint maxContentLength = 10000000 /* ~10MB */);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RequestParser(uint maxContentLength);
|
RequestParser(uint maxContentLength);
|
||||||
|
|
||||||
ErrorCode parseHttpRequest(const QByteArray& data, Request& request);
|
ErrorCode parseHttpRequest(const QByteArray &data, Request &request);
|
||||||
|
|
||||||
bool parseHttpHeader(const QByteArray& data);
|
bool parseHttpHeader(const QByteArray &data);
|
||||||
bool parseStartingLine(const QString &line);
|
bool parseStartingLine(const QString &line);
|
||||||
bool parseContent(const QByteArray& data);
|
bool parseContent(const QByteArray &data);
|
||||||
bool parseFormData(const QByteArray& data);
|
bool parseFormData(const QByteArray &data);
|
||||||
QList<QByteArray> splitMultipartData(const QByteArray& data, const QByteArray& boundary);
|
QList<QByteArray> splitMultipartData(const QByteArray &data, const QByteArray &boundary);
|
||||||
|
|
||||||
static bool parseHeaderLine(const QString& line, QPair<QString, QString>& out);
|
static bool parseHeaderLine(const QString &line, QPair<QString, QString> &out);
|
||||||
static bool parseHeaderValue(const QString& value, QStringMap& out);
|
static bool parseHeaderValue(const QString &value, QStringMap &out);
|
||||||
|
|
||||||
const uint m_maxContentLength;
|
const uint m_maxContentLength;
|
||||||
Request m_request;
|
Request m_request;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,96 +38,87 @@ using namespace Http;
|
|||||||
|
|
||||||
QByteArray ResponseGenerator::generate(Response response)
|
QByteArray ResponseGenerator::generate(Response response)
|
||||||
{
|
{
|
||||||
if (response.headers[HEADER_CONTENT_ENCODING] == "gzip")
|
if (response.headers[HEADER_CONTENT_ENCODING] == "gzip") {
|
||||||
{
|
// A gzip seems to have 23 bytes overhead.
|
||||||
// A gzip seems to have 23 bytes overhead.
|
// Also "Content-Encoding: gzip\r\n" is 26 bytes long
|
||||||
// Also "Content-Encoding: gzip\r\n" is 26 bytes long
|
// So we only benefit from gzip if the message is bigger than 23+26 = 49
|
||||||
// So we only benefit from gzip if the message is bigger than 23+26 = 49
|
// If the message is smaller than 49 bytes we actually send MORE data if we gzip
|
||||||
// If the message is smaller than 49 bytes we actually send MORE data if we gzip
|
QByteArray dest_buf;
|
||||||
QByteArray dest_buf;
|
if ((response.content.size() > 49) && (gCompress(response.content, dest_buf)))
|
||||||
if ((response.content.size() > 49) && (gCompress(response.content, dest_buf)))
|
response.content = dest_buf;
|
||||||
{
|
else
|
||||||
response.content = dest_buf;
|
response.headers.remove(HEADER_CONTENT_ENCODING);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
response.headers.remove(HEADER_CONTENT_ENCODING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.content.length() > 0)
|
if (response.content.length() > 0)
|
||||||
response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length());
|
response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length());
|
||||||
|
|
||||||
QString ret(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n"));
|
QString ret(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n"));
|
||||||
|
|
||||||
QString header;
|
QString header;
|
||||||
foreach (const QString& key, response.headers.keys())
|
foreach (const QString& key, response.headers.keys())
|
||||||
header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]);
|
header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]);
|
||||||
|
|
||||||
ret = ret.arg(response.status.code).arg(response.status.text).arg(header);
|
ret = ret.arg(response.status.code).arg(response.status.text).arg(header);
|
||||||
|
|
||||||
// qDebug() << Q_FUNC_INFO;
|
// qDebug() << Q_FUNC_INFO;
|
||||||
// qDebug() << "HTTP Response header:";
|
// qDebug() << "HTTP Response header:";
|
||||||
// qDebug() << ret;
|
// qDebug() << ret;
|
||||||
|
|
||||||
return ret.toUtf8() + response.content;
|
return ret.toUtf8() + response.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gCompress(QByteArray data, QByteArray& dest_buffer)
|
bool gCompress(QByteArray data, QByteArray& dest_buffer)
|
||||||
{
|
{
|
||||||
static const int BUFSIZE = 128 * 1024;
|
static const int BUFSIZE = 128 * 1024;
|
||||||
char tmp_buf[BUFSIZE];
|
char tmp_buf[BUFSIZE];
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
z_stream strm;
|
z_stream strm;
|
||||||
strm.zalloc = Z_NULL;
|
strm.zalloc = Z_NULL;
|
||||||
strm.zfree = Z_NULL;
|
strm.zfree = Z_NULL;
|
||||||
strm.opaque = Z_NULL;
|
strm.opaque = Z_NULL;
|
||||||
strm.next_in = reinterpret_cast<unsigned char*>(data.data());
|
strm.next_in = reinterpret_cast<unsigned char*>(data.data());
|
||||||
strm.avail_in = data.length();
|
strm.avail_in = data.length();
|
||||||
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
||||||
strm.avail_out = BUFSIZE;
|
strm.avail_out = BUFSIZE;
|
||||||
|
|
||||||
//windowBits = 15+16 to enable gzip
|
//windowBits = 15+16 to enable gzip
|
||||||
//From the zlib manual: windowBits can also be greater than 15 for optional gzip encoding. Add 16 to windowBits
|
//From the zlib manual: windowBits can also be greater than 15 for optional gzip encoding. Add 16 to windowBits
|
||||||
//to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper.
|
//to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper.
|
||||||
ret = deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY);
|
ret = deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
|
||||||
|
|
||||||
if (ret != Z_OK)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
while (strm.avail_in != 0)
|
|
||||||
{
|
|
||||||
ret = deflate(&strm, Z_NO_FLUSH);
|
|
||||||
if (ret != Z_OK)
|
if (ret != Z_OK)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (strm.avail_out == 0)
|
while (strm.avail_in != 0) {
|
||||||
{
|
ret = deflate(&strm, Z_NO_FLUSH);
|
||||||
dest_buffer.append(tmp_buf, BUFSIZE);
|
if (ret != Z_OK)
|
||||||
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
return false;
|
||||||
strm.avail_out = BUFSIZE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int deflate_res = Z_OK;
|
if (strm.avail_out == 0) {
|
||||||
while (deflate_res == Z_OK)
|
dest_buffer.append(tmp_buf, BUFSIZE);
|
||||||
{
|
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
||||||
if (strm.avail_out == 0)
|
strm.avail_out = BUFSIZE;
|
||||||
{
|
}
|
||||||
dest_buffer.append(tmp_buf, BUFSIZE);
|
|
||||||
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
|
||||||
strm.avail_out = BUFSIZE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deflate_res = deflate(&strm, Z_FINISH);
|
int deflate_res = Z_OK;
|
||||||
}
|
while (deflate_res == Z_OK) {
|
||||||
|
if (strm.avail_out == 0) {
|
||||||
|
dest_buffer.append(tmp_buf, BUFSIZE);
|
||||||
|
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf);
|
||||||
|
strm.avail_out = BUFSIZE;
|
||||||
|
}
|
||||||
|
|
||||||
if (deflate_res != Z_STREAM_END)
|
deflate_res = deflate(&strm, Z_FINISH);
|
||||||
return false;
|
}
|
||||||
|
|
||||||
dest_buffer.append(tmp_buf, BUFSIZE - strm.avail_out);
|
if (deflate_res != Z_STREAM_END)
|
||||||
deflateEnd(&strm);
|
return false;
|
||||||
|
|
||||||
return true;
|
dest_buffer.append(tmp_buf, BUFSIZE - strm.avail_out);
|
||||||
|
deflateEnd(&strm);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@
|
|||||||
using namespace Http;
|
using namespace Http;
|
||||||
|
|
||||||
Server::Server(IRequestHandler *requestHandler, QObject* parent)
|
Server::Server(IRequestHandler *requestHandler, QObject* parent)
|
||||||
: QTcpServer(parent)
|
: QTcpServer(parent)
|
||||||
, m_requestHandler(requestHandler)
|
, m_requestHandler(requestHandler)
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
, m_https(false)
|
, m_https(false)
|
||||||
#endif
|
#endif
|
||||||
@ -54,16 +54,16 @@ Server::~Server()
|
|||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
void Server::enableHttps(const QSslCertificate &certificate, const QSslKey &key)
|
void Server::enableHttps(const QSslCertificate &certificate, const QSslKey &key)
|
||||||
{
|
{
|
||||||
m_certificate = certificate;
|
m_certificate = certificate;
|
||||||
m_key = key;
|
m_key = key;
|
||||||
m_https = true;
|
m_https = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::disableHttps()
|
void Server::disableHttps()
|
||||||
{
|
{
|
||||||
m_https = false;
|
m_https = false;
|
||||||
m_certificate.clear();
|
m_certificate.clear();
|
||||||
m_key.clear();
|
m_key.clear();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -73,28 +73,26 @@ void Server::incomingConnection(qintptr socketDescriptor)
|
|||||||
void Server::incomingConnection(int socketDescriptor)
|
void Server::incomingConnection(int socketDescriptor)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
QTcpSocket *serverSocket;
|
QTcpSocket *serverSocket;
|
||||||
#ifndef QT_NO_OPENSSL
|
|
||||||
if (m_https)
|
|
||||||
serverSocket = new QSslSocket(this);
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
serverSocket = new QTcpSocket(this);
|
|
||||||
if (serverSocket->setSocketDescriptor(socketDescriptor))
|
|
||||||
{
|
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
if (m_https)
|
if (m_https)
|
||||||
{
|
serverSocket = new QSslSocket(this);
|
||||||
static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::AnyProtocol);
|
else
|
||||||
static_cast<QSslSocket*>(serverSocket)->setPrivateKey(m_key);
|
|
||||||
static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificate);
|
|
||||||
static_cast<QSslSocket*>(serverSocket)->startServerEncryption();
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
new Connection(serverSocket, m_requestHandler, this);
|
serverSocket = new QTcpSocket(this);
|
||||||
}
|
|
||||||
else
|
if (serverSocket->setSocketDescriptor(socketDescriptor)) {
|
||||||
{
|
#ifndef QT_NO_OPENSSL
|
||||||
serverSocket->deleteLater();
|
if (m_https) {
|
||||||
}
|
static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::AnyProtocol);
|
||||||
|
static_cast<QSslSocket*>(serverSocket)->setPrivateKey(m_key);
|
||||||
|
static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificate);
|
||||||
|
static_cast<QSslSocket*>(serverSocket)->startServerEncryption();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
new Connection(serverSocket, m_requestHandler, this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
serverSocket->deleteLater();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,31 +47,31 @@ class Connection;
|
|||||||
|
|
||||||
class Server : public QTcpServer
|
class Server : public QTcpServer
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Server)
|
Q_DISABLE_COPY(Server)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Server(IRequestHandler *requestHandler, QObject *parent = 0);
|
Server(IRequestHandler *requestHandler, QObject *parent = 0);
|
||||||
~Server();
|
~Server();
|
||||||
|
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
void enableHttps(const QSslCertificate &certificate, const QSslKey &key);
|
void enableHttps(const QSslCertificate &certificate, const QSslKey &key);
|
||||||
void disableHttps();
|
void disableHttps();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
||||||
void incomingConnection(qintptr socketDescriptor);
|
void incomingConnection(qintptr socketDescriptor);
|
||||||
#else
|
#else
|
||||||
void incomingConnection(int socketDescriptor);
|
void incomingConnection(int socketDescriptor);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IRequestHandler *m_requestHandler;
|
IRequestHandler *m_requestHandler;
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
bool m_https;
|
bool m_https;
|
||||||
QSslCertificate m_certificate;
|
QSslCertificate m_certificate;
|
||||||
QSslKey m_key;
|
QSslKey m_key;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -73,27 +73,27 @@ QTracker::QTracker(QObject *parent)
|
|||||||
|
|
||||||
QTracker::~QTracker()
|
QTracker::~QTracker()
|
||||||
{
|
{
|
||||||
if (m_server->isListening())
|
if (m_server->isListening())
|
||||||
qDebug("Shutting down the embedded tracker...");
|
qDebug("Shutting down the embedded tracker...");
|
||||||
// TODO: Store the torrent list
|
// TODO: Store the torrent list
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QTracker::start()
|
bool QTracker::start()
|
||||||
{
|
{
|
||||||
const int listen_port = Preferences::instance()->getTrackerPort();
|
const int listen_port = Preferences::instance()->getTrackerPort();
|
||||||
|
|
||||||
if (m_server->isListening()) {
|
if (m_server->isListening()) {
|
||||||
if (m_server->serverPort() == listen_port) {
|
if (m_server->serverPort() == listen_port) {
|
||||||
// Already listening on the right port, just return
|
// Already listening on the right port, just return
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
// Wrong port, closing the server
|
||||||
|
m_server->close();
|
||||||
}
|
}
|
||||||
// Wrong port, closing the server
|
|
||||||
m_server->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug("Starting the embedded tracker...");
|
qDebug("Starting the embedded tracker...");
|
||||||
// Listen on the predefined port
|
// Listen on the predefined port
|
||||||
return m_server->listen(QHostAddress::Any, listen_port);
|
return m_server->listen(QHostAddress::Any, listen_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
Http::Response QTracker::processRequest(const Http::Request &request, const Http::Environment &env)
|
Http::Response QTracker::processRequest(const Http::Request &request, const Http::Environment &env)
|
||||||
@ -103,12 +103,12 @@ Http::Response QTracker::processRequest(const Http::Request &request, const Http
|
|||||||
//qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString()));
|
//qDebug("QTracker received the following request:\n%s", qPrintable(parser.toString()));
|
||||||
// Is request a GET request?
|
// Is request a GET request?
|
||||||
if (request.method != "GET") {
|
if (request.method != "GET") {
|
||||||
qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method));
|
qDebug("QTracker: Unsupported HTTP request: %s", qPrintable(request.method));
|
||||||
status(100, "Invalid request type");
|
status(100, "Invalid request type");
|
||||||
}
|
}
|
||||||
else if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) {
|
else if (!request.path.startsWith("/announce", Qt::CaseInsensitive)) {
|
||||||
qDebug("QTracker: Unrecognized path: %s", qPrintable(request.path));
|
qDebug("QTracker: Unrecognized path: %s", qPrintable(request.path));
|
||||||
status(100, "Invalid request type");
|
status(100, "Invalid request type");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// OK, this is a GET request
|
// OK, this is a GET request
|
||||||
@ -122,126 +122,127 @@ Http::Response QTracker::processRequest(const Http::Request &request, const Http
|
|||||||
|
|
||||||
void QTracker::respondToAnnounceRequest()
|
void QTracker::respondToAnnounceRequest()
|
||||||
{
|
{
|
||||||
const QStringMap &gets = m_request.gets;
|
const QStringMap &gets = m_request.gets;
|
||||||
TrackerAnnounceRequest annonce_req;
|
TrackerAnnounceRequest annonce_req;
|
||||||
|
|
||||||
// IP
|
// IP
|
||||||
annonce_req.peer.ip = m_env.clientAddress.toString();
|
annonce_req.peer.ip = m_env.clientAddress.toString();
|
||||||
|
|
||||||
// 1. Get info_hash
|
// 1. Get info_hash
|
||||||
if (!gets.contains("info_hash")) {
|
if (!gets.contains("info_hash")) {
|
||||||
qDebug("QTracker: Missing info_hash");
|
qDebug("QTracker: Missing info_hash");
|
||||||
status(101, "Missing info_hash");
|
status(101, "Missing info_hash");
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
annonce_req.info_hash = gets.value("info_hash");
|
|
||||||
// info_hash cannot be longer than 20 bytes
|
|
||||||
/*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());
|
|
||||||
status(150, "Invalid infohash");
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// 2. Get peer ID
|
|
||||||
if (!gets.contains("peer_id")) {
|
|
||||||
qDebug("QTracker: Missing peer_id");
|
|
||||||
status(102, "Missing peer_id");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
annonce_req.peer.peer_id = gets.value("peer_id");
|
|
||||||
// peer_id cannot be longer than 20 bytes
|
|
||||||
/*if (annonce_req.peer.peer_id.length() > 20) {
|
|
||||||
qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id));
|
|
||||||
status(151, "Invalid peerid");
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// 3. Get port
|
|
||||||
if (!gets.contains("port")) {
|
|
||||||
qDebug("QTracker: Missing port");
|
|
||||||
status(103, "Missing port");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool ok = false;
|
|
||||||
annonce_req.peer.port = gets.value("port").toInt(&ok);
|
|
||||||
if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) {
|
|
||||||
qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port);
|
|
||||||
status(103, "Missing port");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Get event
|
|
||||||
annonce_req.event = "";
|
|
||||||
if (gets.contains("event")) {
|
|
||||||
annonce_req.event = gets.value("event");
|
|
||||||
qDebug("QTracker: event is %s", qPrintable(annonce_req.event));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Get numwant
|
|
||||||
annonce_req.numwant = 50;
|
|
||||||
if (gets.contains("numwant")) {
|
|
||||||
int tmp = gets.value("numwant").toInt();
|
|
||||||
if (tmp > 0) {
|
|
||||||
qDebug("QTracker: numwant = %d", tmp);
|
|
||||||
annonce_req.numwant = tmp;
|
|
||||||
}
|
}
|
||||||
}
|
annonce_req.info_hash = gets.value("info_hash");
|
||||||
|
// info_hash cannot be longer than 20 bytes
|
||||||
|
/*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());
|
||||||
|
status(150, "Invalid infohash");
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
// 6. no_peer_id (extension)
|
// 2. Get peer ID
|
||||||
annonce_req.no_peer_id = false;
|
if (!gets.contains("peer_id")) {
|
||||||
if (gets.contains("no_peer_id"))
|
qDebug("QTracker: Missing peer_id");
|
||||||
annonce_req.no_peer_id = true;
|
status(102, "Missing peer_id");
|
||||||
|
return;
|
||||||
// 7. TODO: support "compact" extension
|
|
||||||
|
|
||||||
// Done parsing, now let's reply
|
|
||||||
if (m_torrents.contains(annonce_req.info_hash)) {
|
|
||||||
if (annonce_req.event == "stopped") {
|
|
||||||
qDebug("QTracker: Peer stopped downloading, deleting it from the list");
|
|
||||||
m_torrents[annonce_req.info_hash].remove(annonce_req.peer.qhash());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
annonce_req.peer.peer_id = gets.value("peer_id");
|
||||||
// Unknown torrent
|
// peer_id cannot be longer than 20 bytes
|
||||||
if (m_torrents.size() == MAX_TORRENTS) {
|
/*if (annonce_req.peer.peer_id.length() > 20) {
|
||||||
// Reached max size, remove a random torrent
|
qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id));
|
||||||
m_torrents.erase(m_torrents.begin());
|
status(151, "Invalid peerid");
|
||||||
}
|
return;
|
||||||
}
|
}*/
|
||||||
// Register the user
|
|
||||||
PeerList peers = m_torrents.value(annonce_req.info_hash);
|
|
||||||
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
|
||||||
// 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
|
// 3. Get port
|
||||||
replyWithPeerList(annonce_req);
|
if (!gets.contains("port")) {
|
||||||
|
qDebug("QTracker: Missing port");
|
||||||
|
status(103, "Missing port");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool ok = false;
|
||||||
|
annonce_req.peer.port = gets.value("port").toInt(&ok);
|
||||||
|
if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) {
|
||||||
|
qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port);
|
||||||
|
status(103, "Missing port");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Get event
|
||||||
|
annonce_req.event = "";
|
||||||
|
if (gets.contains("event")) {
|
||||||
|
annonce_req.event = gets.value("event");
|
||||||
|
qDebug("QTracker: event is %s", qPrintable(annonce_req.event));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Get numwant
|
||||||
|
annonce_req.numwant = 50;
|
||||||
|
if (gets.contains("numwant")) {
|
||||||
|
int tmp = gets.value("numwant").toInt();
|
||||||
|
if (tmp > 0) {
|
||||||
|
qDebug("QTracker: numwant = %d", tmp);
|
||||||
|
annonce_req.numwant = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. no_peer_id (extension)
|
||||||
|
annonce_req.no_peer_id = false;
|
||||||
|
if (gets.contains("no_peer_id"))
|
||||||
|
annonce_req.no_peer_id = true;
|
||||||
|
|
||||||
|
// 7. TODO: support "compact" extension
|
||||||
|
|
||||||
|
// Done parsing, now let's reply
|
||||||
|
if (m_torrents.contains(annonce_req.info_hash)) {
|
||||||
|
if (annonce_req.event == "stopped") {
|
||||||
|
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
|
||||||
|
if (m_torrents.size() == MAX_TORRENTS) {
|
||||||
|
// 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);
|
||||||
|
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
||||||
|
// 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(annonce_req);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QTracker::replyWithPeerList(const TrackerAnnounceRequest &annonce_req)
|
void QTracker::replyWithPeerList(const TrackerAnnounceRequest &annonce_req)
|
||||||
{
|
{
|
||||||
// Prepare the entry for bencoding
|
// Prepare the entry for bencoding
|
||||||
libtorrent::entry::dictionary_type reply_dict;
|
libtorrent::entry::dictionary_type reply_dict;
|
||||||
reply_dict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
|
reply_dict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
|
||||||
QList<QPeer> peers = m_torrents.value(annonce_req.info_hash).values();
|
QList<QPeer> peers = m_torrents.value(annonce_req.info_hash).values();
|
||||||
libtorrent::entry::list_type peer_list;
|
libtorrent::entry::list_type peer_list;
|
||||||
foreach (const QPeer & p, peers) {
|
foreach (const QPeer &p, peers) {
|
||||||
//if (p != annonce_req.peer)
|
//if (p != annonce_req.peer)
|
||||||
peer_list.push_back(p.toEntry(annonce_req.no_peer_id));
|
peer_list.push_back(p.toEntry(annonce_req.no_peer_id));
|
||||||
}
|
}
|
||||||
reply_dict["peers"] = libtorrent::entry(peer_list);
|
reply_dict["peers"] = libtorrent::entry(peer_list);
|
||||||
libtorrent::entry reply_entry(reply_dict);
|
libtorrent::entry reply_entry(reply_dict);
|
||||||
// bencode
|
// bencode
|
||||||
std::vector<char> buf;
|
std::vector<char> buf;
|
||||||
libtorrent::bencode(std::back_inserter(buf), reply_entry);
|
libtorrent::bencode(std::back_inserter(buf), reply_entry);
|
||||||
QByteArray reply(&buf[0], static_cast<int>(buf.size()));
|
QByteArray reply(&buf[0], static_cast<int>(buf.size()));
|
||||||
qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData());
|
qDebug("QTracker: reply with the following bencoded data:\n %s", reply.constData());
|
||||||
|
|
||||||
// HTTP reply
|
// HTTP reply
|
||||||
print(reply, Http::CONTENT_TYPE_TXT);
|
print(reply, Http::CONTENT_TYPE_TXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,25 +74,25 @@ namespace Http { class Server; }
|
|||||||
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
|
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
|
||||||
class QTracker : public Http::ResponseBuilder, public Http::IRequestHandler
|
class QTracker : public Http::ResponseBuilder, public Http::IRequestHandler
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(QTracker)
|
Q_DISABLE_COPY(QTracker)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit QTracker(QObject *parent = 0);
|
explicit QTracker(QObject *parent = 0);
|
||||||
~QTracker();
|
~QTracker();
|
||||||
|
|
||||||
bool start();
|
bool start();
|
||||||
Http::Response processRequest(const Http::Request &request, const Http::Environment &env);
|
Http::Response processRequest(const Http::Request &request, const Http::Environment &env);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void respondToAnnounceRequest();
|
void respondToAnnounceRequest();
|
||||||
void replyWithPeerList(const TrackerAnnounceRequest &annonce_req);
|
void replyWithPeerList(const TrackerAnnounceRequest &annonce_req);
|
||||||
|
|
||||||
Http::Server *m_server;
|
Http::Server *m_server;
|
||||||
TorrentList m_torrents;
|
TorrentList m_torrents;
|
||||||
|
|
||||||
Http::Request m_request;
|
Http::Request m_request;
|
||||||
Http::Environment m_env;
|
Http::Environment m_env;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // QTRACKER_H
|
#endif // QTRACKER_H
|
||||||
|
@ -44,17 +44,17 @@
|
|||||||
class UnbanTimer: public QTimer
|
class UnbanTimer: public QTimer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
UnbanTimer(const QHostAddress& peer_ip, QObject *parent)
|
UnbanTimer(const QHostAddress& peer_ip, QObject *parent)
|
||||||
: QTimer(parent), m_peerIp(peer_ip)
|
: QTimer(parent), m_peerIp(peer_ip)
|
||||||
{
|
{
|
||||||
setSingleShot(true);
|
setSingleShot(true);
|
||||||
setInterval(BAN_TIME);
|
setInterval(BAN_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const QHostAddress& peerIp() const { return m_peerIp; }
|
inline const QHostAddress& peerIp() const { return m_peerIp; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHostAddress m_peerIp;
|
QHostAddress m_peerIp;
|
||||||
};
|
};
|
||||||
|
|
||||||
// WebSession
|
// WebSession
|
||||||
@ -79,8 +79,8 @@ struct WebSession
|
|||||||
// AbstractWebApplication
|
// AbstractWebApplication
|
||||||
|
|
||||||
AbstractWebApplication::AbstractWebApplication(QObject *parent)
|
AbstractWebApplication::AbstractWebApplication(QObject *parent)
|
||||||
: Http::ResponseBuilder(parent)
|
: Http::ResponseBuilder(parent)
|
||||||
, session_(0)
|
, session_(0)
|
||||||
{
|
{
|
||||||
QTimer *timer = new QTimer(this);
|
QTimer *timer = new QTimer(this);
|
||||||
timer->setInterval(60000); // 1 min.
|
timer->setInterval(60000); // 1 min.
|
||||||
@ -89,7 +89,7 @@ AbstractWebApplication::AbstractWebApplication(QObject *parent)
|
|||||||
|
|
||||||
AbstractWebApplication::~AbstractWebApplication()
|
AbstractWebApplication::~AbstractWebApplication()
|
||||||
{
|
{
|
||||||
// cleanup sessions data
|
// cleanup sessions data
|
||||||
qDeleteAll(sessions_);
|
qDeleteAll(sessions_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,10 +117,10 @@ Http::Response AbstractWebApplication::processRequest(const Http::Request &reque
|
|||||||
|
|
||||||
void AbstractWebApplication::UnbanTimerEvent()
|
void AbstractWebApplication::UnbanTimerEvent()
|
||||||
{
|
{
|
||||||
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender());
|
UnbanTimer* ubantimer = static_cast<UnbanTimer*>(sender());
|
||||||
qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp().toString()));
|
qDebug("Ban period has expired for %s", qPrintable(ubantimer->peerIp().toString()));
|
||||||
clientFailedAttempts_.remove(ubantimer->peerIp());
|
clientFailedAttempts_.remove(ubantimer->peerIp());
|
||||||
ubantimer->deleteLater();
|
ubantimer->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractWebApplication::removeInactiveSessions()
|
void AbstractWebApplication::removeInactiveSessions()
|
||||||
@ -135,84 +135,74 @@ void AbstractWebApplication::removeInactiveSessions()
|
|||||||
|
|
||||||
bool AbstractWebApplication::sessionInitialize()
|
bool AbstractWebApplication::sessionInitialize()
|
||||||
{
|
{
|
||||||
static const QString SID_START = QLatin1String(C_SID) + QLatin1String("=");
|
static const QString SID_START = QLatin1String(C_SID) + QLatin1String("=");
|
||||||
|
|
||||||
if (session_ == 0)
|
if (session_ == 0)
|
||||||
{
|
|
||||||
QString cookie = request_.headers.value("cookie");
|
|
||||||
//qDebug() << Q_FUNC_INFO << "cookie: " << cookie;
|
|
||||||
|
|
||||||
QString sessionId;
|
|
||||||
int pos = cookie.indexOf(SID_START);
|
|
||||||
if (pos >= 0)
|
|
||||||
{
|
{
|
||||||
pos += SID_START.length();
|
QString cookie = request_.headers.value("cookie");
|
||||||
int end = cookie.indexOf(QRegExp("[,;]"), pos);
|
//qDebug() << Q_FUNC_INFO << "cookie: " << cookie;
|
||||||
sessionId = cookie.mid(pos, end >= 0 ? end - pos : end);
|
|
||||||
|
QString sessionId;
|
||||||
|
int pos = cookie.indexOf(SID_START);
|
||||||
|
if (pos >= 0) {
|
||||||
|
pos += SID_START.length();
|
||||||
|
int end = cookie.indexOf(QRegExp("[,;]"), pos);
|
||||||
|
sessionId = cookie.mid(pos, end >= 0 ? end - pos : end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Additional session check
|
||||||
|
|
||||||
|
if (!sessionId.isNull()) {
|
||||||
|
if (sessions_.contains(sessionId)) {
|
||||||
|
session_ = sessions_[sessionId];
|
||||||
|
session_->updateTimestamp();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qDebug() << Q_FUNC_INFO << "session does not exist!";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Additional session check
|
return false;
|
||||||
|
|
||||||
if (!sessionId.isNull())
|
|
||||||
{
|
|
||||||
if (sessions_.contains(sessionId))
|
|
||||||
{
|
|
||||||
session_ = sessions_[sessionId];
|
|
||||||
session_->updateTimestamp();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qDebug() << Q_FUNC_INFO << "session does not exist!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AbstractWebApplication::readFile(const QString& path, QByteArray &data, QString &type)
|
bool AbstractWebApplication::readFile(const QString& path, QByteArray &data, QString &type)
|
||||||
{
|
{
|
||||||
QString ext = "";
|
QString ext = "";
|
||||||
int index = path.lastIndexOf('.') + 1;
|
int index = path.lastIndexOf('.') + 1;
|
||||||
if (index > 0)
|
if (index > 0)
|
||||||
ext = path.mid(index);
|
ext = path.mid(index);
|
||||||
|
|
||||||
// find translated file in cache
|
// find translated file in cache
|
||||||
if (translatedFiles_.contains(path))
|
if (translatedFiles_.contains(path)) {
|
||||||
{
|
data = translatedFiles_[path];
|
||||||
data = translatedFiles_[path];
|
}
|
||||||
}
|
else {
|
||||||
else
|
QFile file(path);
|
||||||
{
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
QFile file(path);
|
qDebug("File %s was not found!", qPrintable(path));
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
return false;
|
||||||
{
|
}
|
||||||
qDebug("File %s was not found!", qPrintable(path));
|
|
||||||
return false;
|
data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Translate the file
|
||||||
|
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js"))) {
|
||||||
|
QString dataStr = QString::fromUtf8(data.constData());
|
||||||
|
translateDocument(dataStr);
|
||||||
|
|
||||||
|
if (path.endsWith("about.html"))
|
||||||
|
dataStr.replace("${VERSION}", VERSION);
|
||||||
|
|
||||||
|
data = dataStr.toUtf8();
|
||||||
|
translatedFiles_[path] = data; // cashing translated file
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data = file.readAll();
|
type = CONTENT_TYPE_BY_EXT[ext];
|
||||||
file.close();
|
return true;
|
||||||
|
|
||||||
// Translate the file
|
|
||||||
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js")))
|
|
||||||
{
|
|
||||||
QString dataStr = QString::fromUtf8(data.constData());
|
|
||||||
translateDocument(dataStr);
|
|
||||||
|
|
||||||
if (path.endsWith("about.html"))
|
|
||||||
{
|
|
||||||
dataStr.replace("${VERSION}", VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
data = dataStr.toUtf8();
|
|
||||||
translatedFiles_[path] = data; // cashing translated file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type = CONTENT_TYPE_BY_EXT[ext];
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSessionData *AbstractWebApplication::session()
|
WebSessionData *AbstractWebApplication::session()
|
||||||
@ -227,102 +217,95 @@ QString AbstractWebApplication::generateSid()
|
|||||||
QString sid;
|
QString sid;
|
||||||
|
|
||||||
qsrand(QDateTime::currentDateTime().toTime_t());
|
qsrand(QDateTime::currentDateTime().toTime_t());
|
||||||
do
|
do {
|
||||||
{
|
const size_t size = 6;
|
||||||
const size_t size = 6;
|
quint32 tmp[size];
|
||||||
quint32 tmp[size];
|
|
||||||
|
|
||||||
for (size_t i = 0; i < size; ++i)
|
for (size_t i = 0; i < size; ++i)
|
||||||
tmp[i] = qrand();
|
tmp[i] = qrand();
|
||||||
|
|
||||||
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64();
|
sid = QByteArray::fromRawData(reinterpret_cast<const char *>(tmp), sizeof(quint32) * size).toBase64();
|
||||||
}
|
}
|
||||||
while (sessions_.contains(sid));
|
while (sessions_.contains(sid));
|
||||||
|
|
||||||
return sid;
|
return sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractWebApplication::translateDocument(QString& data)
|
void AbstractWebApplication::translateDocument(QString& data)
|
||||||
{
|
{
|
||||||
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR");
|
const QRegExp regex("QBT_TR\\((([^\\)]|\\)(?!QBT_TR))+)\\)QBT_TR");
|
||||||
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?");
|
const QRegExp mnemonic("\\(?&([a-zA-Z]?\\))?");
|
||||||
const std::string contexts[] = {
|
const std::string contexts[] = {
|
||||||
"TransferListFiltersWidget", "TransferListWidget", "PropertiesWidget",
|
"TransferListFiltersWidget", "TransferListWidget", "PropertiesWidget",
|
||||||
"HttpServer", "confirmDeletionDlg", "TrackerList", "TorrentFilesModel",
|
"HttpServer", "confirmDeletionDlg", "TrackerList", "TorrentFilesModel",
|
||||||
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel",
|
"options_imp", "Preferences", "TrackersAdditionDlg", "ScanFoldersModel",
|
||||||
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc",
|
"PropTabBar", "TorrentModel", "downloadFromURL", "MainWindow", "misc",
|
||||||
"StatusBar"
|
"StatusBar"
|
||||||
};
|
};
|
||||||
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]);
|
const size_t context_count = sizeof(contexts) / sizeof(contexts[0]);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
bool found = true;
|
bool found = true;
|
||||||
|
|
||||||
const QString locale = Preferences::instance()->getLocale();
|
const QString locale = Preferences::instance()->getLocale();
|
||||||
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB");
|
bool isTranslationNeeded = !locale.startsWith("en") || locale.startsWith("en_AU") || locale.startsWith("en_GB");
|
||||||
|
|
||||||
while(i < data.size() && found)
|
while(i < data.size() && found) {
|
||||||
{
|
i = regex.indexIn(data, i);
|
||||||
i = regex.indexIn(data, i);
|
if (i >= 0) {
|
||||||
if (i >= 0)
|
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data());
|
||||||
{
|
QByteArray word = regex.cap(1).toUtf8();
|
||||||
//qDebug("Found translatable string: %s", regex.cap(1).toUtf8().data());
|
|
||||||
QByteArray word = regex.cap(1).toUtf8();
|
|
||||||
|
|
||||||
QString translation = word;
|
QString translation = word;
|
||||||
if (isTranslationNeeded)
|
if (isTranslationNeeded) {
|
||||||
{
|
size_t context_index = 0;
|
||||||
size_t context_index = 0;
|
while ((context_index < context_count) && (translation == word)) {
|
||||||
while ((context_index < context_count) && (translation == word))
|
|
||||||
{
|
|
||||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||||
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, QCoreApplication::UnicodeUTF8, 1);
|
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, QCoreApplication::UnicodeUTF8, 1);
|
||||||
#else
|
#else
|
||||||
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, 1);
|
translation = qApp->translate(contexts[context_index].c_str(), word.constData(), 0, 1);
|
||||||
#endif
|
#endif
|
||||||
++context_index;
|
++context_index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove keyboard shortcuts
|
// Remove keyboard shortcuts
|
||||||
translation.replace(mnemonic, "");
|
translation.replace(mnemonic, "");
|
||||||
|
|
||||||
data.replace(i, regex.matchedLength(), translation);
|
data.replace(i, regex.matchedLength(), translation);
|
||||||
i += translation.length();
|
i += translation.length();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
found = false; // no more translatable strings
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
found = false; // no more translatable strings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AbstractWebApplication::isBanned() const
|
bool AbstractWebApplication::isBanned() const
|
||||||
{
|
{
|
||||||
return clientFailedAttempts_.value(env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS;
|
return clientFailedAttempts_.value(env_.clientAddress, 0) >= MAX_AUTH_FAILED_ATTEMPTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AbstractWebApplication::failedAttempts() const
|
int AbstractWebApplication::failedAttempts() const
|
||||||
{
|
{
|
||||||
return clientFailedAttempts_.value(env_.clientAddress, 0);
|
return clientFailedAttempts_.value(env_.clientAddress, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractWebApplication::resetFailedAttempts()
|
void AbstractWebApplication::resetFailedAttempts()
|
||||||
{
|
{
|
||||||
clientFailedAttempts_.remove(env_.clientAddress);
|
clientFailedAttempts_.remove(env_.clientAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractWebApplication::increaseFailedAttempts()
|
void AbstractWebApplication::increaseFailedAttempts()
|
||||||
{
|
{
|
||||||
const int nb_fail = clientFailedAttempts_.value(env_.clientAddress, 0) + 1;
|
const int nb_fail = clientFailedAttempts_.value(env_.clientAddress, 0) + 1;
|
||||||
|
|
||||||
clientFailedAttempts_[env_.clientAddress] = nb_fail;
|
clientFailedAttempts_[env_.clientAddress] = nb_fail;
|
||||||
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS)
|
if (nb_fail == MAX_AUTH_FAILED_ATTEMPTS) {
|
||||||
{
|
// Max number of failed attempts reached
|
||||||
// Max number of failed attempts reached
|
// Start ban period
|
||||||
// Start ban period
|
UnbanTimer* ubantimer = new UnbanTimer(env_.clientAddress, this);
|
||||||
UnbanTimer* ubantimer = new UnbanTimer(env_.clientAddress, this);
|
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent()));
|
||||||
connect(ubantimer, SIGNAL(timeout()), SLOT(UnbanTimerEvent()));
|
ubantimer->start();
|
||||||
ubantimer->start();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AbstractWebApplication::isAuthNeeded()
|
bool AbstractWebApplication::isAuthNeeded()
|
||||||
@ -347,39 +330,37 @@ void AbstractWebApplication::printFile(const QString& path)
|
|||||||
|
|
||||||
bool AbstractWebApplication::sessionStart()
|
bool AbstractWebApplication::sessionStart()
|
||||||
{
|
{
|
||||||
if (session_ == 0)
|
if (session_ == 0) {
|
||||||
{
|
session_ = new WebSession(generateSid());
|
||||||
session_ = new WebSession(generateSid());
|
session_->updateTimestamp();
|
||||||
session_->updateTimestamp();
|
sessions_[session_->id] = session_;
|
||||||
sessions_[session_->id] = session_;
|
|
||||||
|
|
||||||
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
|
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
|
||||||
cookie.setPath(QLatin1String("/"));
|
cookie.setPath(QLatin1String("/"));
|
||||||
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AbstractWebApplication::sessionEnd()
|
bool AbstractWebApplication::sessionEnd()
|
||||||
{
|
{
|
||||||
if ((session_ != 0) && (sessions_.contains(session_->id)))
|
if ((session_ != 0) && (sessions_.contains(session_->id))) {
|
||||||
{
|
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
|
||||||
QNetworkCookie cookie(C_SID, session_->id.toUtf8());
|
cookie.setPath(QLatin1String("/"));
|
||||||
cookie.setPath(QLatin1String("/"));
|
cookie.setExpirationDate(QDateTime::currentDateTime());
|
||||||
cookie.setExpirationDate(QDateTime::currentDateTime());
|
|
||||||
|
|
||||||
sessions_.remove(session_->id);
|
sessions_.remove(session_->id);
|
||||||
delete session_;
|
delete session_;
|
||||||
session_ = 0;
|
session_ = 0;
|
||||||
|
|
||||||
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
header(Http::HEADER_SET_COOKIE, cookie.toRawForm());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AbstractWebApplication::saveTmpFile(const QByteArray &data)
|
QString AbstractWebApplication::saveTmpFile(const QByteArray &data)
|
||||||
@ -398,16 +379,16 @@ QString AbstractWebApplication::saveTmpFile(const QByteArray &data)
|
|||||||
|
|
||||||
QStringMap AbstractWebApplication::initializeContentTypeByExtMap()
|
QStringMap AbstractWebApplication::initializeContentTypeByExtMap()
|
||||||
{
|
{
|
||||||
QStringMap map;
|
QStringMap map;
|
||||||
|
|
||||||
map["htm"] = Http::CONTENT_TYPE_HTML;
|
map["htm"] = Http::CONTENT_TYPE_HTML;
|
||||||
map["html"] = Http::CONTENT_TYPE_HTML;
|
map["html"] = Http::CONTENT_TYPE_HTML;
|
||||||
map["css"] = Http::CONTENT_TYPE_CSS;
|
map["css"] = Http::CONTENT_TYPE_CSS;
|
||||||
map["gif"] = Http::CONTENT_TYPE_GIF;
|
map["gif"] = Http::CONTENT_TYPE_GIF;
|
||||||
map["png"] = Http::CONTENT_TYPE_PNG;
|
map["png"] = Http::CONTENT_TYPE_PNG;
|
||||||
map["js"] = Http::CONTENT_TYPE_JS;
|
map["js"] = Http::CONTENT_TYPE_JS;
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringMap AbstractWebApplication::CONTENT_TYPE_BY_EXT = AbstractWebApplication::initializeContentTypeByExtMap();
|
const QStringMap AbstractWebApplication::CONTENT_TYPE_BY_EXT = AbstractWebApplication::initializeContentTypeByExtMap();
|
||||||
|
@ -33,44 +33,44 @@
|
|||||||
|
|
||||||
// Additional translations for Web UI
|
// Additional translations for Web UI
|
||||||
static const char *__TRANSLATIONS__[] = {
|
static const char *__TRANSLATIONS__[] = {
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "File"),
|
QT_TRANSLATE_NOOP("HttpServer", "File"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Edit"),
|
QT_TRANSLATE_NOOP("HttpServer", "Edit"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Help"),
|
QT_TRANSLATE_NOOP("HttpServer", "Help"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Download Torrents from their URL or Magnet link"),
|
QT_TRANSLATE_NOOP("HttpServer", "Download Torrents from their URL or Magnet link"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Only one link per line"),
|
QT_TRANSLATE_NOOP("HttpServer", "Only one link per line"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Download local torrent"),
|
QT_TRANSLATE_NOOP("HttpServer", "Download local torrent"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Torrent files were correctly added to download list."),
|
QT_TRANSLATE_NOOP("HttpServer", "Torrent files were correctly added to download list."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Point to torrent file"),
|
QT_TRANSLATE_NOOP("HttpServer", "Point to torrent file"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Download"),
|
QT_TRANSLATE_NOOP("HttpServer", "Download"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Are you sure you want to delete the selected torrents from the transfer list and hard disk?"),
|
QT_TRANSLATE_NOOP("HttpServer", "Are you sure you want to delete the selected torrents from the transfer list and hard disk?"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Download rate limit must be greater than 0 or disabled."),
|
QT_TRANSLATE_NOOP("HttpServer", "Download rate limit must be greater than 0 or disabled."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Upload rate limit must be greater than 0 or disabled."),
|
QT_TRANSLATE_NOOP("HttpServer", "Upload rate limit must be greater than 0 or disabled."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections limit must be greater than 0 or disabled."),
|
QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections limit must be greater than 0 or disabled."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections per torrent limit must be greater than 0 or disabled."),
|
QT_TRANSLATE_NOOP("HttpServer", "Maximum number of connections per torrent limit must be greater than 0 or disabled."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Maximum number of upload slots per torrent limit must be greater than 0 or disabled."),
|
QT_TRANSLATE_NOOP("HttpServer", "Maximum number of upload slots per torrent limit must be greater than 0 or disabled."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Unable to save program preferences, qBittorrent is probably unreachable."),
|
QT_TRANSLATE_NOOP("HttpServer", "Unable to save program preferences, qBittorrent is probably unreachable."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Language"),
|
QT_TRANSLATE_NOOP("HttpServer", "Language"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "The port used for incoming connections must be greater than 1024 and less than 65535."),
|
QT_TRANSLATE_NOOP("HttpServer", "The port used for incoming connections must be greater than 1024 and less than 65535."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "The port used for the Web UI must be greater than 1024 and less than 65535."),
|
QT_TRANSLATE_NOOP("HttpServer", "The port used for the Web UI must be greater than 1024 and less than 65535."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "The Web UI username must be at least 3 characters long."),
|
QT_TRANSLATE_NOOP("HttpServer", "The Web UI username must be at least 3 characters long."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "The Web UI password must be at least 3 characters long."),
|
QT_TRANSLATE_NOOP("HttpServer", "The Web UI password must be at least 3 characters long."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Save"),
|
QT_TRANSLATE_NOOP("HttpServer", "Save"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "qBittorrent client is not reachable"),
|
QT_TRANSLATE_NOOP("HttpServer", "qBittorrent client is not reachable"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "HTTP Server"),
|
QT_TRANSLATE_NOOP("HttpServer", "HTTP Server"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "The following parameters are supported:"),
|
QT_TRANSLATE_NOOP("HttpServer", "The following parameters are supported:"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Torrent path"),
|
QT_TRANSLATE_NOOP("HttpServer", "Torrent path"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Torrent name"),
|
QT_TRANSLATE_NOOP("HttpServer", "Torrent name"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "qBittorrent has been shutdown."),
|
QT_TRANSLATE_NOOP("HttpServer", "qBittorrent has been shutdown."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Unable to log in, qBittorrent is probably unreachable."),
|
QT_TRANSLATE_NOOP("HttpServer", "Unable to log in, qBittorrent is probably unreachable."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Invalid Username or Password."),
|
QT_TRANSLATE_NOOP("HttpServer", "Invalid Username or Password."),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Password"),
|
QT_TRANSLATE_NOOP("HttpServer", "Password"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Login"),
|
QT_TRANSLATE_NOOP("HttpServer", "Login"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "qBittorrent web User Interface"),
|
QT_TRANSLATE_NOOP("HttpServer", "qBittorrent web User Interface"),
|
||||||
QT_TRANSLATE_NOOP("HttpServer", "Upload Failed!")
|
QT_TRANSLATE_NOOP("HttpServer", "Upload Failed!")
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct { const char *source; const char *comment; } __COMMENTED_TRANSLATIONS__[] = {
|
static const struct { const char *source; const char *comment; } __COMMENTED_TRANSLATIONS__[] = {
|
||||||
QT_TRANSLATE_NOOP3("HttpServer", "Downloaded", "Is the file downloaded or not?")
|
QT_TRANSLATE_NOOP3("HttpServer", "Downloaded", "Is the file downloaded or not?")
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // EXTRA_TRANSLATIONS_H
|
#endif // EXTRA_TRANSLATIONS_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user