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