Browse Source

Follow project coding style (Issue #2192).

adaptive-webui-19844
Vladimir Golovnev (Glassez) 10 years ago
parent
commit
898d454b78
  1. 89
      src/core/http/connection.cpp
  2. 20
      src/core/http/connection.h
  3. 441
      src/core/http/requestparser.cpp
  4. 35
      src/core/http/requestparser.h
  5. 145
      src/core/http/responsegenerator.cpp
  6. 54
      src/core/http/server.cpp
  7. 24
      src/core/http/server.h
  8. 259
      src/core/qtracker.cpp
  9. 24
      src/core/qtracker.h
  10. 333
      src/webui/abstractwebapplication.cpp
  11. 70
      src/webui/extra_translations.h

89
src/core/http/connection.cpp

@ -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 // Let's see if there's a qvalue of 0.0 following
if (encoding[pos + 4] != ';') //there isn't, so it accepts gzip anyway if (encoding[pos + 4] != ';') //there isn't, so it accepts gzip anyway
return true; return true;
//So let's find = and the next comma //So let's find = and the next comma
pos = encoding.indexOf("=", pos + 4, Qt::CaseInsensitive); pos = encoding.indexOf("=", pos + 4, Qt::CaseInsensitive);
int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive); int comma_pos = encoding.indexOf(",", pos, Qt::CaseInsensitive);
QString value; QString value;
if (comma_pos == -1) if (comma_pos == -1)
value = encoding.mid(pos + 1, comma_pos); value = encoding.mid(pos + 1, comma_pos);
else else
value = encoding.mid(pos + 1, comma_pos - (pos + 1)); value = encoding.mid(pos + 1, comma_pos - (pos + 1));
if (value.toDouble() == 0.0) if (value.toDouble() == 0.0)
return false; return false;
return true; return true;
} }

20
src/core/http/connection.h

@ -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;
}; };
} }

441
src/core/http/requestparser.cpp

@ -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;
}
QByteArray content = data.mid(header_end + EOH.length(), content_length);
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() << Q_FUNC_INFO; // qDebug() << "HTTP Request header:";
// qDebug() << "HTTP Request header:"; // qDebug() << data.left(header_end) << "\n";
// qDebug() << data.left(header_end) << "\n";
request = m_request;
request = m_request; return NoError;
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; qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
return false; 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
{
headerLines.append(line);
} }
}
if (headerLines.isEmpty()) if (headerLines.isEmpty())
return false; // Empty header return false; // Empty header
QStringList::Iterator it = headerLines.begin(); QStringList::Iterator it = headerLines.begin();
if (!parseStartingLine(*it)) if (!parseStartingLine(*it))
return false; return false;
++it; ++it;
for (; it != headerLines.end(); ++it) for (; it != headerLines.end(); ++it) {
{ QPair<QString, QString> header;
QPair<QString, QString> header; if (!parseHeaderLine(*it, header))
if (!parseHeaderLine(*it, header)) return false;
return false;
m_request.headers[header.first] = header.second; m_request.headers[header.first] = header.second;
} }
return true; 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);
{ start = end + sepLength;
ret << data.mid(start, end - start); }
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) {
{ qWarning() << "Could not find boundary in multipart/form-data header!";
if (boundaryRegexNotQuoted.indexIn(content_type) < 0) return false;
{ }
qWarning() << "Could not find boundary in multipart/form-data header!"; else {
return false; boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1();
} }
else }
{ else {
boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1(); boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1();
} }
qDebug() << "Boundary is " << boundary;
QList<QByteArray> parts = splitMultipartData(data, boundary);
qDebug() << parts.size() << "parts in data";
foreach (const QByteArray& part, parts) {
if (!parseFormData(part))
return false;
}
return true;
} }
else
{ qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type);
boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1(); return false;
}
bool RequestParser::parseFormData(const QByteArray& data)
{
// Parse form data header
const int header_end = data.indexOf(EOH);
if (header_end < 0) {
qDebug() << "Invalid form data: \n" << data;
return false;
} }
qDebug() << "Boundary is " << boundary; QString header_str = QString::fromUtf8(data.left(header_end));
QList<QByteArray> parts = splitMultipartData(data, boundary); QStringList lines = header_str.trimmed().split(EOL);
qDebug() << parts.size() << "parts in data"; QStringMap headers;
foreach (const QString& line, lines) {
QPair<QString, QString> header;
if (!parseHeaderLine(line, header))
return false;
foreach (const QByteArray& part, parts) headers[header.first] = header.second;
{ }
if (!parseFormData(part))
QStringMap disposition;
if (!headers.contains("content-disposition")
|| !parseHeaderValue(headers["content-disposition"], disposition)
|| !disposition.contains("name")) {
qDebug() << "Invalid form data header: \n" << header_str;
return false; return false;
} }
return true;
}
qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(content_type); if (disposition.contains("filename")) {
return false; UploadedFile ufile;
} ufile.filename = disposition["filename"];
ufile.type = disposition["content-type"];
ufile.data = data.mid(header_end + EOH.length());
bool RequestParser::parseFormData(const QByteArray& data) m_request.files[disposition["name"]] = ufile;
{ }
// Parse form data header else {
const int header_end = data.indexOf(EOH); m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length()));
if (header_end < 0) }
{
qDebug() << "Invalid form data: \n" << data; return true;
return false;
}
QString header_str = QString::fromUtf8(data.left(header_end));
QStringList lines = header_str.trimmed().split(EOL);
QStringMap headers;
foreach (const QString& line, lines)
{
QPair<QString, QString> header;
if (!parseHeaderLine(line, header))
return false;
headers[header.first] = header.second;
}
QStringMap disposition;
if (!headers.contains("content-disposition") ||
!parseHeaderValue(headers["content-disposition"], disposition) ||
!disposition.contains("name"))
{
qDebug() << "Invalid form data header: \n" << header_str;
return false;
}
if (disposition.contains("filename"))
{
UploadedFile ufile;
ufile.filename = disposition["filename"];
ufile.type = disposition["content-type"];
ufile.data = data.mid(header_end + EOH.length());
m_request.files[disposition["name"]] = ufile;
}
else
{
m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(header_end + EOH.length()));
}
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;
} }

35
src/core/http/requestparser.h

@ -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;
}; };
} }

145
src/core/http/responsegenerator.cpp

@ -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;
foreach (const QString& key, response.headers.keys())
header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]);
QString header; ret = ret.arg(response.status.code).arg(response.status.text).arg(header);
foreach (const QString& key, response.headers.keys())
header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]);
ret = ret.arg(response.status.code).arg(response.status.text).arg(header); // qDebug() << Q_FUNC_INFO;
// qDebug() << "HTTP Response header:";
// qDebug() << Q_FUNC_INFO; // qDebug() << ret;
// qDebug() << "HTTP Response header:";
// 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)
return false;
if (strm.avail_out == 0) if (ret != Z_OK)
{ return false;
dest_buffer.append(tmp_buf, BUFSIZE);
strm.next_out = reinterpret_cast<unsigned char*>(tmp_buf); while (strm.avail_in != 0) {
strm.avail_out = BUFSIZE; ret = deflate(&strm, Z_NO_FLUSH);
} if (ret != Z_OK)
} return false;
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;
}
deflate_res = deflate(&strm, Z_FINISH);
}
if (deflate_res != Z_STREAM_END) if (deflate_res != Z_STREAM_END)
return false; return false;
dest_buffer.append(tmp_buf, BUFSIZE - strm.avail_out); dest_buffer.append(tmp_buf, BUFSIZE - strm.avail_out);
deflateEnd(&strm); deflateEnd(&strm);
return true; return true;
} }

54
src/core/http/server.cpp

@ -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 #ifndef QT_NO_OPENSSL
if (m_https) if (m_https)
serverSocket = new QSslSocket(this); serverSocket = new QSslSocket(this);
else else
#endif #endif
serverSocket = new QTcpSocket(this); serverSocket = new QTcpSocket(this);
if (serverSocket->setSocketDescriptor(socketDescriptor))
{ if (serverSocket->setSocketDescriptor(socketDescriptor)) {
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
if (m_https) if (m_https) {
{ static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::AnyProtocol);
static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::AnyProtocol); static_cast<QSslSocket*>(serverSocket)->setPrivateKey(m_key);
static_cast<QSslSocket*>(serverSocket)->setPrivateKey(m_key); static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificate);
static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificate); static_cast<QSslSocket*>(serverSocket)->startServerEncryption();
static_cast<QSslSocket*>(serverSocket)->startServerEncryption(); }
}
#endif #endif
new Connection(serverSocket, m_requestHandler, this); new Connection(serverSocket, m_requestHandler, this);
} }
else else {
{ serverSocket->deleteLater();
serverSocket->deleteLater(); }
}
} }

24
src/core/http/server.h

@ -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
}; };

259
src/core/qtracker.cpp

@ -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"); annonce_req.info_hash = gets.value("info_hash");
// info_hash cannot be longer than 20 bytes // info_hash cannot be longer than 20 bytes
/*if (annonce_req.info_hash.toLatin1().length() > 20) { /*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()); 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"); status(150, "Invalid infohash");
return; return;
}*/ }*/
// 2. Get peer ID // 2. Get peer ID
if (!gets.contains("peer_id")) { if (!gets.contains("peer_id")) {
qDebug("QTracker: Missing peer_id"); qDebug("QTracker: Missing peer_id");
status(102, "Missing peer_id"); status(102, "Missing peer_id");
return; return;
} }
annonce_req.peer.peer_id = gets.value("peer_id"); annonce_req.peer.peer_id = gets.value("peer_id");
// peer_id cannot be longer than 20 bytes // peer_id cannot be longer than 20 bytes
/*if (annonce_req.peer.peer_id.length() > 20) { /*if (annonce_req.peer.peer_id.length() > 20) {
qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id)); qDebug("QTracker: peer_id is not 20 byte long: %s", qPrintable(annonce_req.peer.peer_id));
status(151, "Invalid peerid"); status(151, "Invalid peerid");
return; return;
}*/ }*/
// 3. Get port // 3. Get port
if (!gets.contains("port")) { if (!gets.contains("port")) {
qDebug("QTracker: Missing port"); qDebug("QTracker: Missing port");
status(103, "Missing port"); status(103, "Missing port");
return; return;
} }
bool ok = false; bool ok = false;
annonce_req.peer.port = gets.value("port").toInt(&ok); annonce_req.peer.port = gets.value("port").toInt(&ok);
if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) { if (!ok || annonce_req.peer.port < 1 || annonce_req.peer.port > 65535) {
qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port); qDebug("QTracker: Invalid port number (%d)", annonce_req.peer.port);
status(103, "Missing port"); status(103, "Missing port");
return; return;
} }
// 4. Get event // 4. Get event
annonce_req.event = ""; annonce_req.event = "";
if (gets.contains("event")) { if (gets.contains("event")) {
annonce_req.event = gets.value("event"); annonce_req.event = gets.value("event");
qDebug("QTracker: event is %s", qPrintable(annonce_req.event)); qDebug("QTracker: event is %s", qPrintable(annonce_req.event));
} }
// 5. Get numwant // 5. Get numwant
annonce_req.numwant = 50; annonce_req.numwant = 50;
if (gets.contains("numwant")) { if (gets.contains("numwant")) {
int tmp = gets.value("numwant").toInt(); int tmp = gets.value("numwant").toInt();
if (tmp > 0) { if (tmp > 0) {
qDebug("QTracker: numwant = %d", tmp); qDebug("QTracker: numwant = %d", tmp);
annonce_req.numwant = tmp; annonce_req.numwant = tmp;
}
} }
}
// 6. no_peer_id (extension) // 6. no_peer_id (extension)
annonce_req.no_peer_id = false; annonce_req.no_peer_id = false;
if (gets.contains("no_peer_id")) if (gets.contains("no_peer_id"))
annonce_req.no_peer_id = true; annonce_req.no_peer_id = true;
// 7. TODO: support "compact" extension // 7. TODO: support "compact" extension
// Done parsing, now let's reply // Done parsing, now let's reply
if (m_torrents.contains(annonce_req.info_hash)) { if (m_torrents.contains(annonce_req.info_hash)) {
if (annonce_req.event == "stopped") { if (annonce_req.event == "stopped") {
qDebug("QTracker: Peer stopped downloading, deleting it from the list"); qDebug("QTracker: Peer stopped downloading, deleting it from the list");
m_torrents[annonce_req.info_hash].remove(annonce_req.peer.qhash()); m_torrents[annonce_req.info_hash].remove(annonce_req.peer.qhash());
return; return;
}
} }
} else { else {
// Unknown torrent // Unknown torrent
if (m_torrents.size() == MAX_TORRENTS) { if (m_torrents.size() == MAX_TORRENTS) {
// Reached max size, remove a random torrent // Reached max size, remove a random torrent
m_torrents.erase(m_torrents.begin()); m_torrents.erase(m_torrents.begin());
}
} }
} // Register the user
// Register the user PeerList peers = m_torrents.value(annonce_req.info_hash);
PeerList peers = m_torrents.value(annonce_req.info_hash); if (peers.size() == MAX_PEERS_PER_TORRENT) {
if (peers.size() == MAX_PEERS_PER_TORRENT) { // Too many peers, remove a random one
// Too many peers, remove a random one peers.erase(peers.begin());
peers.erase(peers.begin()); }
} peers[annonce_req.peer.qhash()] = annonce_req.peer;
peers[annonce_req.peer.qhash()] = annonce_req.peer; m_torrents[annonce_req.info_hash] = peers;
m_torrents[annonce_req.info_hash] = peers;
// Reply
// Reply replyWithPeerList(annonce_req);
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);
} }

24
src/core/qtracker.h

@ -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

333
src/webui/abstractwebapplication.cpp

@ -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);
// TODO: Additional session check if (pos >= 0) {
pos += SID_START.length();
int end = cookie.indexOf(QRegExp("[,;]"), pos);
sessionId = cookie.mid(pos, end >= 0 ? end - pos : end);
}
if (!sessionId.isNull()) // TODO: Additional session check
{
if (sessions_.contains(sessionId)) if (!sessionId.isNull()) {
{ if (sessions_.contains(sessionId)) {
session_ = sessions_[sessionId]; session_ = sessions_[sessionId];
session_->updateTimestamp(); session_->updateTimestamp();
return true; return true;
} }
else else {
{ qDebug() << Q_FUNC_INFO << "session does not exist!";
qDebug() << Q_FUNC_INFO << "session does not exist!"; }
} }
} }
}
return false; 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
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
{
qDebug("File %s was not found!", qPrintable(path));
return false;
} }
else {
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
qDebug("File %s was not found!", qPrintable(path));
return false;
}
data = file.readAll(); data = file.readAll();
file.close(); file.close();
// Translate the file // Translate the file
if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js"))) if ((ext == "html") || ((ext == "js") && !path.endsWith("excanvas-compressed.js"))) {
{ QString dataStr = QString::fromUtf8(data.constData());
QString dataStr = QString::fromUtf8(data.constData()); translateDocument(dataStr);
translateDocument(dataStr);
if (path.endsWith("about.html")) if (path.endsWith("about.html"))
{ dataStr.replace("${VERSION}", VERSION);
dataStr.replace("${VERSION}", VERSION);
}
data = dataStr.toUtf8(); data = dataStr.toUtf8();
translatedFiles_[path] = data; // cashing translated file translatedFiles_[path] = data; // cashing translated file
}
} }
}
type = CONTENT_TYPE_BY_EXT[ext]; type = CONTENT_TYPE_BY_EXT[ext];
return true; 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;
if (isTranslationNeeded) {
QString translation = word; size_t context_index = 0;
if (isTranslationNeeded) while ((context_index < context_count) && (translation == word)) {
{
size_t context_index = 0;
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
translation.replace(mnemonic, "");
data.replace(i, regex.matchedLength(), translation);
i += translation.length();
}
else {
found = false; // no more translatable strings
} }
}
// Remove keyboard shortcuts
translation.replace(mnemonic, "");
data.replace(i, regex.matchedLength(), translation);
i += translation.length();
}
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();

70
src/webui/extra_translations.h

@ -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…
Cancel
Save