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