Replaced qhttpserver with libmicrohttp.

The qhttpserver seems to be riddled with memory leaks and was
continuously crashing. I don't know Qt well enough to fix it so
I have replaced it with libmicrohttp. This is not nearly as
elegant but it is much more stable.
This commit is contained in:
Keith Bennett 2014-03-22 16:45:36 +00:00
parent 2cd6787141
commit b27ba03d42
6 changed files with 234 additions and 114 deletions

View File

@ -172,6 +172,8 @@ if(NOT (${GCRYPT_VERSION_STRING} VERSION_LESS "1.6.0"))
set(GCRYPT_HAS_SALSA20 1) set(GCRYPT_HAS_SALSA20 1)
endif() endif()
find_package(LibMicroHTTPD REQUIRED)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
check_cxx_source_compiles(" check_cxx_source_compiles("

View File

@ -0,0 +1,9 @@
find_path(MHD_INCLUDE_DIR microhttpd.h)
find_library(MHD_LIBRARIES microhttpd)
mark_as_advanced(MHD_LIBRARIES MHD_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibMicroHTTPD DEFAULT_MSG MHD_LIBRARIES MHD_INCLUDE_DIR)

View File

@ -237,7 +237,6 @@ add_library(keepassx_core STATIC ${keepassx_SOURCES})
set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE) set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
add_subdirectory(gui/qocoa) add_subdirectory(gui/qocoa)
add_subdirectory(http/qhttpserver)
add_subdirectory(http/qjson) add_subdirectory(http/qjson)
add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE}) add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE})
@ -245,7 +244,7 @@ target_link_libraries(${PROGNAME}
keepassx_core keepassx_core
Qocoa Qocoa
qjson qjson
qhttpserver ${MHD_LIBRARIES}
${QT_QTCORE_LIBRARY} ${QT_QTCORE_LIBRARY}
${QT_QTGUI_LIBRARY} ${QT_QTGUI_LIBRARY}
${QT_QTNETWORK_LIBRARY} ${QT_QTNETWORK_LIBRARY}

View File

@ -42,17 +42,19 @@
class HttpPlugin: public ISettingsPage { class HttpPlugin: public ISettingsPage {
public: public:
HttpPlugin(DatabaseTabWidget * tabWidget): m_service(new Service(tabWidget)) { HttpPlugin(DatabaseTabWidget * tabWidget) {
m_service = new Service(tabWidget);
} }
virtual ~HttpPlugin() { virtual ~HttpPlugin() {
//delete m_service;
} }
virtual QString name() { virtual QString name() {
return QObject::tr("Http"); return QObject::tr("Http");
} }
virtual QWidget * createWidget() { virtual QWidget * createWidget() {
OptionDialog * dlg = new OptionDialog(); OptionDialog * dlg = new OptionDialog();
QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service.data(), SLOT(removeSharedEncryptionKeys())); QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys()));
QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service.data(), SLOT(removeStoredPermissions())); QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions()));
return dlg; return dlg;
} }
virtual void loadSettings(QWidget * widget) { virtual void loadSettings(QWidget * widget) {
@ -66,7 +68,7 @@ public:
m_service->stop(); m_service->stop();
} }
private: private:
QScopedPointer<Service> m_service; Service *m_service;
}; };
const QString MainWindow::BaseWindowTitle = "KeePassX"; const QString MainWindow::BaseWindowTitle = "KeePassX";

View File

@ -12,37 +12,17 @@
*/ */
#include "Server.h" #include "Server.h"
#include "qhttpserver/qhttpserver.h" #include <microhttpd.h>
#include "qhttpserver/qhttprequest.h"
#include "qhttpserver/qhttpresponse.h"
#include "Protocol.h" #include "Protocol.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include <QtCore/QHash> #include <QtCore/QHash>
#include <QtCore/QCryptographicHash> #include <QtCore/QCryptographicHash>
#include <QtGui/QMessageBox> #include <QtGui/QMessageBox>
#include <QEventLoop>
using namespace KeepassHttpProtocol; using namespace KeepassHttpProtocol;
//////////////////////////////////////////////////////////////////////////////////////////////////// using namespace KeepassHttpProtocol;
/// Request
////////////////////////////////////////////////////////////////////////////////////////////////////
RequestHandler::RequestHandler(QHttpRequest *request, QHttpResponse *response): m_request(request), m_response(response)
{
m_request->storeBody();
connect(m_request, SIGNAL(end()), this, SLOT(onEnd()));
connect(m_response, SIGNAL(done()), this, SLOT(deleteLater()));
}
RequestHandler::~RequestHandler()
{
delete m_request;
}
void RequestHandler::onEnd()
{
Q_EMIT requestComplete(m_request, m_response);
}
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
@ -51,71 +31,14 @@ void RequestHandler::onEnd()
Server::Server(QObject *parent) : Server::Server(QObject *parent) :
QObject(parent), QObject(parent),
m_httpServer(new QHttpServer(this)),
m_started(false) m_started(false)
{ {
connect(m_httpServer, SIGNAL(newRequest(QHttpRequest*,QHttpResponse*)), this, SLOT(handleRequest(QHttpRequest*,QHttpResponse*))); connect(this, SIGNAL(emitRequest(const QByteArray, QByteArray*)),
this, SLOT(handleRequest(const QByteArray, QByteArray*)));
connect(this, SIGNAL(emitOpenDatabase(bool*)),
this, SLOT(handleOpenDatabase(bool*)));
} }
void Server::start()
{
if (m_started)
return;
static const int PORT = 19455;
m_started = m_httpServer->listen(QHostAddress::LocalHost, PORT)
|| m_httpServer->listen(QHostAddress::LocalHostIPv6, PORT);
}
void Server::stop()
{
if (!m_started)
return;
m_httpServer->close();
m_started = false;
}
void Server::handleRequest(QHttpRequest *request, QHttpResponse *response)
{
RequestHandler * h = new RequestHandler(request, response);
connect(h, SIGNAL(requestComplete(QHttpRequest*,QHttpResponse*)), this, SLOT(handleRequestComplete(QHttpRequest*,QHttpResponse*)));
}
void Server::handleRequestComplete(QHttpRequest *request, QHttpResponse *response)
{
Request r;
if (!isDatabaseOpened() && !openDatabase()) {
response->writeHead(QHttpResponse::STATUS_SERVICE_UNAVAILABLE);
response->end();
} else if (request->header("content-type").compare("application/json", Qt::CaseInsensitive) == 0 &&
r.fromJson(request->body())) {
QByteArray hash = QCryptographicHash::hash((getDatabaseRootUuid() + getDatabaseRecycleBinUuid()).toUtf8(),
QCryptographicHash::Sha1).toHex();
Response protocolResp(r, QString::fromAscii(hash));
switch(r.requestType()) {
case INVALID: break;
case TEST_ASSOCIATE: testAssociate(r, &protocolResp); break;
case ASSOCIATE: associate(r, &protocolResp); break;
case GET_LOGINS: getLogins(r, &protocolResp); break;
case GET_LOGINS_COUNT: getLoginsCount(r, &protocolResp); break;
case GET_ALL_LOGINS: getAllLogins(r, &protocolResp); break;
case SET_LOGIN: setLogin(r, &protocolResp); break;
case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break;
}
QByteArray s = protocolResp.toJson().toUtf8();
response->setHeader("Content-Type", "application/json");
response->setHeader("Content-Length", QString::number(s.size()));
response->writeHead(QHttpResponse::STATUS_OK);
response->write(s);
response->end();
} else {
response->writeHead(QHttpResponse::STATUS_BAD_REQUEST);
response->end();
}
}
void Server::testAssociate(const Request& r, Response * protocolResp) void Server::testAssociate(const Request& r, Response * protocolResp)
{ {
@ -202,6 +125,39 @@ void Server::setLogin(const Request &r, Response *protocolResp)
protocolResp->setVerifier(key); protocolResp->setVerifier(key);
} }
int Server::send_response(struct MHD_Connection *connection, const char *page)
{
int ret;
struct MHD_Response *response;
response = MHD_create_response_from_buffer(
strlen(page), static_cast<void*>(const_cast<char*>(page)),
MHD_RESPMEM_PERSISTENT);
if (!response) return MHD_NO;
MHD_add_response_header (response, "Content-Type", "application/json");
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
MHD_destroy_response (response);
return ret;
}
int Server::send_unavailable(struct MHD_Connection *connection)
{
int ret;
struct MHD_Response *response;
response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
if (!response) return MHD_NO;
ret = MHD_queue_response (connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
MHD_destroy_response (response);
return ret;
}
void Server::generatePassword(const Request &r, Response *protocolResp) void Server::generatePassword(const Request &r, Response *protocolResp)
{ {
QString key = getKey(r.id()); QString key = getKey(r.id());
@ -217,3 +173,153 @@ void Server::generatePassword(const Request &r, Response *protocolResp)
memset(password.data(), 0, password.length()); memset(password.data(), 0, password.length());
} }
int Server::request_handler_wrapper(void *me, struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **con_cls)
{
Server *myself = static_cast<Server*>(me);
if (myself)
return myself->request_handler(connection, url, method, version,
upload_data, upload_data_size, con_cls);
else
return MHD_NO;
}
void Server::handleRequest(const QByteArray in, QByteArray *out)
{
*out = QByteArray();
Request r;
if (!r.fromJson(in))
return;
QByteArray hash = QCryptographicHash::hash(
(getDatabaseRootUuid() + getDatabaseRecycleBinUuid()).toUtf8(),
QCryptographicHash::Sha1).toHex();
Response protocolResp(r, QString::fromAscii(hash));
switch(r.requestType()) {
case INVALID: break;
case TEST_ASSOCIATE: testAssociate(r, &protocolResp); break;
case ASSOCIATE: associate(r, &protocolResp); break;
case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break;
case GET_LOGINS: getLogins(r, &protocolResp); break;
case GET_LOGINS_COUNT: getLoginsCount(r, &protocolResp); break;
case GET_ALL_LOGINS: getAllLogins(r, &protocolResp); break;
case SET_LOGIN: setLogin(r, &protocolResp); break;
}
*out = protocolResp.toJson().toUtf8();
Q_EMIT donewrk();
}
void Server::handleOpenDatabase(bool *success)
{
*success = openDatabase();
Q_EMIT donewrk();
}
int Server::request_handler(struct MHD_Connection *connection,
const char *, const char *method, const char *,
const char *upload_data, size_t *upload_data_size, void **con_cls)
{
struct Server::connection_info_struct *con_info =
static_cast<struct Server::connection_info_struct*>(*con_cls);
if (!isDatabaseOpened()) {
bool success;
QEventLoop loop1;
loop1.connect(this, SIGNAL(donewrk()), SLOT(quit()));
Q_EMIT emitOpenDatabase(&success);
loop1.exec();
if (!success)
return send_unavailable(connection);
}
if (con_info == NULL) {
*con_cls = calloc(1, sizeof(*con_info));
return MHD_YES;
}
if (strcmp (method, MHD_HTTP_METHOD_POST) != 0)
return MHD_NO;
if (*upload_data_size == 0) {
if (con_info && con_info->response)
return send_response(connection, con_info->response);
else
return MHD_NO;
}
QString type = MHD_lookup_connection_value(connection,
MHD_HEADER_KIND, "Content-Type");
if (!type.contains("application/json", Qt::CaseInsensitive))
return MHD_NO;
// Now process the POST request
QByteArray post = QByteArray(upload_data, *upload_data_size);
QByteArray s;
QEventLoop loop;
loop.connect(this, SIGNAL(donewrk()), SLOT(quit()));
Q_EMIT emitRequest(post, &s);
loop.exec();
if (s.size() == 0)
return MHD_NO;
con_info->response = static_cast<char*>(calloc(1, s.size()+1));
memcpy(con_info->response, s.data(), s.size());
*upload_data_size = 0;
return MHD_YES;
}
void Server::request_completed(void *, struct MHD_Connection *,
void **con_cls, enum MHD_RequestTerminationCode)
{
struct Server::connection_info_struct *con_info =
static_cast<struct Server::connection_info_struct*>(*con_cls);
if (con_info == NULL)
return;
if (con_info->response) free(con_info->response);
free(con_info);
*con_cls = NULL;
}
void Server::start(void)
{
if (m_started) return;
static const int PORT = 19455;
daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL,
&this->request_handler_wrapper, this,
MHD_OPTION_NOTIFY_COMPLETED,
this->request_completed, NULL,
MHD_OPTION_END);
m_started = true;
}
void Server::stop(void)
{
if (!m_started)
return;
MHD_stop_daemon(daemon);
m_started = false;
}

View File

@ -16,10 +16,8 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QList> #include <QtCore/QList>
#include <microhttpd.h>
class QHttpServer;
class QHttpRequest;
class QHttpResponse;
namespace KeepassHttpProtocol { namespace KeepassHttpProtocol {
@ -27,24 +25,6 @@ class Request;
class Response; class Response;
class Entry; class Entry;
class RequestHandler: public QObject {
Q_OBJECT
public:
RequestHandler(QHttpRequest *request, QHttpResponse *response);
~RequestHandler();
private Q_SLOTS:
void onEnd();
Q_SIGNALS:
void requestComplete(QHttpRequest *request, QHttpResponse *response);
private:
QHttpRequest * m_request;
QHttpResponse *m_response;
};
class Server : public QObject class Server : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -70,8 +50,13 @@ public Q_SLOTS:
void stop(); void stop();
private Q_SLOTS: private Q_SLOTS:
void handleRequest(QHttpRequest * request, QHttpResponse* response); void handleRequest(const QByteArray in, QByteArray *out);
void handleRequestComplete(QHttpRequest * request, QHttpResponse* response); void handleOpenDatabase(bool *success);
Q_SIGNALS:
void emitRequest(const QByteArray in, QByteArray *out);
void emitOpenDatabase(bool *success);
void donewrk();
private: private:
void testAssociate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); void testAssociate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
@ -82,8 +67,25 @@ private:
void setLogin(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); void setLogin(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
void generatePassword(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); void generatePassword(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
QHttpServer * const m_httpServer; static int request_handler_wrapper(void *me,
struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **con_cls);
static void request_completed(void *, struct MHD_Connection *,
void **con_cls, enum MHD_RequestTerminationCode);
int request_handler(struct MHD_Connection *connection,
const char *, const char *method, const char *,
const char *upload_data, size_t *upload_data_size, void **con_cls);
int send_response(struct MHD_Connection *connection, const char *page);
int send_unavailable(struct MHD_Connection *connection);
bool m_started; bool m_started;
struct MHD_Daemon *daemon;
struct connection_info_struct {
char *response;
};
}; };
} /*namespace KeepassHttpProtocol*/ } /*namespace KeepassHttpProtocol*/