1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:network-protocol
4
5#include "qhttpserverrequest_p.h"
6#include "qhttpserverparser_p.h"
7
8#include <QtNetwork/qhttpheaders.h>
9#if QT_CONFIG(http)
10#include <QtNetwork/private/qhttp2connection_p.h>
11#endif
12
13QT_BEGIN_NAMESPACE
14
15using namespace Qt::StringLiterals;
16
17namespace {
18
19QHttpServerRequest::Method parseRequestMethod(QByteArrayView str)
20{
21 if (str == "GET")
22 return QHttpServerRequest::Method::Get;
23 else if (str == "PUT")
24 return QHttpServerRequest::Method::Put;
25 else if (str == "DELETE")
26 return QHttpServerRequest::Method::Delete;
27 else if (str == "POST")
28 return QHttpServerRequest::Method::Post;
29 else if (str == "HEAD")
30 return QHttpServerRequest::Method::Head;
31 else if (str == "OPTIONS")
32 return QHttpServerRequest::Method::Options;
33 else if (str == "PATCH")
34 return QHttpServerRequest::Method::Patch;
35 else if (str == "CONNECT")
36 return QHttpServerRequest::Method::Connect;
37 else
38 return QHttpServerRequest::Method::Unknown;
39}
40
41} // anonymous namespace
42
43/*!
44 \internal
45*/
46bool QHttpServerParser::parseRequestLine(QByteArrayView line)
47{
48 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
49 auto i = line.indexOf(ch: ' ');
50 if (i == -1)
51 return false;
52 const auto requestMethod = line.first(n: i);
53 i++;
54
55 while (i < line.size() && line[i] == ' ')
56 i++;
57
58 auto j = line.indexOf(ch: ' ', from: i);
59 if (j == -1)
60 return false;
61
62 const auto requestUrl = line.sliced(pos: i, n: j - i);
63 i = j + 1;
64
65 while (i < line.size() && line[i] == ' ')
66 i++;
67
68 if (i >= line.size())
69 return false;
70
71 j = line.indexOf(ch: ' ', from: i);
72
73 const auto protocol = j == -1 ? line.sliced(pos: i) : line.sliced(pos: i, n: j - i);
74 if (protocol.size() != 8 || !protocol.startsWith(other: "HTTP"))
75 return false;
76
77 headerParser.setMajorVersion(protocol[5] - '0');
78 headerParser.setMinorVersion(protocol[7] - '0');
79 majorVersion = protocol[5] - '0';
80 minorVersion = protocol[7] - '0';
81
82 method = parseRequestMethod(str: requestMethod);
83 url = QUrl::fromEncoded(input: requestUrl.toByteArray());
84 return true;
85}
86
87/*!
88 \internal
89*/
90qsizetype QHttpServerParser::readRequestLine(QIODevice *socket)
91{
92 if (fragment.isEmpty()) {
93 // reserve bytes for the request line. This is better than always append() which reallocs
94 // the byte array
95 fragment.reserve(asize: 32);
96 }
97
98 qsizetype bytes = 0;
99 char c;
100 qsizetype haveRead = 0;
101
102 do {
103 haveRead = socket->read(data: &c, maxlen: 1);
104 if (haveRead == -1)
105 return -1; // unexpected EOF
106 else if (haveRead == 0)
107 break; // read more later
108 else if (haveRead == 1 && fragment.size() == 0
109 && (c == '\v' || c == '\n' || c == '\r' || c == ' ' || c == '\t'))
110 continue; // Ignore all whitespace that was trailing from a previous request on that
111 // socket
112
113 bytes++;
114
115 // allow both CRLF & LF (only) line endings
116 if (c == '\n') {
117 // remove the CR at the end
118 if (fragment.endsWith(c: '\r')) {
119 fragment.truncate(pos: fragment.size() - 1);
120 }
121 bool ok = parseRequestLine(line: fragment);
122 state = State::ReadingHeader;
123 fragment.clear();
124 if (!ok) {
125 return -1;
126 }
127 break;
128 } else {
129 fragment.append(c);
130 }
131 } while (haveRead == 1);
132
133 return bytes;
134}
135
136/*!
137 \internal
138*/
139qint64 QHttpServerParser::contentLength() const
140{
141 bool ok = false;
142 QByteArray value = headerParser.firstHeaderField(name: "content-length");
143 qint64 length = value.toULongLong(ok: &ok);
144 if (ok)
145 return length;
146 return -1; // the header field is not set
147}
148
149/*!
150 \internal
151*/
152qsizetype QHttpServerParser::readHeader(QIODevice *socket)
153{
154 if (fragment.isEmpty()) {
155 // according to
156 // https://maqentaer.com/devopera-static-backup/http/dev.opera.com/articles/view/mama-http-headers/index.html
157 // the average size of the header block is 381 bytes. reserve bytes. This is better than
158 // always append() which reallocs the byte array.
159 fragment.reserve(asize: 512);
160 }
161
162 qint64 bytes = 0;
163 char c = 0;
164 bool allHeaders = false;
165 qint64 haveRead = 0;
166 do {
167 haveRead = socket->read(data: &c, maxlen: 1);
168 if (haveRead == 0) {
169 // read more later
170 break;
171 } else if (haveRead == -1) {
172 // connection broke down
173 return -1;
174 } else {
175 fragment.append(c);
176 bytes++;
177
178 if (c == '\n') {
179 // check for possible header endings. As per HTTP rfc,
180 // the header endings will be marked by CRLFCRLF. But
181 // we will allow CRLFCRLF, CRLFLF, LFCRLF, LFLF
182 if (fragment.endsWith(bv: "\n\r\n") || fragment.endsWith(bv: "\n\n"))
183 allHeaders = true;
184
185 // there is another case: We have no headers. Then the fragment equals just the line
186 // ending
187 if ((fragment.size() == 2 && fragment.endsWith(bv: "\r\n"))
188 || (fragment.size() == 1 && fragment.endsWith(bv: "\n")))
189 allHeaders = true;
190 }
191 }
192 } while (!allHeaders && haveRead > 0);
193
194 // we received all headers now parse them
195 if (allHeaders) {
196 headerParser.parseHeaders(headers: fragment);
197 headers = headerParser.headers();
198 fragment.clear(); // next fragment
199
200 auto hostUrl = QString::fromUtf8(ba: headerField(name: "host"));
201 if (!hostUrl.isEmpty())
202 url.setAuthority(authority: hostUrl);
203
204 if (url.scheme().isEmpty()) {
205#if QT_CONFIG(ssl)
206 auto sslSocket = qobject_cast<QSslSocket *>(object: socket);
207 url.setScheme(sslSocket && sslSocket->isEncrypted() ? u"https"_s : u"http"_s);
208#else
209 url.setScheme(u"http"_s);
210#endif
211 }
212
213 if (url.host().isEmpty())
214 url.setHost(host: u"127.0.0.1"_s);
215
216 if (url.port() == -1)
217 url.setPort(port);
218
219 bodyLength = contentLength(); // cache the length
220 // cache isChunked() since it is called often
221 // FIXME: the RFC says that anything but "identity" should be interpreted as chunked (4.4
222 // [2])
223 chunkedTransferEncoding = headerField(name: "transfer-encoding").toLower().contains(bv: "chunked");
224
225 QByteArray connectionHeaderField = headerField(name: "connection");
226 upgrade = connectionHeaderField.toLower().contains(bv: "upgrade");
227
228 if (chunkedTransferEncoding || bodyLength > 0) {
229 if (headerField(name: "expect").compare(a: "100-continue", cs: Qt::CaseInsensitive) == 0)
230 state = State::ExpectContinue;
231 else
232 state = State::ReadingData;
233 } else {
234 state = State::AllDone;
235 }
236 }
237 return bytes;
238}
239
240/*!
241 \internal
242*/
243qsizetype QHttpServerParser::sendContinue(QIODevice *socket)
244{
245 qsizetype ret = socket->write(data: "HTTP/1.1 100 Continue\r\n\r\n");
246 state = State::ReadingData;
247 return ret;
248}
249
250/*!
251 \internal
252*/
253QHttpServerParser::QHttpServerParser(const QHostAddress &remoteAddress, quint16 remotePort,
254 const QHostAddress &localAddress, quint16 localPort)
255 : remoteAddress(remoteAddress),
256 remotePort(remotePort),
257 localAddress(localAddress),
258 localPort(localPort)
259{
260 clear();
261}
262
263/*!
264 \internal
265*/
266bool QHttpServerParser::parse(QIODevice *socket)
267{
268 qsizetype read;
269
270 do {
271 switch (state) {
272 case State::AllDone:
273 clear();
274 [[fallthrough]];
275 case State::NothingDone:
276 state = State::ReadingRequestLine;
277 [[fallthrough]];
278 case State::ReadingRequestLine:
279 read = readRequestLine(socket);
280 continue;
281 case State::ReadingHeader:
282 read = readHeader(socket);
283 continue;
284 case State::ExpectContinue:
285 read = sendContinue(socket);
286 continue;
287 case State::ReadingData:
288 if (chunkedTransferEncoding)
289 read = readRequestBodyChunked(socket);
290 else
291 read = readBodyFast(socket);
292
293 if (state == State::AllDone) {
294 body = bodyBuffer.readAll();
295 bodyBuffer.clear();
296 }
297
298 continue;
299 }
300 Q_UNREACHABLE(); // fixes GCC -Wmaybe-uninitialized warning on `read`
301 } while (state != State::AllDone && read > 0);
302
303 return read != -1;
304}
305
306#if QT_CONFIG(http)
307bool QHttpServerParser::parse(QHttp2Stream *socket)
308{
309 clear();
310
311 majorVersion = 2;
312 minorVersion = 0;
313
314 for (const auto &pair : socket->receivedHeaders()) {
315 if (pair.name == ":method") {
316 method = parseRequestMethod(str: pair.value);
317 } else if (pair.name == ":scheme") {
318 url.setScheme(QLatin1StringView(pair.value));
319 } else if (pair.name == ":authority") {
320 url.setAuthority(authority: QLatin1StringView(pair.value));
321 } else if (pair.name == ":path") {
322 auto path = QUrl::fromEncoded(input: pair.value);
323 url.setPath(path: path.path());
324 url.setQuery(query: path.query());
325 } else {
326 headerParser.appendHeaderField(name: pair.name, data: pair.value);
327 }
328 }
329 headers = headerParser.headers();
330
331 if (url.scheme().isEmpty())
332 url.setScheme(u"https"_s);
333
334 if (url.host().isEmpty())
335 url.setHost(host: u"127.0.0.1"_s);
336
337 if (url.port() == -1)
338 url.setPort(port);
339
340 bodyLength = contentLength(); // cache the length
341
342 body = socket->downloadBuffer().readAll();
343
344 return true;
345}
346#endif
347
348/*!
349 \internal
350*/
351void QHttpServerParser::clear()
352{
353 headerParser.clear();
354 bodyLength = -1;
355 contentRead = 0;
356 chunkedTransferEncoding = false;
357 lastChunkRead = false;
358 currentChunkRead = 0;
359 currentChunkSize = 0;
360 upgrade = false;
361 majorVersion = 0;
362 minorVersion = 0;
363
364 fragment.clear();
365 bodyBuffer.clear();
366
367 url.clear();
368 method = QHttpServerRequest::Method::Unknown;
369 headers.clear();
370 body.clear();
371}
372
373// The body reading functions were mostly copied from QHttpNetworkReplyPrivate
374
375/*!
376 \internal
377*/
378// note this function can only be used for non-chunked, non-compressed with
379// known content length
380qsizetype QHttpServerParser::readBodyFast(QIODevice *socket)
381{
382
383 qsizetype toBeRead = qMin(a: socket->bytesAvailable(), b: bodyLength - contentRead);
384 if (!toBeRead)
385 return 0;
386
387 QByteArray bd;
388 bd.resize(size: toBeRead);
389 qsizetype haveRead = socket->read(data: bd.data(), maxlen: toBeRead);
390 if (haveRead == -1) {
391 bd.clear();
392 return 0; // ### error checking here;
393 }
394 bd.resize(size: haveRead);
395
396 bodyBuffer.append(bd);
397
398 contentRead += haveRead;
399
400 if (contentRead == bodyLength)
401 state = State::AllDone;
402
403 return haveRead;
404}
405
406/*!
407 \internal
408*/
409qsizetype QHttpServerParser::readRequestBodyRaw(QIODevice *socket, qsizetype size)
410{
411 // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable()
412 qsizetype bytes = 0;
413 Q_ASSERT(socket);
414
415 int toBeRead = qMin<qsizetype>(a: 128 * 1024, b: qMin<qint64>(a: size, b: socket->bytesAvailable()));
416
417 while (toBeRead > 0) {
418 QByteArray byteData;
419 byteData.resize(size: toBeRead);
420 qsizetype haveRead = socket->read(data: byteData.data(), maxlen: byteData.size());
421 if (haveRead <= 0) {
422 // ### error checking here
423 byteData.clear();
424 return bytes;
425 }
426
427 byteData.resize(size: haveRead);
428 bodyBuffer.append(bd: byteData);
429 bytes += haveRead;
430 size -= haveRead;
431
432 toBeRead = qMin<qsizetype>(a: 128 * 1024, b: qMin<qsizetype>(a: size, b: socket->bytesAvailable()));
433 }
434 return bytes;
435}
436
437/*!
438 \internal
439*/
440qsizetype QHttpServerParser::readRequestBodyChunked(QIODevice *socket)
441{
442 qsizetype bytes = 0;
443 while (socket->bytesAvailable()) {
444 if (!lastChunkRead && currentChunkRead >= currentChunkSize) {
445 // For the first chunk and when we're done with a chunk
446 currentChunkSize = 0;
447 currentChunkRead = 0;
448 if (bytes) {
449 // After a chunk
450 char crlf[2];
451 // read the "\r\n" after the chunk
452 qsizetype haveRead = socket->read(data: crlf, maxlen: 2);
453 // FIXME: This code is slightly broken and not optimal. What if the 2 bytes are not
454 // available yet?! For nice reasons (the toLong in getChunkSize accepting \n at the
455 // beginning it right now still works, but we should definitely fix this.
456
457 if (haveRead != 2)
458 return bytes;
459 bytes += haveRead;
460 }
461 // Note that chunk size gets stored in currentChunkSize, what is returned is the bytes
462 // read
463 bytes += getChunkSize(socket, chunkSize: &currentChunkSize);
464 if (currentChunkSize == -1)
465 break;
466 }
467 // if the chunk size is 0, end of the stream
468 if (currentChunkSize == 0 || lastChunkRead) {
469 lastChunkRead = true;
470 // try to read the "\r\n" after the chunk
471 char crlf[2];
472 qsizetype haveRead = socket->read(data: crlf, maxlen: 2);
473 if (haveRead > 0)
474 bytes += haveRead;
475
476 if ((haveRead == 2 && crlf[0] == '\r' && crlf[1] == '\n')
477 || (haveRead == 1 && crlf[0] == '\n')) {
478 state = State::AllDone;
479 } else if (haveRead == 1 && crlf[0] == '\r') {
480 break; // Still waiting for the last \n
481 } else if (haveRead > 0) {
482 // If we read something else then CRLF, we need to close the channel.
483 // FIXME forceConnectionCloseEnabled = true;
484 state = State::AllDone;
485 }
486 break;
487 }
488
489 // otherwise, try to begin reading this chunk / to read what is missing for this chunk
490 qsizetype haveRead = readRequestBodyRaw(socket, size: currentChunkSize - currentChunkRead);
491 currentChunkRead += haveRead;
492 bytes += haveRead;
493
494 // ### error checking here
495 }
496 return bytes;
497}
498
499/*!
500 \internal
501*/
502qsizetype QHttpServerParser::getChunkSize(QIODevice *socket, qsizetype *chunkSize)
503{
504 qsizetype bytes = 0;
505 char crlf[2];
506 *chunkSize = -1;
507
508 int bytesAvailable = socket->bytesAvailable();
509 // FIXME rewrite to permanent loop without bytesAvailable
510 while (bytesAvailable > bytes) {
511 qsizetype sniffedBytes = socket->peek(data: crlf, maxlen: 2);
512 int fragmentSize = fragment.size();
513
514 // check the next two bytes for a "\r\n", skip blank lines
515 if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n')
516 || (fragmentSize > 1 && fragment.endsWith(c: '\r') && crlf[0] == '\n')) {
517 bytes += socket->read(data: crlf, maxlen: 1); // read the \r or \n
518 if (crlf[0] == '\r')
519 bytes += socket->read(data: crlf, maxlen: 1); // read the \n
520 bool ok = false;
521 // ignore the chunk-extension
522 fragment = fragment.mid(index: 0, len: fragment.indexOf(ch: ';')).trimmed();
523 *chunkSize = fragment.toLong(ok: &ok, base: 16);
524 fragment.clear();
525 break; // size done
526 } else {
527 // read the fragment to the buffer
528 char c = 0;
529 qsizetype haveRead = socket->read(data: &c, maxlen: 1);
530 if (haveRead < 0)
531 return -1;
532
533 bytes += haveRead;
534 fragment.append(c);
535 }
536 }
537
538 return bytes;
539}
540
541QT_END_NAMESPACE
542

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