Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qhttpnetworkreply.cpp
Go to the documentation of this file.
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
6
7#ifndef QT_NO_SSL
8# include <QtNetwork/qsslkey.h>
9# include <QtNetwork/qsslcipher.h>
10# include <QtNetwork/qsslconfiguration.h>
11#endif
12
13#include <private/qdecompresshelper_p.h>
14
16
17using namespace Qt::StringLiterals;
18
23
25{
27 if (d->connection) {
28 d->connection->d_func()->removeReply(this);
29 }
30}
31
33{
34 return d_func()->url;
35}
37{
39 d->url = url;
40}
41
43{
44 return d_func()->redirectUrl;
45}
46
48{
50 d->redirectUrl = url;
51}
52
54{
55 return (statusCode == 301 || statusCode == 302 || statusCode == 303
56 || statusCode == 305 || statusCode == 307 || statusCode == 308);
57}
58
60{
61 return d_func()->contentLength();
62}
63
65{
67 d->setContentLength(length);
68}
69
71{
72 return d_func()->parser.headers();
73}
74
76{
77 return d_func()->headerField(name, defaultValue);
78}
79
81{
83 d->setHeaderField(name, data);
84}
85
87{
89 d->appendHeaderField(name, data);
90}
91
97
99{
100 return d_func()->request;
101}
102
104{
106 d->request = request;
107 d->ssl = request.isSsl();
108}
109
111{
112 return d_func()->parser.getStatusCode();
113}
114
116{
118 d->parser.setStatusCode(code);
119}
120
122{
123 return d_func()->errorString;
124}
125
127{
128 return d_func()->httpErrorCode;
129}
130
132{
133 return d_func()->parser.getReasonPhrase();
134}
135
137{
138 d_func()->parser.setReasonPhrase(reason);
139}
140
142{
144 d->errorString = error;
145}
146
148{
149 return d_func()->parser.getMajorVersion();
150}
151
153{
154 return d_func()->parser.getMinorVersion();
155}
156
158{
159 d_func()->parser.setMajorVersion(version);
160}
161
163{
164 d_func()->parser.setMinorVersion(version);
165}
166
168{
169 Q_D(const QHttpNetworkReply);
170 if (d->connection)
171 return d->connection->d_func()->uncompressedBytesAvailable(*this);
172 else
173 return -1;
174}
175
177{
178 Q_D(const QHttpNetworkReply);
179 if (d->connection)
180 return d->connection->d_func()->uncompressedBytesAvailableNextBlock(*this);
181 else
182 return -1;
183}
184
186{
187 Q_D(const QHttpNetworkReply);
188 return (d->responseData.bufferCount() > 0);
189}
190
192{
194 if (d->responseData.bufferCount() == 0)
195 return QByteArray();
196
197 // we'll take the last buffer, so schedule another read from http
198 if (d->downstreamLimited && d->responseData.bufferCount() == 1 && !isFinished())
199 d->connection->d_func()->readMoreLater(this);
200 return d->responseData.read();
201}
202
204{
206 return d->responseData.readAll();
207}
208
210{
212 return d->responseData.read(amount);
213}
214
215
217{
219 return d->responseData.sizeNextBlock();
220}
221
223{
225 d->downstreamLimited = dsl;
226 d->connection->d_func()->readMoreLater(this);
227}
228
230{
232 d->readBufferMaxSize = size;
233}
234
236{
238 return !d->isChunked() && !d->autoDecompress &&
239 d->bodyLength > 0 && d->parser.getStatusCode() == 200;
240}
241
243{
246 d->userProvidedDownloadBuffer = b;
247}
248
250{
252 return d->userProvidedDownloadBuffer;
253}
254
260
262{
263 return d_func()->state == QHttpNetworkReplyPrivate::Aborted;
264}
265
267{
268 return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState;
269}
270
272{
273 return d_func()->pipeliningUsed;
274}
275
277{
278 return d_func()->h2Used;
279}
280
282{
283 d_func()->h2Used = h2;
284}
285
287{
288 return d_func()->removedContentLength;
289}
290
292{
293 return d_func()->isRedirecting();
294}
295
297{
298 return d_func()->connection;
299}
300
301
304 , state(NothingDoneState)
305 , ssl(false),
306 bodyLength(0), contentRead(0), totalProgress(0),
307 chunkedTransferEncoding(false),
308 connectionCloseEnabled(true),
309 forceConnectionCloseEnabled(false),
310 lastChunkRead(false),
311 currentChunkSize(0), currentChunkRead(0), readBufferMaxSize(0),
312 totallyUploadedData(0),
313 removedContentLength(-1),
315 autoDecompress(false), responseData(), requestIsPrepared(false)
316 ,pipeliningUsed(false), h2Used(false), downstreamLimited(false)
317 ,userProvidedDownloadBuffer(nullptr)
318
319{
320 QString scheme = newUrl.scheme();
321 if (scheme == "preconnect-http"_L1 || scheme == "preconnect-https"_L1)
322 // make sure we do not close the socket after preconnecting
324}
325
327
340
341// TODO: Isn't everything HTTP layer related? We don't need to set connection and connectionChannel to 0 at all
343{
344 connection = nullptr;
345 connectionChannel = nullptr;
346 autoDecompress = false;
348}
349
350// QHttpNetworkReplyPrivate
355
360
362{
363 Q_D(const QHttpNetworkReply);
364 return d->isCompressed();
365}
366
368{
369 // The header "Content-Encoding = gzip" is retained.
370 // Content-Length is removed since the actual one sent by the server is for compressed data
371 constexpr auto name = QByteArrayView("content-length");
373 bool parseOk = false;
374 qint64 value = contentLength.toLongLong(&parseOk);
375 if (parseOk) {
378 }
379}
380
382{
383 if (fragment.isEmpty()) {
384 // reserve bytes for the status line. This is better than always append() which reallocs the byte array
385 fragment.reserve(32);
386 }
387
388 qint64 bytes = 0;
389 char c;
390 qint64 haveRead = 0;
391
392 do {
393 haveRead = socket->read(&c, 1);
394 if (haveRead == -1)
395 return -1; // unexpected EOF
396 else if (haveRead == 0)
397 break; // read more later
398 else if (haveRead == 1 && fragment.size() == 0 && (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31))
399 continue; // Ignore all whitespace that was trailing froma previous request on that socket
400
401 bytes++;
402
403 // allow both CRLF & LF (only) line endings
404 if (c == '\n') {
405 // remove the CR at the end
406 if (fragment.endsWith('\r')) {
408 }
409 bool ok = parseStatus(fragment);
411 fragment.clear();
412 if (!ok) {
413 return -1;
414 }
415 break;
416 } else {
418 }
419
420 // is this a valid reply?
421 if (fragment.size() == 5 && !fragment.startsWith("HTTP/")) {
422 fragment.clear();
423 return -1;
424 }
425 } while (haveRead == 1);
426
427 return bytes;
428}
429
434
436{
437 if (fragment.isEmpty()) {
438 // according to http://dev.opera.com/articles/view/mama-http-headers/ the average size of the header
439 // block is 381 bytes.
440 // reserve bytes. This is better than always append() which reallocs the byte array.
441 fragment.reserve(512);
442 }
443
444 qint64 bytes = 0;
445 char c = 0;
446 bool allHeaders = false;
447 qint64 haveRead = 0;
448 do {
449 haveRead = socket->read(&c, 1);
450 if (haveRead == 0) {
451 // read more later
452 break;
453 } else if (haveRead == -1) {
454 // connection broke down
455 return -1;
456 } else {
458 bytes++;
459
460 if (c == '\n') {
461 // check for possible header endings. As per HTTP rfc,
462 // the header endings will be marked by CRLFCRLF. But
463 // we will allow CRLFCRLF, CRLFLF, LFCRLF, LFLF
464 if (fragment.endsWith("\n\r\n")
465 || fragment.endsWith("\n\n"))
466 allHeaders = true;
467
468 // there is another case: We have no headers. Then the fragment equals just the line ending
469 if ((fragment.size() == 2 && fragment.endsWith("\r\n"))
470 || (fragment.size() == 1 && fragment.endsWith("\n")))
471 allHeaders = true;
472 }
473 }
474 } while (!allHeaders && haveRead > 0);
475
476 // we received all headers now parse them
477 if (allHeaders) {
480 fragment.clear(); // next fragment
481 bodyLength = contentLength(); // cache the length
482
483 // cache isChunked() since it is called often
484 chunkedTransferEncoding = headerField("transfer-encoding").toLower().contains("chunked");
485
486 // cache isConnectionCloseEnabled since it is called often
487 QByteArray connectionHeaderField = headerField("connection");
488 // check for explicit indication of close or the implicit connection close of HTTP/1.0
489 connectionCloseEnabled = (connectionHeaderField.toLower().contains("close") ||
490 headerField("proxy-connection").toLower().contains("close")) ||
491 (parser.getMajorVersion() == 1 && parser.getMinorVersion() == 0 &&
492 (connectionHeaderField.isEmpty() && !headerField("proxy-connection").toLower().contains("keep-alive")));
493 }
494 return bytes;
495}
496
501
506
511
516
517// note this function can only be used for non-chunked, non-compressed with
518// known content length
520{
521 // This first read is to flush the buffer inside the socket
522 qint64 haveRead = 0;
523 haveRead = socket->read(b, bodyLength - contentRead);
524 if (haveRead == -1) {
525 return -1;
526 }
527 contentRead += haveRead;
528
529 if (contentRead == bodyLength) {
531 }
532
533 return haveRead;
534}
535
536// note this function can only be used for non-chunked, non-compressed with
537// known content length
539{
540
543 toBeRead = qMin(toBeRead, readBufferMaxSize);
544
545 if (!toBeRead)
546 return 0;
547
548 QByteArray bd;
549 bd.resize(toBeRead);
550 qint64 haveRead = socket->read(bd.data(), toBeRead);
551 if (haveRead == -1) {
552 bd.clear();
553 return 0; // ### error checking here;
554 }
555 bd.resize(haveRead);
556
557 rb->append(bd);
558
559 if (contentRead + haveRead == bodyLength) {
561 }
562
563 contentRead += haveRead;
564 return haveRead;
565}
566
567
569{
570 qint64 bytes = 0;
571
572 if (isChunked()) {
573 // chunked transfer encoding (rfc 2616, sec 3.6)
575 } else if (bodyLength > 0) {
576 // we have a Content-Length
578 if (contentRead + bytes == bodyLength)
580 } else {
581 // no content length. just read what's possible
583 }
584 contentRead += bytes;
585 return bytes;
586}
587
589{
590 // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable()
591 qint64 bytes = 0;
593 Q_ASSERT(out);
594
595 int toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, socket->bytesAvailable()));
596
598 toBeRead = qMin<qint64>(toBeRead, readBufferMaxSize);
599
600 while (toBeRead > 0) {
601 QByteArray byteData;
602 byteData.resize(toBeRead);
603 qint64 haveRead = socket->read(byteData.data(), byteData.size());
604 if (haveRead <= 0) {
605 // ### error checking here
606 byteData.clear();
607 return bytes;
608 }
609
610 byteData.resize(haveRead);
611 out->append(byteData);
612 bytes += haveRead;
613 size -= haveRead;
614
615 toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, socket->bytesAvailable()));
616 }
617 return bytes;
618
619}
620
622{
623 qint64 bytes = 0;
624 while (socket->bytesAvailable()) {
625
626 if (readBufferMaxSize && (bytes > readBufferMaxSize))
627 break;
628
630 // For the first chunk and when we're done with a chunk
633 if (bytes) {
634 // After a chunk
635 char crlf[2];
636 // read the "\r\n" after the chunk
637 qint64 haveRead = socket->read(crlf, 2);
638 // FIXME: This code is slightly broken and not optimal. What if the 2 bytes are not available yet?!
639 // For nice reasons (the toLong in getChunkSize accepting \n at the beginning
640 // it right now still works, but we should definitely fix this.
641
642 if (haveRead != 2)
643 return bytes; // FIXME
644 bytes += haveRead;
645 }
646 // Note that chunk size gets stored in currentChunkSize, what is returned is the bytes read
648 if (currentChunkSize == -1)
649 break;
650 }
651 // if the chunk size is 0, end of the stream
652 if (currentChunkSize == 0 || lastChunkRead) {
653 lastChunkRead = true;
654 // try to read the "\r\n" after the chunk
655 char crlf[2];
656 qint64 haveRead = socket->read(crlf, 2);
657 if (haveRead > 0)
658 bytes += haveRead;
659
660 if ((haveRead == 2 && crlf[0] == '\r' && crlf[1] == '\n') || (haveRead == 1 && crlf[0] == '\n'))
662 else if (haveRead == 1 && crlf[0] == '\r')
663 break; // Still waiting for the last \n
664 else if (haveRead > 0) {
665 // If we read something else then CRLF, we need to close the channel.
668 }
669 break;
670 }
671
672 // otherwise, try to begin reading this chunk / to read what is missing for this chunk
674 currentChunkRead += haveRead;
675 bytes += haveRead;
676
677 // ### error checking here
678
679 }
680 return bytes;
681}
682
684{
685 qint64 bytes = 0;
686 char crlf[2];
687 *chunkSize = -1;
688
690 // FIXME rewrite to permanent loop without bytesAvailable
691 while (bytesAvailable > bytes) {
692 qint64 sniffedBytes = socket->peek(crlf, 2);
693 int fragmentSize = fragment.size();
694
695 // check the next two bytes for a "\r\n", skip blank lines
696 if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n')
697 ||(fragmentSize > 1 && fragment.endsWith('\r') && crlf[0] == '\n'))
698 {
699 bytes += socket->read(crlf, 1); // read the \r or \n
700 if (crlf[0] == '\r')
701 bytes += socket->read(crlf, 1); // read the \n
702 bool ok = false;
703 // ignore the chunk-extension
704 const auto fragmentView = QByteArrayView(fragment).mid(0, fragment.indexOf(';')).trimmed();
705 *chunkSize = fragmentView.toLong(&ok, 16);
706 fragment.clear();
707 break; // size done
708 } else {
709 // read the fragment to the buffer
710 char c = 0;
711 qint64 haveRead = socket->read(&c, 1);
712 if (haveRead < 0) {
713 return -1; // FIXME
714 }
715 bytes += haveRead;
717 }
718 }
719
720 return bytes;
721}
722
724{
725 // We're in the process of redirecting - if the HTTP status code says so and
726 // followRedirect is switched on
729}
730
732{
733 // for 401 & 407 don't emit the data signals. Content along with these
734 // responses are sent only if the authentication fails.
735 return parser.getStatusCode() != 401 && parser.getStatusCode() != 407;
736}
737
739{
740 int statusCode = parser.getStatusCode();
741 // check whether we can expect content after the headers (rfc 2616, sec4.4)
742 if ((statusCode >= 100 && statusCode < 200)
743 || statusCode == 204 || statusCode == 304)
744 return false;
746 return false; // no body expected for HEAD request
747 qint64 expectedContentLength = contentLength();
748 if (expectedContentLength == 0)
749 return false;
750 if (expectedContentLength == -1 && bodyLength == 0) {
751 // The content-length header was stripped, but its value was 0.
752 // This would be the case for an explicitly zero-length compressed response.
753 return false;
754 }
755 return true;
756}
757
762
763
764// SSL support below
765#ifndef QT_NO_SSL
766
768{
769 Q_D(const QHttpNetworkReply);
770
771 if (!d->connectionChannel)
772 return QSslConfiguration();
773
774 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(d->connectionChannel->socket);
775 if (!sslSocket)
776 return QSslConfiguration();
777
778 return sslSocket->sslConfiguration();
779}
780
782{
784 if (d->connection)
785 d->connection->setSslConfiguration(config);
786}
787
789{
791 if (d->connection)
792 d->connection->ignoreSslErrors();
793}
794
795void QHttpNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
796{
798 if (d->connection)
799 d->connection->ignoreSslErrors(errors);
800}
801
802
803#endif //QT_NO_SSL
804
805
807
808#include "moc_qhttpnetworkreply_p.cpp"
qint64 bytesAvailable() const override
Returns the number of incoming bytes that are waiting to be read.
constexpr QByteArrayView mid(qsizetype pos, qsizetype n=-1) const
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
bool endsWith(char c) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:227
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
void reserve(qsizetype size)
Attempts to allocate memory for at least size bytes.
Definition qbytearray.h:634
qsizetype indexOf(char c, qsizetype from=0) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
void truncate(qsizetype pos)
Truncates the byte array at index position pos.
bool startsWith(QByteArrayView bv) const
Definition qbytearray.h:223
bool contains(char c) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:660
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
void clear()
Clears the contents of the byte array and makes it null.
QByteArray toLower() const &
Definition qbytearray.h:254
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void append(const QByteDataBuffer &other)
Definition qbytedata_p.h:48
static bool isSupportedEncoding(QByteArrayView encoding)
void appendHeaderField(const QByteArray &name, const QByteArray &data)
void removeHeaderField(QByteArrayView name)
bool parseHeaders(QByteArrayView headers)
bool parseStatus(QByteArrayView status)
QByteArray firstHeaderField(QByteArrayView name, const QByteArray &defaultValue=QByteArray()) const
QByteArray headerField(QByteArrayView name, const QByteArray &defaultValue=QByteArray()) const
QPointer< QHttpNetworkConnection > connection
enum QHttpNetworkReplyPrivate::ReplyState state
bool parseStatus(QByteArrayView status)
qint64 readBody(QIODevice *socket, QByteDataBuffer *out)
QPointer< QHttpNetworkConnectionChannel > connectionChannel
qint64 readHeader(QIODevice *socket)
QHttpNetworkReplyPrivate(const QUrl &newUrl=QUrl())
void parseHeader(QByteArrayView header)
qint64 readStatus(QIODevice *socket)
qint64 readBodyFast(QIODevice *socket, QByteDataBuffer *rb)
qint64 readBodyVeryFast(QIODevice *socket, char *b)
qint64 getChunkSize(QIODevice *in, qint64 *chunkSize)
qint64 readReplyBodyChunked(QIODevice *in, QByteDataBuffer *out)
void appendHeaderField(const QByteArray &name, const QByteArray &data)
qint64 readReplyBodyRaw(QIODevice *in, QByteDataBuffer *out, qint64 size)
QHttpNetworkConnection * connection()
void setStatusCode(int code)
QByteArray headerField(QByteArrayView name, const QByteArray &defaultValue=QByteArray()) const override
void setUrl(const QUrl &url) override
QHttpHeaders header() const override
QString errorString() const
bool readAnyAvailable() const
qint64 bytesAvailableNextBlock() const
qint64 removedContentLength() const
qint64 bytesAvailable() const
static bool isHttpRedirect(int statusCode)
QByteArray read(qint64 amount)
void setReasonPhrase(const QString &reason)
void setErrorString(const QString &error)
bool isPipeliningUsed() const
void setMajorVersion(int version)
void setUserProvidedDownloadBuffer(char *)
void setHttp2WasUsed(bool h2Used)
void setReadBufferSize(qint64 size)
void setSslConfiguration(const QSslConfiguration &config)
QNetworkReply::NetworkError errorCode() const
bool supportsUserProvidedDownloadBuffer()
void setDownstreamLimited(bool t)
QHttpNetworkReply(const QUrl &url=QUrl(), QObject *parent=nullptr)
int minorVersion() const override
void setRequest(const QHttpNetworkRequest &request)
QString reasonPhrase() const
qint64 contentLength() const override
QSslConfiguration sslConfiguration() const
QHttpNetworkRequest request() const
void setMinorVersion(int version)
void setRedirectUrl(const QUrl &url)
QUrl url() const override
void setHeaderField(const QByteArray &name, const QByteArray &data) override
void parseHeader(QByteArrayView header)
void setContentLength(qint64 length) override
void appendHeaderField(const QByteArray &name, const QByteArray &data)
int majorVersion() const override
Operation operation() const
\inmodule QtCore \reentrant
Definition qiodevice.h:34
qint64 peek(char *data, qint64 maxlen)
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
NetworkError
Indicates all possible error conditions found during the processing of the request.
\inmodule QtCore
Definition qobject.h:103
The QSslConfiguration class holds the configuration and state of an SSL connection.
The QSslSocket class provides an SSL encrypted socket for both clients and servers.
Definition qsslsocket.h:29
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\inmodule QtCore
Definition qurl.h:94
QString url(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2817
else opt state
[0]
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
DBusConnection * connection
static QString header(const QString &name)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLConfig config
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLboolean GLboolean GLboolean b
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
const GLubyte * c
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
long long qint64
Definition qtypes.h:60
QTextStream out(stdout)
[7]
QUrl url("example.com")
[constructor-url-reference]
QObject::connect nullptr
QTcpSocket * socket
[1]
QNetworkRequest request(url)