keepassxc/src/http/qhttp/qhttpclient.cpp
2017-02-17 02:43:25 +01:00

286 lines
7.2 KiB
C++

#include "private/qhttpclient_private.hpp"
#include <QTimerEvent>
///////////////////////////////////////////////////////////////////////////////
namespace qhttp {
namespace client {
///////////////////////////////////////////////////////////////////////////////
QHttpClient::QHttpClient(QObject *parent)
: QObject(parent), d_ptr(new QHttpClientPrivate(this)) {
QHTTP_LINE_LOG
}
QHttpClient::QHttpClient(QHttpClientPrivate &dd, QObject *parent)
: QObject(parent), d_ptr(&dd) {
QHTTP_LINE_LOG
}
QHttpClient::~QHttpClient() {
QHTTP_LINE_LOG
}
quint32
QHttpClient::timeOut() const {
return d_func()->itimeOut;
}
void
QHttpClient::setTimeOut(quint32 t) {
d_func()->itimeOut = t;
}
bool
QHttpClient::isOpen() const {
return d_func()->isocket.isOpen();
}
void
QHttpClient::killConnection() {
Q_D(QHttpClient);
d->iconnectingTimer.stop();
d->itimer.stop();
d->isocket.close();
}
TBackend
QHttpClient::backendType() const {
return d_func()->isocket.ibackendType;
}
QTcpSocket*
QHttpClient::tcpSocket() const {
return d_func()->isocket.itcpSocket;
}
QLocalSocket*
QHttpClient::localSocket() const {
return d_func()->isocket.ilocalSocket;
}
void
QHttpClient::setConnectingTimeOut(quint32 timeout) {
Q_D(QHttpClient);
if ( timeout == 0 ) {
d->iconnectingTimer.stop();
} else {
d->iconnectingTimer.start(timeout,
Qt::CoarseTimer,
this
);
}
}
bool
QHttpClient::request(THttpMethod method, QUrl url,
const TRequstHandler &reqHandler,
const TResponseHandler &resHandler) {
Q_D(QHttpClient);
d->ireqHandler = nullptr;
d->irespHandler = nullptr;
// if url is a local file (UNIX socket) the host could be empty!
if ( !url.isValid() || url.isEmpty() /*|| url.host().isEmpty()*/ )
return false;
// process handlers
if ( resHandler ) {
d->irespHandler = resHandler;
if ( reqHandler )
d->ireqHandler = reqHandler;
else
d->ireqHandler = [](QHttpRequest* req) ->void {
req->addHeader("connection", "close");
req->end();
};
}
auto requestCreator = [this, method, url]() {
// create request object
if ( d_ptr->ilastRequest )
d_ptr->ilastRequest->deleteLater();
d_ptr->ilastRequest = new QHttpRequest(this);
QObject::connect(d_ptr->ilastRequest, &QHttpRequest::done, [this](bool wasTheLastPacket){
d_ptr->ikeepAlive = !wasTheLastPacket;
});
d_ptr->ilastRequest->d_ptr->imethod = method;
d_ptr->ilastRequest->d_ptr->iurl = url;
};
// connecting to host/server must be the last thing. (after all function handlers and ...)
// check for type
if ( url.scheme().toLower() == QLatin1String("file") ) {
d->isocket.ibackendType = ELocalSocket;
d->initializeSocket();
requestCreator();
if ( d->isocket.isOpen() )
d->onConnected();
else
d->isocket.connectTo(url);
} else {
bool ssl = url.scheme() == "https";
d->isocket.ibackendType = ssl ? ESslSocket : ETcpSocket;
d->initializeSocket();
requestCreator();
if ( d->isocket.isOpen() )
d->onConnected();
else
d->isocket.connectTo(url.host(), url.port(ssl ? 443 : 80));
}
return true;
}
void
QHttpClient::timerEvent(QTimerEvent *e) {
Q_D(QHttpClient);
if ( e->timerId() == d->itimer.timerId() ) {
killConnection();
} else if ( e->timerId() == d->iconnectingTimer.timerId() ) {
d->iconnectingTimer.stop();
emit connectingTimeOut();
}
}
void
QHttpClient::onRequestReady(QHttpRequest *req) {
emit httpConnected(req);
}
void
QHttpClient::onResponseReady(QHttpResponse *res) {
emit newResponse(res);
}
///////////////////////////////////////////////////////////////////////////////
// if server closes the connection, ends the response or by any other reason
// the socket disconnects, then the irequest and iresponse instances may have
// been deleted. In these situations reading more http body or emitting end()
// for incoming request are not possible:
// if ( ilastRequest == nullptr )
// return 0;
int
QHttpClientPrivate::messageBegin(http_parser*) {
itempHeaderField.clear();
itempHeaderValue.clear();
return 0;
}
int
QHttpClientPrivate::status(http_parser* parser, const char* at, size_t length) {
if ( ilastResponse )
ilastResponse->deleteLater();
ilastResponse = new QHttpResponse(q_func());
ilastResponse->d_func()->istatus = static_cast<TStatusCode>(parser->status_code);
ilastResponse->d_func()->iversion = QString("%1.%2")
.arg(parser->http_major)
.arg(parser->http_minor);
ilastResponse->d_func()->icustomStatusMessage = QString::fromUtf8(at, length);
return 0;
}
int
QHttpClientPrivate::headerField(http_parser*, const char* at, size_t length) {
if ( ilastResponse == nullptr )
return 0;
// insert the header we parsed previously
// into the header map
if ( !itempHeaderField.isEmpty() && !itempHeaderValue.isEmpty() ) {
// header names are always lower-cased
ilastResponse->d_func()->iheaders.insert(
itempHeaderField.toLower(),
itempHeaderValue.toLower()
);
// clear header value. this sets up a nice
// feedback loop where the next time
// HeaderValue is called, it can simply append
itempHeaderField.clear();
itempHeaderValue.clear();
}
itempHeaderField.append(at, length);
return 0;
}
int
QHttpClientPrivate::headerValue(http_parser*, const char* at, size_t length) {
itempHeaderValue.append(at, length);
return 0;
}
int
QHttpClientPrivate::headersComplete(http_parser*) {
if ( ilastResponse == nullptr )
return 0;
// Insert last remaining header
ilastResponse->d_func()->iheaders.insert(
itempHeaderField.toLower(),
itempHeaderValue.toLower()
);
if ( irespHandler )
irespHandler(ilastResponse);
else
q_func()->onResponseReady(ilastResponse);
return 0;
}
int
QHttpClientPrivate::body(http_parser*, const char* at, size_t length) {
if ( ilastResponse == nullptr )
return 0;
ilastResponse->d_func()->ireadState = QHttpResponsePrivate::EPartial;
if ( ilastResponse->d_func()->icollectRequired ) {
if ( !ilastResponse->d_func()->append(at, length) ) {
// forcefully dispatch the ilastResponse
finalizeConnection();
}
return 0;
}
emit ilastResponse->data(QByteArray(at, length));
return 0;
}
int
QHttpClientPrivate::messageComplete(http_parser*) {
if ( ilastResponse == nullptr )
return 0;
// response is done
finalizeConnection();
return 0;
}
///////////////////////////////////////////////////////////////////////////////
} // namespace client
} // namespace qhttp
///////////////////////////////////////////////////////////////////////////////