From 706e0921f3bf670cdc655ac8c78903afc42146f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Wed, 22 Jun 2016 19:09:04 +0200 Subject: [PATCH] Update httpserver to 1.6.5 --- .../lib/httpserver/httpconnectionhandler.cpp | 221 ++++++--- .../lib/httpserver/httpconnectionhandler.h | 45 +- .../httpserver/httpconnectionhandlerpool.cpp | 122 ++++- .../httpserver/httpconnectionhandlerpool.h | 42 +- .../server/lib/httpserver/httpcookie.cpp | 150 ++++-- .../server/lib/httpserver/httpcookie.h | 19 +- .../server/lib/httpserver/httpglobal.cpp | 2 +- .../server/lib/httpserver/httplistener.cpp | 69 ++- .../server/lib/httpserver/httplistener.h | 40 +- .../server/lib/httpserver/httprequest.cpp | 418 ++++++++++------ .../server/lib/httpserver/httprequest.h | 57 ++- .../lib/httpserver/httprequesthandler.cpp | 8 +- .../lib/httpserver/httprequesthandler.h | 10 +- .../server/lib/httpserver/httpresponse.cpp | 164 +++++-- .../server/lib/httpserver/httpresponse.h | 184 +++---- .../server/lib/httpserver/httpserver.pri | 35 +- .../server/lib/httpserver/httpsession.cpp | 453 +++++------------- .../server/lib/httpserver/httpsession.h | 213 +++----- .../lib/httpserver/httpsessionstore.cpp | 54 ++- .../server/lib/httpserver/httpsessionstore.h | 16 +- .../lib/httpserver/staticfilecontroller.cpp | 296 +++++------- .../lib/httpserver/staticfilecontroller.h | 71 ++- 22 files changed, 1491 insertions(+), 1198 deletions(-) diff --git a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.cpp b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.cpp index b1044b4a..d07b593e 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.cpp @@ -5,62 +5,107 @@ #include "httpconnectionhandler.h" #include "httpresponse.h" -#include -#include -HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler) +HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration) : QThread() { Q_ASSERT(settings!=0); Q_ASSERT(requestHandler!=0); this->settings=settings; this->requestHandler=requestHandler; + this->sslConfiguration=sslConfiguration; currentRequest=0; - busy = false; + busy=false; + + // Create TCP or SSL socket + createSocket(); + // execute signals in my own thread moveToThread(this); - socket.moveToThread(this); + socket->moveToThread(this); readTimer.moveToThread(this); - connect(&socket, SIGNAL(readyRead()), SLOT(read())); - connect(&socket, SIGNAL(disconnected()), SLOT(disconnected())); + + // Connect signals + connect(socket, SIGNAL(readyRead()), SLOT(read())); + connect(socket, SIGNAL(disconnected()), SLOT(disconnected())); connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout())); readTimer.setSingleShot(true); + qDebug("HttpConnectionHandler (%p): constructed", this); this->start(); } -HttpConnectionHandler::~HttpConnectionHandler() { - socket.close(); +HttpConnectionHandler::~HttpConnectionHandler() +{ quit(); wait(); qDebug("HttpConnectionHandler (%p): destroyed", this); } -void HttpConnectionHandler::run() { - qDebug("HttpConnectionHandler (%p): thread started", this); - try { - exec(); - } - catch (...) { - qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this); - } - qDebug("HttpConnectionHandler (%p): thread stopped", this); - // Change to the main thread, otherwise deleteLater() would not work - moveToThread(QCoreApplication::instance()->thread()); +void HttpConnectionHandler::createSocket() +{ + // If SSL is supported and configured, then create an instance of QSslSocket + #ifndef QT_NO_OPENSSL + if (sslConfiguration) + { + QSslSocket* sslSocket=new QSslSocket(); + sslSocket->setSslConfiguration(*sslConfiguration); + socket=sslSocket; + qDebug("HttpConnectionHandler (%p): SSL is enabled", this); + return; + } + #endif + // else create an instance of QTcpSocket + socket=new QTcpSocket(); } -void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) { +void HttpConnectionHandler::run() +{ + qDebug("HttpConnectionHandler (%p): thread started", this); + try + { + exec(); + } + catch (...) + { + qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this); + } + socket->close(); + delete socket; + readTimer.stop(); + qDebug("HttpConnectionHandler (%p): thread stopped", this); +} + + +void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) +{ qDebug("HttpConnectionHandler (%p): handle new connection", this); busy = true; - Q_ASSERT(socket.isOpen()==false); // if not, then the handler is already busy + Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy - if (!socket.setSocketDescriptor(socketDescriptor)) { - qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket.errorString())); + //UGLY workaround - we need to clear writebuffer before reusing this socket + //https://bugreports.qt-project.org/browse/QTBUG-28914 + socket->connectToHost("",0); + socket->abort(); + + if (!socket->setSocketDescriptor(socketDescriptor)) + { + qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket->errorString())); return; } + + #ifndef QT_NO_OPENSSL + // Switch on encryption, if SSL is configured + if (sslConfiguration) + { + qDebug("HttpConnectionHandler (%p): Starting encryption", this); + ((QSslSocket*)socket)->startServerEncryption(); + } + #endif + // Start timer for read timeout int readTimeout=settings->value("readTimeout",10000).toInt(); readTimer.start(readTimeout); @@ -70,49 +115,60 @@ void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) } -bool HttpConnectionHandler::isBusy() { +bool HttpConnectionHandler::isBusy() +{ return busy; } -void HttpConnectionHandler::setBusy() { +void HttpConnectionHandler::setBusy() +{ this->busy = true; } -void HttpConnectionHandler::readTimeout() { +void HttpConnectionHandler::readTimeout() +{ qDebug("HttpConnectionHandler (%p): read timeout occured",this); //Commented out because QWebView cannot handle this. - //socket.write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n"); + //socket->write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n"); - socket.disconnectFromHost(); + socket->flush(); + socket->disconnectFromHost(); delete currentRequest; currentRequest=0; } -void HttpConnectionHandler::disconnected() { +void HttpConnectionHandler::disconnected() +{ qDebug("HttpConnectionHandler (%p): disconnected", this); - socket.close(); + socket->close(); readTimer.stop(); busy = false; } -void HttpConnectionHandler::read() { - while (socket.bytesAvailable()) { -#ifdef SUPERVERBOSE - qDebug("HttpConnectionHandler (%p): read input",this); -#endif +void HttpConnectionHandler::read() +{ + // The loop adds support for HTTP pipelinig + while (socket->bytesAvailable()) + { + #ifdef SUPERVERBOSE + qDebug("HttpConnectionHandler (%p): read input",this); + #endif // Create new HttpRequest object if necessary - if (!currentRequest) { + if (!currentRequest) + { currentRequest=new HttpRequest(settings); } // Collect data for the request object - while (socket.bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort) { + while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort) + { currentRequest->readFromSocket(socket); - if (currentRequest->getStatus()==HttpRequest::waitForBody) { + if (currentRequest->getStatus()==HttpRequest::waitForBody) + { // Restart timer for read timeout, otherwise it would // expire during large file uploads. int readTimeout=settings->value("readTimeout",10000).toInt(); @@ -121,50 +177,99 @@ void HttpConnectionHandler::read() { } // If the request is aborted, return error message and close the connection - if (currentRequest->getStatus()==HttpRequest::abort) { - socket.write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n"); - socket.disconnectFromHost(); + if (currentRequest->getStatus()==HttpRequest::abort) + { + socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n"); + socket->flush(); + socket->disconnectFromHost(); delete currentRequest; currentRequest=0; return; } // If the request is complete, let the request mapper dispatch it - if (currentRequest->getStatus()==HttpRequest::complete) { + if (currentRequest->getStatus()==HttpRequest::complete) + { readTimer.stop(); qDebug("HttpConnectionHandler (%p): received request",this); - HttpResponse response(&socket); - //response.setHeader("Connection","close"); No funciona bien con NSURLConnection - try { + + // Copy the Connection:close header to the response + HttpResponse response(socket); + bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0; + if (closeConnection) + { + response.setHeader("Connection","close"); + } + + // In case of HTTP 1.0 protocol add the Connection:close header. + // This ensures that the HttpResponse does not activate chunked mode, which is not spported by HTTP 1.0. + else + { + bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0; + if (http1_0) + { + closeConnection=true; + response.setHeader("Connection","close"); + } + } + + // Call the request mapper + try + { requestHandler->service(*currentRequest, response); } - catch (...) { + catch (...) + { qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this); } // Finalize sending the response if not already done - if (!response.hasSentLastPart()) { + if (!response.hasSentLastPart()) + { response.write(QByteArray(),true); } - //socket.disconnectFromHost(); //CAMBIADO sólo se van a soportar conexiones NO persistentes + qDebug("HttpConnectionHandler (%p): finished request",this); - // Close the connection after delivering the response, if requested - if (QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0) { - socket.disconnectFromHost(); + // Find out whether the connection must be closed + if (!closeConnection) + { + // Maybe the request handler or mapper added a Connection:close header in the meantime + bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0; + if (closeResponse==true) + { + closeConnection=true; + } + else + { + // If we have no Content-Length header and did not use chunked mode, then we have to close the + // connection to tell the HTTP client that the end of the response has been reached. + bool hasContentLength=response.getHeaders().contains("Content-Length"); + if (!hasContentLength) + { + bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0; + if (!hasChunkedMode) + { + closeConnection=true; + } + } + } } - else { + + // Close the connection or prepare for the next request on the same connection. + if (closeConnection) + { + socket->flush(); + socket->disconnectFromHost(); + } + else + { // Start timer for next request int readTimeout=settings->value("readTimeout",10000).toInt(); readTimer.start(readTimeout); } - // Prepare for next request delete currentRequest; currentRequest=0; } - else - { - qDebug("HttpConnectionHandler (%p): received request",this); - } } } diff --git a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.h b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.h index 0e8b4483..79c0a786 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.h +++ b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandler.h @@ -6,13 +6,29 @@ #ifndef HTTPCONNECTIONHANDLER_H #define HTTPCONNECTIONHANDLER_H +#ifndef QT_NO_OPENSSL + #include +#endif #include #include #include #include +#include "httpglobal.h" #include "httprequest.h" #include "httprequesthandler.h" +/** Alias type definition, for compatibility to different Qt versions */ +#if QT_VERSION >= 0x050000 + typedef qintptr tSocketDescriptor; +#else + typedef int tSocketDescriptor; +#endif + +/** Alias for QSslConfiguration if OpenSSL is not supported */ +#ifdef QT_NO_OPENSSL + #define QSslConfiguration QObject +#endif + /** The connection handler accepts incoming connections and dispatches incoming requests to to a request mapper. Since HTTP clients can send multiple requests before waiting for the response, @@ -26,26 +42,21 @@

The readTimeout value defines the maximum time to wait for a complete HTTP request. - @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize + @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize. */ - -#if QT_VERSION >= 0x050000 - typedef qintptr tSocketDescriptor; -#else - typedef int tSocketDescriptor; -#endif - -class HttpConnectionHandler : public QThread { +class DECLSPEC HttpConnectionHandler : public QThread { Q_OBJECT Q_DISABLE_COPY(HttpConnectionHandler) + public: /** Constructor. @param settings Configuration settings of the HTTP webserver - @param requestHandler handler that will process each incomin HTTP request + @param requestHandler Handler that will process each incoming HTTP request + @param sslConfiguration SSL (HTTPS) will be used if not NULL */ - HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler); + HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration=NULL); /** Destructor */ virtual ~HttpConnectionHandler(); @@ -61,8 +72,8 @@ private: /** Configuration settings */ QSettings* settings; - /** TCP socket of the current connection */ - QTcpSocket socket; + /** TCP socket of the current connection */ + QTcpSocket* socket; /** Time for read timeout detection */ QTimer readTimer; @@ -76,9 +87,15 @@ private: /** This shows the busy-state from a very early time */ bool busy; - /** Executes the htreads own event loop */ + /** Configuration for SSL */ + QSslConfiguration* sslConfiguration; + + /** Executes the threads own event loop */ void run(); + /** Create SSL or TCP socket */ + void createSocket(); + public slots: /** diff --git a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.cpp b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.cpp index fbf70b49..6dd3d8c1 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.cpp @@ -1,3 +1,10 @@ +#ifndef QT_NO_OPENSSL + #include + #include + #include + #include +#endif +#include #include "httpconnectionhandlerpool.h" HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler) @@ -6,35 +13,46 @@ HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRe Q_ASSERT(settings!=0); this->settings=settings; this->requestHandler=requestHandler; - cleanupTimer.start(settings->value("cleanupInterval",10000).toInt()); + this->sslConfiguration=NULL; + loadSslConfig(); + cleanupTimer.start(settings->value("cleanupInterval",1000).toInt()); connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup())); } -HttpConnectionHandlerPool::~HttpConnectionHandlerPool() { - foreach(HttpConnectionHandler* handler, pool) { - connect(handler,SIGNAL(finished()),handler,SLOT(deleteLater())); - handler->quit(); +HttpConnectionHandlerPool::~HttpConnectionHandlerPool() +{ + // delete all connection handlers and wait until their threads are closed + foreach(HttpConnectionHandler* handler, pool) + { + delete handler; } + delete sslConfiguration; + qDebug("HttpConnectionHandlerPool (%p): destroyed", this); } -HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() { +HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() +{ HttpConnectionHandler* freeHandler=0; mutex.lock(); // find a free handler in pool - foreach(HttpConnectionHandler* handler, pool) { - if (!handler->isBusy()) { + foreach(HttpConnectionHandler* handler, pool) + { + if (!handler->isBusy()) + { freeHandler=handler; freeHandler->setBusy(); break; } } // create a new handler, if necessary - if (!freeHandler) { - int maxConnectionHandlers=settings->value("maxThreads",1000).toInt(); - if (pool.count()value("maxThreads",100).toInt(); + if (pool.count()setBusy(); pool.append(freeHandler); } @@ -44,21 +62,85 @@ HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() { } - -void HttpConnectionHandlerPool::cleanup() { - int maxIdleHandlers=settings->value("minThreads",50).toInt(); +void HttpConnectionHandlerPool::cleanup() +{ + int maxIdleHandlers=settings->value("minThreads",1).toInt(); int idleCounter=0; mutex.lock(); - foreach(HttpConnectionHandler* handler, pool) { - if (!handler->isBusy()) { - if (++idleCounter > maxIdleHandlers) { + foreach(HttpConnectionHandler* handler, pool) + { + if (!handler->isBusy()) + { + if (++idleCounter > maxIdleHandlers) + { + delete handler; pool.removeOne(handler); qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size()); - connect(handler,SIGNAL(finished()),handler,SLOT(deleteLater())); - handler->quit(); break; // remove only one handler in each interval } } } mutex.unlock(); } + + +void HttpConnectionHandlerPool::loadSslConfig() +{ + // If certificate and key files are configured, then load them + QString sslKeyFileName=settings->value("sslKeyFile","").toString(); + QString sslCertFileName=settings->value("sslCertFile","").toString(); + if (!sslKeyFileName.isEmpty() && !sslCertFileName.isEmpty()) + { + #ifdef QT_NO_OPENSSL + qWarning("HttpConnectionHandlerPool: SSL is not supported"); + #else + // Convert relative fileNames to absolute, based on the directory of the config file. + QFileInfo configFile(settings->fileName()); + #ifdef Q_OS_WIN32 + if (QDir::isRelativePath(sslKeyFileName) && settings->format()!=QSettings::NativeFormat) + #else + if (QDir::isRelativePath(sslKeyFileName)) + #endif + { + sslKeyFileName=QFileInfo(configFile.absolutePath(),sslKeyFileName).absoluteFilePath(); + } + #ifdef Q_OS_WIN32 + if (QDir::isRelativePath(sslCertFileName) && settings->format()!=QSettings::NativeFormat) + #else + if (QDir::isRelativePath(sslCertFileName)) + #endif + { + sslCertFileName=QFileInfo(configFile.absolutePath(),sslCertFileName).absoluteFilePath(); + } + + // Load the SSL certificate + QFile certFile(sslCertFileName); + if (!certFile.open(QIODevice::ReadOnly)) + { + qCritical("HttpConnectionHandlerPool: cannot open sslCertFile %s", qPrintable(sslCertFileName)); + return; + } + QSslCertificate certificate(&certFile, QSsl::Pem); + certFile.close(); + + // Load the key file + QFile keyFile(sslKeyFileName); + if (!keyFile.open(QIODevice::ReadOnly)) + { + qCritical("HttpConnectionHandlerPool: cannot open sslKeyFile %s", qPrintable(sslKeyFileName)); + return; + } + QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem); + keyFile.close(); + + // Create the SSL configuration + sslConfiguration=new QSslConfiguration(); + sslConfiguration->setLocalCertificate(certificate); + sslConfiguration->setPrivateKey(sslKey); + sslConfiguration->setPeerVerifyMode(QSslSocket::VerifyNone); + sslConfiguration->setProtocol(QSsl::TlsV1SslV3); + + qDebug("HttpConnectionHandlerPool: SSL settings loaded"); + #endif + } +} diff --git a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.h b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.h index 2dc94338..a9b9908b 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.h +++ b/YACReaderLibrary/server/lib/httpserver/httpconnectionhandlerpool.h @@ -5,29 +5,45 @@ #include #include #include +#include "httpglobal.h" #include "httpconnectionhandler.h" /** - Pool of http connection handlers. Connection handlers are created on demand and idle handlers are - cleaned up in regular time intervals. + Pool of http connection handlers. The size of the pool grows and + shrinks on demand.

Example for the required configuration settings:

-  minThreads=1
+  minThreads=4
   maxThreads=100
-  cleanupInterval=1000
+  cleanupInterval=60000
+  readTimeout=60000
+  ;sslKeyFile=ssl/my.key
+  ;sslCertFile=ssl/my.cert
   maxRequestSize=16000
   maxMultiPartSize=1000000
   
- The pool is empty initially and grows with the number of concurrent - connections. A timer removes one idle connection handler at each - interval, but it leaves some spare handlers in memory to improve - performance. - @see HttpConnectionHandler for description of config settings readTimeout + After server start, the size of the thread pool is always 0. Threads + are started on demand when requests come in. The cleanup timer reduces + the number of idle threads slowly by closing one thread in each interval. + But the configured minimum number of threads are kept running. +

+ For SSL support, you need an OpenSSL certificate file and a key file. + Both can be created with the command +

+      openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout my.key -out my.cert
+  
+

+ Visit http://slproweb.com/products/Win32OpenSSL.html to download the Light version of OpenSSL for Windows. +

+ Please note that a listener with SSL settings can only handle HTTPS protocol. To + support both HTTP and HTTPS simultaneously, you need to start two listeners on different ports - + one with SLL and one without SSL. + @see HttpConnectionHandler for description of the readTimeout @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize */ -class HttpConnectionHandlerPool : public QObject { +class DECLSPEC HttpConnectionHandlerPool : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpConnectionHandlerPool) public: @@ -63,6 +79,12 @@ private: /** Used to synchronize threads */ QMutex mutex; + /** The SSL configuration (certificate, key and other settings) */ + QSslConfiguration* sslConfiguration; + + /** Load SSL configuration */ + void loadSslConfig(); + private slots: /** Received from the clean-up timer. */ diff --git a/YACReaderLibrary/server/lib/httpserver/httpcookie.cpp b/YACReaderLibrary/server/lib/httpserver/httpcookie.cpp index 3f5be929..ce0f5207 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpcookie.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httpcookie.cpp @@ -5,13 +5,15 @@ #include "httpcookie.h" -HttpCookie::HttpCookie() { +HttpCookie::HttpCookie() +{ version=1; maxAge=0; secure=false; } -HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure) { +HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure, const bool httpOnly) +{ this->name=name; this->value=value; this->maxAge=maxAge; @@ -19,171 +21,230 @@ HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int this->comment=comment; this->domain=domain; this->secure=secure; + this->httpOnly=httpOnly; this->version=1; } -HttpCookie::HttpCookie(const QByteArray source) { +HttpCookie::HttpCookie(const QByteArray source) +{ version=1; maxAge=0; secure=false; QList list=splitCSV(source); - foreach(QByteArray part, list) { + foreach(QByteArray part, list) + { // Split the part into name and value QByteArray name; QByteArray value; int posi=part.indexOf('='); - if (posi) { + if (posi) + { name=part.left(posi).trimmed(); value=part.mid(posi+1).trimmed(); } - else { + else + { name=part.trimmed(); value=""; } // Set fields - if (name=="Comment") { + if (name=="Comment") + { comment=value; } - else if (name=="Domain") { + else if (name=="Domain") + { domain=value; } - else if (name=="Max-Age") { + else if (name=="Max-Age") + { maxAge=value.toInt(); } - else if (name=="Path") { + else if (name=="Path") + { path=value; } - else if (name=="Secure") { + else if (name=="Secure") + { secure=true; } - else if (name=="Version") { + else if (name=="HttpOnly") + { + httpOnly=true; + } + else if (name=="Version") + { version=value.toInt(); } else { - if (this->name.isEmpty()) { + if (this->name.isEmpty()) + { this->name=name; this->value=value; } - else { + else + { qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data()); } } } } -QByteArray HttpCookie::toByteArray() const { +QByteArray HttpCookie::toByteArray() const +{ QByteArray buffer(name); buffer.append('='); buffer.append(value); - if (!comment.isEmpty()) { + if (!comment.isEmpty()) + { buffer.append("; Comment="); buffer.append(comment); } - if (!domain.isEmpty()) { + if (!domain.isEmpty()) + { buffer.append("; Domain="); buffer.append(domain); } - if (maxAge!=0) { + if (maxAge!=0) + { buffer.append("; Max-Age="); buffer.append(QByteArray::number(maxAge)); } - if (!path.isEmpty()) { + if (!path.isEmpty()) + { buffer.append("; Path="); buffer.append(path); } if (secure) { buffer.append("; Secure"); } + if (httpOnly) { + buffer.append("; HttpOnly"); + } buffer.append("; Version="); buffer.append(QByteArray::number(version)); return buffer; } -void HttpCookie::setName(const QByteArray name){ +void HttpCookie::setName(const QByteArray name) +{ this->name=name; } -void HttpCookie::setValue(const QByteArray value){ +void HttpCookie::setValue(const QByteArray value) +{ this->value=value; } -void HttpCookie::setComment(const QByteArray comment){ +void HttpCookie::setComment(const QByteArray comment) +{ this->comment=comment; } -void HttpCookie::setDomain(const QByteArray domain){ +void HttpCookie::setDomain(const QByteArray domain) +{ this->domain=domain; } -void HttpCookie::setMaxAge(const int maxAge){ +void HttpCookie::setMaxAge(const int maxAge) +{ this->maxAge=maxAge; } -void HttpCookie::setPath(const QByteArray path){ +void HttpCookie::setPath(const QByteArray path) +{ this->path=path; } -void HttpCookie::setSecure(const bool secure){ +void HttpCookie::setSecure(const bool secure) +{ this->secure=secure; } -QByteArray HttpCookie::getName() const { +void HttpCookie::setHttpOnly(const bool httpOnly) +{ + this->httpOnly=httpOnly; +} + +QByteArray HttpCookie::getName() const +{ return name; } -QByteArray HttpCookie::getValue() const { +QByteArray HttpCookie::getValue() const +{ return value; } -QByteArray HttpCookie::getComment() const { +QByteArray HttpCookie::getComment() const +{ return comment; } -QByteArray HttpCookie::getDomain() const { +QByteArray HttpCookie::getDomain() const +{ return domain; } -int HttpCookie::getMaxAge() const { +int HttpCookie::getMaxAge() const +{ return maxAge; } -QByteArray HttpCookie::getPath() const { +QByteArray HttpCookie::getPath() const +{ return path; } -bool HttpCookie::getSecure() const { +bool HttpCookie::getSecure() const +{ return secure; } -int HttpCookie::getVersion() const { +bool HttpCookie::getHttpOnly() const +{ + return httpOnly; +} + +int HttpCookie::getVersion() const +{ return version; } -QList HttpCookie::splitCSV(const QByteArray source) { +QList HttpCookie::splitCSV(const QByteArray source) +{ bool inString=false; QList list; QByteArray buffer; - for (int i=0; i HttpCookie::splitCSV(const QByteArray source) { } } QByteArray trimmed=buffer.trimmed(); - if (!trimmed.isEmpty()) { + if (!trimmed.isEmpty()) + { list.append(trimmed); } return list; diff --git a/YACReaderLibrary/server/lib/httpserver/httpcookie.h b/YACReaderLibrary/server/lib/httpserver/httpcookie.h index 88688f75..d4857fd9 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpcookie.h +++ b/YACReaderLibrary/server/lib/httpserver/httpcookie.h @@ -8,6 +8,7 @@ #include #include +#include "httpglobal.h" /** HTTP cookie as defined in RFC 2109. This class can also parse @@ -15,7 +16,7 @@ 2109. */ -class HttpCookie +class DECLSPEC HttpCookie { public: @@ -30,9 +31,10 @@ public: @param path Path for that the cookie will be sent, default="/" which means the whole domain @param comment Optional comment, may be displayed by the web browser somewhere @param domain Optional domain for that the cookie will be sent. Defaults to the current domain - @param secure If true, the cookie will only be sent on secure connections + @param secure If true, the cookie will be sent by the browser to the server only on secure connections + @param httpOnly If true, the browser does not allow client-side scripts to access the cookie */ - HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path="/", const QByteArray comment=QByteArray(), const QByteArray domain=QByteArray(), const bool secure=false); + HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path="/", const QByteArray comment=QByteArray(), const QByteArray domain=QByteArray(), const bool secure=false, const bool httpOnly=false); /** Create a cookie from a string. @@ -40,7 +42,7 @@ public: */ HttpCookie(const QByteArray source); - /** Convert this cookie to a string that may be used in a Set-Cookie2 header. */ + /** Convert this cookie to a string that may be used in a Set-Cookie header. */ QByteArray toByteArray() const ; /** @@ -67,9 +69,12 @@ public: /** Set the path for that the cookie will be sent, default="/" which means the whole domain */ void setPath(const QByteArray path); - /** Set secure mode, so that the cokkie will only be sent on secure connections */ + /** Set secure mode, so that the cookie will be sent by the browser to the server only on secure connections */ void setSecure(const bool secure); + /** Set secure mode, so that he browser does not allow client-side scripts to access the cookie */ + void setHttpOnly(const bool httpOnly); + /** Get the name of this cookie */ QByteArray getName() const; @@ -91,6 +96,9 @@ public: /** Get the secure flag of this cookie */ bool getSecure() const; + /** Get the httpOnly of this cookie */ + bool getHttpOnly() const; + /** Returns always 1 */ int getVersion() const; @@ -103,6 +111,7 @@ private: int maxAge; QByteArray path; bool secure; + bool httpOnly; int version; }; diff --git a/YACReaderLibrary/server/lib/httpserver/httpglobal.cpp b/YACReaderLibrary/server/lib/httpserver/httpglobal.cpp index dfdf9cd4..088dab14 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpglobal.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httpglobal.cpp @@ -2,6 +2,6 @@ const char* getQtWebAppLibVersion() { - return "1.5.3"; + return "1.6.5"; } diff --git a/YACReaderLibrary/server/lib/httpserver/httplistener.cpp b/YACReaderLibrary/server/lib/httpserver/httplistener.cpp index b79db686..7314ec74 100644 --- a/YACReaderLibrary/server/lib/httpserver/httplistener.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httplistener.cpp @@ -12,53 +12,76 @@ HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandl : QTcpServer(parent) { Q_ASSERT(settings!=0); + Q_ASSERT(requestHandler!=0); + pool=NULL; + this->settings=settings; + this->requestHandler=requestHandler; // Reqister type of socketDescriptor for signal/slot handling qRegisterMetaType("tSocketDescriptor"); - // Create connection handler pool - this->settings=settings; - pool=new HttpConnectionHandlerPool(settings,requestHandler); // Start listening - int port=settings->value("port",8080).toInt(); - listen(QHostAddress::Any, port); - //Cambiado - int i = 0; - while (!isListening() && i < 1000) { - listen(QHostAddress::Any, (rand() % 45535)+20000); - i++; + listen(); +} + + +HttpListener::~HttpListener() +{ + close(); + qDebug("HttpListener: destroyed"); +} + + +void HttpListener::listen() +{ + if (!pool) + { + pool=new HttpConnectionHandlerPool(settings,requestHandler); + } + QString host = settings->value("host").toString(); + int port=settings->value("port").toInt(); + QTcpServer::listen(host.isEmpty() ? QHostAddress::Any : QHostAddress(host), port); + if (!isListening()) + { + qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString())); } - if(!isListening()) - { - qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString())); - } else { qDebug("HttpListener: Listening on port %i",port); } } -HttpListener::~HttpListener() { - close(); + +void HttpListener::close() { + QTcpServer::close(); qDebug("HttpListener: closed"); - delete pool; - qDebug("HttpListener: destroyed"); + if (pool) { + delete pool; + pool=NULL; + } } void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) { #ifdef SUPERVERBOSE qDebug("HttpListener: New connection"); #endif - HttpConnectionHandler* freeHandler=pool->getConnectionHandler(); + + HttpConnectionHandler* freeHandler=NULL; + if (pool) + { + freeHandler=pool->getConnectionHandler(); + } // Let the handler process the new connection. - if (freeHandler) { + if (freeHandler) + { // The descriptor is passed via signal/slot because the handler lives in another - // thread and cannot open the socket when called by another thread. + // thread and cannot open the socket when directly called by another thread. connect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor))); emit handleConnection(socketDescriptor); disconnect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor))); } - else { + else + { // Reject the connection - qDebug("HttpListener: Too many connections"); + qDebug("HttpListener: Too many incoming connections"); QTcpSocket* socket=new QTcpSocket(this); socket->setSocketDescriptor(socketDescriptor); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); diff --git a/YACReaderLibrary/server/lib/httpserver/httplistener.h b/YACReaderLibrary/server/lib/httpserver/httplistener.h index 6ae5d75c..a88176d0 100644 --- a/YACReaderLibrary/server/lib/httpserver/httplistener.h +++ b/YACReaderLibrary/server/lib/httpserver/httplistener.h @@ -3,12 +3,13 @@ @author Stefan Frings */ -#ifndef LISTENER_H -#define LISTENER_H +#ifndef HTTPLISTENER_H +#define HTTPLISTENER_H #include #include #include +#include "httpglobal.h" #include "httpconnectionhandler.h" #include "httpconnectionhandlerpool.h" #include "httprequesthandler.h" @@ -19,36 +20,54 @@

Example for the required settings in the config file:

+  ;host=192.168.0.100
   port=8080
   minThreads=1
   maxThreads=10
   cleanupInterval=1000
   readTimeout=60000
+  ;sslKeyFile=ssl/my.key
+  ;sslCertFile=ssl/my.cert
   maxRequestSize=16000
   maxMultiPartSize=1000000
   
- The port number is the incoming TCP port that this listener listens to. - @see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads and cleanupInterval - @see HttpConnectionHandler for description of config settings readTimeout + The optional host parameter binds the listener to one network interface. + The listener handles all network interfaces if no host is configured. + The port number specifies the incoming TCP port that this listener listens to. + @see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads, cleanupInterval and ssl settings + @see HttpConnectionHandler for description of the readTimeout @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize */ -class HttpListener : public QTcpServer { +class DECLSPEC HttpListener : public QTcpServer { Q_OBJECT Q_DISABLE_COPY(HttpListener) public: /** Constructor. + Creates a connection pool and starts listening on the configured host and port. @param settings Configuration settings for the HTTP server. Must not be 0. @param requestHandler Processes each received HTTP request, usually by dispatching to controller classes. @param parent Parent object. + @warning Ensure to close or delete the listener before deleting the request handler. */ - HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = 0); + HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = NULL); /** Destructor */ virtual ~HttpListener(); + /** + Restart listeing after close(). + */ + void listen(); + + /** + Closes the listener, waits until all pending requests are processed, + then closes the connection pool. + */ + void close(); + protected: /** Serves new incoming connection requests */ @@ -59,13 +78,16 @@ private: /** Configuration settings for the HTTP server */ QSettings* settings; + /** Point to the reuqest handler which processes all HTTP requests */ + HttpRequestHandler* requestHandler; + /** Pool of connection handlers */ HttpConnectionHandlerPool* pool; signals: /** - Emitted when the connection handler shall process a new incoming onnection. + Sent to the connection handler to process a new incoming connection. @param socketDescriptor references the accepted connection. */ @@ -73,4 +95,4 @@ signals: }; -#endif // LISTENER_H +#endif // HTTPLISTENER_H diff --git a/YACReaderLibrary/server/lib/httpserver/httprequest.cpp b/YACReaderLibrary/server/lib/httpserver/httprequest.cpp index 8ed427b5..0939bd47 100644 --- a/YACReaderLibrary/server/lib/httpserver/httprequest.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httprequest.cpp @@ -8,144 +8,189 @@ #include #include "httpcookie.h" -HttpRequest::HttpRequest(QSettings* settings) { +HttpRequest::HttpRequest(QSettings* settings) +{ status=waitForRequest; currentSize=0; expectedBodySize=0; - maxSize=settings->value("maxRequestSize","32000000").toInt(); - maxMultiPartSize=settings->value("maxMultiPartSize","32000000").toInt(); + maxSize=settings->value("maxRequestSize","16000").toInt(); + maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt(); } -void HttpRequest::readRequest(QTcpSocket& socket) { -#ifdef SUPERVERBOSE - qDebug("HttpRequest: read request"); -#endif +void HttpRequest::readRequest(QTcpSocket* socket) +{ + #ifdef SUPERVERBOSE + qDebug("HttpRequest: read request"); + #endif int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow - QByteArray newData=socket.readLine(toRead).trimmed(); - currentSize+=newData.size(); - if (!newData.isEmpty()) { + lineBuffer.append(socket->readLine(toRead)); + currentSize+=lineBuffer.size(); + if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n')) + { + #ifdef SUPERVERBOSE + qDebug("HttpRequest: collecting more parts until line break"); + #endif + return; + } + QByteArray newData=lineBuffer.trimmed(); + lineBuffer.clear(); + if (!newData.isEmpty()) + { QList list=newData.split(' '); - if (list.count()!=3 || !list.at(2).contains("HTTP")) { + if (list.count()!=3 || !list.at(2).contains("HTTP")) + { qWarning("HttpRequest: received broken HTTP request, invalid first line"); status=abort; } else { - method=list.at(0); + method=list.at(0).trimmed(); path=list.at(1); version=list.at(2); + peerAddress = socket->peerAddress(); status=waitForHeader; } } } -void HttpRequest::readHeader(QTcpSocket& socket) { -#ifdef SUPERVERBOSE - qDebug("HttpRequest: read header"); -#endif +void HttpRequest::readHeader(QTcpSocket* socket) +{ + #ifdef SUPERVERBOSE + qDebug("HttpRequest: read header"); + #endif int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow - QByteArray newData=socket.readLine(toRead).trimmed(); - currentSize+=newData.size(); + lineBuffer.append(socket->readLine(toRead)); + currentSize+=lineBuffer.size(); + if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n')) + { + #ifdef SUPERVERBOSE + qDebug("HttpRequest: collecting more parts until line break"); + #endif + return; + } + QByteArray newData=lineBuffer.trimmed(); + lineBuffer.clear(); int colon=newData.indexOf(':'); - if (colon>0) { + if (colon>0) + { // Received a line with a colon - a header - currentHeader=newData.left(colon); + currentHeader=newData.left(colon).toLower(); QByteArray value=newData.mid(colon+1).trimmed(); headers.insert(currentHeader,value); -#ifdef SUPERVERBOSE - qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data()); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data()); + #endif } - else if (!newData.isEmpty()) { + else if (!newData.isEmpty()) + { // received another line - belongs to the previous header -#ifdef SUPERVERBOSE - qDebug("HttpRequest: read additional line of header"); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: read additional line of header"); + #endif // Received additional line of previous header if (headers.contains(currentHeader)) { headers.insert(currentHeader,headers.value(currentHeader)+" "+newData); } } - else { + else + { // received an empty line - end of headers reached -#ifdef SUPERVERBOSE - qDebug("HttpRequest: headers completed"); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: headers completed"); + #endif // Empty line received, that means all headers have been received // Check for multipart/form-data - QByteArray contentType=headers.value("Content-Type"); - if (contentType.startsWith("multipart/form-data")) { + QByteArray contentType=headers.value("content-type"); + if (contentType.startsWith("multipart/form-data")) + { int posi=contentType.indexOf("boundary="); if (posi>=0) { boundary=contentType.mid(posi+9); + if (boundary.startsWith('"') && boundary.endsWith('"')) + { + boundary = boundary.mid(1,boundary.length()-2); + } } } - QByteArray contentLength=getHeader("Content-Length"); - if (!contentLength.isEmpty()) { + QByteArray contentLength=headers.value("content-length"); + if (!contentLength.isEmpty()) + { expectedBodySize=contentLength.toInt(); } - if (expectedBodySize==0) { -#ifdef SUPERVERBOSE - qDebug("HttpRequest: expect no body"); -#endif + if (expectedBodySize==0) + { + #ifdef SUPERVERBOSE + qDebug("HttpRequest: expect no body"); + #endif status=complete; } - else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize) { + else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize) + { qWarning("HttpRequest: expected body is too large"); status=abort; } - else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize) { + else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize) + { qWarning("HttpRequest: expected multipart body is too large"); status=abort; } else { -#ifdef SUPERVERBOSE - qDebug("HttpRequest: expect %i bytes body",expectedBodySize); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: expect %i bytes body",expectedBodySize); + #endif status=waitForBody; } } } -void HttpRequest::readBody(QTcpSocket& socket) { +void HttpRequest::readBody(QTcpSocket* socket) +{ Q_ASSERT(expectedBodySize!=0); - if (boundary.isEmpty()) { + if (boundary.isEmpty()) + { // normal body, no multipart -#ifdef SUPERVERBOSE - qDebug("HttpRequest: receive body"); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: receive body"); + #endif int toRead=expectedBodySize-bodyData.size(); - QByteArray newData=socket.read(toRead); + QByteArray newData=socket->read(toRead); currentSize+=newData.size(); bodyData.append(newData); - if (bodyData.size()>=expectedBodySize) { + if (bodyData.size()>=expectedBodySize) + { status=complete; } } - else { + else + { // multipart body, store into temp file -#ifdef SUPERVERBOSE - qDebug("HttpRequest: receiving multipart body"); -#endif - if (!tempFile.isOpen()) { + #ifdef SUPERVERBOSE + qDebug("HttpRequest: receiving multipart body"); + #endif + if (!tempFile.isOpen()) + { tempFile.open(); } // Transfer data in 64kb blocks int fileSize=tempFile.size(); int toRead=expectedBodySize-fileSize; - if (toRead>65536) { + if (toRead>65536) + { toRead=65536; } - fileSize+=tempFile.write(socket.read(toRead)); - if (fileSize>=maxMultiPartSize) { + fileSize+=tempFile.write(socket->read(toRead)); + if (fileSize>=maxMultiPartSize) + { qWarning("HttpRequest: received too many multipart bytes"); status=abort; } - else if (fileSize>=expectedBodySize) { -#ifdef SUPERVERBOSE + else if (fileSize>=expectedBodySize) + { + #ifdef SUPERVERBOSE qDebug("HttpRequest: received whole multipart body"); -#endif + #endif tempFile.flush(); - if (tempFile.error()) { + if (tempFile.error()) + { qCritical("HttpRequest: Error writing temp file for multipart body"); } parseMultiPartFile(); @@ -155,87 +200,106 @@ void HttpRequest::readBody(QTcpSocket& socket) { } } -void HttpRequest::decodeRequestParams() { -#ifdef SUPERVERBOSE - qDebug("HttpRequest: extract and decode request parameters"); -#endif +void HttpRequest::decodeRequestParams() +{ + #ifdef SUPERVERBOSE + qDebug("HttpRequest: extract and decode request parameters"); + #endif // Get URL parameters QByteArray rawParameters; int questionMark=path.indexOf('?'); - if (questionMark>=0) { + if (questionMark>=0) + { rawParameters=path.mid(questionMark+1); path=path.left(questionMark); } // Get request body parameters - QByteArray contentType=headers.value("Content-Type"); - if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith("application/x-www-form-urlencoded"))) { - if (rawParameters.isEmpty()) { + QByteArray contentType=headers.value("content-type"); + if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith("application/x-www-form-urlencoded"))) + { + if (!rawParameters.isEmpty()) + { rawParameters.append('&'); rawParameters.append(bodyData); } - else { + else + { rawParameters=bodyData; } } // Split the parameters into pairs of value and name QList list=rawParameters.split('&'); - foreach (QByteArray part, list) { + foreach (QByteArray part, list) + { int equalsChar=part.indexOf('='); - if (equalsChar>=0) { + if (equalsChar>=0) + { QByteArray name=part.left(equalsChar).trimmed(); QByteArray value=part.mid(equalsChar+1).trimmed(); parameters.insert(urlDecode(name),urlDecode(value)); } - else if (!part.isEmpty()){ + else if (!part.isEmpty()) + { // Name without value parameters.insert(urlDecode(part),""); } } } -void HttpRequest::extractCookies() { -#ifdef SUPERVERBOSE - qDebug("HttpRequest: extract cookies"); -#endif - foreach(QByteArray cookieStr, headers.values("Cookie")) { +void HttpRequest::extractCookies() +{ + #ifdef SUPERVERBOSE + qDebug("HttpRequest: extract cookies"); + #endif + foreach(QByteArray cookieStr, headers.values("cookie")) + { QList list=HttpCookie::splitCSV(cookieStr); - foreach(QByteArray part, list) { -#ifdef SUPERVERBOSE - qDebug("HttpRequest: found cookie %s",part.data()); -#endif // Split the part into name and value + foreach(QByteArray part, list) + { + #ifdef SUPERVERBOSE + qDebug("HttpRequest: found cookie %s",part.data()); + #endif // Split the part into name and value QByteArray name; QByteArray value; int posi=part.indexOf('='); - if (posi) { + if (posi) + { name=part.left(posi).trimmed(); value=part.mid(posi+1).trimmed(); } - else { + else + { name=part.trimmed(); value=""; } cookies.insert(name,value); } } - headers.remove("Cookie"); + headers.remove("cookie"); } -void HttpRequest::readFromSocket(QTcpSocket& socket) { +void HttpRequest::readFromSocket(QTcpSocket* socket) +{ Q_ASSERT(status!=complete); - if (status==waitForRequest) { + if (status==waitForRequest) + { readRequest(socket); } - else if (status==waitForHeader) { + else if (status==waitForHeader) + { readHeader(socket); } - else if (status==waitForBody) { + else if (status==waitForBody) + { readBody(socket); } - if (currentSize>maxSize) { + if ((boundary.isEmpty() && currentSize>maxSize) || (!boundary.isEmpty() && currentSize>maxMultiPartSize)) + { qWarning("HttpRequest: received too many bytes"); status=abort; } - if (status==complete) { + if (status==complete) + { // Extract and decode request parameters from url and body decodeRequestParams(); // Extract cookies from headers @@ -244,62 +308,82 @@ void HttpRequest::readFromSocket(QTcpSocket& socket) { } -HttpRequest::RequestStatus HttpRequest::getStatus() const { +HttpRequest::RequestStatus HttpRequest::getStatus() const +{ return status; } -QByteArray HttpRequest::getMethod() const { +QByteArray HttpRequest::getMethod() const +{ return method; } -QByteArray HttpRequest::getPath() const { +QByteArray HttpRequest::getPath() const +{ return urlDecode(path); } -QByteArray HttpRequest::getVersion() const { +const QByteArray& HttpRequest::getRawPath() const +{ + return path; +} + + +QByteArray HttpRequest::getVersion() const +{ return version; } -QByteArray HttpRequest::getHeader(const QByteArray& name) const { - return headers.value(name); +QByteArray HttpRequest::getHeader(const QByteArray& name) const +{ + return headers.value(name.toLower()); } -QList HttpRequest::getHeaders(const QByteArray& name) const { - return headers.values(name); +QList HttpRequest::getHeaders(const QByteArray& name) const +{ + return headers.values(name.toLower()); } -QMultiMap HttpRequest::getHeaderMap() const { +QMultiMap HttpRequest::getHeaderMap() const +{ return headers; } -QByteArray HttpRequest::getParameter(const QByteArray& name) const { +QByteArray HttpRequest::getParameter(const QByteArray& name) const +{ return parameters.value(name); } -QList HttpRequest::getParameters(const QByteArray& name) const { +QList HttpRequest::getParameters(const QByteArray& name) const +{ return parameters.values(name); } -QMultiMap HttpRequest::getParameterMap() const { +QMultiMap HttpRequest::getParameterMap() const +{ return parameters; } -QByteArray HttpRequest::getBody() const { +QByteArray HttpRequest::getBody() const +{ return bodyData; } -QByteArray HttpRequest::urlDecode(const QByteArray source) { +QByteArray HttpRequest::urlDecode(const QByteArray source) +{ QByteArray buffer(source); buffer.replace('+',' '); int percentChar=buffer.indexOf('%'); - while (percentChar>=0) { + while (percentChar>=0) + { bool ok; char byte=buffer.mid(percentChar+1,2).toInt(&ok,16); - if (ok) { + if (ok) + { buffer.replace(percentChar,3,(char*)&byte,1); } percentChar=buffer.indexOf('%',percentChar+1); @@ -308,65 +392,77 @@ QByteArray HttpRequest::urlDecode(const QByteArray source) { } -void HttpRequest::parseMultiPartFile() { +void HttpRequest::parseMultiPartFile() +{ qDebug("HttpRequest: parsing multipart temp file"); tempFile.seek(0); bool finished=false; - while (!tempFile.atEnd() && !finished && !tempFile.error()) { - -#ifdef SUPERVERBOSE - qDebug("HttpRequest: reading multpart headers"); -#endif + while (!tempFile.atEnd() && !finished && !tempFile.error()) + { + #ifdef SUPERVERBOSE + qDebug("HttpRequest: reading multpart headers"); + #endif QByteArray fieldName; QByteArray fileName; - while (!tempFile.atEnd() && !finished && !tempFile.error()) { + while (!tempFile.atEnd() && !finished && !tempFile.error()) + { QByteArray line=tempFile.readLine(65536).trimmed(); - if (line.startsWith("Content-Disposition:")) { - if (line.contains("form-data")) { + if (line.startsWith("Content-Disposition:")) + { + if (line.contains("form-data")) + { int start=line.indexOf(" name=\""); int end=line.indexOf("\"",start+7); - if (start>=0 && end>=start) { + if (start>=0 && end>=start) + { fieldName=line.mid(start+7,end-start-7); } start=line.indexOf(" filename=\""); end=line.indexOf("\"",start+11); - if (start>=0 && end>=start) { + if (start>=0 && end>=start) + { fileName=line.mid(start+11,end-start-11); } -#ifdef SUPERVERBOSE - qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data()); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data()); + #endif } - else { + else + { qDebug("HttpRequest: ignoring unsupported content part %s",line.data()); } } - else if (line.isEmpty()) { + else if (line.isEmpty()) + { break; } } -#ifdef SUPERVERBOSE - qDebug("HttpRequest: reading multpart data"); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: reading multpart data"); + #endif QTemporaryFile* uploadedFile=0; QByteArray fieldValue; - while (!tempFile.atEnd() && !finished && !tempFile.error()) { + while (!tempFile.atEnd() && !finished && !tempFile.error()) + { QByteArray line=tempFile.readLine(65536); - if (line.startsWith("--"+boundary)) { + if (line.startsWith("--"+boundary)) + { // Boundary found. Until now we have collected 2 bytes too much, // so remove them from the last result - if (fileName.isEmpty() && !fieldName.isEmpty()) { + if (fileName.isEmpty() && !fieldName.isEmpty()) + { // last field was a form field fieldValue.remove(fieldValue.size()-2,2); parameters.insert(fieldName,fieldValue); qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data()); } - else if (!fileName.isEmpty() && !fieldName.isEmpty()) { + else if (!fileName.isEmpty() && !fieldName.isEmpty()) + { // last field was a file -#ifdef SUPERVERBOSE - qDebug("HttpRequest: finishing writing to uploaded file"); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: finishing writing to uploaded file"); + #endif uploadedFile->resize(uploadedFile->size()-2); uploadedFile->flush(); uploadedFile->seek(0); @@ -375,57 +471,79 @@ void HttpRequest::parseMultiPartFile() { uploadedFiles.insert(fieldName,uploadedFile); qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size()); } - if (line.contains(boundary+"--")) { + if (line.contains(boundary+"--")) + { finished=true; } break; } - else { - if (fileName.isEmpty() && !fieldName.isEmpty()) { + else + { + if (fileName.isEmpty() && !fieldName.isEmpty()) + { // this is a form field. currentSize+=line.size(); fieldValue.append(line); } - else if (!fileName.isEmpty() && !fieldName.isEmpty()) { + else if (!fileName.isEmpty() && !fieldName.isEmpty()) + { // this is a file - if (!uploadedFile) { + if (!uploadedFile) + { uploadedFile=new QTemporaryFile(); uploadedFile->open(); } uploadedFile->write(line); - if (uploadedFile->error()) { + if (uploadedFile->error()) + { qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString())); } } } } } - if (tempFile.error()) { + if (tempFile.error()) + { qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile.errorString())); } -#ifdef SUPERVERBOSE - qDebug("HttpRequest: finished parsing multipart temp file"); -#endif + #ifdef SUPERVERBOSE + qDebug("HttpRequest: finished parsing multipart temp file"); + #endif } -HttpRequest::~HttpRequest() { - foreach(QByteArray key, uploadedFiles.keys()) { +HttpRequest::~HttpRequest() +{ + foreach(QByteArray key, uploadedFiles.keys()) + { QTemporaryFile* file=uploadedFiles.value(key); file->close(); delete file; } } -QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) { +QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) const +{ return uploadedFiles.value(fieldName); } -QByteArray HttpRequest::getCookie(const QByteArray& name) const { +QByteArray HttpRequest::getCookie(const QByteArray& name) const +{ return cookies.value(name); } /** Get the map of cookies */ -QMap& HttpRequest::getCookieMap() { +QMap& HttpRequest::getCookieMap() +{ return cookies; } +/** + Get the address of the connected client. + Note that multiple clients may have the same IP address, if they + share an internet connection (which is very common). + */ +QHostAddress HttpRequest::getPeerAddress() const +{ + return peerAddress; +} + diff --git a/YACReaderLibrary/server/lib/httpserver/httprequest.h b/YACReaderLibrary/server/lib/httpserver/httprequest.h index e79fd112..c13846af 100644 --- a/YACReaderLibrary/server/lib/httpserver/httprequest.h +++ b/YACReaderLibrary/server/lib/httpserver/httprequest.h @@ -7,12 +7,14 @@ #define HTTPREQUEST_H #include +#include #include #include #include #include #include #include +#include "httpglobal.h" /** This object represents a single HTTP request. It reads the request @@ -31,9 +33,10 @@ The body is always a little larger than the file itself. */ -class HttpRequest { +class DECLSPEC HttpRequest { Q_DISABLE_COPY(HttpRequest) friend class HttpSessionStore; + public: /** Values for getStatus() */ @@ -51,11 +54,12 @@ public: virtual ~HttpRequest(); /** - Read the request from a socket. This method must be called repeatedly + Read the HTTP request from a socket. + This method is called by the connection handler repeatedly until the status is RequestStatus::complete or RequestStatus::abort. @param socket Source of the data */ - void readFromSocket(QTcpSocket& socket); + void readFromSocket(QTcpSocket* socket); /** Get the status of this reqeust. @@ -69,12 +73,15 @@ public: /** Get the decoded path of the HTPP request (e.g. "/index.html") */ QByteArray getPath() const; + /** Get the raw path of the HTTP request (e.g. "/file%20with%20spaces.html") */ + const QByteArray& getRawPath() const; + /** Get the version of the HTPP request (e.g. "HTTP/1.1") */ QByteArray getVersion() const; /** Get the value of a HTTP request header. - @param name Name of the header + @param name Name of the header, not case-senitive. @return If the header occurs multiple times, only the last one is returned. */ @@ -82,16 +89,19 @@ public: /** Get the values of a HTTP request header. - @param name Name of the header + @param name Name of the header, not case-senitive. */ QList getHeaders(const QByteArray& name) const; - /** Get all HTTP request headers */ + /** + * Get all HTTP request headers. Note that the header names + * are returned in lower-case. + */ QMultiMap getHeaderMap() const; /** Get the value of a HTTP request parameter. - @param name Name of the parameter + @param name Name of the parameter, case-sensitive. @return If the parameter occurs multiple times, only the last one is returned. */ @@ -99,14 +109,14 @@ public: /** Get the values of a HTTP request parameter. - @param name Name of the parameter + @param name Name of the parameter, case-sensitive. */ QList getParameters(const QByteArray& name) const; - /** Get all HTTP request parameters */ + /** Get all HTTP request parameters. */ QMultiMap getParameterMap() const; - /** Get the HTTP request body */ + /** Get the HTTP request body. */ QByteArray getBody() const; /** @@ -125,17 +135,24 @@ public: For uploaded files, the method getParameters() returns the original fileName as provided by the calling web browser. */ - QTemporaryFile* getUploadedFile(const QByteArray fieldName); + QTemporaryFile* getUploadedFile(const QByteArray fieldName) const; /** - Get the value of a cookie + Get the value of a cookie. @param name Name of the cookie */ QByteArray getCookie(const QByteArray& name) const; - /** Get the map of cookies */ + /** Get all cookies. */ QMap& getCookieMap(); + /** + Get the address of the connected client. + Note that multiple clients may have the same IP address, if they + share an internet connection (which is very common). + */ + QHostAddress getPeerAddress() const; + private: /** Request headers */ @@ -163,11 +180,14 @@ private: QByteArray version; /** - Status of this request. + Status of this request. For the state engine. @see RequestStatus */ RequestStatus status; + /** Address of the connected peer. */ + QHostAddress peerAddress; + /** Maximum size of requests in bytes. */ int maxSize; @@ -193,13 +213,13 @@ private: void parseMultiPartFile(); /** Sub-procedure of readFromSocket(), read the first line of a request. */ - void readRequest(QTcpSocket& socket); + void readRequest(QTcpSocket* socket); /** Sub-procedure of readFromSocket(), read header lines. */ - void readHeader(QTcpSocket& socket); + void readHeader(QTcpSocket* socket); /** Sub-procedure of readFromSocket(), read the request body. */ - void readBody(QTcpSocket& socket); + void readBody(QTcpSocket* socket); /** Sub-procedure of readFromSocket(), extract and decode request parameters. */ void decodeRequestParams(); @@ -207,6 +227,9 @@ private: /** Sub-procedure of readFromSocket(), extract cookies from headers */ void extractCookies(); + /** Buffer for collecting characters of request and header lines */ + QByteArray lineBuffer; + }; #endif // HTTPREQUEST_H diff --git a/YACReaderLibrary/server/lib/httpserver/httprequesthandler.cpp b/YACReaderLibrary/server/lib/httpserver/httprequesthandler.cpp index d8ad7caf..879ccd4f 100644 --- a/YACReaderLibrary/server/lib/httpserver/httprequesthandler.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httprequesthandler.cpp @@ -9,10 +9,12 @@ HttpRequestHandler::HttpRequestHandler(QObject* parent) : QObject(parent) {} -HttpRequestHandler::~HttpRequestHandler() {} +HttpRequestHandler::~HttpRequestHandler() +{} -void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) { - qCritical("HttpRequestHandler: you need to override the dispatch() function"); +void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) +{ + qCritical("HttpRequestHandler: you need to override the service() function"); qDebug("HttpRequestHandler: request=%s %s %s",request.getMethod().data(),request.getPath().data(),request.getVersion().data()); response.setStatus(501,"not implemented"); response.write("501 not implemented",true); diff --git a/YACReaderLibrary/server/lib/httpserver/httprequesthandler.h b/YACReaderLibrary/server/lib/httpserver/httprequesthandler.h index a5f9f4e2..12ac0cf9 100644 --- a/YACReaderLibrary/server/lib/httpserver/httprequesthandler.h +++ b/YACReaderLibrary/server/lib/httpserver/httprequesthandler.h @@ -6,6 +6,7 @@ #ifndef HTTPREQUESTHANDLER_H #define HTTPREQUESTHANDLER_H +#include "httpglobal.h" #include "httprequest.h" #include "httpresponse.h" @@ -21,13 +22,16 @@ @see StaticFileController which delivers static local files. */ -class HttpRequestHandler : public QObject { +class DECLSPEC HttpRequestHandler : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpRequestHandler) public: - /** Constructor */ - HttpRequestHandler(QObject* parent=0); + /** + * Constructor. + * @param parent Parent object. + */ + HttpRequestHandler(QObject* parent=NULL); /** Destructor */ virtual ~HttpRequestHandler(); diff --git a/YACReaderLibrary/server/lib/httpserver/httpresponse.cpp b/YACReaderLibrary/server/lib/httpserver/httpresponse.cpp index ad8efa29..7dbb600a 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpresponse.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httpresponse.cpp @@ -5,48 +5,62 @@ #include "httpresponse.h" -HttpResponse::HttpResponse(QTcpSocket* socket) { +HttpResponse::HttpResponse(QTcpSocket* socket) +{ this->socket=socket; statusCode=200; statusText="OK"; sentHeaders=false; sentLastPart=false; + chunkedMode=false; } -void HttpResponse::setHeader(QByteArray name, QByteArray value) { - //Q_ASSERT(sentHeaders==false); +void HttpResponse::setHeader(QByteArray name, QByteArray value) +{ + Q_ASSERT(sentHeaders==false); headers.insert(name,value); } -void HttpResponse::setHeader(QByteArray name, int value) { - //Q_ASSERT(sentHeaders==false); +void HttpResponse::setHeader(QByteArray name, int value) +{ + Q_ASSERT(sentHeaders==false); headers.insert(name,QByteArray::number(value)); } -QMap& HttpResponse::getHeaders() { +QMap& HttpResponse::getHeaders() +{ return headers; } -void HttpResponse::setStatus(int statusCode, QByteArray description) { +void HttpResponse::setStatus(int statusCode, QByteArray description) +{ this->statusCode=statusCode; statusText=description; } -void HttpResponse::writeHeaders() { - //Q_ASSERT(sentHeaders==false); +int HttpResponse::getStatusCode() const +{ + return this->statusCode; +} + +void HttpResponse::writeHeaders() +{ + Q_ASSERT(sentHeaders==false); QByteArray buffer; buffer.append("HTTP/1.1 "); buffer.append(QByteArray::number(statusCode)); buffer.append(' '); buffer.append(statusText); buffer.append("\r\n"); - foreach(QByteArray name, headers.keys()) { + foreach(QByteArray name, headers.keys()) + { buffer.append(name); buffer.append(": "); buffer.append(headers.value(name)); buffer.append("\r\n"); } - foreach(HttpCookie cookie,cookies.values()) { + foreach(HttpCookie cookie,cookies.values()) + { buffer.append("Set-Cookie: "); buffer.append(cookie.toByteArray()); buffer.append("\r\n"); @@ -56,14 +70,21 @@ void HttpResponse::writeHeaders() { sentHeaders=true; } -bool HttpResponse::writeToSocket(QByteArray data) { +bool HttpResponse::writeToSocket(QByteArray data) +{ int remaining=data.size(); char* ptr=data.data(); - while (socket->isOpen() && remaining>0) { - // Wait until the previous buffer content is written out, otherwise it could become very large - socket->waitForBytesWritten(-1); + while (socket->isOpen() && remaining>0) + { + // If the output buffer has become large, then wait until it has been sent. + if (socket->bytesToWrite()>16384) + { + socket->waitForBytesWritten(-1); + } + int written=socket->write(ptr,remaining); - if (written==-1) { + if (written==-1) + { return false; } ptr+=written; @@ -72,61 +93,106 @@ bool HttpResponse::writeToSocket(QByteArray data) { return true; } -void HttpResponse::write(QByteArray data, bool lastPart) { - //Q_ASSERT(sentLastPart==false); - if (sentHeaders==false) { - QByteArray connectionMode=headers.value("Connection"); - if (!headers.contains("Content-Length") && !headers.contains("Transfer-Encoding") && connectionMode!="close" && connectionMode!="Close") { - if (!lastPart) { +void HttpResponse::write(QByteArray data, bool lastPart) +{ + Q_ASSERT(sentLastPart==false); + + // Send HTTP headers, if not already done (that happens only on the first call to write()) + if (sentHeaders==false) + { + // If the whole response is generated with a single call to write(), then we know the total + // size of the response and therefore can set the Content-Length header automatically. + if (lastPart) + { + // Automatically set the Content-Length header + headers.insert("Content-Length",QByteArray::number(data.size())); + } + + // else if we will not close the connection at the end, them we must use the chunked mode. + else + { + QByteArray connectionValue=headers.value("Connection",headers.value("connection")); + bool connectionClose=QString::compare(connectionValue,"close",Qt::CaseInsensitive)==0; + if (!connectionClose) + { headers.insert("Transfer-Encoding","chunked"); - } - else { - headers.insert("Content-Length",QByteArray::number(data.size())); + chunkedMode=true; } } + writeHeaders(); } - bool chunked=headers.value("Transfer-Encoding")=="chunked" || headers.value("Transfer-Encoding")=="Chunked"; - if (chunked) { - if (data.size()>0) { - QByteArray buffer=QByteArray::number(data.size(),16); - buffer.append("\r\n"); - writeToSocket(buffer); + + // Send data + if (data.size()>0) + { + if (chunkedMode) + { + if (data.size()>0) + { + QByteArray size=QByteArray::number(data.size(),16); + writeToSocket(size); + writeToSocket("\r\n"); + writeToSocket(data); + writeToSocket("\r\n"); + } + } + else + { writeToSocket(data); - writeToSocket("\r\n"); } } - else { - writeToSocket(data); - } - if (lastPart) { - if (chunked) { + + // Only for the last chunk, send the terminating marker and flush the buffer. + if (lastPart) + { + if (chunkedMode) + { writeToSocket("0\r\n\r\n"); } - else if (!headers.contains("Content-Length")) { - socket->disconnectFromHost(); - } + socket->flush(); sentLastPart=true; } } -void HttpResponse::writeText(QString text, bool lastPart) -{ - write(QByteArray(text.toUtf8()),lastPart); -} -bool HttpResponse::hasSentLastPart() const { +bool HttpResponse::hasSentLastPart() const +{ return sentLastPart; } -void HttpResponse::setCookie(const HttpCookie& cookie) { - //Q_ASSERT(sentHeaders==false); - if (!cookie.getName().isEmpty()) { +void HttpResponse::setCookie(const HttpCookie& cookie) +{ + Q_ASSERT(sentHeaders==false); + if (!cookie.getName().isEmpty()) + { cookies.insert(cookie.getName(),cookie); } } -QMap& HttpResponse::getCookies() { + +QMap& HttpResponse::getCookies() +{ return cookies; } + + +void HttpResponse::redirect(const QByteArray& url) +{ + setStatus(303,"See Other"); + setHeader("Location",url); + write("Redirect",true); +} + + +void HttpResponse::flush() +{ + socket->flush(); +} + + +bool HttpResponse::isConnected() const +{ + return socket->isOpen(); +} diff --git a/YACReaderLibrary/server/lib/httpserver/httpresponse.h b/YACReaderLibrary/server/lib/httpserver/httpresponse.h index 1bdc7733..44054806 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpresponse.h +++ b/YACReaderLibrary/server/lib/httpserver/httpresponse.h @@ -9,12 +9,12 @@ #include #include #include +#include "httpglobal.h" #include "httpcookie.h" /** - This object represents a HTTP response, in particular the response headers. + This object represents a HTTP response, used to return something to the web client.

- Example code for proper response generation:

     response.setStatus(200,"OK"); // optional, because this is the default
     response.writeBody("Hello");
@@ -27,108 +27,132 @@
     response.write("The request cannot be processed because the servers is broken",true);
   

- For performance reason, writing a single or few large packets is better than writing - many small packets. In case of large responses (e.g. file downloads), a Content-Length - header should be set before calling write(). Web Browsers use that information to display - a progress bar. + In case of large responses (e.g. file downloads), a Content-Length header should be set + before calling write(). Web Browsers use that information to display a progress bar. */ -class HttpResponse { +class DECLSPEC HttpResponse { Q_DISABLE_COPY(HttpResponse) public: - /** - Constructor. - @param socket used to write the response - */ - HttpResponse(QTcpSocket* socket); + /** + Constructor. + @param socket used to write the response + */ + HttpResponse(QTcpSocket* socket); - /** - Set a HTTP response header - @param name name of the header - @param value value of the header - */ - void setHeader(QByteArray name, QByteArray value); + /** + Set a HTTP response header. + You must call this method before the first write(). + @param name name of the header + @param value value of the header + */ + void setHeader(QByteArray name, QByteArray value); - /** - Set a HTTP response header - @param name name of the header - @param value value of the header - */ - void setHeader(QByteArray name, int value); + /** + Set a HTTP response header. + You must call this method before the first write(). + @param name name of the header + @param value value of the header + */ + void setHeader(QByteArray name, int value); - /** Get the map of HTTP response headers */ - QMap& getHeaders(); + /** Get the map of HTTP response headers */ + QMap& getHeaders(); - /** Get the map of cookies */ - QMap& getCookies(); + /** Get the map of cookies */ + QMap& getCookies(); - /** - Set status code and description. The default is 200,OK. - */ - void setStatus(int statusCode, QByteArray description=QByteArray()); + /** + Set status code and description. The default is 200,OK. + You must call this method before the first write(). + */ + void setStatus(int statusCode, QByteArray description=QByteArray()); - /** - Write body data to the socket. -

- The HTTP status line and headers are sent automatically before the first - byte of the body gets sent. -

- If the response contains only a single chunk (indicated by lastPart=true), - the response is transferred in traditional mode with a Content-Length - header, which is automatically added if not already set before. -

- Otherwise, each part is transferred in chunked mode. - @param data Data bytes of the body - @param lastPart Indicator, if this is the last part of the response. - */ - void write(QByteArray data, bool lastPart=false); - void writeText(QString text, bool lastPart=false); + /** Return the status code. */ + int getStatusCode() const; - /** - Indicates wheter the body has been sent completely. Used by the connection - handler to terminate the body automatically when necessary. - */ - bool hasSentLastPart() const; + /** + Write body data to the socket. +

+ The HTTP status line, headers and cookies are sent automatically before the body. +

+ If the response contains only a single chunk (indicated by lastPart=true), + then a Content-Length header is automatically set. +

+ Chunked mode is automatically selected if there is no Content-Length header + and also no Connection:close header. + @param data Data bytes of the body + @param lastPart Indicates that this is the last chunk of data and flushes the output buffer. + */ + void write(QByteArray data, bool lastPart=false); - /** - Set a cookie. Cookies are sent together with the headers when the first - call to write() occurs. - */ - void setCookie(const HttpCookie& cookie); + /** + Indicates whether the body has been sent completely (write() has been called with lastPart=true). + */ + bool hasSentLastPart() const; + + /** + Set a cookie. + You must call this method before the first write(). + */ + void setCookie(const HttpCookie& cookie); + + /** + Send a redirect response to the browser. + Cannot be combined with write(). + @param url Destination URL + */ + void redirect(const QByteArray& url); + + /** + * Flush the output buffer (of the underlying socket). + * You normally don't need to call this method because flush is + * automatically called after HttpRequestHandler::service() returns. + */ + void flush(); + + /** + * May be used to check whether the connection to the web client has been lost. + * This might be useful to cancel the generation of large or slow responses. + */ + bool isConnected() const; private: - /** Request headers */ - QMap headers; + /** Request headers */ + QMap headers; - /** Socket for writing output */ - QTcpSocket* socket; + /** Socket for writing output */ + QTcpSocket* socket; - /** HTTP status code*/ - int statusCode; + /** HTTP status code*/ + int statusCode; - /** HTTP status code description */ - QByteArray statusText; + /** HTTP status code description */ + QByteArray statusText; - /** Indicator whether headers have been sent */ - bool sentHeaders; + /** Indicator whether headers have been sent */ + bool sentHeaders; - /** Indicator whether the body has been sent completely */ - bool sentLastPart; + /** Indicator whether the body has been sent completely */ + bool sentLastPart; - /** Cookies */ - QMap cookies; + /** Whether the response is sent in chunked mode */ + bool chunkedMode; - /** Write raw data to the socket. This method blocks until all bytes have been passed to the TCP buffer */ - bool writeToSocket(QByteArray data); + /** Cookies */ + QMap cookies; - /** - Write the response HTTP status and headers to the socket. - Calling this method is optional, because writeBody() calls - it automatically when required. - */ - void writeHeaders(); + /** Write raw data to the socket. This method blocks until all bytes have been passed to the TCP buffer */ + bool writeToSocket(QByteArray data); + + /** + Write the response HTTP status and headers to the socket. + Calling this method is optional, because writeBody() calls + it automatically when required. + */ + void writeHeaders(); }; diff --git a/YACReaderLibrary/server/lib/httpserver/httpserver.pri b/YACReaderLibrary/server/lib/httpserver/httpserver.pri index a109a573..fb78772e 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpserver.pri +++ b/YACReaderLibrary/server/lib/httpserver/httpserver.pri @@ -1,12 +1,33 @@ INCLUDEPATH += $$PWD DEPENDPATH += $$PWD -HEADERS += $$PWD/httplistener.h $$PWD/httpconnectionhandler.h $$PWD/httpconnectionhandlerpool.h $$PWD/httprequest.h $$PWD/httpresponse.h $$PWD/httpcookie.h $$PWD/httprequesthandler.h -HEADERS += $$PWD/httpsession.h $$PWD/httpsessionstore.h -HEADERS += $$PWD/staticfilecontroller.h +QT += network -SOURCES += $$PWD/httplistener.cpp $$PWD/httpconnectionhandler.cpp $$PWD/httpconnectionhandlerpool.cpp $$PWD/httprequest.cpp $$PWD/httpresponse.cpp $$PWD/httpcookie.cpp $$PWD/httprequesthandler.cpp -SOURCES += $$PWD/httpsession.cpp $$PWD/httpsessionstore.cpp -SOURCES += $$PWD/staticfilecontroller.cpp +# Enable very detailed debug messages when compiling the debug version +CONFIG(debug, debug|release) { + DEFINES += SUPERVERBOSE +} -OTHER_FILES += $$PWD/../doc/readme.txt +HEADERS += $$PWD/httpglobal.h \ + $$PWD/httplistener.h \ + $$PWD/httpconnectionhandler.h \ + $$PWD/httpconnectionhandlerpool.h \ + $$PWD/httprequest.h \ + $$PWD/httpresponse.h \ + $$PWD/httpcookie.h \ + $$PWD/httprequesthandler.h \ + $$PWD/httpsession.h \ + $$PWD/httpsessionstore.h \ + $$PWD/staticfilecontroller.h + +SOURCES += $$PWD/httpglobal.cpp \ + $$PWD/httplistener.cpp \ + $$PWD/httpconnectionhandler.cpp \ + $$PWD/httpconnectionhandlerpool.cpp \ + $$PWD/httprequest.cpp \ + $$PWD/httpresponse.cpp \ + $$PWD/httpcookie.cpp \ + $$PWD/httprequesthandler.cpp \ + $$PWD/httpsession.cpp \ + $$PWD/httpsessionstore.cpp \ + $$PWD/staticfilecontroller.cpp diff --git a/YACReaderLibrary/server/lib/httpserver/httpsession.cpp b/YACReaderLibrary/server/lib/httpserver/httpsession.cpp index 1bfe2f07..165f93a2 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpsession.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httpsession.cpp @@ -8,374 +8,179 @@ #include -HttpSession::HttpSession(bool canStore) { - if (canStore) { - dataPtr=new HttpSessionData(); - dataPtr->refCount=1; - dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); - dataPtr->id=QUuid::createUuid().toString().toLatin1(); - dataPtr->yacreaderSessionData.comic = 0; - dataPtr->yacreaderSessionData.comicId = 0; - dataPtr->yacreaderSessionData.remoteComic = 0; - dataPtr->yacreaderSessionData.remoteComicId = 0; +HttpSession::HttpSession(bool canStore) +{ + if (canStore) + { + dataPtr=new HttpSessionData(); + dataPtr->refCount=1; + dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); + dataPtr->id=QUuid::createUuid().toString().toLocal8Bit(); #ifdef SUPERVERBOSE - qDebug("HttpSession: created new session data with id %s",dataPtr->id.data()); + qDebug("HttpSession: created new session data with id %s",dataPtr->id.data()); #endif - } - else { - dataPtr=0; - } + } + else + { + dataPtr=0; + } } -HttpSession::HttpSession(const HttpSession& other) { - dataPtr=other.dataPtr; - if (dataPtr) { - dataPtr->lock.lockForWrite(); - dataPtr->refCount++; +HttpSession::HttpSession(const HttpSession& other) +{ + dataPtr=other.dataPtr; + if (dataPtr) + { + dataPtr->lock.lockForWrite(); + dataPtr->refCount++; #ifdef SUPERVERBOSE - qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); + qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); #endif - dataPtr->lock.unlock(); - } + dataPtr->lock.unlock(); + } } -HttpSession& HttpSession::operator= (const HttpSession& other) { - HttpSessionData* oldPtr=dataPtr; - dataPtr=other.dataPtr; - if (dataPtr) { - dataPtr->lock.lockForWrite(); - dataPtr->refCount++; +HttpSession& HttpSession::operator= (const HttpSession& other) +{ + HttpSessionData* oldPtr=dataPtr; + dataPtr=other.dataPtr; + if (dataPtr) + { + dataPtr->lock.lockForWrite(); + dataPtr->refCount++; #ifdef SUPERVERBOSE - qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); + qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); #endif - dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); - dataPtr->lock.unlock(); - } - if (oldPtr) { - int refCount; - oldPtr->lock.lockForRead(); - refCount=oldPtr->refCount--; + dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); + dataPtr->lock.unlock(); + } + if (oldPtr) + { + int refCount; + oldPtr->lock.lockForRead(); + refCount=oldPtr->refCount--; #ifdef SUPERVERBOSE - qDebug("HttpSession: refCount of %s is %i",oldPtr->id.data(),oldPtr->refCount); + qDebug("HttpSession: refCount of %s is %i",oldPtr->id.data(),oldPtr->refCount); #endif - oldPtr->lock.unlock(); - if (refCount==0) { - delete oldPtr; - } - } - return *this; + oldPtr->lock.unlock(); + if (refCount==0) + { + delete oldPtr; + } + } + return *this; } -HttpSession::~HttpSession() { - if (dataPtr) { - int refCount; - dataPtr->lock.lockForRead(); - refCount=--dataPtr->refCount; +HttpSession::~HttpSession() +{ + if (dataPtr) { + int refCount; + dataPtr->lock.lockForRead(); + refCount=--dataPtr->refCount; #ifdef SUPERVERBOSE - qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); + qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount); #endif - dataPtr->lock.unlock(); - if (refCount==0) { - qDebug("HttpSession: deleting data"); - delete dataPtr; - } - } + dataPtr->lock.unlock(); + if (refCount==0) + { + qDebug("HttpSession: deleting data"); + delete dataPtr; + } + } } -QByteArray HttpSession::getId() const { - if (dataPtr) { - return dataPtr->id; - } - else { - return QByteArray(); - } +QByteArray HttpSession::getId() const +{ + if (dataPtr) + { + return dataPtr->id; + } + else + { + return QByteArray(); + } } bool HttpSession::isNull() const { - return dataPtr==0; + return dataPtr==0; } -void HttpSession::set(const QByteArray& key, const QVariant& value) { - if (dataPtr) { - dataPtr->lock.lockForWrite(); - dataPtr->values.insert(key,value); - dataPtr->lock.unlock(); - } -} - -void HttpSession::remove(const QByteArray& key) { - if (dataPtr) { - dataPtr->lock.lockForWrite(); - dataPtr->values.remove(key); - dataPtr->lock.unlock(); - } -} - -QVariant HttpSession::get(const QByteArray& key) const { - QVariant value; - if (dataPtr) { - dataPtr->lock.lockForRead(); - value=dataPtr->values.value(key); - dataPtr->lock.unlock(); - } - return value; -} - -bool HttpSession::contains(const QByteArray& key) const { - bool found=false; - if (dataPtr) { - dataPtr->lock.lockForRead(); - found=dataPtr->values.contains(key); - dataPtr->lock.unlock(); - } - return found; -} - -QMap HttpSession::getAll() const { - QMap values; - if (dataPtr) { - dataPtr->lock.lockForRead(); - values=dataPtr->values; - dataPtr->lock.unlock(); - } - return values; -} - -qint64 HttpSession::getLastAccess() const { - qint64 value=0; - if (dataPtr) { - dataPtr->lock.lockForRead(); - value=dataPtr->lastAccess; - dataPtr->lock.unlock(); - } - return value; -} - - -void HttpSession::setLastAccess() { - if (dataPtr) { - dataPtr->lock.lockForRead(); - dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); - dataPtr->lock.unlock(); - } -} - -//AÑADIDO -//sets -bool HttpSession::isComicOnDevice(const QString & hash) +void HttpSession::set(const QByteArray& key, const QVariant& value) { - if(dataPtr) - return dataPtr->yacreaderSessionData.comicsOnDevice.contains(hash); - else - return false; -} -bool HttpSession::isComicDownloaded(const QString & hash) -{ - if(dataPtr) - return dataPtr->yacreaderSessionData.downloadedComics.contains(hash); - else - return false; -} -void HttpSession::setComicOnDevice(const QString & hash) -{ - if(dataPtr) - { - dataPtr->yacreaderSessionData.comicsOnDevice.insert(hash); - } -} -void HttpSession::setComicsOnDevice(const QSet & set) -{ - if(dataPtr) - { - dataPtr->yacreaderSessionData.comicsOnDevice = set; - } -} -void HttpSession::setDownloadedComic(const QString & hash) -{ - if(dataPtr) - { - dataPtr->yacreaderSessionData.downloadedComics.insert(hash); - } -} -QSet HttpSession::getComicsOnDevice() -{ - if(dataPtr) - return dataPtr->yacreaderSessionData.comicsOnDevice ; - else - return QSet(); -} -QSet HttpSession::getDownloadedComics() -{ - if(dataPtr) - return dataPtr->yacreaderSessionData.downloadedComics ; - else - return QSet(); -} - -void HttpSession::clearComics() -{ - if(dataPtr) + if (dataPtr) { - dataPtr->yacreaderSessionData.comicsOnDevice.clear(); - dataPtr->yacreaderSessionData.downloadedComics.clear(); - } -} -//current comic (import) -qulonglong HttpSession::getCurrentComicId() -{ - if(dataPtr) - return dataPtr->yacreaderSessionData.comicId ; - else - return 0; -} -Comic* HttpSession::getCurrentComic() -{ - if(dataPtr) - { - return dataPtr->yacreaderSessionData.comic ; - } - else - return 0; -} -void HttpSession::dismissCurrentComic() -{ - if(dataPtr) - { - if(dataPtr->yacreaderSessionData.comic != 0) - { - dataPtr->yacreaderSessionData.comic->deleteLater(); - dataPtr->yacreaderSessionData.comic = 0; - } - dataPtr->yacreaderSessionData.comicId = 0; - } -} -void HttpSession::setCurrentComic(qulonglong id, Comic * comic) -{ - if(dataPtr) - { - dismissCurrentComic(); - dataPtr->yacreaderSessionData.comicId = id; - dataPtr->yacreaderSessionData.comic = comic; - } -} - -//current comic (read) -qulonglong HttpSession::getCurrentRemoteComicId() -{ - if(dataPtr) - return dataPtr->yacreaderSessionData.remoteComicId ; - else - return 0; -} -Comic* HttpSession::getCurrentRemoteComic() -{ - if(dataPtr) - { - return dataPtr->yacreaderSessionData.remoteComic ; - } - else - return 0; -} -void HttpSession::dismissCurrentRemoteComic() -{ - if(dataPtr) - { - if(dataPtr->yacreaderSessionData.remoteComic != 0) - { - dataPtr->yacreaderSessionData.remoteComic->deleteLater(); - dataPtr->yacreaderSessionData.remoteComic = 0; - } - dataPtr->yacreaderSessionData.remoteComicId = 0; - } -} -void HttpSession::setCurrentRemoteComic(qulonglong id, Comic * comic) -{ - if(dataPtr) - { - dismissCurrentRemoteComic(); - dataPtr->yacreaderSessionData.remoteComicId = id; - dataPtr->yacreaderSessionData.remoteComic = comic; + dataPtr->lock.lockForWrite(); + dataPtr->values.insert(key,value); + dataPtr->lock.unlock(); } } - -QString HttpSession::getDeviceType() +void HttpSession::remove(const QByteArray& key) { - if(dataPtr) - { - return dataPtr->yacreaderSessionData.device; - } - return ""; -} -QString HttpSession::getDisplayType() -{ - if(dataPtr) - { - return dataPtr->yacreaderSessionData.display; - } - return ""; -} -void HttpSession::setDeviceType(const QString & device) -{ - if(dataPtr) - { - //dataPtr->yacreaderSessionData.comicsOnDevice.clear(); //TODO crear un método clear que limpie la sesión completamente - //dataPtr->yacreaderSessionData.downloadedComics.clear(); - dataPtr->yacreaderSessionData.device = device; - } -} -void HttpSession::setDisplayType(const QString & display) -{ - if(dataPtr) - { - dataPtr->yacreaderSessionData.display = display; - } + if (dataPtr) + { + dataPtr->lock.lockForWrite(); + dataPtr->values.remove(key); + dataPtr->lock.unlock(); + } } -void HttpSession::clearNavigationPath() +QVariant HttpSession::get(const QByteArray& key) const { - if(dataPtr) - dataPtr->yacreaderSessionData.navigationPath.clear(); + QVariant value; + if (dataPtr) + { + dataPtr->lock.lockForRead(); + value=dataPtr->values.value(key); + dataPtr->lock.unlock(); + } + return value; } -QPair HttpSession::popNavigationItem() +bool HttpSession::contains(const QByteArray& key) const { - if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty())) - return dataPtr->yacreaderSessionData.navigationPath.pop(); - return QPair(); + bool found=false; + if (dataPtr) + { + dataPtr->lock.lockForRead(); + found=dataPtr->values.contains(key); + dataPtr->lock.unlock(); + } + return found; } -QPair HttpSession::topNavigationItem() +QMap HttpSession::getAll() const { - if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty())) - return dataPtr->yacreaderSessionData.navigationPath.top(); - return QPair(); + QMap values; + if (dataPtr) + { + dataPtr->lock.lockForRead(); + values=dataPtr->values; + dataPtr->lock.unlock(); + } + return values; } -void HttpSession::pushNavigationItem(const QPair &item) +qint64 HttpSession::getLastAccess() const { - if(dataPtr) - dataPtr->yacreaderSessionData.navigationPath.push(item); + qint64 value=0; + if (dataPtr) + { + dataPtr->lock.lockForRead(); + value=dataPtr->lastAccess; + dataPtr->lock.unlock(); + } + return value; } -void HttpSession::updateTopItem(const QPair &item) -{ - if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty())) - { - dataPtr->yacreaderSessionData.navigationPath.pop(); - dataPtr->yacreaderSessionData.navigationPath.push(item); - } else if(dataPtr) - { - dataPtr->yacreaderSessionData.navigationPath.push(item); - } -} -QStack > HttpSession::getNavigationPath() +void HttpSession::setLastAccess() { - if(dataPtr) - return dataPtr->yacreaderSessionData.navigationPath; - else - return QStack >(); + if (dataPtr) + { + dataPtr->lock.lockForRead(); + dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); + dataPtr->lock.unlock(); + } } - diff --git a/YACReaderLibrary/server/lib/httpserver/httpsession.h b/YACReaderLibrary/server/lib/httpserver/httpsession.h index a95f818f..fa0ee3f9 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpsession.h +++ b/YACReaderLibrary/server/lib/httpserver/httpsession.h @@ -9,10 +9,7 @@ #include #include #include - -#include -#include -#include "comic.h" +#include "httpglobal.h" /** This class stores data for a single HTTP session. @@ -21,172 +18,100 @@ @see HttpSessionStore should be used to create and get instances of this class. */ -class HttpSession { +class DECLSPEC HttpSession { public: - /** - Constructor. - @param canStore The session can store data, if this parameter is true. - Otherwise all calls to set() and remove() do not have any effect. - */ - HttpSession(bool canStore=false); + /** + Constructor. + @param canStore The session can store data, if this parameter is true. + Otherwise all calls to set() and remove() do not have any effect. + */ + HttpSession(bool canStore=false); - /** - Copy constructor. Creates another HttpSession object that shares the - data of the other object. - */ - HttpSession(const HttpSession& other); + /** + Copy constructor. Creates another HttpSession object that shares the + data of the other object. + */ + HttpSession(const HttpSession& other); - /** - Copy operator. Detaches from the current shared data and attaches to - the data of the other object. - */ - HttpSession& operator= (const HttpSession& other); + /** + Copy operator. Detaches from the current shared data and attaches to + the data of the other object. + */ + HttpSession& operator= (const HttpSession& other); - /** - Destructor. Detaches from the shared data. - */ - virtual ~HttpSession(); + /** + Destructor. Detaches from the shared data. + */ + virtual ~HttpSession(); - /** Get the unique ID of this session. This method is thread safe. */ - QByteArray getId() const; + /** Get the unique ID of this session. This method is thread safe. */ + QByteArray getId() const; - /** - Null sessions cannot store data. All calls to set() and remove() - do not have any effect.This method is thread safe. - */ - bool isNull() const; + /** + Null sessions cannot store data. All calls to set() and remove() + do not have any effect.This method is thread safe. + */ + bool isNull() const; - /** Set a value. This method is thread safe. */ - void set(const QByteArray& key, const QVariant& value); + /** Set a value. This method is thread safe. */ + void set(const QByteArray& key, const QVariant& value); - /** Remove a value. This method is thread safe. */ - void remove(const QByteArray& key); + /** Remove a value. This method is thread safe. */ + void remove(const QByteArray& key); - /** Get a value. This method is thread safe. */ - QVariant get(const QByteArray& key) const; + /** Get a value. This method is thread safe. */ + QVariant get(const QByteArray& key) const; - /** Check if a key exists. This method is thread safe. */ - bool contains(const QByteArray& key) const; - - /** - Get a copy of all data stored in this session. - Changes to the session do not affect the copy and vice versa. - This method is thread safe. - */ - QMap getAll() const; - - /** - Get the timestamp of last access. That is the time when the last - HttpSessionStore::getSession() has been called. - This method is thread safe. - */ - qint64 getLastAccess() const; - - /** - Set the timestamp of last access, to renew the timeout period. - Called by HttpSessionStore::getSession(). - This method is thread safe. - */ - void setLastAccess(); - - //AÑADIDO - //sets - void setComicsOnDevice(const QSet & set); - void setComicOnDevice(const QString & hash); - void setDownloadedComic(const QString & hash); - bool isComicOnDevice(const QString & hash); - bool isComicDownloaded(const QString & hash); - QSet getComicsOnDevice(); - QSet getDownloadedComics(); - void clearComics(); - - //current comic (import) - qulonglong getCurrentComicId(); - Comic * getCurrentComic(); - void dismissCurrentComic(); - void setCurrentComic(qulonglong id, Comic * comic); - - //current comic (read) - qulonglong getCurrentRemoteComicId(); - Comic * getCurrentRemoteComic(); - void dismissCurrentRemoteComic(); - void setCurrentRemoteComic(qulonglong id, Comic * comic); - - //device identification - QString getDeviceType(); - QString getDisplayType(); - void setDeviceType(const QString & device); - void setDisplayType(const QString & display); - - - /*int popPage(); - void pushPage(int page); - int topPage(); - - void clearFoldersPath(); - int popFolder(); - void pushFolder(int page); - int topFolder(); - QStack getFoldersPath();*/ - - void clearNavigationPath(); - QPair popNavigationItem(); - QPair topNavigationItem(); - void pushNavigationItem(const QPair & item); - void updateTopItem(const QPair & item); - - //TODO replace QPair by a custom class for storing folderId, page and folderName(save some DB accesses) - QStack > getNavigationPath(); + /** Check if a key exists. This method is thread safe. */ + bool contains(const QByteArray& key) const; + /** + Get a copy of all data stored in this session. + Changes to the session do not affect the copy and vice versa. + This method is thread safe. + */ + QMap getAll() const; + /** + Get the timestamp of last access. That is the time when the last + HttpSessionStore::getSession() has been called. + This method is thread safe. + */ + qint64 getLastAccess() const; + /** + Set the timestamp of last access, to renew the timeout period. + Called by HttpSessionStore::getSession(). + This method is thread safe. + */ + void setLastAccess(); private: - struct YACReaderSessionData { - //cómics disponibles en dispositivo - QSet comicsOnDevice; - //cómics que han sido descargados o están siendo descargados en esta sesión - QSet downloadedComics; - //cómic actual que está siendo descargado - QString device; - QString display; - qulonglong comicId; - qulonglong remoteComicId; + struct HttpSessionData { - //folder_id, page_number - QStack > navigationPath; + /** Unique ID */ + QByteArray id; - Comic * comic; - Comic * remoteComic; - }; + /** Timestamp of last access, set by the HttpSessionStore */ + qint64 lastAccess; - struct HttpSessionData { + /** Reference counter */ + int refCount; - /** Unique ID */ - QByteArray id; + /** Used to synchronize threads */ + QReadWriteLock lock; - /** Timestamp of last access, set by the HttpSessionStore */ - qint64 lastAccess; + /** Storage for the key/value pairs; */ + QMap values; - /** Reference counter */ - int refCount; + }; - /** Used to synchronize threads */ - QReadWriteLock lock; - - /** Storage for the key/value pairs; */ - QMap values; - - YACReaderSessionData yacreaderSessionData; - - }; - - /** Pointer to the shared data. */ - HttpSessionData* dataPtr; + /** Pointer to the shared data. */ + HttpSessionData* dataPtr; }; diff --git a/YACReaderLibrary/server/lib/httpserver/httpsessionstore.cpp b/YACReaderLibrary/server/lib/httpserver/httpsessionstore.cpp index 87ce7d4a..db6e1c2e 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpsessionstore.cpp +++ b/YACReaderLibrary/server/lib/httpserver/httpsessionstore.cpp @@ -11,10 +11,10 @@ HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent) :QObject(parent) { this->settings=settings; - connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(timerEvent())); + connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(sessionTimerEvent())); cleanupTimer.start(60000); cookieName=settings->value("cookieName","sessionid").toByteArray(); - expirationTime=settings->value("expirationTime",864000000).toInt(); + expirationTime=settings->value("expirationTime",3600000).toInt(); qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime); } @@ -23,18 +23,22 @@ HttpSessionStore::~HttpSessionStore() cleanupTimer.stop(); } -QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) { +QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) +{ // The session ID in the response has priority because this one will be used in the next request. mutex.lock(); // Get the session ID from the response cookie QByteArray sessionId=response.getCookies().value(cookieName).getValue(); - if (sessionId.isEmpty()) { + if (sessionId.isEmpty()) + { // Get the session ID from the request cookie sessionId=request.getCookie(cookieName); } // Clear the session ID if there is no such session in the storage. - if (!sessionId.isEmpty()) { - if (!sessions.contains(sessionId)) { + if (!sessionId.isEmpty()) + { + if (!sessions.contains(sessionId)) + { qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data()); sessionId.clear(); } @@ -43,21 +47,31 @@ QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& re return sessionId; } -HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) { +HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) +{ QByteArray sessionId=getSessionId(request,response); mutex.lock(); - if (!sessionId.isEmpty()) { + if (!sessionId.isEmpty()) + { HttpSession session=sessions.value(sessionId); - if (!session.isNull()) { + if (!session.isNull()) + { mutex.unlock(); + // Refresh the session cookie + QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray(); + QByteArray cookiePath=settings->value("cookiePath").toByteArray(); + QByteArray cookieComment=settings->value("cookieComment").toByteArray(); + QByteArray cookieDomain=settings->value("cookieDomain").toByteArray(); + response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain)); session.setLastAccess(); return session; } } // Need to create a new session - if (allowCreate) { + if (allowCreate) + { QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray(); - QByteArray cookiePath=settings->value("cookiePath","/").toByteArray(); + QByteArray cookiePath=settings->value("cookiePath").toByteArray(); QByteArray cookieComment=settings->value("cookieComment").toByteArray(); QByteArray cookieDomain=settings->value("cookieDomain").toByteArray(); HttpSession session(true); @@ -72,7 +86,8 @@ HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& res return HttpSession(); } -HttpSession HttpSessionStore::getSession(const QByteArray id) { +HttpSession HttpSessionStore::getSession(const QByteArray id) +{ mutex.lock(); HttpSession session=sessions.value(id); mutex.unlock(); @@ -80,19 +95,19 @@ HttpSession HttpSessionStore::getSession(const QByteArray id) { return session; } -void HttpSessionStore::timerEvent() { - // Todo: find a way to delete sessions only if no controller is accessing them +void HttpSessionStore::sessionTimerEvent() +{ mutex.lock(); qint64 now=QDateTime::currentMSecsSinceEpoch(); QMap::iterator i = sessions.begin(); - while (i != sessions.end()) { + while (i != sessions.end()) + { QMap::iterator prev = i; ++i; HttpSession session=prev.value(); qint64 lastAccess=session.getLastAccess(); - if (now-lastAccess>expirationTime) { //TODO cleaning up will cause current opened comic to be deleted, so clients won't be able to download it - //If the cleaning occurs in the midle of a download it going to cause issues - //Temporal fix: use a big expirationTime = 10 days + if (now-lastAccess>expirationTime) + { qDebug("HttpSessionStore: session %s expired",session.getId().data()); sessions.erase(prev); } @@ -102,7 +117,8 @@ void HttpSessionStore::timerEvent() { /** Delete a session */ -void HttpSessionStore::removeSession(HttpSession session) { +void HttpSessionStore::removeSession(HttpSession session) +{ mutex.lock(); sessions.remove(session.getId()); mutex.unlock(); diff --git a/YACReaderLibrary/server/lib/httpserver/httpsessionstore.h b/YACReaderLibrary/server/lib/httpserver/httpsessionstore.h index e65260d7..5f5efdb4 100644 --- a/YACReaderLibrary/server/lib/httpserver/httpsessionstore.h +++ b/YACReaderLibrary/server/lib/httpserver/httpsessionstore.h @@ -10,6 +10,7 @@ #include #include #include +#include "httpglobal.h" #include "httpsession.h" #include "httpresponse.h" #include "httprequest.h" @@ -25,17 +26,17 @@

   cookiePath=/
   cookieComment=Session ID
-  cookieDomain=stefanfrings.de
+  ;cookieDomain=stefanfrings.de
   
*/ -class HttpSessionStore : public QObject { +class DECLSPEC HttpSessionStore : public QObject { Q_OBJECT Q_DISABLE_COPY(HttpSessionStore) public: /** Constructor. */ - HttpSessionStore(QSettings* settings, QObject* parent); + HttpSessionStore(QSettings* settings, QObject* parent=NULL); /** Destructor */ virtual ~HttpSessionStore(); @@ -75,14 +76,15 @@ public: /** Delete a session */ void removeSession(HttpSession session); +protected: + /** Storage for the sessions */ + QMap sessions; + private: /** Configuration settings */ QSettings* settings; - /** Storage for the sessions */ - QMap sessions; - /** Timer to remove expired sessions */ QTimer cleanupTimer; @@ -98,7 +100,7 @@ private: private slots: /** Called every minute to cleanup expired sessions. */ - void timerEvent(); + void sessionTimerEvent(); }; #endif // HTTPSESSIONSTORE_H diff --git a/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.cpp b/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.cpp index c16a3c28..b85e175e 100644 --- a/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.cpp +++ b/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.cpp @@ -7,31 +7,25 @@ #include #include #include -#include "httpsession.h" -#include "static.h" -#include - StaticFileController::StaticFileController(QSettings* settings, QObject* parent) :HttpRequestHandler(parent) { maxAge=settings->value("maxAge","60000").toInt(); encoding=settings->value("encoding","UTF-8").toString(); - docroot=settings->value("path","./server/docroot").toString(); - // Convert relative path to absolute, based on the directory of the config file. -#ifdef Q_OS_WIN32 - if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat) -#else - if (QDir::isRelativePath(docroot)) -#endif + docroot=settings->value("path",".").toString(); + if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://"))) + { + // Convert relative path to absolute, based on the directory of the config file. + #ifdef Q_OS_WIN32 + if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat) + #else + if (QDir::isRelativePath(docroot)) + #endif { -#if defined Q_OS_UNIX && ! defined Q_OS_MAC - QFileInfo configFile(QString(DATADIR)+"/yacreader"); - docroot=QFileInfo(QString(DATADIR)+"/yacreader",docroot).absoluteFilePath(); -#else - QFileInfo configFile(QCoreApplication::applicationDirPath()); - docroot=QFileInfo(QCoreApplication::applicationDirPath(),docroot).absoluteFilePath(); -#endif + QFileInfo configFile(settings->fileName()); + docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath(); + } } qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge); maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt(); @@ -41,195 +35,151 @@ StaticFileController::StaticFileController(QSettings* settings, QObject* parent) } -void StaticFileController::service(HttpRequest& request, HttpResponse& response) { +void StaticFileController::service(HttpRequest& request, HttpResponse& response) +{ QByteArray path=request.getPath(); - // Forbid access to files outside the docroot directory - if (path.startsWith("/..")) { - qWarning("StaticFileController: somebody attempted to access a file outside the docroot directory"); - response.setStatus(403,"forbidden"); - response.write("403 forbidden",true); - } - - //TODO(DONE) carga sensible al dispositivo y a la localización - QString stringPath = path; - QStringList paths = QString(path).split('/'); - QString fileName = paths.last(); - stringPath.remove(fileName); - HttpSession session=Static::sessionStore->getSession(request,response,false); - QString device = session.getDeviceType(); - QString display = session.getDisplayType(); - if(fileName.endsWith(".png")) - fileName = getDeviceAwareFileName(fileName, device, display, request.getHeader("Accept-Language"), stringPath); - else - fileName = getDeviceAwareFileName(fileName, device, request.getHeader("Accept-Language"), stringPath); - QString newPath = stringPath.append(fileName); - path = newPath.toLocal8Bit(); - - //CAMBIADO - response.setHeader("Connection","close"); - //END_TODO - // Check if we have the file in cache - //qint64 now=QDateTime::currentMSecsSinceEpoch(); - // mutex.lock(); - // CacheEntry* entry=cache.object(path); - //if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) { - // QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock. - // mutex.unlock(); - // qDebug("StaticFileController: Cache hit for %s",path.data()); - // setContentType(path,response); - // response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); - // response.write(document); - //} - //else { - - // mutex.unlock(); - //qDebug("StaticFileController: Cache miss for %s",path.data()); + qint64 now=QDateTime::currentMSecsSinceEpoch(); + mutex.lock(); + CacheEntry* entry=cache.object(path); + if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) + { + QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock. + QByteArray filename=entry->filename; + mutex.unlock(); + qDebug("StaticFileController: Cache hit for %s",path.data()); + setContentType(filename,response); + response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); + response.write(document); + } + else + { + mutex.unlock(); // The file is not in cache. + qDebug("StaticFileController: Cache miss for %s",path.data()); + // Forbid access to files outside the docroot directory + if (path.contains("/..")) + { + qWarning("StaticFileController: detected forbidden characters in path %s",path.data()); + response.setStatus(403,"forbidden"); + response.write("403 forbidden",true); + return; + } // If the filename is a directory, append index.html. - if (QFileInfo(docroot+path).isDir()) { + if (QFileInfo(docroot+path).isDir()) + { path+="/index.html"; } - - + // Try to open the file QFile file(docroot+path); - if (file.exists()) { - qDebug("StaticFileController: Open file %s",qPrintable(file.fileName())); - if (file.open(QIODevice::ReadOnly)) { - setContentType(path,response); - //response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); - //if (file.size()<=maxCachedFileSize) { - // // Return the file content and store it also in the cache - // entry=new CacheEntry(); - // while (!file.atEnd() && !file.error()) { - // QByteArray buffer=file.read(65536); - // response.write(buffer); - // entry->document.append(buffer); - // } - // entry->created=now; - // mutex.lock(); - // cache.insert(request.getPath(),entry,entry->document.size()); - // mutex.unlock(); - //} - //else { - // Return the file content, do not store in cache*/ - while (!file.atEnd() && !file.error()) { - response.write(file.read(131072)); - //} + qDebug("StaticFileController: Open file %s",qPrintable(file.fileName())); + if (file.open(QIODevice::ReadOnly)) + { + setContentType(path,response); + response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000)); + if (file.size()<=maxCachedFileSize) + { + // Return the file content and store it also in the cache + entry=new CacheEntry(); + while (!file.atEnd() && !file.error()) + { + QByteArray buffer=file.read(65536); + response.write(buffer); + entry->document.append(buffer); } - file.close(); + entry->created=now; + entry->filename=path; + mutex.lock(); + cache.insert(request.getPath(),entry,entry->document.size()); + mutex.unlock(); } - else { + else + { + // Return the file content, do not store in cache + while (!file.atEnd() && !file.error()) + { + response.write(file.read(65536)); + } + } + file.close(); + } + else { + if (file.exists()) + { qWarning("StaticFileController: Cannot open existing file %s for reading",qPrintable(file.fileName())); response.setStatus(403,"forbidden"); response.write("403 forbidden",true); } + else + { + response.setStatus(404,"not found"); + response.write("404 not found",true); + } } - else { - response.setStatus(404,"not found"); - response.write("404 not found",true); - } - //} + } } -void StaticFileController::setContentType(QString fileName, HttpResponse& response) const { - if (fileName.endsWith(".png")) { +void StaticFileController::setContentType(QString fileName, HttpResponse& response) const +{ + if (fileName.endsWith(".png")) + { response.setHeader("Content-Type", "image/png"); } - else if (fileName.endsWith(".jpg")) { + else if (fileName.endsWith(".jpg")) + { response.setHeader("Content-Type", "image/jpeg"); } - else if (fileName.endsWith(".gif")) { + else if (fileName.endsWith(".gif")) + { response.setHeader("Content-Type", "image/gif"); } - else if (fileName.endsWith(".pdf")) { + else if (fileName.endsWith(".pdf")) + { response.setHeader("Content-Type", "application/pdf"); } - else if (fileName.endsWith(".txt")) { + else if (fileName.endsWith(".txt")) + { response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding)); } - else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) { + else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) + { response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding)); } - else if (fileName.endsWith(".css")) { + else if (fileName.endsWith(".css")) + { response.setHeader("Content-Type", "text/css"); } - else if (fileName.endsWith(".js")) { + else if (fileName.endsWith(".js")) + { response.setHeader("Content-Type", "text/javascript"); } + else if (fileName.endsWith(".svg")) + { + response.setHeader("Content-Type", "image/svg+xml"); + } + else if (fileName.endsWith(".woff")) + { + response.setHeader("Content-Type", "font/woff"); + } + else if (fileName.endsWith(".woff2")) + { + response.setHeader("Content-Type", "font/woff2"); + } + else if (fileName.endsWith(".ttf")) + { + response.setHeader("Content-Type", "application/x-font-ttf"); + } + else if (fileName.endsWith(".eot")) + { + response.setHeader("Content-Type", "application/vnd.ms-fontobject"); + } + else if (fileName.endsWith(".otf")) + { + response.setHeader("Content-Type", "application/font-otf"); + } // Todo: add all of your content types -} - -bool StaticFileController::exists(QString localizedName, QString path) const -{ - QString fileName=docroot+"/"+path + localizedName; - QFile file(fileName); - return file.exists(); -} - -//retorna fileName si no se encontró alternativa traducida ó fileName-locale.extensión si se encontró -QString StaticFileController::getLocalizedFileName(QString fileName, QString locales, QString path) const -{ - QSet tried; // used to suppress duplicate attempts - QStringList locs=locales.split(',',QString::SkipEmptyParts); - QStringList fileNameParts = fileName.split('.'); - QString file = fileNameParts.first(); - QString extension = fileNameParts.last(); - // Search for exact match - foreach (QString loc,locs) { - loc.replace(QRegExp(";.*"),""); - loc.replace('-','_'); - QString localizedName=file+"-"+loc.trimmed()+"."+extension; - if (!tried.contains(localizedName)) { - if(exists(localizedName, path)) - return localizedName; - tried.insert(localizedName); - } - } - - // Search for correct language but any country - foreach (QString loc,locs) { - loc.replace(QRegExp("[;_-].*"),""); - QString localizedName=file+"-"+loc.trimmed()+"."+extension; - if (!tried.contains(localizedName)) { - if(exists(localizedName, path)) - return localizedName; - tried.insert(localizedName); - } - } - - return fileName; -} - -QString StaticFileController::getDeviceAwareFileName(QString fileName, QString device, QString locales, QString path) const -{ - QFileInfo fi(fileName); - QString baseName = fi.baseName(); - QString extension = fi.completeSuffix(); - - QString completeFileName = getLocalizedFileName(baseName+"_"+device+"."+extension,locales,path); - - if(QFile(docroot+"/"+path+completeFileName).exists()) - return completeFileName; //existe un archivo específico para este dispositivo y locales - else - return getLocalizedFileName(fileName,locales,path); //no hay archivo específico para el dispositivo, pero puede haberlo para estas locales -} - -QString StaticFileController::getDeviceAwareFileName(QString fileName, QString device, QString display, QString locales, QString path) const -{ - QFileInfo fi(fileName); - QString baseName = fi.baseName(); - QString extension = fi.completeSuffix(); - - QString completeFileName = baseName+display+"."+extension; - if(QFile(docroot+"/"+path+completeFileName).exists()) - return completeFileName; else { - completeFileName = baseName+"_"+device+display+"."+extension; - if((QFile(docroot+"/"+path+completeFileName).exists())) - return completeFileName; + qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName)); } - - return fileName; } diff --git a/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.h b/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.h index 26413398..b28c8ff6 100644 --- a/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.h +++ b/YACReaderLibrary/server/lib/httpserver/staticfilecontroller.h @@ -6,19 +6,20 @@ #ifndef STATICFILECONTROLLER_H #define STATICFILECONTROLLER_H +#include +#include +#include "httpglobal.h" #include "httprequest.h" #include "httpresponse.h" #include "httprequesthandler.h" -#include -#include /** Delivers static files. It is usually called by the applications main request handler when - the caller request a path that is mapped to static files. + the caller requests a path that is mapped to static files.

The following settings are required in the config file:

-  path=docroot
+  path=../docroot
   encoding=UTF-8
   maxAge=60000
   cacheTime=60000
@@ -39,54 +40,48 @@
   received a related HTTP request.
 */
 
-class StaticFileController : public HttpRequestHandler  {
-	Q_OBJECT
-	Q_DISABLE_COPY(StaticFileController);
+class DECLSPEC StaticFileController : public HttpRequestHandler  {
+    Q_OBJECT
+    Q_DISABLE_COPY(StaticFileController)
 public:
 
-	/** Constructor */
-	StaticFileController(QSettings* settings, QObject* parent = 0);
+    /** Constructor */
+    StaticFileController(QSettings* settings, QObject* parent = NULL);
 
-	/** Generates the response */
-	void service(HttpRequest& request, HttpResponse& response);
+    /** Generates the response */
+    void service(HttpRequest& request, HttpResponse& response);
 
 private:
 
-	/** Encoding of text files */
-	QString encoding;
+    /** Encoding of text files */
+    QString encoding;
 
-	/** Root directory of documents */
-	QString docroot;
+    /** Root directory of documents */
+    QString docroot;
 
-	/** Maximum age of files in the browser cache */
-	int maxAge;    
+    /** Maximum age of files in the browser cache */
+    int maxAge;
 
-	struct CacheEntry {
-		QByteArray document;
-		qint64 created;
-	};
+    struct CacheEntry {
+        QByteArray document;
+        qint64 created;
+        QByteArray filename;
+    };
 
-	/** Timeout for each cached file */
-	int cacheTimeout;
+    /** Timeout for each cached file */
+    int cacheTimeout;
 
+    /** Maximum size of files in cache, larger files are not cached */
+    int maxCachedFileSize;
 
-	/** Maximum size of files in cache, larger files are not cached */
-	int maxCachedFileSize;
+    /** Cache storage */
+    QCache cache;
 
-	/** Cache storage */
-	QCache cache;
+    /** Used to synchronize cache access for threads */
+    QMutex mutex;
 
-	/** Used to synchronize cache access for threads */
-	QMutex mutex;
-
-	/** Set a content-type header in the response depending on the ending of the filename */
-	void setContentType(QString file, HttpResponse& response) const;
-
-	QString getLocalizedFileName(QString fileName, QString locales, QString path) const;
-	QString getDeviceAwareFileName(QString fileName, QString device, QString locales, QString path) const;
-    QString getDeviceAwareFileName(QString fileName, QString device, QString display, QString locales, QString path) const;
-
-	bool exists(QString localizedName, QString path) const;
+    /** Set a content-type header in the response depending on the ending of the filename */
+    void setContentType(QString file, HttpResponse& response) const;
 };
 
 #endif // STATICFILECONTROLLER_H