diff --git a/httpserver/copyright.txt b/httpserver/copyright.txt
new file mode 100644
index 000000000..1c5a04719
--- /dev/null
+++ b/httpserver/copyright.txt
@@ -0,0 +1,11 @@
+This program and its source is distributed under the Lesser General Public License.
+http://www.gnu.org/licenses/lgpl.html
+
+When you modify the library, you have to publish your modified version for free under LGPL license.
+
+Author and Copyright owner: Stefan Frings
+stefan@stefanfrings.de
+http://www.stefanfrings.de
+
+The qtservice module had been originally published by Trolltech under the LGPL license as well,
+but is now re-published by Digia under the less restrictive BSD License. 
diff --git a/httpserver/httpconnectionhandler.cpp b/httpserver/httpconnectionhandler.cpp
new file mode 100644
index 000000000..aa60ea0ef
--- /dev/null
+++ b/httpserver/httpconnectionhandler.cpp
@@ -0,0 +1,277 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httpconnectionhandler.h"
+#include "httpresponse.h"
+
+using namespace stefanfrings;
+
+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;
+
+    // Create TCP or SSL socket
+    createSocket();
+
+    // execute signals in my own thread
+    moveToThread(this);
+    socket->moveToThread(this);
+    readTimer.moveToThread(this);
+
+    // 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()
+{
+    quit();
+    wait();
+    qDebug("HttpConnectionHandler (%p): destroyed", this);
+}
+
+
+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::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
+
+    //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);
+    // delete previous request
+    delete currentRequest;
+    currentRequest=0;
+}
+
+
+bool HttpConnectionHandler::isBusy()
+{
+    return busy;
+}
+
+void HttpConnectionHandler::setBusy()
+{
+    this->busy = true;
+}
+
+
+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");
+
+    while(socket->bytesToWrite()) socket->waitForBytesWritten();
+    socket->disconnectFromHost();
+    delete currentRequest;
+    currentRequest=0;
+}
+
+
+void HttpConnectionHandler::disconnected()
+{
+    qDebug("HttpConnectionHandler (%p): disconnected", this);
+    socket->close();
+    readTimer.stop();
+    busy = false;
+}
+
+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)
+        {
+            currentRequest=new HttpRequest(settings);
+        }
+
+        // Collect data for the request object
+        while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort)
+        {
+            currentRequest->readFromSocket(socket);
+            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();
+                readTimer.start(readTimeout);
+            }
+        }
+
+        // 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");
+            while(socket->bytesToWrite()) socket->waitForBytesWritten();
+            socket->disconnectFromHost();
+            delete currentRequest;
+            currentRequest=0;
+            return;
+        }
+
+        // If the request is complete, let the request mapper dispatch it
+        if (currentRequest->getStatus()==HttpRequest::complete)
+        {
+            readTimer.stop();
+            qDebug("HttpConnectionHandler (%p): received request",this);
+
+            // 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 (...)
+            {
+                qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this);
+            }
+
+            // Finalize sending the response if not already done
+            if (!response.hasSentLastPart())
+            {
+                response.write(QByteArray(),true);
+            }
+
+            qDebug("HttpConnectionHandler (%p): finished request",this);
+
+            // 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;
+                        }
+                    }
+                }
+            }
+
+            // Close the connection or prepare for the next request on the same connection.
+            if (closeConnection)
+            {
+                while(socket->bytesToWrite()) socket->waitForBytesWritten();
+                socket->disconnectFromHost();
+            }
+            else
+            {
+                // Start timer for next request
+                int readTimeout=settings->value("readTimeout",10000).toInt();
+                readTimer.start(readTimeout);
+            }
+            delete currentRequest;
+            currentRequest=0;
+        }
+    }
+}
diff --git a/httpserver/httpconnectionhandler.h b/httpserver/httpconnectionhandler.h
new file mode 100644
index 000000000..c68f4281a
--- /dev/null
+++ b/httpserver/httpconnectionhandler.h
@@ -0,0 +1,124 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPCONNECTIONHANDLER_H
+#define HTTPCONNECTIONHANDLER_H
+
+#ifndef QT_NO_OPENSSL
+   #include <QSslConfiguration>
+#endif
+#include <QTcpSocket>
+#include <QSettings>
+#include <QTimer>
+#include <QThread>
+#include "httpglobal.h"
+#include "httprequest.h"
+#include "httprequesthandler.h"
+
+namespace stefanfrings {
+
+/** 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,
+  the incoming requests are queued and processed one after the other.
+  <p>
+  Example for the required configuration settings:
+  <code><pre>
+  readTimeout=60000
+  maxRequestSize=16000
+  maxMultiPartSize=1000000
+  </pre></code>
+  <p>
+  The readTimeout value defines the maximum time to wait for a complete HTTP request.
+  @see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize.
+*/
+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 incoming HTTP request
+      @param sslConfiguration SSL (HTTPS) will be used if not NULL
+    */
+    HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration=NULL);
+
+    /** Destructor */
+    virtual ~HttpConnectionHandler();
+
+    /** Returns true, if this handler is in use. */
+    bool isBusy();
+
+    /** Mark this handler as busy */
+    void setBusy();
+
+private:
+
+    /** Configuration settings */
+    QSettings* settings;
+
+    /** TCP socket of the current connection  */
+    QTcpSocket* socket;
+
+    /** Time for read timeout detection */
+    QTimer readTimer;
+
+    /** Storage for the current incoming HTTP request */
+    HttpRequest* currentRequest;
+
+    /** Dispatches received requests to services */
+    HttpRequestHandler* requestHandler;
+
+    /** This shows the busy-state from a very early time */
+    bool busy;
+
+    /** Configuration for SSL */
+    QSslConfiguration* sslConfiguration;
+
+    /** Executes the threads own event loop */
+    void run();
+
+    /**  Create SSL or TCP socket */
+    void createSocket();
+
+public slots:
+
+    /**
+      Received from from the listener, when the handler shall start processing a new connection.
+      @param socketDescriptor references the accepted connection.
+    */
+    void handleConnection(tSocketDescriptor socketDescriptor);
+
+private slots:
+
+    /** Received from the socket when a read-timeout occured */
+    void readTimeout();
+
+    /** Received from the socket when incoming data can be read */
+    void read();
+
+    /** Received from the socket when a connection has been closed */
+    void disconnected();
+
+};
+
+} // end of namespace
+
+#endif // HTTPCONNECTIONHANDLER_H
diff --git a/httpserver/httpconnectionhandlerpool.cpp b/httpserver/httpconnectionhandlerpool.cpp
new file mode 100644
index 000000000..3a3615b56
--- /dev/null
+++ b/httpserver/httpconnectionhandlerpool.cpp
@@ -0,0 +1,148 @@
+#ifndef QT_NO_OPENSSL
+    #include <QSslSocket>
+    #include <QSslKey>
+    #include <QSslCertificate>
+    #include <QSslConfiguration>
+#endif
+#include <QDir>
+#include "httpconnectionhandlerpool.h"
+
+using namespace stefanfrings;
+
+HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler)
+    : QObject()
+{
+    Q_ASSERT(settings!=0);
+    this->settings=settings;
+    this->requestHandler=requestHandler;
+    this->sslConfiguration=NULL;
+    loadSslConfig();
+    cleanupTimer.start(settings->value("cleanupInterval",1000).toInt());
+    connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
+}
+
+
+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* freeHandler=0;
+    mutex.lock();
+    // find a free handler in pool
+    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",100).toInt();
+        if (pool.count()<maxConnectionHandlers)
+        {
+            freeHandler=new HttpConnectionHandler(settings,requestHandler,sslConfiguration);
+            freeHandler->setBusy();
+            pool.append(freeHandler);
+        }
+    }
+    mutex.unlock();
+    return freeHandler;
+}
+
+
+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)
+            {
+                delete handler;
+                pool.removeOne(handler);
+                qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size());
+                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/httpserver/httpconnectionhandlerpool.h b/httpserver/httpconnectionhandlerpool.h
new file mode 100644
index 000000000..c6279f1a2
--- /dev/null
+++ b/httpserver/httpconnectionhandlerpool.h
@@ -0,0 +1,99 @@
+#ifndef HTTPCONNECTIONHANDLERPOOL_H
+#define HTTPCONNECTIONHANDLERPOOL_H
+
+#include <QList>
+#include <QTimer>
+#include <QObject>
+#include <QMutex>
+#include "httpglobal.h"
+#include "httpconnectionhandler.h"
+
+namespace stefanfrings {
+
+/**
+  Pool of http connection handlers. The size of the pool grows and
+  shrinks on demand.
+  <p>
+  Example for the required configuration settings:
+  <code><pre>
+  minThreads=4
+  maxThreads=100
+  cleanupInterval=60000
+  readTimeout=60000
+  ;sslKeyFile=ssl/my.key
+  ;sslCertFile=ssl/my.cert
+  maxRequestSize=16000
+  maxMultiPartSize=1000000
+  </pre></code>
+  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.
+  <p>
+  For SSL support, you need an OpenSSL certificate file and a key file.
+  Both can be created with the command
+  <code><pre>
+      openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout my.key -out my.cert
+  </pre></code>
+  <p>
+  Visit http://slproweb.com/products/Win32OpenSSL.html to download the Light version of OpenSSL for Windows.
+  <p>
+  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 DECLSPEC HttpConnectionHandlerPool : public QObject {
+    Q_OBJECT
+    Q_DISABLE_COPY(HttpConnectionHandlerPool)
+public:
+
+    /**
+      Constructor.
+      @param settings Configuration settings for the HTTP server. Must not be 0.
+      @param requestHandler The handler that will process each received HTTP request.
+      @warning The requestMapper gets deleted by the destructor of this pool
+    */
+    HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler);
+
+    /** Destructor */
+    virtual ~HttpConnectionHandlerPool();
+
+    /** Get a free connection handler, or 0 if not available. */
+    HttpConnectionHandler* getConnectionHandler();
+
+private:
+
+    /** Settings for this pool */
+    QSettings* settings;
+
+    /** Will be assigned to each Connectionhandler during their creation */
+    HttpRequestHandler* requestHandler;
+
+    /** Pool of connection handlers */
+    QList<HttpConnectionHandler*> pool;
+
+    /** Timer to clean-up unused connection handler */
+    QTimer cleanupTimer;
+
+    /** 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.  */
+    void cleanup();
+
+};
+
+} // end of namespace
+
+#endif // HTTPCONNECTIONHANDLERPOOL_H
diff --git a/httpserver/httpcookie.cpp b/httpserver/httpcookie.cpp
new file mode 100644
index 000000000..f09a2c373
--- /dev/null
+++ b/httpserver/httpcookie.cpp
@@ -0,0 +1,263 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httpcookie.h"
+
+using namespace stefanfrings;
+
+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, const bool httpOnly)
+{
+    this->name=name;
+    this->value=value;
+    this->maxAge=maxAge;
+    this->path=path;
+    this->comment=comment;
+    this->domain=domain;
+    this->secure=secure;
+    this->httpOnly=httpOnly;
+    this->version=1;
+}
+
+HttpCookie::HttpCookie(const QByteArray source)
+{
+    version=1;
+    maxAge=0;
+    secure=false;
+    QList<QByteArray> list=splitCSV(source);
+    foreach(QByteArray part, list)
+    {
+
+        // Split the part into name and value
+        QByteArray name;
+        QByteArray value;
+        int posi=part.indexOf('=');
+        if (posi)
+        {
+            name=part.left(posi).trimmed();
+            value=part.mid(posi+1).trimmed();
+        }
+        else
+        {
+            name=part.trimmed();
+            value="";
+        }
+
+        // Set fields
+        if (name=="Comment")
+        {
+            comment=value;
+        }
+        else if (name=="Domain")
+        {
+            domain=value;
+        }
+        else if (name=="Max-Age")
+        {
+            maxAge=value.toInt();
+        }
+        else if (name=="Path")
+        {
+            path=value;
+        }
+        else if (name=="Secure")
+        {
+            secure=true;
+        }
+        else if (name=="HttpOnly")
+        {
+            httpOnly=true;
+        }
+        else if (name=="Version")
+        {
+            version=value.toInt();
+        }
+        else {
+            if (this->name.isEmpty())
+            {
+                this->name=name;
+                this->value=value;
+            }
+            else
+            {
+                qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data());
+            }
+        }
+    }
+}
+
+QByteArray HttpCookie::toByteArray() const
+{
+    QByteArray buffer(name);
+    buffer.append('=');
+    buffer.append(value);
+    if (!comment.isEmpty())
+    {
+        buffer.append("; Comment=");
+        buffer.append(comment);
+    }
+    if (!domain.isEmpty())
+    {
+        buffer.append("; Domain=");
+        buffer.append(domain);
+    }
+    if (maxAge!=0)
+    {
+        buffer.append("; Max-Age=");
+        buffer.append(QByteArray::number(maxAge));
+    }
+    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)
+{
+    this->name=name;
+}
+
+void HttpCookie::setValue(const QByteArray value)
+{
+    this->value=value;
+}
+
+void HttpCookie::setComment(const QByteArray comment)
+{
+    this->comment=comment;
+}
+
+void HttpCookie::setDomain(const QByteArray domain)
+{
+    this->domain=domain;
+}
+
+void HttpCookie::setMaxAge(const int maxAge)
+{
+    this->maxAge=maxAge;
+}
+
+void HttpCookie::setPath(const QByteArray path)
+{
+    this->path=path;
+}
+
+void HttpCookie::setSecure(const bool secure)
+{
+    this->secure=secure;
+}
+
+void HttpCookie::setHttpOnly(const bool httpOnly)
+{
+    this->httpOnly=httpOnly;
+}
+
+QByteArray HttpCookie::getName() const
+{
+    return name;
+}
+
+QByteArray HttpCookie::getValue() const
+{
+    return value;
+}
+
+QByteArray HttpCookie::getComment() const
+{
+    return comment;
+}
+
+QByteArray HttpCookie::getDomain() const
+{
+    return domain;
+}
+
+int HttpCookie::getMaxAge() const
+{
+    return maxAge;
+}
+
+QByteArray HttpCookie::getPath() const
+{
+    return path;
+}
+
+bool HttpCookie::getSecure() const
+{
+    return secure;
+}
+
+bool HttpCookie::getHttpOnly() const
+{
+    return httpOnly;
+}
+
+int HttpCookie::getVersion() const
+{
+    return version;
+}
+
+QList<QByteArray> HttpCookie::splitCSV(const QByteArray source)
+{
+    bool inString=false;
+    QList<QByteArray> list;
+    QByteArray buffer;
+    for (int i=0; i<source.size(); ++i)
+    {
+        char c=source.at(i);
+        if (inString==false)
+        {
+            if (c=='\"')
+            {
+                inString=true;
+            }
+            else if (c==';')
+            {
+                QByteArray trimmed=buffer.trimmed();
+                if (!trimmed.isEmpty())
+                {
+                    list.append(trimmed);
+                }
+                buffer.clear();
+            }
+            else
+            {
+                buffer.append(c);
+            }
+        }
+        else
+        {
+            if (c=='\"')
+            {
+                inString=false;
+            }
+            else {
+                buffer.append(c);
+            }
+        }
+    }
+    QByteArray trimmed=buffer.trimmed();
+    if (!trimmed.isEmpty())
+    {
+        list.append(trimmed);
+    }
+    return list;
+}
diff --git a/httpserver/httpcookie.h b/httpserver/httpcookie.h
new file mode 100644
index 000000000..236888928
--- /dev/null
+++ b/httpserver/httpcookie.h
@@ -0,0 +1,123 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPCOOKIE_H
+#define HTTPCOOKIE_H
+
+#include <QList>
+#include <QByteArray>
+#include "httpglobal.h"
+
+namespace stefanfrings {
+
+/**
+  HTTP cookie as defined in RFC 2109. This class can also parse
+  RFC 2965 cookies, but skips fields that are not defined in RFC
+  2109.
+*/
+
+class DECLSPEC HttpCookie
+{
+public:
+
+    /** Creates an empty cookie */
+    HttpCookie();
+
+    /**
+      Create a cookie and set name/value pair.
+      @param name name of the cookie
+      @param value value of the cookie
+      @param maxAge maximum age of the cookie in seconds. 0=discard immediately
+      @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 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, const bool httpOnly=false);
+
+    /**
+      Create a cookie from a string.
+      @param source String as received in a HTTP Cookie2 header.
+    */
+    HttpCookie(const QByteArray source);
+
+    /** Convert this cookie to a string that may be used in a Set-Cookie header. */
+    QByteArray toByteArray() const ;
+
+    /**
+      Split a string list into parts, where each part is delimited by semicolon.
+      Semicolons within double quotes are skipped. Double quotes are removed.
+    */
+    static QList<QByteArray> splitCSV(const QByteArray source);
+
+    /** Set the name of this cookie */
+    void setName(const QByteArray name);
+
+    /** Set the value of this cookie */
+    void setValue(const QByteArray value);
+
+    /** Set the comment of this cookie */
+    void setComment(const QByteArray comment);
+
+    /** Set the domain of this cookie */
+    void setDomain(const QByteArray domain);
+
+    /** Set the maximum age of this cookie in seconds. 0=discard immediately */
+    void setMaxAge(const int maxAge);
+
+    /** 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 cookie will be sent by the browser to the server only on secure connections */
+    void setSecure(const bool secure);
+
+    /** Set HTTP-only 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;
+
+    /** Get the value of this cookie */
+    QByteArray getValue() const;
+
+    /** Get the comment of this cookie */
+    QByteArray getComment() const;
+
+    /** Get the domain of this cookie */
+    QByteArray getDomain() const;
+
+    /** Get the maximum age of this cookie in seconds. */
+    int getMaxAge() const;
+
+    /** Set the path of this cookie */
+    QByteArray getPath() const;
+
+    /** Get the secure flag of this cookie */
+    bool getSecure() const;
+
+    /** Get the HTTP-only flag of this cookie */
+    bool getHttpOnly() const;
+
+    /** Returns always 1 */
+    int getVersion() const;
+
+private:
+
+    QByteArray name;
+    QByteArray value;
+    QByteArray comment;
+    QByteArray domain;
+    int maxAge;
+    QByteArray path;
+    bool secure;
+    bool httpOnly;
+    int version;
+
+};
+
+} // end of namespace
+
+#endif // HTTPCOOKIE_H
diff --git a/httpserver/httpglobal.cpp b/httpserver/httpglobal.cpp
new file mode 100644
index 000000000..8ccfb02dd
--- /dev/null
+++ b/httpserver/httpglobal.cpp
@@ -0,0 +1,7 @@
+#include "httpglobal.h"
+
+const char* getQtWebAppLibVersion()
+{
+    return "1.7.3";
+}
+
diff --git a/httpserver/httpglobal.h b/httpserver/httpglobal.h
new file mode 100644
index 000000000..e7e856a97
--- /dev/null
+++ b/httpserver/httpglobal.h
@@ -0,0 +1,28 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPGLOBAL_H
+#define HTTPGLOBAL_H
+
+#include <QtGlobal>
+
+// This is specific to Windows dll's
+#if defined(Q_OS_WIN)
+    #if defined(QTWEBAPPLIB_EXPORT)
+        #define DECLSPEC Q_DECL_EXPORT
+    #elif defined(QTWEBAPPLIB_IMPORT)
+        #define DECLSPEC Q_DECL_IMPORT
+    #endif
+#endif
+#if !defined(DECLSPEC)
+    #define DECLSPEC
+#endif
+
+/** Get the library version number */
+DECLSPEC const char* getQtWebAppLibVersion();
+
+
+#endif // HTTPGLOBAL_H
+
diff --git a/httpserver/httplistener.cpp b/httpserver/httplistener.cpp
new file mode 100644
index 000000000..b07c4b8c7
--- /dev/null
+++ b/httpserver/httplistener.cpp
@@ -0,0 +1,90 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httplistener.h"
+#include "httpconnectionhandler.h"
+#include "httpconnectionhandlerpool.h"
+#include <QCoreApplication>
+
+using namespace stefanfrings;
+
+HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject *parent)
+    : 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>("tSocketDescriptor");
+    // Start listening
+    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()));
+    }
+    else {
+        qDebug("HttpListener: Listening on port %i",port);
+    }
+}
+
+
+void HttpListener::close() {
+    QTcpServer::close();
+    qDebug("HttpListener: closed");
+    if (pool) {
+        delete pool;
+        pool=NULL;
+    }
+}
+
+void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) {
+#ifdef SUPERVERBOSE
+    qDebug("HttpListener: New connection");
+#endif
+
+    HttpConnectionHandler* freeHandler=NULL;
+    if (pool)
+    {
+        freeHandler=pool->getConnectionHandler();
+    }
+
+    // Let the handler process the new connection.
+    if (freeHandler)
+    {
+        // The descriptor is passed via event queue because the handler lives in another thread
+        QMetaObject::invokeMethod(freeHandler, "handleConnection", Qt::QueuedConnection, Q_ARG(tSocketDescriptor, socketDescriptor));
+    }
+    else
+    {
+        // Reject the connection
+        qDebug("HttpListener: Too many incoming connections");
+        QTcpSocket* socket=new QTcpSocket(this);
+        socket->setSocketDescriptor(socketDescriptor);
+        connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
+        socket->write("HTTP/1.1 503 too many connections\r\nConnection: close\r\n\r\nToo many connections\r\n");
+        socket->disconnectFromHost();
+    }
+}
diff --git a/httpserver/httplistener.h b/httpserver/httplistener.h
new file mode 100644
index 000000000..b1dbfbc9f
--- /dev/null
+++ b/httpserver/httplistener.h
@@ -0,0 +1,102 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPLISTENER_H
+#define HTTPLISTENER_H
+
+#include <QTcpServer>
+#include <QSettings>
+#include <QBasicTimer>
+#include "httpglobal.h"
+#include "httpconnectionhandler.h"
+#include "httpconnectionhandlerpool.h"
+#include "httprequesthandler.h"
+
+namespace stefanfrings {
+
+/**
+  Listens for incoming TCP connections and and passes all incoming HTTP requests to your implementation of HttpRequestHandler,
+  which processes the request and generates the response (usually a HTML document).
+  <p>
+  Example for the required settings in the config file:
+  <code><pre>
+  ;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
+  </pre></code>
+  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 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 = 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 */
+    void incomingConnection(tSocketDescriptor socketDescriptor);
+
+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:
+
+    /**
+      Sent to the connection handler to process a new incoming connection.
+      @param socketDescriptor references the accepted connection.
+    */
+
+    void handleConnection(tSocketDescriptor socketDescriptor);
+
+};
+
+} // end of namespace
+
+#endif // HTTPLISTENER_H
diff --git a/httpserver/httprequest.cpp b/httpserver/httprequest.cpp
new file mode 100644
index 000000000..a6ac16ec7
--- /dev/null
+++ b/httpserver/httprequest.cpp
@@ -0,0 +1,569 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httprequest.h"
+#include <QList>
+#include <QDir>
+#include "httpcookie.h"
+
+using namespace stefanfrings;
+
+HttpRequest::HttpRequest(QSettings* settings)
+{
+    status=waitForRequest;
+    currentSize=0;
+    expectedBodySize=0;
+    maxSize=settings->value("maxRequestSize","16000").toInt();
+    maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
+    tempFile=NULL;
+}
+
+
+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
+    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<QByteArray> list=newData.split(' ');
+        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).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
+    int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
+    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)
+    {
+        // Received a line with a colon - a header
+        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
+    }
+    else if (!newData.isEmpty())
+    {
+        // received another line - belongs to the previous header
+        #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
+    {
+        // received an empty line - end of headers reached
+        #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"))
+        {
+            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=headers.value("content-length");
+        if (!contentLength.isEmpty())
+        {
+            expectedBodySize=contentLength.toInt();
+        }
+        if (expectedBodySize==0)
+        {
+            #ifdef SUPERVERBOSE
+                qDebug("HttpRequest: expect no body");
+            #endif
+            status=complete;
+        }
+        else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize)
+        {
+            qWarning("HttpRequest: expected body is too large");
+            status=abort;
+        }
+        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
+            status=waitForBody;
+        }
+    }
+}
+
+void HttpRequest::readBody(QTcpSocket* socket)
+{
+    Q_ASSERT(expectedBodySize!=0);
+    if (boundary.isEmpty())
+    {
+        // normal body, no multipart
+        #ifdef SUPERVERBOSE
+            qDebug("HttpRequest: receive body");
+        #endif
+        int toRead=expectedBodySize-bodyData.size();
+        QByteArray newData=socket->read(toRead);
+        currentSize+=newData.size();
+        bodyData.append(newData);
+        if (bodyData.size()>=expectedBodySize)
+        {
+            status=complete;
+        }
+    }
+    else
+    {
+        // multipart body, store into temp file
+        #ifdef SUPERVERBOSE
+            qDebug("HttpRequest: receiving multipart body");
+        #endif
+        // Create an object for the temporary file, if not already present
+        if (tempFile == NULL)
+        {
+            tempFile = new QTemporaryFile;
+        }
+        if (!tempFile->isOpen())
+        {
+            tempFile->open();
+        }
+        // Transfer data in 64kb blocks
+        int fileSize=tempFile->size();
+        int toRead=expectedBodySize-fileSize;
+        if (toRead>65536)
+        {
+            toRead=65536;
+        }
+        fileSize+=tempFile->write(socket->read(toRead));
+        if (fileSize>=maxMultiPartSize)
+        {
+            qWarning("HttpRequest: received too many multipart bytes");
+            status=abort;
+        }
+        else if (fileSize>=expectedBodySize)
+        {
+        #ifdef SUPERVERBOSE
+            qDebug("HttpRequest: received whole multipart body");
+        #endif
+            tempFile->flush();
+            if (tempFile->error())
+            {
+                qCritical("HttpRequest: Error writing temp file for multipart body");
+            }
+            parseMultiPartFile();
+            tempFile->close();
+            status=complete;
+        }
+    }
+}
+
+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)
+    {
+        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())
+        {
+            rawParameters.append('&');
+            rawParameters.append(bodyData);
+        }
+        else
+        {
+            rawParameters=bodyData;
+        }
+    }
+    // Split the parameters into pairs of value and name
+    QList<QByteArray> list=rawParameters.split('&');
+    foreach (QByteArray part, list)
+    {
+        int equalsChar=part.indexOf('=');
+        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())
+        {
+            // Name without value
+            parameters.insert(urlDecode(part),"");
+        }
+    }
+}
+
+void HttpRequest::extractCookies()
+{
+    #ifdef SUPERVERBOSE
+        qDebug("HttpRequest: extract cookies");
+    #endif
+    foreach(QByteArray cookieStr, headers.values("cookie"))
+    {
+        QList<QByteArray> 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
+            QByteArray name;
+            QByteArray value;
+            int posi=part.indexOf('=');
+            if (posi)
+            {
+                name=part.left(posi).trimmed();
+                value=part.mid(posi+1).trimmed();
+            }
+            else
+            {
+                name=part.trimmed();
+                value="";
+            }
+            cookies.insert(name,value);
+        }
+    }
+    headers.remove("cookie");
+}
+
+void HttpRequest::readFromSocket(QTcpSocket* socket)
+{
+    Q_ASSERT(status!=complete);
+    if (status==waitForRequest)
+    {
+        readRequest(socket);
+    }
+    else if (status==waitForHeader)
+    {
+        readHeader(socket);
+    }
+    else if (status==waitForBody)
+    {
+        readBody(socket);
+    }
+    if ((boundary.isEmpty() && currentSize>maxSize) || (!boundary.isEmpty() && currentSize>maxMultiPartSize))
+    {
+        qWarning("HttpRequest: received too many bytes");
+        status=abort;
+    }
+    if (status==complete)
+    {
+        // Extract and decode request parameters from url and body
+        decodeRequestParams();
+        // Extract cookies from headers
+        extractCookies();
+    }
+}
+
+
+HttpRequest::RequestStatus HttpRequest::getStatus() const
+{
+    return status;
+}
+
+
+QByteArray HttpRequest::getMethod() const
+{
+    return method;
+}
+
+
+QByteArray HttpRequest::getPath() const
+{
+    return urlDecode(path);
+}
+
+
+const QByteArray& HttpRequest::getRawPath() const
+{
+    return path;
+}
+
+
+QByteArray HttpRequest::getVersion() const
+{
+    return version;
+}
+
+
+QByteArray HttpRequest::getHeader(const QByteArray& name) const
+{
+    return headers.value(name.toLower());
+}
+
+QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const
+{
+    return headers.values(name.toLower());
+}
+
+QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const
+{
+    return headers;
+}
+
+QByteArray HttpRequest::getParameter(const QByteArray& name) const
+{
+    return parameters.value(name);
+}
+
+QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const
+{
+    return parameters.values(name);
+}
+
+QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const
+{
+    return parameters;
+}
+
+QByteArray HttpRequest::getBody() const
+{
+    return bodyData;
+}
+
+QByteArray HttpRequest::urlDecode(const QByteArray source)
+{
+    QByteArray buffer(source);
+    buffer.replace('+',' ');
+    int percentChar=buffer.indexOf('%');
+    while (percentChar>=0)
+    {
+        bool ok;
+        char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
+        if (ok)
+        {
+            buffer.replace(percentChar,3,(char*)&byte,1);
+        }
+        percentChar=buffer.indexOf('%',percentChar+1);
+    }
+    return buffer;
+}
+
+
+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
+        QByteArray fieldName;
+        QByteArray fileName;
+        while (!tempFile->atEnd() && !finished && !tempFile->error())
+        {
+            QByteArray line=tempFile->readLine(65536).trimmed();
+            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)
+                    {
+                        fieldName=line.mid(start+7,end-start-7);
+                    }
+                    start=line.indexOf(" filename=\"");
+                    end=line.indexOf("\"",start+11);
+                    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
+                }
+                else
+                {
+                    qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
+                }
+            }
+            else if (line.isEmpty())
+            {
+                break;
+            }
+        }
+
+        #ifdef SUPERVERBOSE
+            qDebug("HttpRequest: reading multpart data");
+        #endif
+        QTemporaryFile* uploadedFile=0;
+        QByteArray fieldValue;
+        while (!tempFile->atEnd() && !finished && !tempFile->error())
+        {
+            QByteArray line=tempFile->readLine(65536);
+            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())
+                {
+                    // 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())
+                {
+                    // last field was a file
+                    #ifdef SUPERVERBOSE
+                        qDebug("HttpRequest: finishing writing to uploaded file");
+                    #endif
+                    uploadedFile->resize(uploadedFile->size()-2);
+                    uploadedFile->flush();
+                    uploadedFile->seek(0);
+                    parameters.insert(fieldName,fileName);
+                    qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
+                    uploadedFiles.insert(fieldName,uploadedFile);
+                    qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size());
+                }
+                if (line.contains(boundary+"--"))
+                {
+                    finished=true;
+                }
+                break;
+            }
+            else
+            {
+                if (fileName.isEmpty() && !fieldName.isEmpty())
+                {
+                    // this is a form field.
+                    currentSize+=line.size();
+                    fieldValue.append(line);
+                }
+                else if (!fileName.isEmpty() && !fieldName.isEmpty())
+                {
+                    // this is a file
+                    if (!uploadedFile)
+                    {
+                        uploadedFile=new QTemporaryFile();
+                        uploadedFile->open();
+                    }
+                    uploadedFile->write(line);
+                    if (uploadedFile->error())
+                    {
+                        qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
+                    }
+                }
+            }
+        }
+    }
+    if (tempFile->error())
+    {
+        qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile->errorString()));
+    }
+    #ifdef SUPERVERBOSE
+        qDebug("HttpRequest: finished parsing multipart temp file");
+    #endif
+}
+
+HttpRequest::~HttpRequest()
+{
+    foreach(QByteArray key, uploadedFiles.keys())
+    {
+        QTemporaryFile* file=uploadedFiles.value(key);
+        if (file->isOpen())
+        {
+            file->close();
+        }
+        delete file;
+    }
+    if (tempFile != NULL)
+    {
+        if (tempFile->isOpen())
+        {
+            tempFile->close();
+        }
+        delete tempFile;
+    }
+}
+
+QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) const
+{
+    return uploadedFiles.value(fieldName);
+}
+
+QByteArray HttpRequest::getCookie(const QByteArray& name) const
+{
+    return cookies.value(name);
+}
+
+/** Get the map of cookies */
+QMap<QByteArray,QByteArray>& 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/httpserver/httprequest.h b/httpserver/httprequest.h
new file mode 100644
index 000000000..42ad41984
--- /dev/null
+++ b/httpserver/httprequest.h
@@ -0,0 +1,239 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPREQUEST_H
+#define HTTPREQUEST_H
+
+#include <QByteArray>
+#include <QHostAddress>
+#include <QTcpSocket>
+#include <QMap>
+#include <QMultiMap>
+#include <QSettings>
+#include <QTemporaryFile>
+#include <QUuid>
+#include "httpglobal.h"
+
+namespace stefanfrings {
+
+/**
+  This object represents a single HTTP request. It reads the request
+  from a TCP socket and provides getters for the individual parts
+  of the request.
+  <p>
+  The follwing config settings are required:
+  <code><pre>
+  maxRequestSize=16000
+  maxMultiPartSize=1000000
+  </pre></code>
+  <p>
+  MaxRequestSize is the maximum size of a HTTP request. In case of
+  multipart/form-data requests (also known as file-upload), the maximum
+  size of the body must not exceed maxMultiPartSize.
+  The body is always a little larger than the file itself.
+*/
+
+class DECLSPEC HttpRequest {
+    Q_DISABLE_COPY(HttpRequest)
+    friend class HttpSessionStore;
+
+public:
+
+    /** Values for getStatus() */
+    enum RequestStatus {waitForRequest, waitForHeader, waitForBody, complete, abort};
+
+    /**
+      Constructor.
+      @param settings Configuration settings
+    */
+    HttpRequest(QSettings* settings);
+
+    /**
+      Destructor.
+    */
+    virtual ~HttpRequest();
+
+    /**
+      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);
+
+    /**
+      Get the status of this reqeust.
+      @see RequestStatus
+    */
+    RequestStatus getStatus() const;
+
+    /** Get the method of the HTTP request  (e.g. "GET") */
+    QByteArray getMethod() const;
+
+    /** 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, not case-senitive.
+      @return If the header occurs multiple times, only the last
+      one is returned.
+    */
+    QByteArray getHeader(const QByteArray& name) const;
+
+    /**
+      Get the values of a HTTP request header.
+      @param name Name of the header, not case-senitive.
+    */
+    QList<QByteArray> getHeaders(const QByteArray& name) const;
+
+    /**
+     * Get all HTTP request headers. Note that the header names
+     * are returned in lower-case.
+     */
+    QMultiMap<QByteArray,QByteArray> getHeaderMap() const;
+
+    /**
+      Get the value of a HTTP request parameter.
+      @param name Name of the parameter, case-sensitive.
+      @return If the parameter occurs multiple times, only the last
+      one is returned.
+    */
+    QByteArray getParameter(const QByteArray& name) const;
+
+    /**
+      Get the values of a HTTP request parameter.
+      @param name Name of the parameter, case-sensitive.
+    */
+    QList<QByteArray> getParameters(const QByteArray& name) const;
+
+    /** Get all HTTP request parameters. */
+    QMultiMap<QByteArray,QByteArray> getParameterMap() const;
+
+    /** Get the HTTP request body.  */
+    QByteArray getBody() const;
+
+    /**
+      Decode an URL parameter.
+      E.g. replace "%23" by '#' and replace '+' by ' '.
+      @param source The url encoded strings
+      @see QUrl::toPercentEncoding for the reverse direction
+    */
+    static QByteArray urlDecode(const QByteArray source);
+
+    /**
+      Get an uploaded file. The file is already open. It will
+      be closed and deleted by the destructor of this HttpRequest
+      object (after processing the request).
+      <p>
+      For uploaded files, the method getParameters() returns
+      the original fileName as provided by the calling web browser.
+    */
+    QTemporaryFile* getUploadedFile(const QByteArray fieldName) const;
+
+    /**
+      Get the value of a cookie.
+      @param name Name of the cookie
+    */
+    QByteArray getCookie(const QByteArray& name) const;
+
+    /** Get all cookies. */
+    QMap<QByteArray,QByteArray>& 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 */
+    QMultiMap<QByteArray,QByteArray> headers;
+
+    /** Parameters of the request */
+    QMultiMap<QByteArray,QByteArray> parameters;
+
+    /** Uploaded files of the request, key is the field name. */
+    QMap<QByteArray,QTemporaryFile*> uploadedFiles;
+
+    /** Received cookies */
+    QMap<QByteArray,QByteArray> cookies;
+
+    /** Storage for raw body data */
+    QByteArray bodyData;
+
+    /** Request method */
+    QByteArray method;
+
+    /** Request path (in raw encoded format) */
+    QByteArray path;
+
+    /** Request protocol version */
+    QByteArray version;
+
+    /**
+      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;
+
+    /** Maximum allowed size of multipart forms in bytes. */
+    int maxMultiPartSize;
+
+    /** Current size */
+    int currentSize;
+
+    /** Expected size of body */
+    int expectedBodySize;
+
+    /** Name of the current header, or empty if no header is being processed */
+    QByteArray currentHeader;
+
+    /** Boundary of multipart/form-data body. Empty if there is no such header */
+    QByteArray boundary;
+
+    /** Temp file, that is used to store the multipart/form-data body */
+    QTemporaryFile* tempFile;
+
+    /** Parse the multipart body, that has been stored in the temp file. */
+    void parseMultiPartFile();
+
+    /** Sub-procedure of readFromSocket(), read the first line of a request. */
+    void readRequest(QTcpSocket* socket);
+
+    /** Sub-procedure of readFromSocket(), read header lines. */
+    void readHeader(QTcpSocket* socket);
+
+    /** Sub-procedure of readFromSocket(), read the request body. */
+    void readBody(QTcpSocket* socket);
+
+    /** Sub-procedure of readFromSocket(), extract and decode request parameters. */
+    void decodeRequestParams();
+
+    /** Sub-procedure of readFromSocket(), extract cookies from headers */
+    void extractCookies();
+
+    /** Buffer for collecting characters of request and header lines */
+    QByteArray lineBuffer;
+
+};
+
+} // end of namespace
+
+#endif // HTTPREQUEST_H
diff --git a/httpserver/httprequesthandler.cpp b/httpserver/httprequesthandler.cpp
new file mode 100644
index 000000000..f3a5fbe76
--- /dev/null
+++ b/httpserver/httprequesthandler.cpp
@@ -0,0 +1,23 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httprequesthandler.h"
+
+using namespace stefanfrings;
+
+HttpRequestHandler::HttpRequestHandler(QObject* parent)
+    : QObject(parent)
+{}
+
+HttpRequestHandler::~HttpRequestHandler()
+{}
+
+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/httpserver/httprequesthandler.h b/httpserver/httprequesthandler.h
new file mode 100644
index 000000000..b9c33550e
--- /dev/null
+++ b/httpserver/httprequesthandler.h
@@ -0,0 +1,53 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPREQUESTHANDLER_H
+#define HTTPREQUESTHANDLER_H
+
+#include "httpglobal.h"
+#include "httprequest.h"
+#include "httpresponse.h"
+
+namespace stefanfrings {
+
+/**
+   The request handler generates a response for each HTTP request. Web Applications
+   usually have one central request handler that maps incoming requests to several
+   controllers (servlets) based on the requested path.
+   <p>
+   You need to override the service() method or you will always get an HTTP error 501.
+   <p>
+   @warning Be aware that the main request handler instance must be created on the heap and
+   that it is used by multiple threads simultaneously.
+   @see StaticFileController which delivers static local files.
+*/
+
+class DECLSPEC HttpRequestHandler : public QObject {
+    Q_OBJECT
+    Q_DISABLE_COPY(HttpRequestHandler)
+public:
+
+    /**
+     * Constructor.
+     * @param parent Parent object.
+     */
+    HttpRequestHandler(QObject* parent=NULL);
+
+    /** Destructor */
+    virtual ~HttpRequestHandler();
+
+    /**
+      Generate a response for an incoming HTTP request.
+      @param request The received HTTP request
+      @param response Must be used to return the response
+      @warning This method must be thread safe
+    */
+    virtual void service(HttpRequest& request, HttpResponse& response);
+
+};
+
+} // end of namespace
+
+#endif // HTTPREQUESTHANDLER_H
diff --git a/httpserver/httpresponse.cpp b/httpserver/httpresponse.cpp
new file mode 100644
index 000000000..c7b3eec95
--- /dev/null
+++ b/httpserver/httpresponse.cpp
@@ -0,0 +1,200 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httpresponse.h"
+
+using namespace stefanfrings;
+
+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);
+    headers.insert(name,value);
+}
+
+void HttpResponse::setHeader(QByteArray name, int value)
+{
+    Q_ASSERT(sentHeaders==false);
+    headers.insert(name,QByteArray::number(value));
+}
+
+QMap<QByteArray,QByteArray>& HttpResponse::getHeaders()
+{
+    return headers;
+}
+
+void HttpResponse::setStatus(int statusCode, QByteArray description)
+{
+    this->statusCode=statusCode;
+    statusText=description;
+}
+
+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())
+    {
+        buffer.append(name);
+        buffer.append(": ");
+        buffer.append(headers.value(name));
+        buffer.append("\r\n");
+    }
+    foreach(HttpCookie cookie,cookies.values())
+    {
+        buffer.append("Set-Cookie: ");
+        buffer.append(cookie.toByteArray());
+        buffer.append("\r\n");
+    }
+    buffer.append("\r\n");
+    writeToSocket(buffer);
+    sentHeaders=true;
+}
+
+bool HttpResponse::writeToSocket(QByteArray data)
+{
+    int remaining=data.size();
+    char* ptr=data.data();
+    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)
+        {
+          return false;
+        }
+        ptr+=written;
+        remaining-=written;
+    }
+    return true;
+}
+
+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");
+                chunkedMode=true;
+            }
+        }
+
+        writeHeaders();
+    }
+
+    // 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);
+        }
+    }
+
+    // Only for the last chunk, send the terminating marker and flush the buffer.
+    if (lastPart)
+    {
+        if (chunkedMode)
+        {
+            writeToSocket("0\r\n\r\n");
+        }
+        socket->flush();
+        sentLastPart=true;
+    }
+}
+
+
+bool HttpResponse::hasSentLastPart() const
+{
+    return sentLastPart;
+}
+
+
+void HttpResponse::setCookie(const HttpCookie& cookie)
+{
+    Q_ASSERT(sentHeaders==false);
+    if (!cookie.getName().isEmpty())
+    {
+        cookies.insert(cookie.getName(),cookie);
+    }
+}
+
+
+QMap<QByteArray,HttpCookie>& 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/httpserver/httpresponse.h b/httpserver/httpresponse.h
new file mode 100644
index 000000000..8aa6524a2
--- /dev/null
+++ b/httpserver/httpresponse.h
@@ -0,0 +1,163 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPRESPONSE_H
+#define HTTPRESPONSE_H
+
+#include <QMap>
+#include <QString>
+#include <QTcpSocket>
+#include "httpglobal.h"
+#include "httpcookie.h"
+
+namespace stefanfrings {
+
+/**
+  This object represents a HTTP response, used to return something to the web client.
+  <p>
+  <code><pre>
+    response.setStatus(200,"OK"); // optional, because this is the default
+    response.writeBody("Hello");
+    response.writeBody("World!",true);
+  </pre></code>
+  <p>
+  Example how to return an error:
+  <code><pre>
+    response.setStatus(500,"server error");
+    response.write("The request cannot be processed because the servers is broken",true);
+  </pre></code>
+  <p>
+  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 DECLSPEC HttpResponse {
+    Q_DISABLE_COPY(HttpResponse)
+public:
+
+    /**
+      Constructor.
+      @param socket used to write the response
+    */
+    HttpResponse(QTcpSocket* socket);
+
+    /**
+      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.
+      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<QByteArray,QByteArray>& getHeaders();
+
+    /** Get the map of cookies */
+    QMap<QByteArray,HttpCookie>& getCookies();
+
+    /**
+      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());
+
+    /** Return the status code. */
+    int getStatusCode() const;
+
+    /**
+      Write body data to the socket.
+      <p>
+      The HTTP status line, headers and cookies are sent automatically before the body.
+      <p>
+      If the response contains only a single chunk (indicated by lastPart=true),
+      then a Content-Length header is automatically set.
+      <p>
+      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);
+
+    /**
+      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<QByteArray,QByteArray> headers;
+
+    /** Socket for writing output */
+    QTcpSocket* socket;
+
+    /** HTTP status code*/
+    int statusCode;
+
+    /** HTTP status code description */
+    QByteArray statusText;
+
+    /** Indicator whether headers have been sent */
+    bool sentHeaders;
+
+    /** Indicator whether the body has been sent completely */
+    bool sentLastPart;
+
+    /** Whether the response is sent in chunked mode */
+    bool chunkedMode;
+
+    /** Cookies */
+    QMap<QByteArray,HttpCookie> cookies;
+
+    /** 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();
+
+};
+
+} // end of namespace
+
+#endif // HTTPRESPONSE_H
diff --git a/httpserver/httpserver.pri b/httpserver/httpserver.pri
new file mode 100644
index 000000000..9bfabd24e
--- /dev/null
+++ b/httpserver/httpserver.pri
@@ -0,0 +1,33 @@
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+
+QT += network
+
+# Enable very detailed debug messages when compiling the debug version
+CONFIG(debug, debug|release) {
+    DEFINES += SUPERVERBOSE
+}
+
+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/httpserver/httpserver.pro b/httpserver/httpserver.pro
new file mode 100644
index 000000000..33d84ee65
--- /dev/null
+++ b/httpserver/httpserver.pro
@@ -0,0 +1,45 @@
+#--------------------------------------------------------
+#
+# Pro file for Android and Windows builds with Qt Creator
+#
+#--------------------------------------------------------
+
+QT += core network
+
+TEMPLATE = lib
+TARGET = httpserver
+
+INCLUDEPATH += $$PWD
+
+CONFIG(Release):build_subdir = release
+CONFIG(Debug):build_subdir = debug
+
+# Enable very detailed debug messages when compiling the debug version
+CONFIG(debug, debug|release) {
+    DEFINES += SUPERVERBOSE
+}
+
+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
+           
\ No newline at end of file
diff --git a/httpserver/httpsession.cpp b/httpserver/httpsession.cpp
new file mode 100644
index 000000000..7bcac7f01
--- /dev/null
+++ b/httpserver/httpsession.cpp
@@ -0,0 +1,187 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httpsession.h"
+#include <QDateTime>
+#include <QUuid>
+
+using namespace stefanfrings;
+
+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());
+#endif
+    }
+    else
+    {
+        dataPtr=0;
+    }
+}
+
+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);
+#endif
+        dataPtr->lock.unlock();
+    }
+}
+
+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);
+#endif
+        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);
+#endif
+        oldPtr->lock.unlock();
+        if (refCount==0)
+        {
+            delete oldPtr;
+        }
+    }
+    return *this;
+}
+
+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);
+#endif
+        dataPtr->lock.unlock();
+        if (refCount==0)
+        {
+            qDebug("HttpSession: deleting data");
+            delete dataPtr;
+        }
+    }
+}
+
+
+QByteArray HttpSession::getId() const
+{
+    if (dataPtr)
+    {
+        return dataPtr->id;
+    }
+    else
+    {
+        return QByteArray();
+    }
+}
+
+bool HttpSession::isNull() const {
+    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<QByteArray,QVariant> HttpSession::getAll() const
+{
+    QMap<QByteArray,QVariant> 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();
+    }
+}
diff --git a/httpserver/httpsession.h b/httpserver/httpsession.h
new file mode 100644
index 000000000..e303eb1ef
--- /dev/null
+++ b/httpserver/httpsession.h
@@ -0,0 +1,122 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPSESSION_H
+#define HTTPSESSION_H
+
+#include <QByteArray>
+#include <QVariant>
+#include <QReadWriteLock>
+#include "httpglobal.h"
+
+namespace stefanfrings {
+
+/**
+  This class stores data for a single HTTP session.
+  A session can store any number of key/value pairs. This class uses implicit
+  sharing for read and write access. This class is thread safe.
+  @see HttpSessionStore should be used to create and get instances of this class.
+*/
+
+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);
+
+    /**
+      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);
+
+
+    /**
+      Destructor. Detaches from the shared data.
+    */
+    virtual ~HttpSession();
+
+    /** 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;
+
+    /** 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);
+
+    /** 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<QByteArray,QVariant> 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 HttpSessionData {
+
+        /** Unique ID */
+        QByteArray id;
+
+        /** Timestamp of last access, set by the HttpSessionStore */
+        qint64 lastAccess;
+
+        /** Reference counter */
+        int refCount;
+
+        /** Used to synchronize threads */
+        QReadWriteLock lock;
+
+        /** Storage for the key/value pairs; */
+        QMap<QByteArray,QVariant> values;
+
+    };
+
+    /** Pointer to the shared data. */
+    HttpSessionData* dataPtr;
+
+};
+
+} // end of namespace
+
+#endif // HTTPSESSION_H
diff --git a/httpserver/httpsessionstore.cpp b/httpserver/httpsessionstore.cpp
new file mode 100644
index 000000000..79e99c851
--- /dev/null
+++ b/httpserver/httpsessionstore.cpp
@@ -0,0 +1,127 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "httpsessionstore.h"
+#include <QDateTime>
+#include <QUuid>
+
+using namespace stefanfrings;
+
+HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent)
+    :QObject(parent)
+{
+    this->settings=settings;
+    connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(sessionTimerEvent()));
+    cleanupTimer.start(60000);
+    cookieName=settings->value("cookieName","sessionid").toByteArray();
+    expirationTime=settings->value("expirationTime",3600000).toInt();
+    qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime);
+}
+
+HttpSessionStore::~HttpSessionStore()
+{
+    cleanupTimer.stop();
+}
+
+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())
+    {
+        // 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))
+        {
+            qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data());
+            sessionId.clear();
+        }
+    }
+    mutex.unlock();
+    return sessionId;
+}
+
+HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate)
+{
+    QByteArray sessionId=getSessionId(request,response);
+    mutex.lock();
+    if (!sessionId.isEmpty())
+    {
+        HttpSession session=sessions.value(sessionId);
+        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)
+    {
+        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();
+        HttpSession session(true);
+        qDebug("HttpSessionStore: create new session with ID %s",session.getId().data());
+        sessions.insert(session.getId(),session);
+        response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain));
+        mutex.unlock();
+        return session;
+    }
+    // Return a null session
+    mutex.unlock();
+    return HttpSession();
+}
+
+HttpSession HttpSessionStore::getSession(const QByteArray id)
+{
+    mutex.lock();
+    HttpSession session=sessions.value(id);
+    mutex.unlock();
+    session.setLastAccess();
+    return session;
+}
+
+void HttpSessionStore::sessionTimerEvent()
+{
+    mutex.lock();
+    qint64 now=QDateTime::currentMSecsSinceEpoch();
+    QMap<QByteArray,HttpSession>::iterator i = sessions.begin();
+    while (i != sessions.end())
+    {
+        QMap<QByteArray,HttpSession>::iterator prev = i;
+        ++i;
+        HttpSession session=prev.value();
+        qint64 lastAccess=session.getLastAccess();
+        if (now-lastAccess>expirationTime)
+        {
+            qDebug("HttpSessionStore: session %s expired",session.getId().data());
+            sessions.erase(prev);
+        }
+    }
+    mutex.unlock();
+}
+
+
+/** Delete a session */
+void HttpSessionStore::removeSession(HttpSession session)
+{
+    mutex.lock();
+    sessions.remove(session.getId());
+    mutex.unlock();
+}
diff --git a/httpserver/httpsessionstore.h b/httpserver/httpsessionstore.h
new file mode 100644
index 000000000..1d0d5ca86
--- /dev/null
+++ b/httpserver/httpsessionstore.h
@@ -0,0 +1,110 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef HTTPSESSIONSTORE_H
+#define HTTPSESSIONSTORE_H
+
+#include <QObject>
+#include <QMap>
+#include <QTimer>
+#include <QMutex>
+#include "httpglobal.h"
+#include "httpsession.h"
+#include "httpresponse.h"
+#include "httprequest.h"
+
+namespace stefanfrings {
+
+/**
+  Stores HTTP sessions and deletes them when they have expired.
+  The following configuration settings are required in the config file:
+  <code><pre>
+  expirationTime=3600000
+  cookieName=sessionid
+  </pre></code>
+  The following additional configurations settings are optionally:
+  <code><pre>
+  cookiePath=/
+  cookieComment=Session ID
+  ;cookieDomain=stefanfrings.de
+  </pre></code>
+*/
+
+class DECLSPEC HttpSessionStore : public QObject {
+    Q_OBJECT
+    Q_DISABLE_COPY(HttpSessionStore)
+public:
+
+    /** Constructor. */
+    HttpSessionStore(QSettings* settings, QObject* parent=NULL);
+
+    /** Destructor */
+    virtual ~HttpSessionStore();
+
+    /**
+       Get the ID of the current HTTP session, if it is valid.
+       This method is thread safe.
+       @warning Sessions may expire at any time, so subsequent calls of
+       getSession() might return a new session with a different ID.
+       @param request Used to get the session cookie
+       @param response Used to get and set the new session cookie
+       @return Empty string, if there is no valid session.
+    */
+    QByteArray getSessionId(HttpRequest& request, HttpResponse& response);
+
+    /**
+       Get the session of a HTTP request, eventually create a new one.
+       This method is thread safe. New sessions can only be created before
+       the first byte has been written to the HTTP response.
+       @param request Used to get the session cookie
+       @param response Used to get and set the new session cookie
+       @param allowCreate can be set to false, to disable the automatic creation of a new session.
+       @return If autoCreate is disabled, the function returns a null session if there is no session.
+       @see HttpSession::isNull()
+    */
+    HttpSession getSession(HttpRequest& request, HttpResponse& response, bool allowCreate=true);
+
+    /**
+       Get a HTTP session by it's ID number.
+       This method is thread safe.
+       @return If there is no such session, the function returns a null session.
+       @param id ID number of the session
+       @see HttpSession::isNull()
+    */
+    HttpSession getSession(const QByteArray id);
+
+    /** Delete a session */
+    void removeSession(HttpSession session);
+
+protected:
+    /** Storage for the sessions */
+    QMap<QByteArray,HttpSession> sessions;
+
+private:
+
+    /** Configuration settings */
+    QSettings* settings;
+
+    /** Timer to remove expired sessions */
+    QTimer cleanupTimer;
+
+    /** Name of the session cookie */
+    QByteArray cookieName;
+
+    /** Time when sessions expire (in ms)*/
+    int expirationTime;
+
+    /** Used to synchronize threads */
+    QMutex mutex;
+
+private slots:
+
+    /** Called every minute to cleanup expired sessions. */
+    void sessionTimerEvent();
+};
+
+} // end of namespace
+
+#endif // HTTPSESSIONSTORE_H
diff --git a/httpserver/lgpl-3.0.txt b/httpserver/lgpl-3.0.txt
new file mode 100644
index 000000000..65c5ca88a
--- /dev/null
+++ b/httpserver/lgpl-3.0.txt
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/httpserver/readme.md b/httpserver/readme.md
new file mode 100644
index 000000000..a1522ecde
--- /dev/null
+++ b/httpserver/readme.md
@@ -0,0 +1,12 @@
+## QtWebApp httpserver ##
+
+This is the httpserver part of QtWebApp from Stefan Frings
+
+  - [Link to the main page](http://stefanfrings.de/qtwebapp/index-en.html)
+  - [Link to API documentation](http://stefanfrings.de/qtwebapp/api/index.html)
+
+Files copied over from the original 'doc' folder:
+
+  - copyright.txt
+  - lgpl-3.0.txt
+  - releasenotes.txts
diff --git a/httpserver/releasenotes.txt b/httpserver/releasenotes.txt
new file mode 100644
index 000000000..43e1da05d
--- /dev/null
+++ b/httpserver/releasenotes.txt
@@ -0,0 +1,250 @@
+Dont forget to update the release number also in
+QtWebApp.pro and httpserver/httpglobal.cpp.
+
+1.7.3
+25.04.2017
+Wait until all data are sent before closing connections.
+
+1.7.2
+17.01.2017
+Fixed compile error with MSVC.
+
+1.7.1
+10.11.2016
+Fixed a possible memory leak in case of broken Multipart HTTP Requests.
+
+1.7.0
+08.11.2016
+Introduced namespace "stefanfrings".
+Improved performance a little.
+
+1.6.7
+10.10.2016
+Fix type of socketDescriptor in qtservice library.
+Add support for INFO log messages (new since QT 5.5).
+Improve indentation of log messages.
+
+1.6.6
+25.07.2016
+Removed useless mutex from TemplateLoader.
+Add mutex to TemplateCache (which is now needed).
+
+1.6.5
+10.06.2016
+Incoming HTTP request headers are now processed case-insensitive.
+Add support for the HttpOnly flag of cookies.
+
+1.6.4
+27.03.2016
+Fixed constructor of Template class did not load the source file properly.
+Template loader and cache were not affected.
+
+1.6.3
+11.03.2016
+Fixed compilation error.
+Added missing implementation of HttpRequest::getPeerAddress().
+
+1.6.2
+06.03.2016
+Added mime types for some file extensions.
+
+1.6.1
+25.01.2016
+Fixed parser of boundary value in multi-part request, which caused that
+QHttpMultipart did not work on client side.
+
+1.6.0
+29.12.2015
+Much better output buffering, reduces the number of small IP packages.
+
+1.5.13
+29.12.2015
+Improved performance a little.
+Add support for old HTTP 1.0 clients.
+Add HttpResposne::flush() and HttpResponse::isConnected() which are helpful to support
+SSE from HTML 5 specification.
+
+1.5.12
+11.12.2015
+Fix program crash when using SSL with a variable sized thread pool on Windows.
+Changed name of HttpSessionStore::timerEvent() to fix compiler warnings since Qt 5.0.
+Add HttpRequest::getRawPath().
+HttpSessionStore::sessions is now protected.
+
+1.5.11
+21.11.2015
+Fix project file for Mac OS.
+Add HttpRequest::getPeerAddress() and HttpResponse::getStatusCode().
+
+1.5.10
+01.09.2015
+Modified StaticFileController to support ressource files (path starting with ":/" or "qrc://").
+
+1.5.9
+06.08.2015
+New HttpListener::listen() method, to restart listening after close.
+Add missing include for QObject in logger.h.
+Add a call to flush() before closing connections, which solves an issue with nginx.
+
+1.5.8
+26.07.2015
+Fixed segmentation fault error when closing the application while a HTTP request is in progress.
+New HttpListener::close() method to simplifly proper shutdown.
+
+1.5.7
+20.07.2015
+Fix Qt 5.5 compatibility issue.
+
+1.5.6
+22.06.2015
+Fixed compilation failes if QT does not support SSL.
+
+1.5.5
+16.06.2015
+Improved performance of SSL connections.
+
+1.5.4
+15.06.2015
+Support for Qt versions without OpenSsl.
+
+1.5.3
+22.05.2015
+Fixed Windows issue: QsslSocket cannot be closed from other threads than it was created in.
+
+1.5.2
+12.05.2015
+Fixed Windows issue: QSslSocket cannot send signals to another thread than it was created in.
+
+1.5.1
+14.04.2015
+Add support for pipelining.
+
+1.5.0
+03.04.2015
+Add support for HTTPS.
+
+1.4.2
+03.04.2015
+Fixed HTTP request did not work if it was split into multipe IP packages.
+
+1.4.1
+20.03.2015
+Fixed session cookie expires while the user is active, expiration time was not prolonged on each request.
+
+1.4.0
+14.03.2015
+This release has a new directory structure and new project files to support the creation of a shared library (*.dll or *.so).
+
+1.3.8
+12.03.2015
+Improved shutdown procedure.
+New config setting "host" which binds the listener to a specific network interface.
+
+1.3.7
+14.01.2015
+Fixed setting maxMultiPartSize worked only with file-uploads but not with form-data.
+
+1.3.6
+16.09.2014
+Fixed DualFileLogger produces no output.
+
+1.3.5
+11.06.2014
+Fixed a multi-threading issue with race condition in StaticFileController.
+
+1.3.4
+04.06.2014
+Fixed wrong content type when the StaticFileController returns a cached index.html.
+
+1.3.3
+17.03.2014
+Improved security of StaticFileController by denying "/.." in any position of the request path.
+Improved performance of StaticFileController a little.
+New convenience method HttpResponse::redirect(url).
+Fixed a missing return statement in StaticFileController.
+
+1.3.2
+08.01.2014
+Fixed HTTP Server ignoring URL parameters when the request contains POST parameters.
+
+1.3.1
+15.08.2013
+Fixed HTTP server not accepting connections on 64bit OS with QT 5.
+
+1.3.0
+20.04.2013
+Updated for compatibility QT 5. You may still use QT 4.7 or 4.8, if you like.
+Also added support for logging source file name, line number and function name.
+
+1.2.13
+03.03.2013
+Fixed Logger writing wrong timestamp for buffered messages.
+Improved shutdown procedure. The webserver now processes all final signals before the destructor finishes.
+
+1.2.12
+01.03.2013
+Fixed HttpResponse sending first part of data repeatedly when the amount of data is larger than the available memory for I/O buffer.
+
+1.2.11
+06.01.2013
+Added clearing the write buffer when accepting a new connection, so that it does not send remaining data from an aborted previous connection (which is possibly a bug in QT).
+
+1.2.10
+18.12.2012
+Reduced memory usage of HttpResponse in case of large response.
+
+1.2.9
+29.07.2012
+Added a mutex to HttpConnectionHandlerPool to fix a concurrency issue when a pooled object gets taken from the cache while it times out.
+Modified HttpConnectionHandler so that it does not throw an exception anymore when a connection gets closed by the peer in the middle of a read.
+
+1.2.8
+22.07.2012
+Fixed a possible concurrency issue when the file cache is so small that it stores less files than the number of threads.
+
+1.2.7
+18.07.2012
+Fixed HttpRequest ignores additional URL parameters of POST requests.
+Fixed HttpRequest ignores POST parameters of body if there is no Content-Type header.
+Removed unused tempdir variable from HttpRequest.
+Added mutex to cache of StaticFileController to prevent concurrency problems.
+Removed HTTP response with status 408 after read timeout. Connection gets simply closed now.
+
+1.2.6
+29.06.2012
+Fixed a compilation error on 64 bit if super verbose debugging is enabled.
+Fixed a typo in static file controller related to the document type header.
+
+1.2.5
+27.06.2012
+Fixed error message "QThread: Destroyed while thread is still running" during program termination.
+
+1.2.4
+02.06.2012
+Fixed template engine skipping variable tokens when a value is shorter than the token.
+
+1.2.3
+26.12.2011
+Fixed null pointer error when the HTTP server aborts a request that is too large.
+
+1.2.2
+06.11.2011
+Fixed compilation error on 64 bit platforms.
+
+1.2.1
+22.10.2011
+Fixed a multi-threading bug in HttpConnectionHandler.
+
+1.2.0
+05.12.2010
+Added a controller that serves static files, with cacheing.
+
+
+1.1.0
+19.10.2010
+Added support for sessions.
+Separated the base classes into individual libraries.
+
+1.0.0
+17.10.2010
+First release
diff --git a/httpserver/staticfilecontroller.cpp b/httpserver/staticfilecontroller.cpp
new file mode 100644
index 000000000..e6195766d
--- /dev/null
+++ b/httpserver/staticfilecontroller.cpp
@@ -0,0 +1,187 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#include "staticfilecontroller.h"
+#include <QFileInfo>
+#include <QDir>
+#include <QDateTime>
+
+using namespace stefanfrings;
+
+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",".").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
+        {
+            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();
+    cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
+    cacheTimeout=settings->value("cacheTime","60000").toInt();
+    qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,cache.maxCost());
+}
+
+
+void StaticFileController::service(HttpRequest& request, HttpResponse& response)
+{
+    QByteArray path=request.getPath();
+    // 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.
+        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())
+        {
+            path+="/index.html";
+        }
+        // Try to open the file
+        QFile file(docroot+path);
+        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;
+                entry->filename=path;
+                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(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);
+            }
+        }
+    }
+}
+
+void StaticFileController::setContentType(QString fileName, HttpResponse& response) const
+{
+    if (fileName.endsWith(".png"))
+    {
+        response.setHeader("Content-Type", "image/png");
+    }
+    else if (fileName.endsWith(".jpg"))
+    {
+        response.setHeader("Content-Type", "image/jpeg");
+    }
+    else if (fileName.endsWith(".gif"))
+    {
+        response.setHeader("Content-Type", "image/gif");
+    }
+    else if (fileName.endsWith(".pdf"))
+    {
+        response.setHeader("Content-Type", "application/pdf");
+    }
+    else if (fileName.endsWith(".txt"))
+    {
+        response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding));
+    }
+    else if (fileName.endsWith(".html") || fileName.endsWith(".htm"))
+    {
+        response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding));
+    }
+    else if (fileName.endsWith(".css"))
+    {
+        response.setHeader("Content-Type", "text/css");
+    }
+    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
+    else
+    {
+        qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName));
+    }
+}
diff --git a/httpserver/staticfilecontroller.h b/httpserver/staticfilecontroller.h
new file mode 100644
index 000000000..6b2bd1c7a
--- /dev/null
+++ b/httpserver/staticfilecontroller.h
@@ -0,0 +1,91 @@
+/**
+  @file
+  @author Stefan Frings
+*/
+
+#ifndef STATICFILECONTROLLER_H
+#define STATICFILECONTROLLER_H
+
+#include <QCache>
+#include <QMutex>
+#include "httpglobal.h"
+#include "httprequest.h"
+#include "httpresponse.h"
+#include "httprequesthandler.h"
+
+namespace stefanfrings {
+
+/**
+  Delivers static files. It is usually called by the applications main request handler when
+  the caller requests a path that is mapped to static files.
+  <p>
+  The following settings are required in the config file:
+  <code><pre>
+  path=../docroot
+  encoding=UTF-8
+  maxAge=60000
+  cacheTime=60000
+  cacheSize=1000000
+  maxCachedFileSize=65536
+  </pre></code>
+  The path is relative to the directory of the config file. In case of windows, if the
+  settings are in the registry, the path is relative to the current working directory.
+  <p>
+  The encoding is sent to the web browser in case of text and html files.
+  <p>
+  The cache improves performance of small files when loaded from a network
+  drive. Large files are not cached. Files are cached as long as possible,
+  when cacheTime=0. The maxAge value (in msec!) controls the remote browsers cache.
+  <p>
+  Do not instantiate this class in each request, because this would make the file cache
+  useless. Better create one instance during start-up and call it when the application
+  received a related HTTP request.
+*/
+
+class DECLSPEC StaticFileController : public HttpRequestHandler  {
+    Q_OBJECT
+    Q_DISABLE_COPY(StaticFileController)
+public:
+
+    /** Constructor */
+    StaticFileController(QSettings* settings, QObject* parent = NULL);
+
+    /** Generates the response */
+    void service(HttpRequest& request, HttpResponse& response);
+
+private:
+
+    /** Encoding of text files */
+    QString encoding;
+
+    /** Root directory of documents */
+    QString docroot;
+
+    /** Maximum age of files in the browser cache */
+    int maxAge;
+
+    struct CacheEntry {
+        QByteArray document;
+        qint64 created;
+        QByteArray filename;
+    };
+
+    /** Timeout for each cached file */
+    int cacheTimeout;
+
+    /** Maximum size of files in cache, larger files are not cached */
+    int maxCachedFileSize;
+
+    /** Cache storage */
+    QCache<QString,CacheEntry> cache;
+
+    /** 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;
+};
+
+} // end of namespace
+
+#endif // STATICFILECONTROLLER_H
diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro
index 0e41e3d60..499e74d95 100644
--- a/sdrangel.windows.pro
+++ b/sdrangel.windows.pro
@@ -7,6 +7,7 @@
 TEMPLATE = subdirs
 SUBDIRS = sdrbase
 CONFIG(MINGW64)SUBDIRS += nanomsg
+SUBDIRS += httpserver
 SUBDIRS += fcdhid
 SUBDIRS += fcdlib
 SUBDIRS += librtlsdr