1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qhttpserverhttp1protocolhandler_p.h"
5
6#include <QtCore/qloggingcategory.h>
7#include <QtCore/qmetaobject.h>
8#include <QtCore/qthread.h>
9#include <QtCore/qpointer.h>
10#include <QtHttpServer/qabstracthttpserver.h>
11#include <QtHttpServer/qhttpserverrequest.h>
12#include <QtHttpServer/qhttpserverresponder.h>
13#include <QtNetwork/qlocalsocket.h>
14#include <QtNetwork/qtcpsocket.h>
15
16#include <private/qabstracthttpserver_p.h>
17#include <private/qhttpserverliterals_p.h>
18#include <private/qhttpserverrequest_p.h>
19
20QT_BEGIN_NAMESPACE
21
22Q_LOGGING_CATEGORY(lcHttpServerHttp1Handler, "qt.httpserver.http1handler")
23
24// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
25static const std::map<QHttpServerResponder::StatusCode, QByteArray> statusString{
26#define XX(name, string) { QHttpServerResponder::StatusCode::name, QByteArrayLiteral(string) }
27 XX(Continue, "Continue"),
28 XX(SwitchingProtocols, "Switching Protocols"),
29 XX(Processing, "Processing"),
30 XX(Ok, "OK"),
31 XX(Created, "Created"),
32 XX(Accepted, "Accepted"),
33 XX(NonAuthoritativeInformation, "Non-Authoritative Information"),
34 XX(NoContent, "No Content"),
35 XX(ResetContent, "Reset Content"),
36 XX(PartialContent, "Partial Content"),
37 XX(MultiStatus, "Multi-Status"),
38 XX(AlreadyReported, "Already Reported"),
39 XX(IMUsed, "I'm Used"),
40 XX(MultipleChoices, "Multiple Choices"),
41 XX(MovedPermanently, "Moved Permanently"),
42 XX(Found, "Found"),
43 XX(SeeOther, "See Other"),
44 XX(NotModified, "Not Modified"),
45 XX(UseProxy, "Use Proxy"),
46 XX(TemporaryRedirect, "Temporary Redirect"),
47 XX(PermanentRedirect, "Permanent Redirect"),
48 XX(BadRequest, "Bad Request"),
49 XX(Unauthorized, "Unauthorized"),
50 XX(PaymentRequired, "Payment Required"),
51 XX(Forbidden, "Forbidden"),
52 XX(NotFound, "Not Found"),
53 XX(MethodNotAllowed, "Method Not Allowed"),
54 XX(NotAcceptable, "Not Acceptable"),
55 XX(ProxyAuthenticationRequired, "Proxy Authentication Required"),
56 XX(RequestTimeout, "Request Timeout"),
57 XX(Conflict, "Conflict"),
58 XX(Gone, "Gone"),
59 XX(LengthRequired, "Length Required"),
60 XX(PreconditionFailed, "Precondition Failed"),
61 XX(PayloadTooLarge, "Request Entity Too Large"),
62 XX(UriTooLong, "Request-URI Too Long"),
63 XX(UnsupportedMediaType, "Unsupported Media Type"),
64 XX(RequestRangeNotSatisfiable, "Requested Range Not Satisfiable"),
65 XX(ExpectationFailed, "Expectation Failed"),
66 XX(ImATeapot, "I'm a teapot"),
67 XX(MisdirectedRequest, "Misdirected Request"),
68 XX(UnprocessableEntity, "Unprocessable Entity"),
69 XX(Locked, "Locked"),
70 XX(FailedDependency, "Failed Dependency"),
71 XX(UpgradeRequired, "Upgrade Required"),
72 XX(PreconditionRequired, "Precondition Required"),
73 XX(TooManyRequests, "Too Many Requests"),
74 XX(RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"),
75 XX(UnavailableForLegalReasons, "Unavailable For Legal Reasons"),
76 XX(InternalServerError, "Internal Server Error"),
77 XX(NotImplemented, "Not Implemented"),
78 XX(BadGateway, "Bad Gateway"),
79 XX(ServiceUnavailable, "Service Unavailable"),
80 XX(GatewayTimeout, "Gateway Timeout"),
81 XX(HttpVersionNotSupported, "HTTP Version Not Supported"),
82 XX(VariantAlsoNegotiates, "Variant Also Negotiates"),
83 XX(InsufficientStorage, "Insufficient Storage"),
84 XX(LoopDetected, "Loop Detected"),
85 XX(NotExtended, "Not Extended"),
86 XX(NetworkAuthenticationRequired, "Network Authentication Required"),
87 XX(NetworkConnectTimeoutError, "Network Connect Timeout Error"),
88#undef XX
89};
90
91namespace {
92
93template <qint64 BUFFERSIZE = 128 * 1024>
94struct IOChunkedTransfer
95{
96 // TODO This is not the fastest implementation, as it does read & write
97 // in a sequential fashion, but these operation could potentially overlap.
98 // TODO Can we implement it without the buffer? Direct write to the target buffer
99 // would be great.
100
101 static constexpr qint64 bufferSize = BUFFERSIZE;
102 static constexpr qint64 targetWriteBufferSaturation = bufferSize / 2;
103 char buffer[BUFFERSIZE];
104 qint64 beginIndex = -1;
105 qint64 endIndex = -1;
106 QPointer<QIODevice> source;
107 const QPointer<QIODevice> sink;
108 const QMetaObject::Connection bytesWrittenConnection;
109 const QMetaObject::Connection readyReadConnection;
110 bool inRead = false;
111
112 IOChunkedTransfer(QIODevice *input, QIODevice *output) :
113 source(input),
114 sink(output),
115 bytesWrittenConnection(connectToBytesWritten(that: this, device: output)),
116 readyReadConnection(QObject::connect(source.data(), &QIODevice::readyRead, source.data(),
117 [this]() { readFromInput(); }))
118 {
119 Q_ASSERT(!source->atEnd()); // TODO error out
120 QObject::connect(sink.data(), &QObject::destroyed, source.data(), &QObject::deleteLater);
121 QObject::connect(source.data(), &QObject::destroyed, source.data(), [this]() {
122 delete this;
123 });
124 readFromInput();
125 }
126
127 ~IOChunkedTransfer()
128 {
129 QObject::disconnect(bytesWrittenConnection);
130 QObject::disconnect(readyReadConnection);
131 }
132
133 static QMetaObject::Connection connectToBytesWritten(IOChunkedTransfer *that, QIODevice *device)
134 {
135 auto send = [that]() { that->writeToOutput(); };
136#if QT_CONFIG(ssl)
137 if (auto *sslSocket = qobject_cast<QSslSocket *>(object: device)) {
138 return QObject::connect(sslSocket, &QSslSocket::encryptedBytesWritten, sslSocket,
139 std::move(send));
140 }
141#endif
142 return QObject::connect(device, &QIODevice::bytesWritten, device, std::move(send));
143 }
144
145 inline bool isBufferEmpty()
146 {
147 Q_ASSERT(beginIndex <= endIndex);
148 return beginIndex == endIndex;
149 }
150
151 void readFromInput()
152 {
153 if (inRead)
154 return;
155 if (source.isNull())
156 return;
157
158 if (!isBufferEmpty()) // We haven't consumed all the data yet.
159 return;
160 QScopedValueRollback inReadGuard(inRead, true);
161
162 while (isBufferEmpty()) {
163 beginIndex = 0;
164 endIndex = source->read(buffer, bufferSize);
165 if (endIndex < 0) {
166 endIndex = beginIndex; // Mark the buffer as empty
167 qCWarning(lcHttpServerHttp1Handler, "Error reading chunk: %ls",
168 qUtf16Printable(source->errorString()));
169 break;
170 }
171 if (endIndex == 0)
172 break;
173 memset(buffer + endIndex, 0, sizeof(buffer) - std::size_t(endIndex));
174 writeToOutput();
175 }
176 }
177
178 void writeToOutput()
179 {
180 if (sink.isNull() || source.isNull())
181 return;
182
183 if (isBufferEmpty())
184 return;
185
186 // If downstream has enough data to write already,
187 // don't bother writing more now. That would only lead to
188 // higher, unnecessary memory usage.
189 if (sink->bytesToWrite() >= targetWriteBufferSaturation)
190 return;
191#if QT_CONFIG(ssl)
192 if (auto *sslSocket = qobject_cast<QSslSocket *>(object: sink.data())) {
193 const qint64 budget = targetWriteBufferSaturation - sink->bytesToWrite();
194 if (sslSocket->encryptedBytesToWrite() >= budget)
195 return;
196 }
197#endif
198
199 const auto writtenBytes = sink->write(buffer + beginIndex, endIndex);
200 if (writtenBytes < 0) {
201 qCWarning(lcHttpServerHttp1Handler, "Error writing chunk: %ls",
202 qUtf16Printable(sink->errorString()));
203 return;
204 }
205 beginIndex += writtenBytes;
206 if (isBufferEmpty()) {
207 if (source->bytesAvailable() && !inRead)
208 readFromInput();
209 else if (source->atEnd()) // Finishing
210 source->deleteLater();
211 }
212 }
213};
214
215} // anonymous namespace
216
217
218QHttpServerHttp1ProtocolHandler::QHttpServerHttp1ProtocolHandler(QAbstractHttpServer *server,
219 QIODevice *socket)
220 : QHttpServerStream(server),
221 server(server),
222 socket(socket),
223 tcpSocket(qobject_cast<QTcpSocket *>(object: socket)),
224#if QT_CONFIG(localserver)
225 localSocket(qobject_cast<QLocalSocket*>(object: socket)),
226#endif
227 request(initRequestFromSocket(socket: tcpSocket))
228{
229 socket->setParent(this);
230
231 if (tcpSocket) {
232 qCDebug(lcHttpServerHttp1Handler) << "Connection from:" << tcpSocket->peerAddress();
233 connect(sender: socket, signal: &QTcpSocket::readyRead,
234 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
235 connect(sender: tcpSocket, signal: &QTcpSocket::disconnected,
236 context: this, slot: &QHttpServerHttp1ProtocolHandler::socketDisconnected);
237#if QT_CONFIG(localserver)
238 } else if (localSocket) {
239 qCDebug(lcHttpServerHttp1Handler) << "Connection from:" << localSocket->serverName();
240 connect(sender: socket, signal: &QLocalSocket::readyRead,
241 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
242 connect(sender: localSocket, signal: &QLocalSocket::disconnected,
243 context: this, slot: &QHttpServerHttp1ProtocolHandler::socketDisconnected);
244#endif
245 }
246}
247
248void QHttpServerHttp1ProtocolHandler::responderDestroyed()
249{
250 Q_ASSERT(QThread::currentThread() == thread());
251 if (protocolChanged) {
252 deleteLater();
253 return;
254 }
255 Q_ASSERT(handlingRequest);
256 handlingRequest = false;
257
258 if (tcpSocket) {
259 if (tcpSocket->state() != QAbstractSocket::ConnectedState) {
260 deleteLater();
261 } else {
262 connect(sender: tcpSocket, signal: &QTcpSocket::readyRead,
263 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
264 QMetaObject::invokeMethod(object: tcpSocket, function: &QTcpSocket::readyRead, type: Qt::QueuedConnection);
265 }
266#if QT_CONFIG(localserver)
267 } else if (localSocket) {
268 if (localSocket->state() != QLocalSocket::ConnectedState) {
269 deleteLater();
270 } else {
271 connect(sender: localSocket, signal: &QLocalSocket::readyRead,
272 context: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
273 QMetaObject::invokeMethod(object: localSocket, function: &QLocalSocket::readyRead, type: Qt::QueuedConnection);
274 }
275#endif
276 }
277}
278
279void QHttpServerHttp1ProtocolHandler::startHandlingRequest()
280{
281 handlingRequest = true;
282}
283
284void QHttpServerHttp1ProtocolHandler::socketDisconnected()
285{
286 if (!handlingRequest)
287 deleteLater();
288}
289
290void QHttpServerHttp1ProtocolHandler::handleReadyRead()
291{
292 if (handlingRequest)
293 return;
294
295 if (!socket->isTransactionStarted())
296 socket->startTransaction();
297
298 if (!request.d->parse(socket)) {
299 if (tcpSocket)
300 tcpSocket->disconnectFromHost();
301#if QT_CONFIG(localserver)
302 else if (localSocket)
303 localSocket->disconnectFromServer();
304#endif
305 return;
306 }
307
308 if (request.d->state != QHttpServerRequestPrivate::State::AllDone)
309 return; // Partial read
310
311 qCDebug(lcHttpServerHttp1Handler) << "Request:" << request;
312
313 QHttpServerResponder responder(this);
314
315#if defined(QT_WEBSOCKETS_LIB)
316 if (auto *tcpSocket = qobject_cast<QTcpSocket*>(socket)) {
317 if (request.d->upgrade) { // Upgrade
318 const auto &upgradeValue = request.value(QByteArrayLiteral("upgrade"));
319 if (upgradeValue.compare(QByteArrayLiteral("websocket"), Qt::CaseInsensitive) == 0) {
320 const auto upgradeResponse = server->verifyWebSocketUpgrade(request);
321 static const auto signal =
322 QMetaMethod::fromSignal(&QAbstractHttpServer::newWebSocketConnection);
323 if (server->isSignalConnected(signal)
324 && upgradeResponse.type()
325 != QHttpServerWebSocketUpgradeResponse::ResponseType::PassToNext) {
326 if (upgradeResponse.type()
327 == QHttpServerWebSocketUpgradeResponse::ResponseType::Accept) {
328 // Socket will now be managed by websocketServer
329 protocolChanged = true;
330 socket->disconnect();
331 socket->rollbackTransaction();
332 socket->setParent(nullptr);
333 server->d_func()->websocketServer.handleConnection(tcpSocket);
334 Q_EMIT socket->readyRead();
335 } else {
336 qCDebug(lcHttpServerHttp1Handler, "WebSocket upgrade denied: %ls",
337 qUtf16Printable(QLatin1StringView(upgradeResponse.denyMessage())));
338 QByteArray buffer;
339 buffer.append("HTTP/1.1 ");
340 buffer.append(QByteArray::number(quint32(upgradeResponse.denyStatus())));
341 buffer.append(" ");
342 buffer.append(upgradeResponse.denyMessage());
343 buffer.append("\r\n\r\n");
344 tcpSocket->write(buffer);
345 }
346 } else {
347 if (!server->isSignalConnected(signal)) {
348 qCWarning(lcHttpServerHttp1Handler,
349 "WebSocket received but no slots connected to "
350 "QWebSocketServer::newConnection");
351 }
352 server->missingHandler(request, responder);
353 tcpSocket->disconnectFromHost();
354 }
355 return;
356 }
357 }
358 }
359#endif // QT_WEBSOCKETS_LIB
360
361 socket->commitTransaction();
362
363 if (!server->handleRequest(request, responder))
364 server->missingHandler(request, responder);
365
366 if (handlingRequest)
367 disconnect(sender: socket, signal: &QIODevice::readyRead, receiver: this, slot: &QHttpServerHttp1ProtocolHandler::handleReadyRead);
368 else if (socket->bytesAvailable() > 0)
369 QMetaObject::invokeMethod(object: socket, function: &QIODevice::readyRead, type: Qt::QueuedConnection);
370}
371
372void QHttpServerHttp1ProtocolHandler::write(const QByteArray &body, const QHttpHeaders &headers,
373 QHttpServerResponder::StatusCode status, quint32 streamId)
374{
375 Q_UNUSED(streamId);
376 Q_ASSERT(state == TransferState::Ready);
377 writeStatusAndHeaders(status, headers);
378 write(data: body);
379 state = TransferState::Ready;
380}
381
382void QHttpServerHttp1ProtocolHandler::write(QHttpServerResponder::StatusCode status, quint32 streamId)
383{
384 Q_UNUSED(streamId);
385 Q_ASSERT(state == TransferState::Ready);
386 QHttpHeaders headers;
387 headers.append(name: QHttpHeaders::WellKnownHeader::ContentType,
388 value: QHttpServerLiterals::contentTypeXEmpty());
389 headers.append(name: QHttpHeaders::WellKnownHeader::ContentLength, value: "0");
390 writeStatusAndHeaders(status, headers);
391 state = TransferState::Ready;
392}
393
394void QHttpServerHttp1ProtocolHandler::write(QIODevice *data, const QHttpHeaders &headers,
395 QHttpServerResponder::StatusCode status, quint32 streamId)
396{
397 Q_UNUSED(streamId);
398 Q_ASSERT(state == TransferState::Ready);
399 std::unique_ptr<QIODevice, QScopedPointerDeleteLater> input(data);
400
401 input->setParent(nullptr);
402 if (!input->isOpen()) {
403 if (!input->open(mode: QIODevice::ReadOnly)) {
404 // TODO Add developer error handling
405 qCDebug(lcHttpServerHttp1Handler, "500: Could not open device %ls",
406 qUtf16Printable(input->errorString()));
407 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId: 0);
408 return;
409 }
410 } else if (!(input->openMode() & QIODevice::ReadOnly)) {
411 // TODO Add developer error handling
412 qCDebug(lcHttpServerHttp1Handler) << "500: Device is opened in a wrong mode"
413 << input->openMode();
414 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId: 0);
415 return;
416 }
417
418 QHttpHeaders allHeaders(headers);
419 if (!input->isSequential()) { // Non-sequential QIODevice should know its data size
420 allHeaders.append(name: QHttpHeaders::WellKnownHeader::ContentLength,
421 value: QByteArray::number(input->size()));
422 }
423
424 writeStatusAndHeaders(status, headers: allHeaders);
425
426 if (input->atEnd()) {
427 qCDebug(lcHttpServerHttp1Handler, "No more data available.");
428 return;
429 }
430
431 // input takes ownership of the IOChunkedTransfer pointer inside his constructor
432 new IOChunkedTransfer<>(input.release(), socket);
433 state = TransferState::Ready;
434}
435
436void QHttpServerHttp1ProtocolHandler::writeBeginChunked(const QHttpHeaders &headers,
437 QHttpServerResponder::StatusCode status,
438 quint32 streamId)
439{
440 Q_UNUSED(streamId);
441 Q_ASSERT(state == TransferState::Ready);
442 QHttpHeaders allHeaders(headers);
443 allHeaders.append(name: QHttpHeaders::WellKnownHeader::TransferEncoding, value: "chunked");
444 writeStatusAndHeaders(status, headers: allHeaders);
445 state = TransferState::ChunkedTransferBegun;
446}
447
448void QHttpServerHttp1ProtocolHandler::writeChunk(const QByteArray &data, quint32 streamId)
449{
450 Q_UNUSED(streamId);
451 Q_ASSERT(state == TransferState::ChunkedTransferBegun);
452
453 if (data.length() == 0) {
454 qCWarning(lcHttpServerHttp1Handler, "Chunk must have length > 0");
455 return;
456 }
457
458 write(data: QByteArray::number(data.length(), base: 16));
459 write(data: "\r\n");
460 write(data);
461 write(data: "\r\n");
462}
463
464void QHttpServerHttp1ProtocolHandler::writeEndChunked(const QByteArray &data,
465 const QHttpHeaders &trailers,
466 quint32 streamId)
467{
468 Q_UNUSED(streamId);
469 Q_ASSERT(state == TransferState::ChunkedTransferBegun);
470 writeChunk(data, streamId: 0);
471 write(data: "0\r\n");
472 for (qsizetype i = 0; i < trailers.size(); ++i) {
473 const auto name = trailers.nameAt(i);
474 const auto value = trailers.valueAt(i);
475 writeHeader(key: { name.data(), name.size() }, value: value.toByteArray());
476 }
477 write(data: "\r\n");
478 state = TransferState::Ready;
479}
480
481void QHttpServerHttp1ProtocolHandler::writeStatusAndHeaders(QHttpServerResponder::StatusCode status,
482 const QHttpHeaders &headers)
483{
484 Q_ASSERT(state == TransferState::Ready);
485 QByteArray payload;
486 payload.append(s: "HTTP/1.1 ");
487 payload.append(a: QByteArray::number(quint32(status)));
488 const auto it = statusString.find(x: status);
489 if (it != statusString.end()) {
490 payload.append(s: " ");
491 payload.append(a: statusString.at(k: status));
492 }
493 payload.append(s: "\r\n");
494
495 for (qsizetype i = 0; i < headers.size(); ++i) {
496 const auto name = headers.nameAt(i);
497 payload.append(a: QByteArrayView(name.data(), name.size()) + ": "
498 + headers.valueAt(i).toByteArray() + "\r\n");
499 }
500 payload.append(s: "\r\n");
501 write(data: payload);
502 state = TransferState::HeadersSent;
503}
504
505void QHttpServerHttp1ProtocolHandler::writeHeader(const QByteArray &key, const QByteArray &value)
506{
507 write(data: key + ": " + value + "\r\n");
508}
509
510void QHttpServerHttp1ProtocolHandler::write(const QByteArray &ba)
511{
512 Q_ASSERT(QThread::currentThread() == thread());
513 socket->write(data: ba);
514}
515
516void QHttpServerHttp1ProtocolHandler::write(const char *body, qint64 size)
517{
518 Q_ASSERT(QThread::currentThread() == thread());
519 socket->write(data: body, len: size);
520}
521
522QT_END_NAMESPACE
523

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qthttpserver/src/httpserver/qhttpserverhttp1protocolhandler.cpp