cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
protocolhttp2.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2018 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "protocolhttp2.h"
6
7#include "hpack.h"
8#include "server.h"
9#include "socket.h"
10
11#include <QEventLoop>
12#include <QLoggingCategory>
13
14using namespace Cutelyst;
15using namespace Qt::Literals::StringLiterals;
16
17Q_LOGGING_CATEGORY(C_SERVER_H2, "cutelyst.server.http2", QtWarningMsg)
18
19namespace {
20struct h2_frame {
21 quint8 size2;
22 quint8 size1;
23 quint8 size0;
24 quint8 type;
25 quint8 flags;
26 quint8 rbit_stream_id3;
27 quint8 rbit_stream_id2;
28 quint8 rbit_stream_id1;
29 quint8 rbit_stream_id0;
30};
31
32enum SettingsFlags { FlagSettingsAck = 0x1 };
33
34enum PingFlags { FlagPingAck = 0x1 };
35
36enum HeaderFlags {
37 FlagHeadersEndStream = 0x1,
38 FlagHeadersEndHeaders = 0x4,
39 FlagHeadersPadded = 0x8,
40 FlagHeadersPriority = 0x20,
41};
42
43enum PushPromiseFlags {
44 FlagPushPromiseEndHeaders = 0x4,
45 FlagPushPromisePadded = 0x8,
46};
47
48enum DataFlags {
49 FlagDataEndStream = 0x1,
50 FlagDataPadded = 0x8,
51};
52
53enum FrameType {
54 FrameData = 0x0,
55 FrameHeaders = 0x1,
56 FramePriority = 0x2,
57 FrameRstStream = 0x3,
58 FrameSettings = 0x4,
59 FramePushPromise = 0x5,
60 FramePing = 0x6,
61 FrameGoaway = 0x7,
62 FrameWindowUpdate = 0x8,
63 FrameContinuation = 0x9
64};
65
66enum ErrorCodes {
67 ErrorNoError = 0x0,
68 ErrorProtocolError = 0x1,
69 ErrorInternalError = 0x2,
70 ErrorFlowControlError = 0x3,
71 ErrorSettingsTimeout = 0x4,
72 ErrorStreamClosed = 0x5,
73 ErrorFrameSizeError = 0x6,
74 ErrorRefusedStream = 0x7,
75 ErrorCancel = 0x8,
76 ErrorCompressionError = 0x9,
77 ErrorConnectError = 0xA,
78 ErrorEnhanceYourCalm = 0xB,
79 ErrorInadequateSecurity = 0xC,
80 ErrorHttp11Required = 0xD
81};
82
83enum Settings {
84 SETTINGS_HEADER_TABLE_SIZE = 0x1,
85 SETTINGS_ENABLE_PUSH = 0x2,
86 SETTINGS_MAX_CONCURRENT_STREAMS = 0x3,
87 SETTINGS_INITIAL_WINDOW_SIZE = 0x4,
88 SETTINGS_MAX_FRAME_SIZE = 0x5,
89 SETTINGS_MAX_HEADER_LIST_SIZE = 0x6,
90 SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x8,
91};
92
93constexpr int PREFACE_SIZE = 24;
94} // namespace
95
96ProtocolHttp2::ProtocolHttp2(Server *wsgi)
97 : Protocol(wsgi)
98 , m_headerTableSize(qint32(wsgi->http2HeaderTableSize()))
99{
100 m_bufferSize = qMin(m_bufferSize, 2147483647);
101
102 // 2^14 + 9 (octects)
103 if (m_bufferSize < 16393) {
104 qFatal("HTTP/2 Protocol requires that buffer-size to be at least '16393' in size, current "
105 "value is '%s'",
106 QByteArray::number(m_bufferSize).constData());
107 }
108
109 m_maxFrameSize = quint32(m_bufferSize - 9);
110}
111
112ProtocolHttp2::~ProtocolHttp2()
113{
114}
115
116Protocol::Type ProtocolHttp2::type() const
117{
118 return Protocol::Type::Http2;
119}
120
121void ProtocolHttp2::parse(Socket *sock, QIODevice *io) const
122{
123 auto request = static_cast<ProtoRequestHttp2 *>(sock->protoData);
124
125 qint64 bytesAvailable = io->bytesAvailable();
126 // qCDebug(C_SERVER_H2) << sock << "READ available" << bytesAvailable << "buffer size" <<
127 // request->buf_size << "default buffer size" << m_bufferSize ;
128
129 do {
130 const qint64 len =
131 io->read(request->buffer + request->buf_size, m_bufferSize - request->buf_size);
132 bytesAvailable -= len;
133
134 if (len > 0) {
135 request->buf_size += len;
136 int ret = 0;
137 while (request->buf_size && ret == 0) {
138 // qDebug() << "Current buffer size" << request->connState <<
139 // request->buf_size;//QByteArray(request->buffer,
140 // request->buf_size);
141 if (request->connState == ProtoRequestHttp2::MethodLine) {
142 if (request->buf_size >= PREFACE_SIZE) {
143 if (memcmp(request->buffer, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24) == 0) {
144 // qCDebug(C_SERVER_H2) << "Got MAGIC" <<
145 // sizeof(struct h2_frame);
146 request->buf_size -= PREFACE_SIZE;
147 memmove(request->buffer,
148 request->buffer + PREFACE_SIZE,
149 size_t(request->buf_size));
150 request->connState = ProtoRequestHttp2::H2Frames;
151
152 sendSettings(io,
153 {
154 {SETTINGS_ENABLE_CONNECT_PROTOCOL, 0},
155 {SETTINGS_MAX_FRAME_SIZE, m_maxFrameSize},
156 {SETTINGS_HEADER_TABLE_SIZE, m_headerTableSize},
157 });
158 } else {
159 qCDebug(C_SERVER_H2) << "Protocol Error: Invalid connection preface"
160 << sock->remoteAddress.toString();
161 // RFC 7540 says this MAY be omitted, so let's reduce further processing
162 // ret = sendGoAway(io, request->maxStreamId,
163 // ErrorProtocolError);
164 sock->connectionClose();
165 return;
166 }
167 } else {
168 // qDebug() << "MAGIC needs more data" <<
169 // bytesAvailable;
170 break;
171 }
172 } else if (request->connState == ProtoRequestHttp2::H2Frames) {
173 if (request->buf_size >= int(sizeof(struct h2_frame))) {
174 auto fr = reinterpret_cast<struct h2_frame *>(request->buffer);
175 H2Frame frame;
176 frame.len = quint32(fr->size0 | (fr->size1 << 8) | (fr->size2 << 16));
177 frame.streamId =
178 quint32(fr->rbit_stream_id0 | (fr->rbit_stream_id1 << 8) |
179 (fr->rbit_stream_id2 << 16) |
180 ((fr->rbit_stream_id3 & ~0x80) << 24)); // Ignore first bit
181 frame.type = fr->type;
182 frame.flags = fr->flags;
183 request->pktsize =
184 quint32(fr->size0 | (fr->size1 << 8) | (fr->size2 << 16));
185 request->stream_id =
186 quint32(fr->rbit_stream_id0 | (fr->rbit_stream_id1 << 8) |
187 (fr->rbit_stream_id2 << 16) |
188 ((fr->rbit_stream_id3 & ~0x80) << 24)); // Ignore first bit
189
190 // qDebug() << "Frame type" << fr->type
191 // << "flags" << fr->flags
192 // << "stream-id" << request->stream_id
193 // << "required size" << request->pktsize
194 // << "available" << (request->buf_size -
195 // sizeof(struct h2_frame));
196
197 if (frame.streamId && !(frame.streamId & 1)) {
198 ret = sendGoAway(io, request->maxStreamId, ErrorProtocolError);
199 break;
200 }
201
202 if (request->pktsize > m_maxFrameSize) {
203 // qDebug() << "Frame too big" <<
204 // request->pktsize << m_bufferSize;
205 ret = sendGoAway(io, request->maxStreamId, ErrorFrameSizeError);
206 break;
207 }
208
209 if (request->pktsize >
210 (quint32(request->buf_size) - sizeof(struct h2_frame))) {
211 // qDebug() << "need more data" <<
212 // bytesAvailable;
213 break;
214 }
215
216 if (request->streamForContinuation) {
217 if (fr->type == FrameContinuation &&
218 request->streamForContinuation == frame.streamId) {
219 fr->type = FrameHeaders;
220 } else {
221 ret = sendGoAway(io, request->maxStreamId, ErrorProtocolError);
222 break;
223 }
224 }
225
226 if (fr->type == FrameSettings) {
227 ret = parseSettings(request, io, frame);
228 } else if (fr->type == FramePriority) {
229 ret = parsePriority(request, io, frame);
230 } else if (fr->type == FrameHeaders) {
231 ret = parseHeaders(request, io, frame);
232 } else if (fr->type == FramePing) {
233 ret = parsePing(request, io, frame);
234 } else if (fr->type == FrameData) {
235 ret = parseData(request, io, frame);
236 } else if (fr->type == FramePushPromise) {
237 // Client can not PUSH
238 ret = sendGoAway(io, request->maxStreamId, ErrorProtocolError);
239 break;
240 } else if (fr->type == FrameRstStream) {
241 ret = parseRstStream(request, io, frame);
242 } else if (fr->type == FrameWindowUpdate) {
243 ret = parseWindowUpdate(request, io, frame);
244 } else if (fr->type == FrameGoaway) {
245 sock->connectionClose();
246 return;
247 } else if (fr->type == FrameContinuation) {
248 ret = sendGoAway(io, request->maxStreamId, ErrorProtocolError);
249 break;
250 } else {
251 qCDebug(C_SERVER_H2) << "Unknown frame type" << fr->type;
252 // Implementations MUST ignore and discard any frame that has a type
253 // that is unknown.
254 }
255
256 request->buf_size -= 9 + request->pktsize;
257 memmove(request->buffer,
258 request->buffer + 9 + request->pktsize,
259 size_t(request->buf_size));
260 }
261 }
262 }
263
264 if (ret) {
265 // qDebug() << "Got error closing" << ret;
266 sock->connectionClose();
267 }
268 } else {
269 qCWarning(C_SERVER_H2) << "Failed to read from socket" << io->errorString();
270 break;
271 }
272 } while (bytesAvailable);
273}
274
275ProtocolData *ProtocolHttp2::createData(Socket *sock) const
276{
277 return new ProtoRequestHttp2(sock, m_bufferSize);
278}
279
280int ProtocolHttp2::parseSettings(ProtoRequestHttp2 *request, QIODevice *io, const H2Frame &fr) const
281{
282 // qDebug() << "Consumming SETTINGS";
283 if ((fr.flags & FlagSettingsAck && fr.len) || fr.len % 6) {
284 sendGoAway(io, request->maxStreamId, ErrorFrameSizeError);
285 return 1;
286 } else if (fr.streamId) {
287 sendGoAway(io, request->maxStreamId, ErrorProtocolError);
288 return 1;
289 }
290
291 if (!(fr.flags & FlagSettingsAck)) {
292 QVector<std::pair<quint16, quint32>> settings;
293 uint pos = 0;
294 while (request->pktsize > pos) {
295 quint16 identifier = net_be16(request->buffer + 9 + pos);
296 quint32 value = net_be32(request->buffer + 9 + 2 + pos);
297 settings.push_back({identifier, value});
298 pos += 6;
299 // qDebug() << "SETTINGS" << identifier << value;
300 if (identifier == SETTINGS_ENABLE_PUSH) {
301 if (value > 1) {
302 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
303 }
304
305 request->canPush = value;
306 } else if (identifier == SETTINGS_INITIAL_WINDOW_SIZE) {
307 if (value > 2147483647) {
308 return sendGoAway(io, request->maxStreamId, ErrorFlowControlError);
309 }
310
311 const qint32 difference = qint32(value) - request->settingsInitialWindowSize;
312 request->settingsInitialWindowSize = qint32(value);
313
314 for (const auto &stream : std::as_const(request->streams)) {
315 stream->windowSize += difference;
316 stream->windowUpdated();
317 // qCDebug(C_SERVER_H2) << "updating stream" << it.key() <<
318 // "to window" << stream->windowSize;
319 }
320 } else if (identifier == SETTINGS_MAX_FRAME_SIZE) {
321 if (value < 16384 || value > 16777215) {
322 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
323 }
324 request->settingsMaxFrameSize = value;
325 }
326 }
327 sendSettingsAck(io);
328 }
329
330 return ErrorNoError;
331}
332
333int ProtocolHttp2::parseData(ProtoRequestHttp2 *request, QIODevice *io, const H2Frame &fr) const
334{
335 // qCDebug(C_SERVER_H2) << "Consuming DATA" << fr.len;
336 if (fr.streamId == 0) {
337 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
338 }
339
340 quint8 padLength = 0;
341 if (fr.flags & FlagDataPadded) {
342 padLength = quint8(*(request->buffer + 9));
343 if (padLength >= fr.len) {
344 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
345 }
346 }
347
348 H2Stream *stream;
349 auto streamIt = request->streams.constFind(fr.streamId);
350 if (streamIt != request->streams.constEnd()) {
351 stream = streamIt.value();
352
353 if (stream->state == H2Stream::Idle) {
354 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
355 } else if (stream->state == H2Stream::HalfClosed || stream->state == H2Stream::Closed) {
356 return sendGoAway(io, request->maxStreamId, ErrorStreamClosed);
357 }
358 } else {
359 return sendGoAway(io, request->maxStreamId, ErrorStreamClosed);
360 }
361
362 // qCDebug(C_SERVER_H2) << "Frame data" << padLength << "state" << stream->state <<
363 // "content-length" << stream->contentLength;
364
365 if (!stream->body) {
366 stream->body = createBody(request->contentLength);
367 if (!stream->body) {
368 // Failed to create body to store data
369 return sendGoAway(io, request->maxStreamId, ErrorInternalError);
370 }
371 }
372 stream->body->write(request->buffer + 9, fr.len - padLength);
373
374 stream->consumedData += fr.len - padLength;
375 if (stream->contentLength != -1 &&
376 ((fr.flags & FlagDataEndStream && stream->contentLength != stream->consumedData) ||
377 (stream->contentLength > stream->consumedData))) {
378 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
379 }
380
381 if (fr.flags & FlagDataEndStream) {
382 queueStream(request->sock, stream);
383 }
384
385 return ErrorNoError;
386}
387
388int ProtocolHttp2::parseHeaders(ProtoRequestHttp2 *request, QIODevice *io, const H2Frame &fr) const
389{
390 // qCDebug(C_SERVER_H2) << "Consumming HEADERS" << bool(fr.flags & FlagHeadersEndStream);
391 if (fr.streamId == 0) {
392 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
393 }
394 if (fr.len > request->settingsMaxFrameSize) {
395 return sendGoAway(io, request->maxStreamId, ErrorFrameSizeError);
396 }
397 int pos = 0;
398 char *ptr = request->buffer + 9;
399 quint8 padLength = 0;
400 if (fr.flags & FlagHeadersPadded) {
401 padLength = quint8(*(ptr + pos));
402 if (padLength > fr.len) {
403 // qCDebug(C_SERVER_H2) << "header pad length";
404 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
405 }
406
407 pos += 1;
408 }
409
410 quint32 streamDependency = 0;
411 // quint8 weight = 0;
412 if (fr.flags & FlagHeadersPriority) {
413 // TODO disable exclusive bit
414 streamDependency = net_be32(ptr + pos);
415 if (fr.streamId == streamDependency) {
416 // qCDebug(C_SERVER_H2) << "header stream dep";
417 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
418 }
419
420 pos += 4;
421 // weight = quint8(*(ptr + pos)) + 1;
422 pos += 1;
423 }
424 ptr += pos;
425
426 H2Stream *stream;
427 auto streamIt = request->streams.constFind(fr.streamId);
428 if (streamIt != request->streams.constEnd()) {
429 stream = streamIt.value();
430
431 // qCDebug(C_SERVER_H2) << "------------" << !(fr.flags & FlagHeadersEndStream) <<
432 // stream->state << request->streamForContinuation ;
433
434 if (!(fr.flags & FlagHeadersEndStream) && stream->state == H2Stream::Open &&
435 request->streamForContinuation == 0) {
436 qCDebug(C_SERVER_H2) << "header FlagHeadersEndStream stream->headers.size()";
437 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
438 }
439 if (stream->state == H2Stream::HalfClosed && request->streamForContinuation == 0) {
440 return sendGoAway(io, request->maxStreamId, ErrorStreamClosed);
441 }
442 if (stream->state == H2Stream::Closed) {
443 return sendGoAway(io, request->maxStreamId, ErrorStreamClosed);
444 }
445 } else {
446 if (request->maxStreamId >= fr.streamId) {
447 // qCDebug(C_SERVER_H2) << "header maxStreamId ";
448 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
449 }
450 request->maxStreamId = fr.streamId;
451
452 stream = new H2Stream(fr.streamId, request->settingsInitialWindowSize, request);
453 if (useStats) {
454 stream->startOfRequest = std::chrono::steady_clock::now();
455 }
456 request->streams.insert(fr.streamId, stream);
457 }
458
459 if (stream->state == H2Stream::Idle) {
460 stream->state = H2Stream::Open;
461 }
462
463 if (fr.flags & FlagHeadersEndStream) {
464 stream->state = H2Stream::HalfClosed;
465 }
466
467 if (!request->hpack) {
468 request->hpack = new HPack(m_headerTableSize);
469 }
470
471 if (fr.flags & FlagHeadersEndHeaders) {
472 request->streamForContinuation = 0;
473 if (!request->headersBuffer.isEmpty()) {
474 request->headersBuffer.append(ptr, qint32(fr.len) - pos - padLength);
475 }
476 } else {
477 // qCDebug(C_SERVER_H2) << "Setting HEADERS for continuation on stream" <<
478 // fr.streamId;
479 request->streamForContinuation = fr.streamId;
480 request->headersBuffer.append(ptr, qint32(fr.len) - pos - padLength);
481 return 0;
482 }
483
484 quint8 *it;
485 quint8 *itEnd;
486 if (request->headersBuffer.size()) {
487 it = reinterpret_cast<quint8 *>(request->headersBuffer.begin());
488 itEnd = reinterpret_cast<quint8 *>(request->headersBuffer.end());
489 } else {
490 it = reinterpret_cast<quint8 *>(ptr);
491 itEnd = it + fr.len - pos - padLength;
492 }
493
494 int ret = request->hpack->decode(it, itEnd, stream);
495 if (ret) {
496 // qDebug() << "Headers parser error" << ret << QByteArray(ptr + pos, fr.len - pos -
497 // padLength).toHex();
498 return sendGoAway(io, request->maxStreamId, quint32(ret));
499 }
500
501 // qDebug() << "Headers" << padLength << streamDependency << weight << "stream headers size"
502 // << stream->headers /*<< QByteArray(ptr + pos, fr.len - pos - padLength).toHex()*/ << ret;
503
504 if ((stream->state == H2Stream::HalfClosed || fr.flags & FlagHeadersEndStream) &&
505 request->streamForContinuation == 0) {
506
507 // Process request
508 queueStream(request->sock, stream);
509 }
510
511 return 0;
512}
513
514int ProtocolHttp2::parsePriority(ProtoRequestHttp2 *sock, QIODevice *io, const H2Frame &fr) const
515{
516 // qDebug() << "Consumming PRIORITY";
517 if (fr.len != 5) {
518 return sendGoAway(io, sock->maxStreamId, ErrorFrameSizeError);
519 } else if (fr.streamId == 0) {
520 return sendGoAway(io, sock->maxStreamId, ErrorProtocolError);
521 }
522
523 uint pos = 0;
524 while (fr.len > pos) {
525 // TODO store/disable EXCLUSIVE bit
526 quint32 exclusiveAndStreamDep = net_be32(sock->buffer + 9 + pos);
527 // quint8 weigth = *reinterpret_cast<quint8 *>(sock->buffer + 9 + 4 + pos) + 1;
528 // settings.push_back({ identifier, value });
529 // sock->pktsize -= 6;
530
531 if (fr.streamId == exclusiveAndStreamDep) {
532 // qDebug() << "PRIO error2" << exclusiveAndStreamDep << fr.streamId;
533
534 return sendGoAway(io, sock->maxStreamId, ErrorProtocolError);
535 }
536
537 pos += 6;
538 // qDebug() << "PRIO" << exclusiveAndStreamDep << weigth;
539 }
540
541 return 0;
542}
543
544int ProtocolHttp2::parsePing(ProtoRequestHttp2 *request, QIODevice *io, const H2Frame &fr) const
545{
546 // qCDebug(C_SERVER_H2) << "Got PING" << fr.flags;
547 if (fr.len != 8) {
548 return sendGoAway(io, request->maxStreamId, ErrorFrameSizeError);
549 } else if (fr.streamId) {
550 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
551 }
552
553 if (!(fr.flags & FlagPingAck)) {
554 sendPing(io, FlagPingAck, request->buffer + 9, 8);
555 }
556 return 0;
557}
558
559int ProtocolHttp2::parseRstStream(ProtoRequestHttp2 *request,
560 QIODevice *io,
561 const H2Frame &fr) const
562{
563 // qCDebug(C_SERVER_H2) << "Consuming RST_STREAM";
564
565 if (fr.streamId == 0) {
566 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
567 } else if (request->pktsize != 4) {
568 return sendGoAway(io, request->maxStreamId, ErrorFrameSizeError);
569 }
570
571 H2Stream *stream;
572 auto streamIt = request->streams.constFind(fr.streamId);
573 if (streamIt != request->streams.constEnd()) {
574 stream = streamIt.value();
575
576 // qCDebug(C_SERVER_H2) << "Consuming RST_STREAM state" << stream->state;
577 if (stream->state == H2Stream::Idle) {
578 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
579 }
580
581 } else {
582 return sendGoAway(io, request->maxStreamId, ErrorStreamClosed);
583 }
584
585 stream->state = H2Stream::Closed;
586
587 // quint32 errorCode = h2_be32(request->buffer + 9);
588 // qCDebug(C_SERVER_H2) << "RST frame" << errorCode;
589
590 return 0;
591}
592
593int ProtocolHttp2::parseWindowUpdate(ProtoRequestHttp2 *request,
594 QIODevice *io,
595 const H2Frame &fr) const
596{
597 if (fr.len != 4) {
598 return sendGoAway(io, request->maxStreamId, ErrorFrameSizeError);
599 }
600
601 quint32 windowSizeIncrement = net_be32(request->buffer + 9);
602 if (windowSizeIncrement == 0) {
603 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
604 }
605
606 // qDebug() << "Consuming WINDOW_UPDATE" << fr.streamId << "increment" << windowSizeIncrement
607 // << request;
608
609 if (fr.streamId) {
610 H2Stream *stream;
611 auto streamIt = request->streams.constFind(fr.streamId);
612 if (streamIt != request->streams.constEnd()) {
613 stream = streamIt.value();
614
615 if (stream->state == H2Stream::Idle) {
616 return sendGoAway(io, request->maxStreamId, ErrorProtocolError);
617 }
618 } else {
619 return sendGoAway(io, request->maxStreamId, ErrorStreamClosed);
620 }
621
622 const qint64 result = qint64(stream->windowSize) + windowSizeIncrement;
623 if (result > 2147483647) {
624 stream->state = H2Stream::Closed;
625 return sendRstStream(io, fr.streamId, ErrorFlowControlError);
626 }
627 stream->windowSize = qint32(result);
628 stream->windowUpdated();
629
630 } else {
631 const qint64 result = qint64(request->windowSize) + windowSizeIncrement;
632 if (result > 2147483647) {
633 return sendGoAway(io, request->maxStreamId, ErrorFlowControlError);
634 }
635 request->windowSize = qint32(result);
636
637 if (result > 0) {
638 for (const auto &stream : std::as_const(request->streams)) {
639 stream->windowUpdated();
640 }
641 }
642 }
643
644 return 0;
645}
646
647int ProtocolHttp2::sendGoAway(QIODevice *io, quint32 lastStreamId, quint32 error) const
648{
649 // qDebug() << "GOAWAY" << error;
650 QByteArray data;
651 data.append(char(lastStreamId >> 24));
652 data.append(char(lastStreamId >> 16));
653 data.append(char(lastStreamId >> 8));
654 data.append(char(lastStreamId));
655 data.append(char(error >> 24));
656 data.append(char(error >> 16));
657 data.append(char(error >> 8));
658 data.append(char(error));
659 // quint64 data = error;
660 // sendFrame(io, FrameGoaway, 0, 0, reinterpret_cast<const char *>(&data), 4);
661 int ret = sendFrame(io, FrameGoaway, 0, 0, data.constData(), 8);
662 // qDebug() << ret << int(error);
663 return error || ret;
664}
665
666int ProtocolHttp2::sendRstStream(QIODevice *io, quint32 streamId, quint32 error) const
667{
668 // qDebug() << "RST_STREAM" << streamId << error;
669 QByteArray data;
670 data.append(char(error >> 24));
671 data.append(char(error >> 16));
672 data.append(char(error >> 8));
673 data.append(char(error));
674 // quint64 data = error;
675 // sendFrame(io, FrameGoaway, 0, 0, reinterpret_cast<const char *>(&data), 4);
676 int ret = sendFrame(io, FrameRstStream, 0, streamId, data.constData(), 4);
677 // qDebug() << ret << int(error);
678 return error || ret;
679}
680
681int ProtocolHttp2::sendSettings(QIODevice *io,
682 const std::vector<std::pair<quint16, quint32>> &settings) const
683{
684 QByteArray data;
685 for (const std::pair<quint16, quint32> &pair : settings) {
686 data.append(char(pair.first >> 8));
687 data.append(char(pair.first));
688 data.append(char(pair.second >> 24));
689 data.append(char(pair.second >> 16));
690 data.append(char(pair.second >> 8));
691 data.append(char(pair.second));
692 }
693 // qDebug() << "Send settings" << data.toHex();
694 return sendFrame(io, FrameSettings, 0, 0, data.constData(), data.length());
695}
696
697int ProtocolHttp2::sendSettingsAck(QIODevice *io) const
698{
699 return sendFrame(io, FrameSettings, FlagSettingsAck);
700}
701
702int ProtocolHttp2::sendPing(QIODevice *io, quint8 flags, const char *data, qint32 dataLen) const
703{
704 return sendFrame(io, FramePing, flags, 0, data, dataLen);
705}
706
707int ProtocolHttp2::sendData(QIODevice *io,
708 quint32 streamId,
709 qint32 windowSize,
710 const char *data,
711 qint32 dataLen) const
712{
713 if (windowSize < 1) {
714 // qDebug() << "Window size too small, holding";
715 return 0;
716 }
717 if (windowSize < dataLen) {
718 qint32 i = 0;
719 quint8 flags = 0;
720 while (i < dataLen) {
721 int ret = sendFrame(io, FrameData, flags, streamId, data + i, windowSize);
722 if (ret) {
723 return ret;
724 }
725 i += windowSize;
726 if ((i + 1) == dataLen) {
727 flags = FlagDataEndStream;
728 }
729 }
730 return 0;
731 } else {
732 return sendFrame(io, FrameData, FlagDataEndStream, streamId, data, dataLen);
733 }
734}
735
736int ProtocolHttp2::sendFrame(QIODevice *io,
737 quint8 type,
738 quint8 flags,
739 quint32 streamId,
740 const char *data,
741 qint32 dataLen) const
742{
743 h2_frame fr;
744
745 fr.size2 = quint8(dataLen >> 16);
746 fr.size1 = quint8(dataLen >> 8);
747 fr.size0 = quint8(dataLen);
748 fr.type = type;
749 fr.flags = flags;
750 fr.rbit_stream_id3 = quint8(streamId >> 24);
751 fr.rbit_stream_id2 = quint8(streamId >> 16);
752 fr.rbit_stream_id1 = quint8(streamId >> 8);
753 fr.rbit_stream_id0 = quint8(streamId);
754
755 // qCDebug(C_SERVER_H2) << "Sending frame"
756 // << type
757 // << flags
758 // << streamId
759 // << dataLen;
760
761 // qCDebug(C_SERVER_H2) << "Frame" << QByteArray(reinterpret_cast<const char *>(&fr),
762 // sizeof(struct h2_frame)).toHex();
763 if (io->write(reinterpret_cast<const char *>(&fr), sizeof(struct h2_frame)) !=
764 sizeof(struct h2_frame)) {
765 return -1;
766 }
767 // qCDebug(C_SERVER_H2) << "Frame data" << QByteArray(data, dataLen).toHex();
768 if (dataLen && io->write(data, dataLen) != dataLen) {
769 return -1;
770 }
771 return 0;
772}
773
774void ProtocolHttp2::queueStream(Socket *socket, H2Stream *stream) const
775{
776 ++socket->processing;
777 if (stream->body) {
778 stream->body->seek(0);
779 }
780 Q_EMIT socket->engine->processRequestAsync(stream);
781}
782
783bool ProtocolHttp2::upgradeH2C(Socket *socket,
784 QIODevice *io,
785 const Cutelyst::EngineRequest &request)
786{
787 const Cutelyst::Headers &headers = request.headers;
788 if (headers.header("Upgrade").compare("h2c") == 0 &&
789 headers.connection().compare("Upgrade, HTTP2-Settings") == 0) {
790 const auto settings = headers.header("Http2-Settings");
791 if (!settings.isEmpty()) {
792 io->write("HTTP/1.1 101 Switching Protocols\r\n"
793 "Connection: Upgrade\r\n"
794 "Upgrade: h2c\r\n\r\n");
795 socket->proto = this;
796 auto protoRequest = new ProtoRequestHttp2(socket, m_bufferSize);
797 protoRequest->upgradedFrom = socket->protoData;
798 socket->protoData = protoRequest;
799
800 protoRequest->hpack = new HPack(m_headerTableSize);
801 protoRequest->maxStreamId = 1;
802
803 auto stream = new H2Stream(1, 65535, protoRequest);
804 stream->method = request.method;
805 stream->path = request.path;
806 stream->query = request.query;
807 stream->remoteUser = request.remoteUser;
808 stream->headers = request.headers;
809 stream->startOfRequest = std::chrono::steady_clock::now();
810 stream->status = request.status;
811 stream->body = request.body;
812
813 stream->state = H2Stream::HalfClosed;
814 protoRequest->streams.insert(1, stream);
815 protoRequest->maxStreamId = 1;
816
817 sendSettings(io,
818 {
819 {SETTINGS_MAX_FRAME_SIZE, m_maxFrameSize},
820 {SETTINGS_HEADER_TABLE_SIZE, m_headerTableSize},
821 });
822
823 // Process request
824 queueStream(socket, stream);
825 qCDebug(C_SERVER_H2) << "upgraded";
826 return true;
827 }
828 }
829 return false;
830}
831
832ProtoRequestHttp2::ProtoRequestHttp2(Cutelyst::Socket *sock, int bufferSize)
833 : ProtocolData(sock, bufferSize)
834{
835}
836
837ProtoRequestHttp2::~ProtoRequestHttp2()
838{
839}
840
841void ProtoRequestHttp2::setupNewConnection(Socket *sock)
842{
843 Q_UNUSED(sock)
844}
845
846H2Stream::H2Stream(quint32 _streamId, qint32 _initialWindowSize, ProtoRequestHttp2 *protoRequestH2)
847 : protoRequest(protoRequestH2)
848 , streamId(_streamId)
849 , windowSize(_initialWindowSize)
850{
851 protocol = "HTTP/2"_ba;
852 serverAddress = protoRequestH2->sock->serverAddress;
853 remoteAddress = protoRequestH2->sock->remoteAddress;
854 remotePort = protoRequestH2->sock->remotePort;
855 isSecure = protoRequestH2->sock->isSecure;
856}
857
858H2Stream::~H2Stream()
859{
860 if (loop) {
861 loop->exit(-1);
862 delete loop;
863 }
864}
865
866qint64 H2Stream::doWrite(const char *data, qint64 len)
867{
868 int ret = -1;
869 auto parser = dynamic_cast<ProtocolHttp2 *>(protoRequest->sock->proto);
870
871 qint64 remainingData = len;
872 qint64 sent = 0;
873 while (remainingData > 0 && state != H2Stream::Closed) {
874 qint64 availableWindowSize = qMin(windowSize, protoRequest->windowSize);
875 // qCDebug(C_SERVER_H2) << "H2Stream::doWrite" << len << streamId <<
876 // "availableWindowSize" << availableWindowSize
877 // << "remaining data" << remainingData
878 // << "stream" << this << protoRequest;
879 availableWindowSize =
880 qMin(availableWindowSize, (qint64) protoRequest->settingsMaxFrameSize);
881 if (availableWindowSize == 0) {
882 if (!loop) {
883 loop = new QEventLoop;
884 }
885 if (loop->exec() == 0) {
886 continue;
887 }
888 return -1;
889 }
890
891 // qCDebug(C_SERVER_H2) << "H2Stream::doWrite" << len << streamId <<
892 // "availableWindowSize" << availableWindowSize
893 // << "remaining data" << remainingData;
894
895 if (availableWindowSize > remainingData) {
896 ret = parser->sendFrame(protoRequest->io,
897 FrameData,
898 FlagDataEndStream,
899 streamId,
900 data + sent,
901 qint32(remainingData));
902 remainingData = 0;
903 protoRequest->windowSize -= remainingData;
904 windowSize -= remainingData;
905 sent += remainingData;
906 } else {
907 ret = parser->sendFrame(protoRequest->io,
908 FrameData,
909 0x0,
910 streamId,
911 data + sent,
912 qint32(availableWindowSize));
913 remainingData -= availableWindowSize;
914 protoRequest->windowSize -= availableWindowSize;
915 windowSize -= availableWindowSize;
916 sent += availableWindowSize;
917 }
918
919 // qCDebug(C_SERVER_H2) << "H2Stream::doWrite ret" << ret << (ret == 0 ? len : -1);
920 }
921
922 return ret == 0 ? len : -1;
923}
924
925bool H2Stream::writeHeaders(quint16 status, const Cutelyst::Headers &headers)
926{
927 QByteArray buf;
928 protoRequest->hpack->encodeHeaders(
929 status, headers, buf, static_cast<ServerEngine *>(protoRequest->sock->engine));
930
931 auto parser = dynamic_cast<ProtocolHttp2 *>(protoRequest->sock->proto);
932
933 int ret = parser->sendFrame(protoRequest->io,
934 FrameHeaders,
935 FlagHeadersEndHeaders,
936 streamId,
937 buf.constData(),
938 buf.size());
939
940 return ret == 0;
941}
942
944{
945 state = Closed;
946 protoRequest->streams.remove(streamId);
947 protoRequest->sock->requestFinished();
948 delete this;
949}
950
951void H2Stream::windowUpdated()
952{
953 // qDebug() << "WINDOW_UPDATED" << protoRequest->windowSize << windowSize << loop << (loop &&
954 // loop->isRunning()) << this << protoRequest;
955
956 if (protoRequest->windowSize > 0 && windowSize > 0 && loop && loop->isRunning()) {
957 loop->quit();
958 }
959}
960
961#include "moc_protocolhttp2.cpp"
TimePointSteady startOfRequest
void processRequestAsync(Cutelyst::EngineRequest *request)
qint64 doWrite(const char *data, qint64 len) override final
void processingFinished() override final
bool writeHeaders(quint16 status, const Cutelyst::Headers &headers) override final
Container for HTTP headers.
Definition headers.h:24
QByteArray connection() const noexcept
Definition headers.cpp:296
QByteArray header(QAnyStringView key) const noexcept
Definition headers.cpp:393
Implements a web server.
Definition server.h:60
The Cutelyst namespace holds all public Cutelyst API.