1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qhttpserverrequest_p.h"
5
6#include <QtHttpServer/qhttpserverrequest.h>
7#include <QtNetwork/qhttpheaders.h>
8
9#include <QtCore/qdebug.h>
10#include <QtCore/qloggingcategory.h>
11#include <QtNetwork/qtcpsocket.h>
12#if QT_CONFIG(ssl)
13#include <QtNetwork/qsslsocket.h>
14#endif
15#if QT_CONFIG(http)
16#include <QtNetwork/private/qhttp2connection_p.h>
17#endif
18
19Q_LOGGING_CATEGORY(lc, "qt.httpserver.request")
20
21QT_BEGIN_NAMESPACE
22
23using namespace Qt::StringLiterals;
24
25#if !defined(QT_NO_DEBUG_STREAM)
26
27/*!
28 \fn QDebug QHttpServerRequest::operator<<(QDebug debug, const QHttpServerRequest &request)
29
30 Writes information about \a request to the \a debug stream.
31
32 \sa QDebug
33 */
34Q_HTTPSERVER_EXPORT QDebug operator<<(QDebug debug, const QHttpServerRequest &request)
35{
36 QDebugStateSaver saver(debug);
37 debug.nospace() << "QHttpServerRequest(";
38 debug << "(Url: " << request.url() << ")";
39 debug << "(Headers: " << request.headers() << ")";
40 debug << "(RemoteHost: " << request.remoteAddress() << ")";
41 debug << "(BodySize: " << request.body().size() << ")";
42 debug << ')';
43 return debug;
44}
45
46#endif
47
48namespace {
49
50QHttpServerRequest::Method parseRequestMethod(QByteArrayView str)
51{
52 if (str == "GET")
53 return QHttpServerRequest::Method::Get;
54 else if (str == "PUT")
55 return QHttpServerRequest::Method::Put;
56 else if (str == "DELETE")
57 return QHttpServerRequest::Method::Delete;
58 else if (str == "POST")
59 return QHttpServerRequest::Method::Post;
60 else if (str == "HEAD")
61 return QHttpServerRequest::Method::Head;
62 else if (str == "OPTIONS")
63 return QHttpServerRequest::Method::Options;
64 else if (str == "PATCH")
65 return QHttpServerRequest::Method::Patch;
66 else if (str == "CONNECT")
67 return QHttpServerRequest::Method::Connect;
68 else
69 return QHttpServerRequest::Method::Unknown;
70}
71
72} // anonymous namespace
73
74/*!
75 \internal
76*/
77bool QHttpServerRequestPrivate::parseRequestLine(QByteArrayView line)
78{
79 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
80 auto i = line.indexOf(ch: ' ');
81 if (i == -1)
82 return false;
83 const auto requestMethod = line.first(n: i);
84 i++;
85
86 while (i < line.size() && line[i] == ' ')
87 i++;
88
89 auto j = line.indexOf(ch: ' ', from: i);
90 if (j == -1)
91 return false;
92
93 const auto requestUrl = line.sliced(pos: i, n: j - i);
94 i = j + 1;
95
96 while (i < line.size() && line[i] == ' ')
97 i++;
98
99 if (i >= line.size())
100 return false;
101
102 j = line.indexOf(ch: ' ', from: i);
103
104 const auto protocol = j == -1 ? line.sliced(pos: i) : line.sliced(pos: i, n: j - i);
105 if (protocol.size() != 8 || !protocol.startsWith(other: "HTTP"))
106 return false;
107
108 parser.setMajorVersion(protocol[5] - '0');
109 parser.setMinorVersion(protocol[7] - '0');
110
111 method = parseRequestMethod(str: requestMethod);
112 url = QUrl::fromEncoded(input: requestUrl.toByteArray());
113 return true;
114}
115
116/*!
117 \internal
118*/
119qsizetype QHttpServerRequestPrivate::readRequestLine(QIODevice *socket)
120{
121 if (fragment.isEmpty()) {
122 // reserve bytes for the request line. This is better than always append() which reallocs
123 // the byte array
124 fragment.reserve(asize: 32);
125 }
126
127 qsizetype bytes = 0;
128 char c;
129 qsizetype haveRead = 0;
130
131 do {
132 haveRead = socket->read(data: &c, maxlen: 1);
133 if (haveRead == -1)
134 return -1; // unexpected EOF
135 else if (haveRead == 0)
136 break; // read more later
137 else if (haveRead == 1 && fragment.size() == 0
138 && (c == '\v' || c == '\n' || c == '\r' || c == ' ' || c == '\t'))
139 continue; // Ignore all whitespace that was trailing from a previous request on that
140 // socket
141
142 bytes++;
143
144 // allow both CRLF & LF (only) line endings
145 if (c == '\n') {
146 // remove the CR at the end
147 if (fragment.endsWith(c: '\r')) {
148 fragment.truncate(pos: fragment.size() - 1);
149 }
150 bool ok = parseRequestLine(line: fragment);
151 state = State::ReadingHeader;
152 fragment.clear();
153 if (!ok) {
154 return -1;
155 }
156 break;
157 } else {
158 fragment.append(c);
159 }
160 } while (haveRead == 1);
161
162 return bytes;
163}
164
165/*!
166 \internal
167*/
168qint64 QHttpServerRequestPrivate::contentLength() const
169{
170 bool ok = false;
171 QByteArray value = parser.firstHeaderField(name: "content-length");
172 qint64 length = value.toULongLong(ok: &ok);
173 if (ok)
174 return length;
175 return -1; // the header field is not set
176}
177
178/*!
179 \internal
180*/
181qsizetype QHttpServerRequestPrivate::readHeader(QIODevice *socket)
182{
183 if (fragment.isEmpty()) {
184 // according to
185 // https://maqentaer.com/devopera-static-backup/http/dev.opera.com/articles/view/mama-http-headers/index.html
186 // the average size of the header block is 381 bytes. reserve bytes. This is better than
187 // always append() which reallocs the byte array.
188 fragment.reserve(asize: 512);
189 }
190
191 qint64 bytes = 0;
192 char c = 0;
193 bool allHeaders = false;
194 qint64 haveRead = 0;
195 do {
196 haveRead = socket->read(data: &c, maxlen: 1);
197 if (haveRead == 0) {
198 // read more later
199 break;
200 } else if (haveRead == -1) {
201 // connection broke down
202 return -1;
203 } else {
204 fragment.append(c);
205 bytes++;
206
207 if (c == '\n') {
208 // check for possible header endings. As per HTTP rfc,
209 // the header endings will be marked by CRLFCRLF. But
210 // we will allow CRLFCRLF, CRLFLF, LFCRLF, LFLF
211 if (fragment.endsWith(bv: "\n\r\n") || fragment.endsWith(bv: "\n\n"))
212 allHeaders = true;
213
214 // there is another case: We have no headers. Then the fragment equals just the line
215 // ending
216 if ((fragment.size() == 2 && fragment.endsWith(bv: "\r\n"))
217 || (fragment.size() == 1 && fragment.endsWith(bv: "\n")))
218 allHeaders = true;
219 }
220 }
221 } while (!allHeaders && haveRead > 0);
222
223 // we received all headers now parse them
224 if (allHeaders) {
225 parser.parseHeaders(headers: fragment);
226 fragment.clear(); // next fragment
227
228 auto hostUrl = QString::fromUtf8(ba: headerField(name: "host"));
229 if (!hostUrl.isEmpty())
230 url.setAuthority(authority: hostUrl);
231
232 if (url.scheme().isEmpty()) {
233#if QT_CONFIG(ssl)
234 auto sslSocket = qobject_cast<QSslSocket *>(object: socket);
235 url.setScheme(sslSocket && sslSocket->isEncrypted() ? u"https"_s : u"http"_s);
236#else
237 url.setScheme(u"http"_s);
238#endif
239 }
240
241 if (url.host().isEmpty())
242 url.setHost(host: u"127.0.0.1"_s);
243
244 if (url.port() == -1)
245 url.setPort(port);
246
247 bodyLength = contentLength(); // cache the length
248
249 // cache isChunked() since it is called often
250 // FIXME: the RFC says that anything but "identity" should be interpreted as chunked (4.4
251 // [2])
252 chunkedTransferEncoding = headerField(name: "transfer-encoding").toLower().contains(bv: "chunked");
253
254 QByteArray connectionHeaderField = headerField(name: "connection");
255 upgrade = connectionHeaderField.toLower().contains(bv: "upgrade");
256
257 if (chunkedTransferEncoding || bodyLength > 0) {
258 if (headerField(name: "expect").compare(a: "100-continue", cs: Qt::CaseInsensitive) == 0)
259 state = State::ExpectContinue;
260 else
261 state = State::ReadingData;
262 } else {
263 state = State::AllDone;
264 }
265 }
266 return bytes;
267}
268
269/*!
270 \internal
271*/
272qsizetype QHttpServerRequestPrivate::sendContinue(QIODevice *socket)
273{
274 qsizetype ret = socket->write(data: "HTTP/1.1 100 Continue\r\n\r\n");
275 state = State::ReadingData;
276 return ret;
277}
278
279/*!
280 \internal
281*/
282QHttpServerRequestPrivate::QHttpServerRequestPrivate(const QHostAddress &remoteAddress,
283 quint16 remotePort,
284 const QHostAddress &localAddress,
285 quint16 localPort)
286 : remoteAddress(remoteAddress),
287 remotePort(remotePort),
288 localAddress(localAddress),
289 localPort(localPort)
290{
291 clear();
292}
293
294#if QT_CONFIG(ssl)
295/*!
296 \internal
297*/
298QHttpServerRequestPrivate::QHttpServerRequestPrivate(const QHostAddress &remoteAddress,
299 quint16 remotePort,
300 const QHostAddress &localAddress,
301 quint16 localPort,
302 const QSslConfiguration &sslConfiguration)
303 : remoteAddress(remoteAddress),
304 remotePort(remotePort),
305 localAddress(localAddress),
306 localPort(localPort),
307 sslConfiguration(sslConfiguration)
308{
309 clear();
310}
311#endif
312
313/*!
314 \internal
315*/
316bool QHttpServerRequestPrivate::parse(QIODevice *socket)
317{
318 qsizetype read;
319
320 do {
321 switch (state) {
322 case State::AllDone:
323 clear();
324 [[fallthrough]];
325 case State::NothingDone:
326 state = State::ReadingRequestLine;
327 [[fallthrough]];
328 case State::ReadingRequestLine:
329 read = readRequestLine(socket);
330 continue;
331 case State::ReadingHeader:
332 read = readHeader(socket);
333 continue;
334 case State::ExpectContinue:
335 read = sendContinue(socket);
336 continue;
337 case State::ReadingData:
338 if (chunkedTransferEncoding)
339 read = readRequestBodyChunked(socket);
340 else
341 read = readBodyFast(socket);
342
343 if (state == State::AllDone) {
344 body = bodyBuffer.readAll();
345 bodyBuffer.clear();
346 }
347
348 continue;
349 }
350 Q_UNREACHABLE(); // fixes GCC -Wmaybe-uninitialized warning on `read`
351 } while (state != State::AllDone && read > 0);
352
353 return read != -1;
354}
355
356#if QT_CONFIG(http)
357bool QHttpServerRequestPrivate::parse(QHttp2Stream *socket)
358{
359 parser.clear();
360
361 for (const auto &pair : socket->receivedHeaders()) {
362 if (pair.name == ":method") {
363 method = parseRequestMethod(str: pair.value);
364 } else if (pair.name == ":scheme") {
365 url.setScheme(QLatin1StringView(pair.value));
366 } else if (pair.name == ":authority") {
367 url.setAuthority(authority: QLatin1StringView(pair.value));
368 } else if (pair.name == ":path") {
369 auto path = QUrl::fromEncoded(input: pair.value);
370 url.setPath(path: path.path());
371 url.setQuery(query: path.query());
372 } else {
373 parser.appendHeaderField(name: pair.name, data: pair.value);
374 }
375 }
376
377 if (url.scheme().isEmpty())
378 url.setScheme(u"https"_s);
379
380 if (url.host().isEmpty())
381 url.setHost(host: u"127.0.0.1"_s);
382
383 if (url.port() == -1)
384 url.setPort(port);
385
386 bodyLength = contentLength(); // cache the length
387
388 body = socket->downloadBuffer().readAll();
389
390 return true;
391}
392#endif
393
394/*!
395 \internal
396*/
397void QHttpServerRequestPrivate::clear()
398{
399 parser.clear();
400 bodyLength = -1;
401 contentRead = 0;
402 chunkedTransferEncoding = false;
403 lastChunkRead = false;
404 currentChunkRead = 0;
405 currentChunkSize = 0;
406 upgrade = false;
407
408 fragment.clear();
409 bodyBuffer.clear();
410 body.clear();
411}
412
413// The body reading functions were mostly copied from QHttpNetworkReplyPrivate
414
415/*!
416 \internal
417*/
418// note this function can only be used for non-chunked, non-compressed with
419// known content length
420qsizetype QHttpServerRequestPrivate::readBodyFast(QIODevice *socket)
421{
422
423 qsizetype toBeRead = qMin(a: socket->bytesAvailable(), b: bodyLength - contentRead);
424 if (!toBeRead)
425 return 0;
426
427 QByteArray bd;
428 bd.resize(size: toBeRead);
429 qsizetype haveRead = socket->read(data: bd.data(), maxlen: toBeRead);
430 if (haveRead == -1) {
431 bd.clear();
432 return 0; // ### error checking here;
433 }
434 bd.resize(size: haveRead);
435
436 bodyBuffer.append(bd);
437
438 contentRead += haveRead;
439
440 if (contentRead == bodyLength)
441 state = State::AllDone;
442
443 return haveRead;
444}
445
446/*!
447 \internal
448*/
449qsizetype QHttpServerRequestPrivate::readRequestBodyRaw(QIODevice *socket, qsizetype size)
450{
451 // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable()
452 qsizetype bytes = 0;
453 Q_ASSERT(socket);
454
455 int toBeRead = qMin<qsizetype>(a: 128 * 1024, b: qMin<qint64>(a: size, b: socket->bytesAvailable()));
456
457 while (toBeRead > 0) {
458 QByteArray byteData;
459 byteData.resize(size: toBeRead);
460 qsizetype haveRead = socket->read(data: byteData.data(), maxlen: byteData.size());
461 if (haveRead <= 0) {
462 // ### error checking here
463 byteData.clear();
464 return bytes;
465 }
466
467 byteData.resize(size: haveRead);
468 bodyBuffer.append(bd: byteData);
469 bytes += haveRead;
470 size -= haveRead;
471
472 toBeRead = qMin<qsizetype>(a: 128 * 1024, b: qMin<qsizetype>(a: size, b: socket->bytesAvailable()));
473 }
474 return bytes;
475}
476
477/*!
478 \internal
479*/
480qsizetype QHttpServerRequestPrivate::readRequestBodyChunked(QIODevice *socket)
481{
482 qsizetype bytes = 0;
483 while (socket->bytesAvailable()) {
484 if (!lastChunkRead && currentChunkRead >= currentChunkSize) {
485 // For the first chunk and when we're done with a chunk
486 currentChunkSize = 0;
487 currentChunkRead = 0;
488 if (bytes) {
489 // After a chunk
490 char crlf[2];
491 // read the "\r\n" after the chunk
492 qsizetype haveRead = socket->read(data: crlf, maxlen: 2);
493 // FIXME: This code is slightly broken and not optimal. What if the 2 bytes are not
494 // available yet?! For nice reasons (the toLong in getChunkSize accepting \n at the
495 // beginning it right now still works, but we should definitely fix this.
496
497 if (haveRead != 2)
498 return bytes;
499 bytes += haveRead;
500 }
501 // Note that chunk size gets stored in currentChunkSize, what is returned is the bytes
502 // read
503 bytes += getChunkSize(socket, chunkSize: &currentChunkSize);
504 if (currentChunkSize == -1)
505 break;
506 }
507 // if the chunk size is 0, end of the stream
508 if (currentChunkSize == 0 || lastChunkRead) {
509 lastChunkRead = true;
510 // try to read the "\r\n" after the chunk
511 char crlf[2];
512 qsizetype haveRead = socket->read(data: crlf, maxlen: 2);
513 if (haveRead > 0)
514 bytes += haveRead;
515
516 if ((haveRead == 2 && crlf[0] == '\r' && crlf[1] == '\n')
517 || (haveRead == 1 && crlf[0] == '\n')) {
518 state = State::AllDone;
519 } else if (haveRead == 1 && crlf[0] == '\r') {
520 break; // Still waiting for the last \n
521 } else if (haveRead > 0) {
522 // If we read something else then CRLF, we need to close the channel.
523 // FIXME forceConnectionCloseEnabled = true;
524 state = State::AllDone;
525 }
526 break;
527 }
528
529 // otherwise, try to begin reading this chunk / to read what is missing for this chunk
530 qsizetype haveRead = readRequestBodyRaw(socket, size: currentChunkSize - currentChunkRead);
531 currentChunkRead += haveRead;
532 bytes += haveRead;
533
534 // ### error checking here
535 }
536 return bytes;
537}
538
539/*!
540 \internal
541*/
542qsizetype QHttpServerRequestPrivate::getChunkSize(QIODevice *socket, qsizetype *chunkSize)
543{
544 qsizetype bytes = 0;
545 char crlf[2];
546 *chunkSize = -1;
547
548 int bytesAvailable = socket->bytesAvailable();
549 // FIXME rewrite to permanent loop without bytesAvailable
550 while (bytesAvailable > bytes) {
551 qsizetype sniffedBytes = socket->peek(data: crlf, maxlen: 2);
552 int fragmentSize = fragment.size();
553
554 // check the next two bytes for a "\r\n", skip blank lines
555 if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n')
556 || (fragmentSize > 1 && fragment.endsWith(c: '\r') && crlf[0] == '\n')) {
557 bytes += socket->read(data: crlf, maxlen: 1); // read the \r or \n
558 if (crlf[0] == '\r')
559 bytes += socket->read(data: crlf, maxlen: 1); // read the \n
560 bool ok = false;
561 // ignore the chunk-extension
562 fragment = fragment.mid(index: 0, len: fragment.indexOf(ch: ';')).trimmed();
563 *chunkSize = fragment.toLong(ok: &ok, base: 16);
564 fragment.clear();
565 break; // size done
566 } else {
567 // read the fragment to the buffer
568 char c = 0;
569 qsizetype haveRead = socket->read(data: &c, maxlen: 1);
570 if (haveRead < 0)
571 return -1;
572
573 bytes += haveRead;
574 fragment.append(c);
575 }
576 }
577
578 return bytes;
579}
580
581/*!
582 \class QHttpServerRequest
583 \since 6.4
584 \inmodule QtHttpServer
585 \brief Encapsulates an HTTP request.
586
587 API for accessing the different parameters of an incoming request.
588*/
589
590/*!
591 \enum QHttpServerRequest::Method
592
593 This enum type specifies an HTTP request method:
594
595 \value Unknown
596 An unknown method.
597 \value Get
598 HTTP GET method.
599 \value Put
600 HTTP PUT method.
601 \value Delete
602 HTTP DELETE method.
603 \value Post
604 HTTP POST method.
605 \value Head
606 HTTP HEAD method.
607 \value Options
608 HTTP OPTIONS method.
609 \value Patch
610 HTTP PATCH method (\l {https://www.rfc-editor.org/rfc/rfc5789}{RFC 5789}).
611 \value Connect
612 HTTP CONNECT method.
613 \value Trace
614 HTTP TRACE method.
615 \value AnyKnown
616 Combination of all known methods.
617*/
618
619/*!
620 \internal
621*/
622QHttpServerRequest::QHttpServerRequest(const QHostAddress &remoteAddress, quint16 remotePort,
623 const QHostAddress &localAddress, quint16 localPort)
624 : d(new QHttpServerRequestPrivate(remoteAddress, remotePort, localAddress, localPort))
625{}
626
627#if QT_CONFIG(ssl)
628/*!
629 \internal
630*/
631QHttpServerRequest::QHttpServerRequest(const QHostAddress &remoteAddress, quint16 remotePort,
632 const QHostAddress &localAddress, quint16 localPort,
633 const QSslConfiguration &sslConfiguration)
634 : d(new QHttpServerRequestPrivate(remoteAddress, remotePort,
635 localAddress, localPort,
636 sslConfiguration))
637{}
638#endif
639
640/*!
641 Destroys a QHttpServerRequest
642*/
643QHttpServerRequest::~QHttpServerRequest()
644{}
645
646/*!
647 Returns the combined value of all headers with the named \a key.
648*/
649QByteArray QHttpServerRequest::value(const QByteArray &key) const
650{
651 return d->parser.combinedHeaderValue(name: key);
652}
653
654/*!
655 Returns the URL the request asked for.
656*/
657QUrl QHttpServerRequest::url() const
658{
659 return d->url;
660}
661
662/*!
663 Returns the query in the request.
664*/
665QUrlQuery QHttpServerRequest::query() const
666{
667 return QUrlQuery(d->url.query());
668}
669
670/*!
671 Returns the method of the request.
672*/
673QHttpServerRequest::Method QHttpServerRequest::method() const
674{
675 return d->method;
676}
677
678/*!
679 \fn const QHttpHeaders &QHttpServerRequest::headers() const &
680 \fn QHttpHeaders QHttpServerRequest::headers() &&
681
682 Returns all the request headers.
683*/
684const QHttpHeaders &QHttpServerRequest::headers() const &
685{
686 return d->parser.headers();
687}
688
689QHttpHeaders QHttpServerRequest::headers() &&
690{
691 return std::move(d->parser).headers();
692}
693
694/*!
695 Returns the body of the request.
696*/
697QByteArray QHttpServerRequest::body() const
698{
699 return d->body;
700}
701
702/*!
703 Returns the address of the origin host of the request.
704*/
705QHostAddress QHttpServerRequest::remoteAddress() const
706{
707 return d->remoteAddress;
708}
709
710/*!
711 Returns the port of the origin host of the request.
712
713 \since 6.5
714*/
715quint16 QHttpServerRequest::remotePort() const
716{
717 return d->remotePort;
718}
719
720/*!
721 Returns the host address of the local socket which received the request.
722
723 \since 6.5
724*/
725QHostAddress QHttpServerRequest::localAddress() const
726{
727 return d->localAddress;
728}
729
730/*!
731 Returns the port of the local socket which received the request.
732
733 \since 6.5
734*/
735quint16 QHttpServerRequest::localPort() const
736{
737 return d->localPort;
738}
739
740#if QT_CONFIG(ssl)
741/*!
742 Returns the configuration of the established TLS connection.
743 The configurations will return true for isNull() if the connection
744 is not using TLS.
745
746 \since 6.7
747*/
748QSslConfiguration QHttpServerRequest::sslConfiguration() const
749{
750 return d->sslConfiguration;
751}
752#endif
753
754QT_END_NAMESPACE
755
756#include "moc_qhttpserverrequest.cpp"
757

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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