1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:network-protocol
4
5#include "http2frames_p.h"
6
7#include <QtNetwork/qabstractsocket.h>
8
9#include <algorithm>
10#include <utility>
11
12QT_BEGIN_NAMESPACE
13
14namespace Http2
15{
16
17// HTTP/2 frames are defined by RFC7540, clauses 4 and 6.
18
19Frame::Frame()
20 : buffer(frameHeaderSize)
21{
22}
23
24FrameType Frame::type() const
25{
26 Q_ASSERT(buffer.size() >= frameHeaderSize);
27
28 if (int(buffer[3]) >= int(FrameType::LAST_FRAME_TYPE))
29 return FrameType::LAST_FRAME_TYPE;
30
31 return FrameType(buffer[3]);
32}
33
34quint32 Frame::streamID() const
35{
36 Q_ASSERT(buffer.size() >= frameHeaderSize);
37 return qFromBigEndian<quint32>(src: &buffer[5]);
38}
39
40FrameFlags Frame::flags() const
41{
42 Q_ASSERT(buffer.size() >= frameHeaderSize);
43 return FrameFlags(buffer[4]);
44}
45
46quint32 Frame::payloadSize() const
47{
48 Q_ASSERT(buffer.size() >= frameHeaderSize);
49 return buffer[0] << 16 | buffer[1] << 8 | buffer[2];
50}
51
52uchar Frame::padding() const
53{
54 Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
55
56 if (!flags().testFlag(flag: FrameFlag::PADDED))
57 return 0;
58
59 switch (type()) {
60 case FrameType::DATA:
61 case FrameType::PUSH_PROMISE:
62 case FrameType::HEADERS:
63 Q_ASSERT(buffer.size() > frameHeaderSize);
64 return buffer[frameHeaderSize];
65 default:
66 return 0;
67 }
68}
69
70bool Frame::priority(quint32 *streamID, uchar *weight) const
71{
72 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
73
74 if (buffer.size() <= frameHeaderSize)
75 return false;
76
77 const uchar *src = &buffer[0] + frameHeaderSize;
78 if (type() == FrameType::HEADERS && flags().testFlag(flag: FrameFlag::PADDED))
79 ++src;
80
81 if ((type() == FrameType::HEADERS && flags().testFlag(flag: FrameFlag::PRIORITY))
82 || type() == FrameType::PRIORITY) {
83 if (streamID)
84 *streamID = qFromBigEndian<quint32>(src);
85 if (weight)
86 *weight = src[4];
87 return true;
88 }
89
90 return false;
91}
92
93FrameStatus Frame::validateHeader() const
94{
95 // Should be called only on a frame with
96 // a complete header.
97 Q_ASSERT(buffer.size() >= frameHeaderSize);
98
99 const auto framePayloadSize = payloadSize();
100 // 4.2 Frame Size
101 if (framePayloadSize > maxPayloadSize)
102 return FrameStatus::sizeError;
103
104 switch (type()) {
105 case FrameType::SETTINGS:
106 // SETTINGS ACK can not have any payload.
107 // The payload of a SETTINGS frame consists of zero
108 // or more parameters, each consisting of an unsigned
109 // 16-bit setting identifier and an unsigned 32-bit value.
110 // Thus the payload size must be a multiple of 6.
111 if (flags().testFlag(flag: FrameFlag::ACK) ? framePayloadSize : framePayloadSize % 6)
112 return FrameStatus::sizeError;
113 break;
114 case FrameType::PRIORITY:
115 // 6.3 PRIORITY
116 if (framePayloadSize != 5)
117 return FrameStatus::sizeError;
118 break;
119 case FrameType::PING:
120 // 6.7 PING
121 if (framePayloadSize != 8)
122 return FrameStatus::sizeError;
123 break;
124 case FrameType::GOAWAY:
125 // 6.8 GOAWAY
126 if (framePayloadSize < 8)
127 return FrameStatus::sizeError;
128 break;
129 case FrameType::RST_STREAM:
130 case FrameType::WINDOW_UPDATE:
131 // 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
132 if (framePayloadSize != 4)
133 return FrameStatus::sizeError;
134 break;
135 case FrameType::PUSH_PROMISE:
136 // 6.6 PUSH_PROMISE
137 if (framePayloadSize < 4)
138 return FrameStatus::sizeError;
139 break;
140 default:
141 // DATA/HEADERS/CONTINUATION will be verified
142 // when we have payload.
143 // Frames of unknown types are ignored (5.1)
144 break;
145 }
146
147 return FrameStatus::goodFrame;
148}
149
150FrameStatus Frame::validatePayload() const
151{
152 // Should be called only on a complete frame with a valid header.
153 Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
154
155 // Ignored, 5.1
156 if (type() == FrameType::LAST_FRAME_TYPE)
157 return FrameStatus::goodFrame;
158
159 auto size = payloadSize();
160 Q_ASSERT(buffer.size() >= frameHeaderSize && size == buffer.size() - frameHeaderSize);
161
162 const uchar *src = size ? &buffer[0] + frameHeaderSize : nullptr;
163 const auto frameFlags = flags();
164 switch (type()) {
165 // 6.1 DATA, 6.2 HEADERS
166 case FrameType::DATA:
167 case FrameType::HEADERS:
168 if (frameFlags.testFlag(flag: FrameFlag::PADDED)) {
169 if (!size || size < src[0])
170 return FrameStatus::sizeError;
171 size -= src[0];
172 }
173 if (type() == FrameType::HEADERS && frameFlags.testFlag(flag: FrameFlag::PRIORITY)) {
174 if (size < 5)
175 return FrameStatus::sizeError;
176 }
177 break;
178 // 6.6 PUSH_PROMISE
179 case FrameType::PUSH_PROMISE:
180 if (frameFlags.testFlag(flag: FrameFlag::PADDED)) {
181 if (!size || size < src[0])
182 return FrameStatus::sizeError;
183 size -= src[0];
184 }
185
186 if (size < 4)
187 return FrameStatus::sizeError;
188 break;
189 default:
190 break;
191 }
192
193 return FrameStatus::goodFrame;
194}
195
196
197quint32 Frame::dataSize() const
198{
199 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
200
201 quint32 size = payloadSize();
202 if (flags().testFlag(flag: FrameFlag::PADDED)) {
203 const uchar pad = padding();
204 // + 1 one for a byte with padding number itself:
205 size -= pad + 1;
206 }
207
208 if (priority())
209 size -= 5;
210
211 return size;
212}
213
214quint32 Frame::hpackBlockSize() const
215{
216 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
217
218 const auto frameType = type();
219 Q_ASSERT(frameType == FrameType::HEADERS ||
220 frameType == FrameType::PUSH_PROMISE ||
221 frameType == FrameType::CONTINUATION);
222
223 quint32 size = dataSize();
224 if (frameType == FrameType::PUSH_PROMISE) {
225 Q_ASSERT(size >= 4);
226 size -= 4;
227 }
228
229 return size;
230}
231
232const uchar *Frame::dataBegin() const
233{
234 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
235 if (buffer.size() <= frameHeaderSize)
236 return nullptr;
237
238 const uchar *src = &buffer[0] + frameHeaderSize;
239 if (flags().testFlag(flag: FrameFlag::PADDED))
240 ++src;
241
242 if (priority())
243 src += 5;
244
245 return src;
246}
247
248const uchar *Frame::hpackBlockBegin() const
249{
250 Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
251
252 const auto frameType = type();
253 Q_ASSERT(frameType == FrameType::HEADERS ||
254 frameType == FrameType::PUSH_PROMISE ||
255 frameType == FrameType::CONTINUATION);
256
257 const uchar *begin = dataBegin();
258 if (frameType == FrameType::PUSH_PROMISE)
259 begin += 4; // That's a promised stream, skip it.
260 return begin;
261}
262
263FrameStatus FrameReader::read(QIODevice &socket)
264{
265 if (offset < frameHeaderSize) {
266 if (!readHeader(socket))
267 return FrameStatus::incompleteFrame;
268
269 const auto status = frame.validateHeader();
270 if (status != FrameStatus::goodFrame) {
271 // No need to read any payload.
272 return status;
273 }
274
275 if (Http2PredefinedParameters::maxPayloadSize < frame.payloadSize())
276 return FrameStatus::sizeError;
277
278 frame.buffer.resize(new_size: frame.payloadSize() + frameHeaderSize);
279 }
280
281 if (offset < frame.buffer.size() && !readPayload(socket))
282 return FrameStatus::incompleteFrame;
283
284 // Reset the offset, our frame can be re-used
285 // now (re-read):
286 offset = 0;
287
288 return frame.validatePayload();
289}
290
291bool FrameReader::readHeader(QIODevice &socket)
292{
293 Q_ASSERT(offset < frameHeaderSize);
294
295 auto &buffer = frame.buffer;
296 if (buffer.size() < frameHeaderSize)
297 buffer.resize(new_size: frameHeaderSize);
298
299 const auto chunkSize = socket.read(data: reinterpret_cast<char *>(&buffer[offset]),
300 maxlen: frameHeaderSize - offset);
301 if (chunkSize > 0)
302 offset += chunkSize;
303
304 return offset == frameHeaderSize;
305}
306
307bool FrameReader::readPayload(QIODevice &socket)
308{
309 Q_ASSERT(offset < frame.buffer.size());
310 Q_ASSERT(frame.buffer.size() > frameHeaderSize);
311
312 auto &buffer = frame.buffer;
313 // Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32.
314 const auto chunkSize = socket.read(data: reinterpret_cast<char *>(&buffer[offset]),
315 maxlen: qint64(buffer.size() - offset));
316 if (chunkSize > 0)
317 offset += quint32(chunkSize);
318
319 return offset == buffer.size();
320}
321
322FrameWriter::FrameWriter()
323{
324}
325
326FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID)
327{
328 start(type, flags, streamID);
329}
330
331void FrameWriter::setOutboundFrame(Frame &&newFrame)
332{
333 frame = std::move(newFrame);
334 updatePayloadSize();
335}
336
337void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID)
338{
339 auto &buffer = frame.buffer;
340
341 buffer.resize(new_size: frameHeaderSize);
342 // The first three bytes - payload size, which is 0 for now.
343 buffer[0] = 0;
344 buffer[1] = 0;
345 buffer[2] = 0;
346
347 buffer[3] = uchar(type);
348 buffer[4] = uchar(flags);
349
350 qToBigEndian(src: streamID, dest: &buffer[5]);
351}
352
353void FrameWriter::setPayloadSize(quint32 size)
354{
355 auto &buffer = frame.buffer;
356
357 Q_ASSERT(buffer.size() >= frameHeaderSize);
358 Q_ASSERT(size <= maxPayloadSize);
359
360 buffer[0] = size >> 16;
361 buffer[1] = size >> 8;
362 buffer[2] = size;
363}
364
365void FrameWriter::setType(FrameType type)
366{
367 Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
368 frame.buffer[3] = uchar(type);
369}
370
371void FrameWriter::setFlags(FrameFlags flags)
372{
373 Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
374 frame.buffer[4] = uchar(flags);
375}
376
377void FrameWriter::addFlag(FrameFlag flag)
378{
379 setFlags(frame.flags() | flag);
380}
381
382void FrameWriter::append(const uchar *begin, const uchar *end)
383{
384 Q_ASSERT(begin && end);
385 Q_ASSERT(begin < end);
386
387 frame.buffer.insert(position: frame.buffer.end(), first: begin, last: end);
388 updatePayloadSize();
389}
390
391void FrameWriter::updatePayloadSize()
392{
393 const quint32 size = quint32(frame.buffer.size() - frameHeaderSize);
394 Q_ASSERT(size <= maxPayloadSize);
395 setPayloadSize(size);
396}
397
398bool FrameWriter::write(QIODevice &socket) const
399{
400 auto &buffer = frame.buffer;
401 Q_ASSERT(buffer.size() >= frameHeaderSize);
402 // Do some sanity check first:
403
404 Q_ASSERT(int(frame.type()) < int(FrameType::LAST_FRAME_TYPE));
405 Q_ASSERT(frame.validateHeader() == FrameStatus::goodFrame);
406
407 const auto nWritten = socket.write(data: reinterpret_cast<const char *>(&buffer[0]),
408 len: buffer.size());
409 return nWritten != -1 && size_type(nWritten) == buffer.size();
410}
411
412bool FrameWriter::writeHEADERS(QIODevice &socket, quint32 sizeLimit)
413{
414 auto &buffer = frame.buffer;
415 Q_ASSERT(buffer.size() >= frameHeaderSize);
416
417 if (sizeLimit > quint32(maxPayloadSize))
418 sizeLimit = quint32(maxPayloadSize);
419
420 if (quint32(buffer.size() - frameHeaderSize) <= sizeLimit) {
421 addFlag(flag: FrameFlag::END_HEADERS);
422 updatePayloadSize();
423 return write(socket);
424 }
425
426 // Our HPACK block does not fit into the size limit, remove
427 // END_HEADERS bit from the first frame, we'll later set
428 // it on the last CONTINUATION frame:
429 setFlags(frame.flags() & ~FrameFlags(FrameFlag::END_HEADERS));
430 // Write a frame's header (not controlled by sizeLimit) and
431 // as many bytes of payload as we can within sizeLimit,
432 // then send CONTINUATION frames, as needed.
433 setPayloadSize(sizeLimit);
434 const quint32 firstChunkSize = frameHeaderSize + sizeLimit;
435 qint64 written = socket.write(data: reinterpret_cast<const char *>(&buffer[0]),
436 len: firstChunkSize);
437
438 if (written != qint64(firstChunkSize))
439 return false;
440
441 FrameWriter continuationWriter(FrameType::CONTINUATION, FrameFlag::EMPTY, frame.streamID());
442 quint32 offset = firstChunkSize;
443
444 while (offset != buffer.size()) {
445 const auto chunkSize = std::min(a: sizeLimit, b: quint32(buffer.size() - offset));
446 if (chunkSize + offset == buffer.size())
447 continuationWriter.addFlag(flag: FrameFlag::END_HEADERS);
448 continuationWriter.setPayloadSize(chunkSize);
449 if (!continuationWriter.write(socket))
450 return false;
451 written = socket.write(data: reinterpret_cast<const char *>(&buffer[offset]),
452 len: chunkSize);
453 if (written != qint64(chunkSize))
454 return false;
455
456 offset += chunkSize;
457 }
458
459 return true;
460}
461
462bool FrameWriter::writeDATA(QIODevice &socket, quint32 sizeLimit,
463 const uchar *src, quint32 size)
464{
465 // With DATA frame(s) we always have:
466 // 1) frame's header (9 bytes)
467 // 2) a separate payload (from QNonContiguousByteDevice).
468 // We either fit within a sizeLimit, or split into several
469 // DATA frames.
470
471 Q_ASSERT(src);
472
473 if (sizeLimit > quint32(maxPayloadSize))
474 sizeLimit = quint32(maxPayloadSize);
475 // We NEVER set END_STREAM, since QHttp2ProtocolHandler works with
476 // QNonContiguousByteDevice and this 'writeDATA' is probably
477 // not the last one for a given request.
478 // This has to be done externally (sending an empty DATA frame with END_STREAM).
479 for (quint32 offset = 0; offset != size;) {
480 const auto chunkSize = std::min(a: size - offset, b: sizeLimit);
481 setPayloadSize(chunkSize);
482 // Frame's header first:
483 if (!write(socket))
484 return false;
485 // Payload (if any):
486 if (chunkSize) {
487 const auto written = socket.write(data: reinterpret_cast<const char*>(src + offset),
488 len: chunkSize);
489 if (written != qint64(chunkSize))
490 return false;
491 }
492
493 offset += chunkSize;
494 }
495
496 return true;
497}
498
499} // Namespace Http2
500
501QT_END_NAMESPACE
502

source code of qtbase/src/network/access/http2/http2frames.cpp