Fix #1688 - Fragmented http-Headers (#1741)

This commit is contained in:
LordGrey 2024-05-18 09:14:30 +02:00 committed by GitHub
parent 051072ee46
commit 4a5b0b6bf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 130 additions and 124 deletions

View File

@ -37,7 +37,7 @@ QString QtHttpClientWrapper::getGuid (void)
{ {
m_guid = QString::fromLocal8Bit ( m_guid = QString::fromLocal8Bit (
QCryptographicHash::hash ( QCryptographicHash::hash (
QByteArray::number ((quint64) (this)), QByteArray::number (reinterpret_cast<quint64>(this)),
QCryptographicHash::Md5 QCryptographicHash::Md5
).toHex () ).toHex ()
); );
@ -50,66 +50,70 @@ void QtHttpClientWrapper::onClientDataReceived (void)
{ {
if (m_sockClient != Q_NULLPTR) if (m_sockClient != Q_NULLPTR)
{ {
while (m_sockClient->bytesAvailable ()) while (m_sockClient->bytesAvailable () != 0)
{ {
QByteArray line = m_sockClient->readLine (); QByteArray line = m_sockClient->readLine ();
switch (m_parsingStatus) // handle parsing steps switch (m_parsingStatus) // handle parsing steps
{ {
case AwaitingRequest: // "command url version" × 1 case AwaitingRequest: // "command url version" × 1
{
QString str = QString::fromUtf8 (line).trimmed ();
QStringList parts = QStringUtils::split(str,SPACE, QStringUtils::SplitBehavior::SkipEmptyParts);
if (parts.size () == 3)
{ {
QString str = QString::fromUtf8 (line).trimmed (); const QString& command = parts.at (0);
QStringList parts = QStringUtils::split(str,SPACE, QStringUtils::SplitBehavior::SkipEmptyParts); const QString& url = parts.at (1);
if (parts.size () == 3) const QString& version = parts.at (2);
{
QString command = parts.at (0);
QString url = parts.at (1);
QString version = parts.at (2);
if (version == QtHttpServer::HTTP_VERSION) if (version == QtHttpServer::HTTP_VERSION)
{ {
m_currentRequest = new QtHttpRequest (this, m_serverHandle); m_currentRequest = new QtHttpRequest (this, m_serverHandle);
m_currentRequest->setClientInfo(m_sockClient->localAddress(), m_sockClient->peerAddress()); m_currentRequest->setClientInfo(m_sockClient->localAddress(), m_sockClient->peerAddress());
m_currentRequest->setUrl (QUrl (url)); m_currentRequest->setUrl (QUrl (url));
m_currentRequest->setCommand (command); m_currentRequest->setCommand (command);
m_parsingStatus = AwaitingHeaders; m_parsingStatus = AwaitingHeaders;
}
else
{
m_parsingStatus = ParsingError;
// Error : unhandled HTTP version
}
} }
else else
{ {
m_parsingStatus = ParsingError; m_parsingStatus = ParsingError;
// Error : incorrect HTTP command line // Error : unhandled HTTP version
} }
break;
} }
case AwaitingHeaders: // "header: value" × N (until empty line) else
{ {
QByteArray raw = line.trimmed (); m_parsingStatus = ParsingError;
// Error : incorrect HTTP command line
}
m_fragment.clear();
break;
}
case AwaitingHeaders: // "header: value" × N (until empty line)
{
m_fragment.append(line);
if ( m_fragment.endsWith(CRLF))
{
QByteArray raw = m_fragment.trimmed ();
if (!raw.isEmpty ()) // parse headers if (!raw.isEmpty ()) // parse headers
{ {
int pos = raw.indexOf (COLON); int pos = raw.indexOf (COLON);
if (pos > 0) if (pos > 0)
{ {
QByteArray header = raw.left (pos).trimmed(); QByteArray header = raw.left (pos).trimmed();
QByteArray value = raw.mid (pos +1).trimmed(); QByteArray value = raw.mid (pos +1).trimmed();
m_currentRequest->addHeader (header, value); m_currentRequest->addHeader (header, value);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
if (header.compare(QtHttpHeader::ContentLength, Qt::CaseInsensitive) == 0) if (header.compare(QtHttpHeader::ContentLength, Qt::CaseInsensitive) == 0)
#else #else
if (header.toLower() == QtHttpHeader::ContentLength.toLower()) if (header.toLower() == QtHttpHeader::ContentLength.toLower())
#endif #endif
{ {
bool ok = false; bool isConversionOk = false;
const int len = value.toInt (&ok, 10); const int len = value.toInt (&isConversionOk, 10);
if (ok) if (isConversionOk)
{ {
m_currentRequest->addHeader (QtHttpHeader::ContentLength, QByteArray::number (len)); m_currentRequest->addHeader (QtHttpHeader::ContentLength, QByteArray::number (len));
} }
@ -132,107 +136,109 @@ void QtHttpClientWrapper::onClientDataReceived (void)
m_parsingStatus = RequestParsed; m_parsingStatus = RequestParsed;
} }
} }
m_fragment.clear();
break;
} }
case AwaitingContent: // raw data × N (until EOF ??)
break;
}
case AwaitingContent: // raw data × N (until EOF ??)
{
m_currentRequest->appendRawData (line);
if (m_currentRequest->getRawDataSize () == m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt ())
{ {
m_currentRequest->appendRawData (line); m_parsingStatus = RequestParsed;
if (m_currentRequest->getRawDataSize () == m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt ())
{
m_parsingStatus = RequestParsed;
}
break;
}
default:
{
break;
} }
break;
}
default:
{
break;
}
} }
switch (m_parsingStatus) // handle parsing status end/error switch (m_parsingStatus) // handle parsing status end/error
{ {
case RequestParsed: // a valid request has ben fully parsed case RequestParsed: // a valid request has ben fully parsed
{
// Catch websocket header "Upgrade"
if(m_currentRequest->getHeader(QtHttpHeader::Upgrade).toLower() == "websocket")
{ {
// Catch websocket header "Upgrade" if(m_websocketClient == Q_NULLPTR)
if(m_currentRequest->getHeader(QtHttpHeader::Upgrade) == "websocket")
{ {
if(m_websocketClient == Q_NULLPTR) // disconnect this slot from socket for further requests
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
// disabling packet bunching
m_sockClient->setSocketOption(QAbstractSocket::LowDelayOption, 1);
m_sockClient->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, this);
}
break;
}
// add post data to request and catch /jsonrpc subroute url
if ( m_currentRequest->getCommand() == "POST")
{
QtHttpPostData postData;
QByteArray data = m_currentRequest->getRawData();
QList<QByteArray> parts = data.split('&');
for (int i = 0; i < parts.size(); ++i)
{
QList<QByteArray> keyValue = parts.at(i).split('=');
QByteArray value;
if (keyValue.size()>1)
{ {
// disconnect this slot from socket for further requests value = QByteArray::fromPercentEncoding(keyValue.at(1));
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
// disabling packet bunching
m_sockClient->setSocketOption(QAbstractSocket::LowDelayOption, 1);
m_sockClient->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, this);
} }
postData.insert(QString::fromUtf8(keyValue.at(0)),value);
}
m_currentRequest->setPostData(postData);
// catch /jsonrpc in url, we need async callback, StaticFileServing is sync
QString path = m_currentRequest->getUrl ().path ();
QStringList uri_parts = QStringUtils::split(path,'/', QStringUtils::SplitBehavior::SkipEmptyParts);
if ( ! uri_parts.empty() && uri_parts.at(0) == "json-rpc" )
{
if(m_webJsonRpc == Q_NULLPTR)
{
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, m_localConnection, this);
}
m_webJsonRpc->handleMessage(m_currentRequest);
break; break;
} }
// add post data to request and catch /jsonrpc subroute url
if ( m_currentRequest->getCommand() == "POST")
{
QtHttpPostData postData;
QByteArray data = m_currentRequest->getRawData();
QList<QByteArray> parts = data.split('&');
for (int i = 0; i < parts.size(); ++i)
{
QList<QByteArray> keyValue = parts.at(i).split('=');
QByteArray value;
if (keyValue.size()>1)
{
value = QByteArray::fromPercentEncoding(keyValue.at(1));
}
postData.insert(QString::fromUtf8(keyValue.at(0)),value);
}
m_currentRequest->setPostData(postData);
// catch /jsonrpc in url, we need async callback, StaticFileServing is sync
QString path = m_currentRequest->getUrl ().path ();
QStringList uri_parts = QStringUtils::split(path,'/', QStringUtils::SplitBehavior::SkipEmptyParts);
if ( ! uri_parts.empty() && uri_parts.at(0) == "json-rpc" )
{
if(m_webJsonRpc == Q_NULLPTR)
{
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, m_localConnection, this);
}
m_webJsonRpc->handleMessage(m_currentRequest);
break;
}
}
QtHttpReply reply (m_serverHandle);
connect (&reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested);
connect (&reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested);
emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request
m_parsingStatus = sendReplyToClient (&reply);
break;
} }
case ParsingError: // there was an error durin one of parsing steps
{
m_sockClient->readAll (); // clear remaining buffer to ignore content
QtHttpReply reply (m_serverHandle);
reply.setStatusCode (QtHttpReply::BadRequest);
reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
reply.appendRawData (CRLF);
m_parsingStatus = sendReplyToClient (&reply);
break; QtHttpReply reply (m_serverHandle);
} connect (&reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested);
default: connect (&reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested);
{ emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request
break; m_parsingStatus = sendReplyToClient (&reply);
}
break;
}
case ParsingError: // there was an error durin one of parsing steps
{
m_sockClient->readAll (); // clear remaining buffer to ignore content
QtHttpReply reply (m_serverHandle);
reply.setStatusCode (QtHttpReply::BadRequest);
reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
reply.appendRawData (CRLF);
m_parsingStatus = sendReplyToClient (&reply);
break;
}
default:
{
break;
}
} }
} }
} }
@ -315,10 +321,9 @@ QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHtt
{ {
if (!reply->useChunked ()) if (!reply->useChunked ())
{ {
//reply->appendRawData (CRLF);
// send all headers and all data in one shot // send all headers and all data in one shot
reply->requestSendHeaders (); emit reply->requestSendHeaders ();
reply->requestSendData (); emit reply->requestSendData ();
} }
else else
{ {
@ -331,7 +336,7 @@ QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHtt
{ {
static const QByteArray & CLOSE = QByteArrayLiteral ("close"); static const QByteArray & CLOSE = QByteArrayLiteral ("close");
if (m_currentRequest->getHeader(QtHttpHeader::Connection) == CLOSE) if (m_currentRequest->getHeader(QtHttpHeader::Connection).toLower() == CLOSE)
{ {
// must close connection after this request // must close connection after this request
m_sockClient->close (); m_sockClient->close ();

View File

@ -58,6 +58,7 @@ private:
const bool m_localConnection; const bool m_localConnection;
WebSocketClient * m_websocketClient; WebSocketClient * m_websocketClient;
WebJsonRpc * m_webJsonRpc; WebJsonRpc * m_webJsonRpc;
QByteArray m_fragment;
}; };
#endif // QTHTTPCLIENTWRAPPER_H #endif // QTHTTPCLIENTWRAPPER_H