1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qhttpserverhttp2protocolhandler_p.h"
6
7#include <QtCore/qloggingcategory.h>
8#include <QtHttpServer/qabstracthttpserver.h>
9#include <QtHttpServer/qhttpserverresponse.h>
10#include <QtNetwork/private/qhttp2connection_p.h>
11#include <QtNetwork/qtcpsocket.h>
12
13#include <private/qhttpserverrequest_p.h>
14#include <private/qhttpserverliterals_p.h>
15#include <private/qhttpserverresponder_p.h>
16
17QT_BEGIN_NAMESPACE
18
19Q_STATIC_LOGGING_CATEGORY(lcHttpServerHttp2Handler, "qt.httpserver.http2handler")
20
21namespace {
22
23void toHeaderPairs(HPack::HttpHeader &fields, const QHttpHeaders &headers)
24{
25 for (qsizetype i = 0; i < headers.size(); ++i) {
26 const auto name = headers.nameAt(i);
27 fields.push_back(x: HPack::HeaderField(QByteArray(name.data(), name.size()),
28 headers.valueAt(i).toByteArray()));
29 }
30}
31
32} // anonymous namespace
33
34QHttpServerHttp2ProtocolHandler::QHttpServerHttp2ProtocolHandler(QAbstractHttpServer *server,
35 QIODevice *socket,
36 QHttpServerRequestFilter *filter)
37 : QHttpServerStream(socket, server),
38 m_server(server),
39 m_socket(socket),
40 m_tcpSocket(qobject_cast<QTcpSocket *>(object: socket)),
41 m_filter(filter)
42{
43 socket->setParent(this);
44
45 m_connection = QHttp2Connection::createDirectServerConnection(socket,
46 config: server->http2Configuration());
47 if (!m_connection)
48 return;
49
50 Q_ASSERT(m_tcpSocket);
51
52 connect(sender: m_tcpSocket,
53 signal: &QTcpSocket::readyRead,
54 context: m_connection,
55 slot: &QHttp2Connection::handleReadyRead);
56
57 connect(sender: m_tcpSocket,
58 signal: &QTcpSocket::disconnected,
59 context: m_connection,
60 slot: &QHttp2Connection::handleConnectionClosure);
61
62 connect(sender: m_tcpSocket,
63 signal: &QTcpSocket::disconnected,
64 context: this,
65 slot: &QHttpServerHttp2ProtocolHandler::socketDisconnected);
66
67 connect(sender: m_connection,
68 signal: &QHttp2Connection::newIncomingStream,
69 context: this,
70 slot: &QHttpServerHttp2ProtocolHandler::onStreamCreated);
71
72 lastActiveTimer.start();
73}
74
75void QHttpServerHttp2ProtocolHandler::responderDestroyed()
76{
77 m_responderCounter--;
78}
79
80void QHttpServerHttp2ProtocolHandler::startHandlingRequest()
81{
82 m_responderCounter++;
83}
84
85void QHttpServerHttp2ProtocolHandler::socketDisconnected()
86{
87 if (m_responderCounter == 0)
88 deleteLater();
89}
90
91void QHttpServerHttp2ProtocolHandler::write(const QByteArray &body, const QHttpHeaders &headers,
92 QHttpServerResponder::StatusCode status,
93 quint32 streamId)
94{
95 QHttp2Stream *stream = getStream(streamId);
96 if (!stream)
97 return;
98
99 writeHeadersAndStatus(headers, status, endStream: false, streamId);
100
101 QBuffer *buffer = new QBuffer(stream);
102 buffer->setData(body);
103 buffer->open(openMode: QIODevice::ReadOnly);
104
105 connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: buffer, slot: &QObject::deleteLater);
106 stream->sendDATA(device: buffer, endStream: true);
107}
108
109void QHttpServerHttp2ProtocolHandler::write(QHttpServerResponder::StatusCode status,
110 quint32 streamId)
111{
112 QHttpHeaders headers;
113 headers.append(name: QHttpHeaders::WellKnownHeader::ContentType,
114 value: QHttpServerLiterals::contentTypeXEmpty());
115 headers.append(name: QHttpHeaders::WellKnownHeader::ContentLength, value: "0");
116
117 // RFC 9113, 8.1
118 // A HEADERS frame with the END_STREAM flag set that carries
119 // an informational status code is malformed
120 bool isInfoStatus = QHttpServerResponder::StatusCode::Continue <= status
121 && status < QHttpServerResponder::StatusCode::Ok;
122 writeHeadersAndStatus(headers, status, endStream: !isInfoStatus, streamId);
123}
124
125void QHttpServerHttp2ProtocolHandler::write(QIODevice *data, const QHttpHeaders &headers,
126 QHttpServerResponder::StatusCode status, quint32 streamId)
127{
128 std::unique_ptr<QIODevice, QScopedPointerDeleteLater> input(data);
129
130 QHttp2Stream *stream = getStream(streamId);
131 if (!stream)
132 return;
133
134 if (!input->isOpen()) {
135 if (!input->open(mode: QIODevice::ReadOnly)) {
136 // TODO Add developer error handling
137 qCDebug(lcHttpServerHttp2Handler, "500: Could not open device %ls",
138 qUtf16Printable(input->errorString()));
139 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId);
140 return;
141 }
142 } else if (!(input->openMode() & QIODevice::ReadOnly)) {
143 // TODO Add developer error handling
144 qCDebug(lcHttpServerHttp2Handler) << "500: Device is opened in a wrong mode"
145 << input->openMode();
146 write(status: QHttpServerResponder::StatusCode::InternalServerError, streamId);
147 return;
148 }
149
150 QHttpHeaders allHeaders(headers);
151 if (!data->isSequential()) { // Non-sequential QIODevice should know its data size
152 allHeaders.append(name: QHttpHeaders::WellKnownHeader::ContentLength,
153 value: QByteArray::number(data->size()));
154 }
155
156 writeHeadersAndStatus(headers, status, endStream: false, streamId);
157
158 input->setParent(stream);
159 connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: input.get(), slot: &QObject::deleteLater);
160 stream->sendDATA(device: input.release(), endStream: true);
161}
162
163void QHttpServerHttp2ProtocolHandler::writeBeginChunked(const QHttpHeaders &headers,
164 QHttpServerResponder::StatusCode status,
165 quint32 streamId)
166{
167 writeHeadersAndStatus(headers, status, endStream: false, streamId);
168}
169
170void QHttpServerHttp2ProtocolHandler::writeChunk(const QByteArray &body, quint32 streamId)
171{
172 enqueueChunk(body, allEnqueued: false, trailers: {}, streamId);
173}
174
175void QHttpServerHttp2ProtocolHandler::writeEndChunked(const QByteArray &body,
176 const QHttpHeaders &trailers,
177 quint32 streamId)
178{
179 enqueueChunk(body, allEnqueued: true, trailers, streamId);
180}
181
182void QHttpServerHttp2ProtocolHandler::enqueueChunk(const QByteArray &body, bool allEnqueued,
183 const QHttpHeaders &trailers, quint32 streamId)
184{
185 QHttp2Stream *stream = getStream(streamId);
186 if (!stream)
187 return;
188
189 auto &queue = m_streamQueue[streamId];
190
191 if (!trailers.isEmpty()) {
192 Q_ASSERT(queue.trailers.empty());
193 toHeaderPairs(fields&: queue.trailers, headers: trailers);
194 }
195
196 queue.data.enqueue(t: body);
197 if (allEnqueued)
198 queue.allEnqueued = true;
199
200 if (!stream->isUploadingDATA())
201 sendToStream(streamId);
202}
203
204void QHttpServerHttp2ProtocolHandler::writeHeadersAndStatus(const QHttpHeaders &headers,
205 QHttpServerResponder::StatusCode status,
206 bool endStream, quint32 streamId)
207{
208 QHttp2Stream *stream = getStream(streamId);
209 if (!stream)
210 return;
211
212 HPack::HttpHeader h;
213 h.push_back(x: HPack::HeaderField(":status", QByteArray::number(quint32(status))));
214 toHeaderPairs(fields&: h, headers);
215 stream->sendHEADERS(headers: h, endStream);
216}
217
218QHttp2Stream *QHttpServerHttp2ProtocolHandler::getStream(quint32 streamId) const
219{
220 QHttp2Stream *stream = m_connection->getStream(streamId);
221 Q_ASSERT(stream);
222
223 if (stream && stream->isActive())
224 return stream;
225
226 return nullptr;
227}
228
229void QHttpServerHttp2ProtocolHandler::onStreamCreated(QHttp2Stream *stream)
230{
231 const quint32 id = stream->streamID();
232 m_streamQueue.insert(key: id, value: QHttpServerHttp2Queue());
233
234 auto onStateChanged = [this, id](QHttp2Stream::State newState) {
235 switch (newState) {
236 case QHttp2Stream::State::HalfClosedRemote:
237 onStreamHalfClosed(streamId: id);
238 break;
239 case QHttp2Stream::State::Closed:
240 onStreamClosed(streamId: id);
241 break;
242 default:
243 break;
244 }
245 };
246
247 auto &connections = m_streamConnections[id];
248 connections << connect(sender: stream,
249 signal: &QHttp2Stream::stateChanged,
250 context: this,
251 slot&: onStateChanged,
252 type: Qt::QueuedConnection);
253
254 connections << connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: this,
255 slot: [this, id]() { sendToStream(streamId: id); });
256
257 lastActiveTimer.restart();
258}
259
260void QHttpServerHttp2ProtocolHandler::onStreamHalfClosed(quint32 streamId)
261{
262 auto stream = m_connection->getStream(streamId);
263 Q_ASSERT(stream);
264 if (!stream)
265 return;
266
267 auto requestReceived = parser.parse(socket: stream);
268 if (!requestReceived)
269 return;
270
271#if QT_CONFIG(ssl)
272 auto request = QHttpServerRequest::create(parser, configuration: sslConfiguration);
273#else
274 auto request = QHttpServerRequest::create(parser);
275#endif
276
277 qCDebug(lcHttpServerHttp2Handler) << "Request:" << request;
278
279 QHttpServerResponder responder(this);
280 responder.d_ptr->m_streamId = streamId;
281
282 if (!m_filter->isRequestWithinRate(peerAddress: m_tcpSocket->peerAddress())) {
283 responder.sendResponse(
284 response: QHttpServerResponse(QHttpServerResponder::StatusCode::TooManyRequests));
285 } else if (!m_server->handleRequest(request, responder)) {
286 m_server->missingHandler(request, responder);
287 }
288}
289
290void QHttpServerHttp2ProtocolHandler::onStreamClosed(quint32 streamId)
291{
292 auto connections = m_streamConnections.take(key: streamId);
293 for (auto &c : connections)
294 disconnect(c);
295
296 m_streamQueue.remove(key: streamId);
297}
298
299void QHttpServerHttp2ProtocolHandler::checkKeepAliveTimeout()
300{
301 if (m_streamQueue.size() > 0 || m_responderCounter > 0)
302 return;
303
304 if (lastActiveTimer.durationElapsed() > m_server->configuration().keepAliveTimeout()) {
305 m_connection->close();
306 m_tcpSocket->abort();
307 }
308}
309
310void QHttpServerHttp2ProtocolHandler::sendToStream(quint32 streamId)
311{
312 QHttp2Stream *stream = getStream(streamId);
313 if (!stream)
314 return;
315
316 if (stream->isUploadingDATA())
317 return;
318
319 auto &queue = m_streamQueue[streamId];
320 if (!queue.data.isEmpty()) {
321 QBuffer *buffer = new QBuffer(stream);
322 buffer->setData(queue.data.dequeue());
323 buffer->open(openMode: QIODevice::ReadOnly);
324 connect(sender: stream, signal: &QHttp2Stream::uploadFinished, context: buffer, slot: &QObject::deleteLater);
325 bool endStream = queue.allEnqueued && queue.data.isEmpty() && queue.trailers.empty();
326 stream->sendDATA(device: buffer, endStream);
327 } else if (!queue.trailers.empty()) {
328 stream->sendHEADERS(headers: queue.trailers, endStream: true);
329 queue.trailers.clear();
330 }
331}
332
333QT_END_NAMESPACE
334

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