/src/qtbase/src/network/access/qhttpnetworkreply.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 "qhttpnetworkreply_p.h" |
6 | | #include "qhttpnetworkconnection_p.h" |
7 | | |
8 | | #ifndef QT_NO_SSL |
9 | | # include <QtNetwork/qsslkey.h> |
10 | | # include <QtNetwork/qsslcipher.h> |
11 | | # include <QtNetwork/qsslconfiguration.h> |
12 | | #endif |
13 | | |
14 | | #include <private/qdecompresshelper_p.h> |
15 | | |
16 | | QT_BEGIN_NAMESPACE |
17 | | |
18 | | using namespace Qt::StringLiterals; |
19 | | |
20 | | QHttpNetworkReply::QHttpNetworkReply(const QUrl &url, QObject *parent) |
21 | 0 | : QObject(*new QHttpNetworkReplyPrivate(url), parent) |
22 | 0 | { |
23 | 0 | } |
24 | | |
25 | | QHttpNetworkReply::~QHttpNetworkReply() |
26 | 0 | { |
27 | 0 | Q_D(QHttpNetworkReply); |
28 | 0 | if (d->connection) { |
29 | 0 | d->connection->d_func()->removeReply(this); |
30 | 0 | } |
31 | 0 | } |
32 | | |
33 | | QUrl QHttpNetworkReply::url() const |
34 | 0 | { |
35 | 0 | return d_func()->url; |
36 | 0 | } |
37 | | void QHttpNetworkReply::setUrl(const QUrl &url) |
38 | 0 | { |
39 | 0 | Q_D(QHttpNetworkReply); |
40 | 0 | d->url = url; |
41 | 0 | } |
42 | | |
43 | | QUrl QHttpNetworkReply::redirectUrl() const |
44 | 0 | { |
45 | 0 | return d_func()->redirectUrl; |
46 | 0 | } |
47 | | |
48 | | void QHttpNetworkReply::setRedirectUrl(const QUrl &url) |
49 | 0 | { |
50 | 0 | Q_D(QHttpNetworkReply); |
51 | 0 | d->redirectUrl = url; |
52 | 0 | } |
53 | | |
54 | | bool QHttpNetworkReply::isHttpRedirect(int statusCode) |
55 | 0 | { |
56 | 0 | return (statusCode == 301 || statusCode == 302 || statusCode == 303 |
57 | 0 | || statusCode == 305 || statusCode == 307 || statusCode == 308); |
58 | 0 | } |
59 | | |
60 | | qint64 QHttpNetworkReply::contentLength() const |
61 | 0 | { |
62 | 0 | return d_func()->contentLength(); |
63 | 0 | } |
64 | | |
65 | | void QHttpNetworkReply::setContentLength(qint64 length) |
66 | 0 | { |
67 | 0 | Q_D(QHttpNetworkReply); |
68 | 0 | d->setContentLength(length); |
69 | 0 | } |
70 | | |
71 | | QHttpHeaders QHttpNetworkReply::header() const |
72 | 0 | { |
73 | 0 | return d_func()->parser.headers(); |
74 | 0 | } |
75 | | |
76 | | QByteArray QHttpNetworkReply::headerField(QByteArrayView name, const QByteArray &defaultValue) const |
77 | 0 | { |
78 | 0 | return d_func()->headerField(name, defaultValue); |
79 | 0 | } |
80 | | |
81 | | void QHttpNetworkReply::setHeaderField(const QByteArray &name, const QByteArray &data) |
82 | 0 | { |
83 | 0 | Q_D(QHttpNetworkReply); |
84 | 0 | d->setHeaderField(name, data); |
85 | 0 | } |
86 | | |
87 | | void QHttpNetworkReply::appendHeaderField(const QByteArray &name, const QByteArray &data) |
88 | 0 | { |
89 | 0 | Q_D(QHttpNetworkReply); |
90 | 0 | d->appendHeaderField(name, data); |
91 | 0 | } |
92 | | |
93 | | void QHttpNetworkReply::parseHeader(QByteArrayView header) |
94 | 0 | { |
95 | 0 | Q_D(QHttpNetworkReply); |
96 | 0 | d->parseHeader(header); |
97 | 0 | } |
98 | | |
99 | | QHttpNetworkRequest QHttpNetworkReply::request() const |
100 | 0 | { |
101 | 0 | return d_func()->request; |
102 | 0 | } |
103 | | |
104 | | void QHttpNetworkReply::setRequest(const QHttpNetworkRequest &request) |
105 | 0 | { |
106 | 0 | Q_D(QHttpNetworkReply); |
107 | 0 | d->request = request; |
108 | 0 | d->ssl = request.isSsl(); |
109 | 0 | } |
110 | | |
111 | | int QHttpNetworkReply::statusCode() const |
112 | 0 | { |
113 | 0 | return d_func()->parser.getStatusCode(); |
114 | 0 | } |
115 | | |
116 | | void QHttpNetworkReply::setStatusCode(int code) |
117 | 0 | { |
118 | 0 | Q_D(QHttpNetworkReply); |
119 | 0 | d->parser.setStatusCode(code); |
120 | 0 | } |
121 | | |
122 | | QString QHttpNetworkReply::errorString() const |
123 | 0 | { |
124 | 0 | return d_func()->errorString; |
125 | 0 | } |
126 | | |
127 | | QNetworkReply::NetworkError QHttpNetworkReply::errorCode() const |
128 | 0 | { |
129 | 0 | return d_func()->httpErrorCode; |
130 | 0 | } |
131 | | |
132 | | QString QHttpNetworkReply::reasonPhrase() const |
133 | 0 | { |
134 | 0 | return d_func()->parser.getReasonPhrase(); |
135 | 0 | } |
136 | | |
137 | | void QHttpNetworkReply::setReasonPhrase(const QString &reason) |
138 | 0 | { |
139 | 0 | d_func()->parser.setReasonPhrase(reason); |
140 | 0 | } |
141 | | |
142 | | void QHttpNetworkReply::setErrorString(const QString &error) |
143 | 0 | { |
144 | 0 | Q_D(QHttpNetworkReply); |
145 | 0 | d->errorString = error; |
146 | 0 | } |
147 | | |
148 | | int QHttpNetworkReply::majorVersion() const |
149 | 0 | { |
150 | 0 | return d_func()->parser.getMajorVersion(); |
151 | 0 | } |
152 | | |
153 | | int QHttpNetworkReply::minorVersion() const |
154 | 0 | { |
155 | 0 | return d_func()->parser.getMinorVersion(); |
156 | 0 | } |
157 | | |
158 | | void QHttpNetworkReply::setMajorVersion(int version) |
159 | 0 | { |
160 | 0 | d_func()->parser.setMajorVersion(version); |
161 | 0 | } |
162 | | |
163 | | void QHttpNetworkReply::setMinorVersion(int version) |
164 | 0 | { |
165 | 0 | d_func()->parser.setMinorVersion(version); |
166 | 0 | } |
167 | | |
168 | | qint64 QHttpNetworkReply::bytesAvailable() const |
169 | 0 | { |
170 | 0 | Q_D(const QHttpNetworkReply); |
171 | 0 | if (d->connection) |
172 | 0 | return d->connection->d_func()->uncompressedBytesAvailable(*this); |
173 | 0 | else |
174 | 0 | return -1; |
175 | 0 | } |
176 | | |
177 | | qint64 QHttpNetworkReply::bytesAvailableNextBlock() const |
178 | 0 | { |
179 | 0 | Q_D(const QHttpNetworkReply); |
180 | 0 | if (d->connection) |
181 | 0 | return d->connection->d_func()->uncompressedBytesAvailableNextBlock(*this); |
182 | 0 | else |
183 | 0 | return -1; |
184 | 0 | } |
185 | | |
186 | | bool QHttpNetworkReply::readAnyAvailable() const |
187 | 0 | { |
188 | 0 | Q_D(const QHttpNetworkReply); |
189 | 0 | return (d->responseData.bufferCount() > 0); |
190 | 0 | } |
191 | | |
192 | | QByteArray QHttpNetworkReply::readAny() |
193 | 0 | { |
194 | 0 | Q_D(QHttpNetworkReply); |
195 | 0 | if (d->responseData.bufferCount() == 0) |
196 | 0 | return QByteArray(); |
197 | | |
198 | | // we'll take the last buffer, so schedule another read from http |
199 | 0 | if (d->downstreamLimited && d->responseData.bufferCount() == 1 && !isFinished()) |
200 | 0 | d->connection->d_func()->readMoreLater(this); |
201 | 0 | return d->responseData.read(); |
202 | 0 | } |
203 | | |
204 | | QByteArray QHttpNetworkReply::readAll() |
205 | 0 | { |
206 | 0 | Q_D(QHttpNetworkReply); |
207 | 0 | return d->responseData.readAll(); |
208 | 0 | } |
209 | | |
210 | | QByteArray QHttpNetworkReply::read(qint64 amount) |
211 | 0 | { |
212 | 0 | Q_D(QHttpNetworkReply); |
213 | 0 | return d->responseData.read(amount); |
214 | 0 | } |
215 | | |
216 | | |
217 | | qint64 QHttpNetworkReply::sizeNextBlock() |
218 | 0 | { |
219 | 0 | Q_D(QHttpNetworkReply); |
220 | 0 | return d->responseData.sizeNextBlock(); |
221 | 0 | } |
222 | | |
223 | | void QHttpNetworkReply::setDownstreamLimited(bool dsl) |
224 | 0 | { |
225 | 0 | Q_D(QHttpNetworkReply); |
226 | 0 | d->downstreamLimited = dsl; |
227 | 0 | d->connection->d_func()->readMoreLater(this); |
228 | 0 | } |
229 | | |
230 | | void QHttpNetworkReply::setReadBufferSize(qint64 size) |
231 | 0 | { |
232 | 0 | Q_D(QHttpNetworkReply); |
233 | 0 | d->readBufferMaxSize = size; |
234 | 0 | } |
235 | | |
236 | | bool QHttpNetworkReply::supportsUserProvidedDownloadBuffer() |
237 | 0 | { |
238 | 0 | Q_D(QHttpNetworkReply); |
239 | 0 | return !d->isChunked() && !d->autoDecompress && |
240 | 0 | d->bodyLength > 0 && d->parser.getStatusCode() == 200; |
241 | 0 | } |
242 | | |
243 | | void QHttpNetworkReply::setUserProvidedDownloadBuffer(char* b) |
244 | 0 | { |
245 | 0 | Q_D(QHttpNetworkReply); |
246 | 0 | if (supportsUserProvidedDownloadBuffer()) |
247 | 0 | d->userProvidedDownloadBuffer = b; |
248 | 0 | } |
249 | | |
250 | | char* QHttpNetworkReply::userProvidedDownloadBuffer() |
251 | 0 | { |
252 | 0 | Q_D(QHttpNetworkReply); |
253 | 0 | return d->userProvidedDownloadBuffer; |
254 | 0 | } |
255 | | |
256 | | void QHttpNetworkReply::abort() |
257 | 0 | { |
258 | 0 | Q_D(QHttpNetworkReply); |
259 | 0 | d->state = QHttpNetworkReplyPrivate::Aborted; |
260 | 0 | } |
261 | | |
262 | | bool QHttpNetworkReply::isAborted() const |
263 | 0 | { |
264 | 0 | return d_func()->state == QHttpNetworkReplyPrivate::Aborted; |
265 | 0 | } |
266 | | |
267 | | bool QHttpNetworkReply::isFinished() const |
268 | 0 | { |
269 | 0 | return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState; |
270 | 0 | } |
271 | | |
272 | | bool QHttpNetworkReply::isPipeliningUsed() const |
273 | 0 | { |
274 | 0 | return d_func()->pipeliningUsed; |
275 | 0 | } |
276 | | |
277 | | bool QHttpNetworkReply::isHttp2Used() const |
278 | 0 | { |
279 | 0 | return d_func()->h2Used; |
280 | 0 | } |
281 | | |
282 | | void QHttpNetworkReply::setHttp2WasUsed(bool h2) |
283 | 0 | { |
284 | 0 | d_func()->h2Used = h2; |
285 | 0 | } |
286 | | |
287 | | qint64 QHttpNetworkReply::removedContentLength() const |
288 | 0 | { |
289 | 0 | return d_func()->removedContentLength; |
290 | 0 | } |
291 | | |
292 | | bool QHttpNetworkReply::isRedirecting() const |
293 | 0 | { |
294 | 0 | return d_func()->isRedirecting(); |
295 | 0 | } |
296 | | |
297 | | QHttpNetworkConnection* QHttpNetworkReply::connection() |
298 | 0 | { |
299 | 0 | return d_func()->connection; |
300 | 0 | } |
301 | | |
302 | | |
303 | | QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) |
304 | 0 | : QHttpNetworkHeaderPrivate(newUrl) |
305 | 0 | , state(NothingDoneState) |
306 | 0 | , ssl(false), |
307 | 0 | bodyLength(0), contentRead(0), totalProgress(0), |
308 | 0 | chunkedTransferEncoding(false), |
309 | 0 | connectionCloseEnabled(true), |
310 | 0 | forceConnectionCloseEnabled(false), |
311 | 0 | lastChunkRead(false), |
312 | 0 | currentChunkSize(0), currentChunkRead(0), readBufferMaxSize(0), |
313 | 0 | totallyUploadedData(0), |
314 | 0 | removedContentLength(-1), |
315 | 0 | connection(nullptr), |
316 | 0 | autoDecompress(false), responseData(), requestIsPrepared(false) |
317 | 0 | ,pipeliningUsed(false), h2Used(false), downstreamLimited(false) |
318 | 0 | ,userProvidedDownloadBuffer(nullptr) |
319 | | |
320 | 0 | { |
321 | 0 | QString scheme = newUrl.scheme(); |
322 | 0 | if (scheme == "preconnect-http"_L1 || scheme == "preconnect-https"_L1) |
323 | | // make sure we do not close the socket after preconnecting |
324 | 0 | connectionCloseEnabled = false; |
325 | 0 | } |
326 | | |
327 | 0 | QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate() = default; |
328 | | |
329 | | void QHttpNetworkReplyPrivate::clearHttpLayerInformation() |
330 | 0 | { |
331 | 0 | state = NothingDoneState; |
332 | 0 | bodyLength = 0; |
333 | 0 | contentRead = 0; |
334 | 0 | totalProgress = 0; |
335 | 0 | currentChunkSize = 0; |
336 | 0 | currentChunkRead = 0; |
337 | 0 | lastChunkRead = false; |
338 | 0 | connectionCloseEnabled = true; |
339 | 0 | parser.clear(); |
340 | 0 | } |
341 | | |
342 | | // TODO: Isn't everything HTTP layer related? We don't need to set connection and connectionChannel to 0 at all |
343 | | void QHttpNetworkReplyPrivate::clear() |
344 | 0 | { |
345 | 0 | connection = nullptr; |
346 | 0 | connectionChannel = nullptr; |
347 | 0 | autoDecompress = false; |
348 | 0 | clearHttpLayerInformation(); |
349 | 0 | } |
350 | | |
351 | | // QHttpNetworkReplyPrivate |
352 | | qint64 QHttpNetworkReplyPrivate::bytesAvailable() const |
353 | 0 | { |
354 | 0 | return (state != ReadingDataState ? 0 : fragment.size()); |
355 | 0 | } |
356 | | |
357 | | bool QHttpNetworkReplyPrivate::isCompressed() const |
358 | 0 | { |
359 | 0 | return QDecompressHelper::isSupportedEncoding(headerField("content-encoding")); |
360 | 0 | } |
361 | | |
362 | | bool QHttpNetworkReply::isCompressed() const |
363 | 0 | { |
364 | 0 | Q_D(const QHttpNetworkReply); |
365 | 0 | return d->isCompressed(); |
366 | 0 | } |
367 | | |
368 | | void QHttpNetworkReplyPrivate::removeAutoDecompressHeader() |
369 | 0 | { |
370 | | // The header "Content-Encoding = gzip" is retained. |
371 | | // Content-Length is removed since the actual one sent by the server is for compressed data |
372 | 0 | constexpr auto name = QByteArrayView("content-length"); |
373 | 0 | QByteArray contentLength = parser.firstHeaderField(name); |
374 | 0 | bool parseOk = false; |
375 | 0 | qint64 value = contentLength.toLongLong(&parseOk); |
376 | 0 | if (parseOk) { |
377 | 0 | removedContentLength = value; |
378 | 0 | parser.removeHeaderField(name); |
379 | 0 | } |
380 | 0 | } |
381 | | |
382 | | qint64 QHttpNetworkReplyPrivate::readStatus(QIODevice *socket) |
383 | 0 | { |
384 | 0 | if (fragment.isEmpty()) { |
385 | | // reserve bytes for the status line. This is better than always append() which reallocs the byte array |
386 | 0 | fragment.reserve(32); |
387 | 0 | } |
388 | |
|
389 | 0 | qint64 bytes = 0; |
390 | 0 | char c; |
391 | 0 | qint64 haveRead = 0; |
392 | |
|
393 | 0 | do { |
394 | 0 | haveRead = socket->read(&c, 1); |
395 | 0 | if (haveRead == -1) |
396 | 0 | return -1; // unexpected EOF |
397 | 0 | else if (haveRead == 0) |
398 | 0 | break; // read more later |
399 | 0 | else if (haveRead == 1 && fragment.size() == 0 && (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31)) |
400 | 0 | continue; // Ignore all whitespace that was trailing froma previous request on that socket |
401 | | |
402 | 0 | bytes++; |
403 | | |
404 | | // allow both CRLF & LF (only) line endings |
405 | 0 | if (c == '\n') { |
406 | | // remove the CR at the end |
407 | 0 | if (fragment.endsWith('\r')) { |
408 | 0 | fragment.truncate(fragment.size()-1); |
409 | 0 | } |
410 | 0 | bool ok = parseStatus(fragment); |
411 | 0 | state = ReadingHeaderState; |
412 | 0 | fragment.clear(); |
413 | 0 | if (!ok) { |
414 | 0 | return -1; |
415 | 0 | } |
416 | 0 | break; |
417 | 0 | } else { |
418 | 0 | fragment.append(c); |
419 | 0 | } |
420 | | |
421 | | // is this a valid reply? |
422 | 0 | if (fragment.size() == 5 && !fragment.startsWith("HTTP/")) { |
423 | 0 | fragment.clear(); |
424 | 0 | return -1; |
425 | 0 | } |
426 | 0 | } while (haveRead == 1); |
427 | | |
428 | 0 | return bytes; |
429 | 0 | } |
430 | | |
431 | | bool QHttpNetworkReplyPrivate::parseStatus(QByteArrayView status) |
432 | 0 | { |
433 | 0 | return parser.parseStatus(status); |
434 | 0 | } |
435 | | |
436 | | qint64 QHttpNetworkReplyPrivate::readHeader(QIODevice *socket) |
437 | 0 | { |
438 | 0 | if (fragment.isEmpty()) { |
439 | | // according to http://dev.opera.com/articles/view/mama-http-headers/ the average size of the header |
440 | | // block is 381 bytes. |
441 | | // reserve bytes. This is better than always append() which reallocs the byte array. |
442 | 0 | fragment.reserve(512); |
443 | 0 | } |
444 | |
|
445 | 0 | qint64 bytes = 0; |
446 | 0 | char c = 0; |
447 | 0 | bool allHeaders = false; |
448 | 0 | qint64 haveRead = 0; |
449 | 0 | do { |
450 | 0 | haveRead = socket->read(&c, 1); |
451 | 0 | if (haveRead == 0) { |
452 | | // read more later |
453 | 0 | break; |
454 | 0 | } else if (haveRead == -1) { |
455 | | // connection broke down |
456 | 0 | return -1; |
457 | 0 | } else { |
458 | 0 | fragment.append(c); |
459 | 0 | bytes++; |
460 | |
|
461 | 0 | if (c == '\n') { |
462 | | // check for possible header endings. As per HTTP rfc, |
463 | | // the header endings will be marked by CRLFCRLF. But |
464 | | // we will allow CRLFCRLF, CRLFLF, LFCRLF, LFLF |
465 | 0 | if (fragment.endsWith("\n\r\n") |
466 | 0 | || fragment.endsWith("\n\n")) |
467 | 0 | allHeaders = true; |
468 | | |
469 | | // there is another case: We have no headers. Then the fragment equals just the line ending |
470 | 0 | if ((fragment.size() == 2 && fragment.endsWith("\r\n")) |
471 | 0 | || (fragment.size() == 1 && fragment.endsWith("\n"))) |
472 | 0 | allHeaders = true; |
473 | 0 | } |
474 | 0 | } |
475 | 0 | } while (!allHeaders && haveRead > 0); |
476 | | |
477 | | // we received all headers now parse them |
478 | 0 | if (allHeaders) { |
479 | 0 | parseHeader(fragment); |
480 | 0 | state = ReadingDataState; |
481 | 0 | fragment.clear(); // next fragment |
482 | 0 | bodyLength = contentLength(); // cache the length |
483 | | |
484 | | // cache isChunked() since it is called often |
485 | 0 | chunkedTransferEncoding = headerField("transfer-encoding").toLower().contains("chunked"); |
486 | | |
487 | | // cache isConnectionCloseEnabled since it is called often |
488 | 0 | QByteArray connectionHeaderField = headerField("connection"); |
489 | | // check for explicit indication of close or the implicit connection close of HTTP/1.0 |
490 | 0 | connectionCloseEnabled = (connectionHeaderField.toLower().contains("close") || |
491 | 0 | headerField("proxy-connection").toLower().contains("close")) || |
492 | 0 | (parser.getMajorVersion() == 1 && parser.getMinorVersion() == 0 && |
493 | 0 | (connectionHeaderField.isEmpty() && !headerField("proxy-connection").toLower().contains("keep-alive"))); |
494 | 0 | } |
495 | 0 | return bytes; |
496 | 0 | } |
497 | | |
498 | | void QHttpNetworkReplyPrivate::parseHeader(QByteArrayView header) |
499 | 0 | { |
500 | 0 | parser.parseHeaders(header); |
501 | 0 | } |
502 | | |
503 | | void QHttpNetworkReplyPrivate::appendHeaderField(const QByteArray &name, const QByteArray &data) |
504 | 0 | { |
505 | 0 | parser.appendHeaderField(name, data); |
506 | 0 | } |
507 | | |
508 | | bool QHttpNetworkReplyPrivate::isChunked() |
509 | 0 | { |
510 | 0 | return chunkedTransferEncoding; |
511 | 0 | } |
512 | | |
513 | | bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled() |
514 | 0 | { |
515 | 0 | return connectionCloseEnabled || forceConnectionCloseEnabled; |
516 | 0 | } |
517 | | |
518 | | // note this function can only be used for non-chunked, non-compressed with |
519 | | // known content length |
520 | | qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QIODevice *socket, char *b) |
521 | 0 | { |
522 | | // This first read is to flush the buffer inside the socket |
523 | 0 | qint64 haveRead = 0; |
524 | 0 | haveRead = socket->read(b, bodyLength - contentRead); |
525 | 0 | if (haveRead == -1) { |
526 | 0 | return -1; |
527 | 0 | } |
528 | 0 | contentRead += haveRead; |
529 | |
|
530 | 0 | if (contentRead == bodyLength) { |
531 | 0 | state = AllDoneState; |
532 | 0 | } |
533 | |
|
534 | 0 | return haveRead; |
535 | 0 | } |
536 | | |
537 | | // note this function can only be used for non-chunked, non-compressed with |
538 | | // known content length |
539 | | qint64 QHttpNetworkReplyPrivate::readBodyFast(QIODevice *socket, QByteDataBuffer *rb) |
540 | 0 | { |
541 | |
|
542 | 0 | qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead); |
543 | 0 | if (readBufferMaxSize) |
544 | 0 | toBeRead = qMin(toBeRead, readBufferMaxSize); |
545 | |
|
546 | 0 | if (!toBeRead) |
547 | 0 | return 0; |
548 | | |
549 | 0 | QByteArray bd; |
550 | 0 | bd.resize(toBeRead); |
551 | 0 | qint64 haveRead = socket->read(bd.data(), toBeRead); |
552 | 0 | if (haveRead == -1) { |
553 | 0 | bd.clear(); |
554 | 0 | return 0; // ### error checking here; |
555 | 0 | } |
556 | 0 | bd.resize(haveRead); |
557 | |
|
558 | 0 | rb->append(bd); |
559 | |
|
560 | 0 | if (contentRead + haveRead == bodyLength) { |
561 | 0 | state = AllDoneState; |
562 | 0 | } |
563 | |
|
564 | 0 | contentRead += haveRead; |
565 | 0 | return haveRead; |
566 | 0 | } |
567 | | |
568 | | |
569 | | qint64 QHttpNetworkReplyPrivate::readBody(QIODevice *socket, QByteDataBuffer *out) |
570 | 0 | { |
571 | 0 | qint64 bytes = 0; |
572 | |
|
573 | 0 | if (isChunked()) { |
574 | | // chunked transfer encoding (rfc 2616, sec 3.6) |
575 | 0 | bytes += readReplyBodyChunked(socket, out); |
576 | 0 | } else if (bodyLength > 0) { |
577 | | // we have a Content-Length |
578 | 0 | bytes += readReplyBodyRaw(socket, out, bodyLength - contentRead); |
579 | 0 | if (contentRead + bytes == bodyLength) |
580 | 0 | state = AllDoneState; |
581 | 0 | } else { |
582 | | // no content length. just read what's possible |
583 | 0 | bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable()); |
584 | 0 | } |
585 | 0 | contentRead += bytes; |
586 | 0 | return bytes; |
587 | 0 | } |
588 | | |
589 | | qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QIODevice *socket, QByteDataBuffer *out, qint64 size) |
590 | 0 | { |
591 | | // FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable() |
592 | 0 | qint64 bytes = 0; |
593 | 0 | Q_ASSERT(socket); |
594 | 0 | Q_ASSERT(out); |
595 | |
|
596 | 0 | int toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, socket->bytesAvailable())); |
597 | |
|
598 | 0 | if (readBufferMaxSize) |
599 | 0 | toBeRead = qMin<qint64>(toBeRead, readBufferMaxSize); |
600 | |
|
601 | 0 | while (toBeRead > 0) { |
602 | 0 | QByteArray byteData; |
603 | 0 | byteData.resize(toBeRead); |
604 | 0 | qint64 haveRead = socket->read(byteData.data(), byteData.size()); |
605 | 0 | if (haveRead <= 0) { |
606 | | // ### error checking here |
607 | 0 | byteData.clear(); |
608 | 0 | return bytes; |
609 | 0 | } |
610 | | |
611 | 0 | byteData.resize(haveRead); |
612 | 0 | out->append(byteData); |
613 | 0 | bytes += haveRead; |
614 | 0 | size -= haveRead; |
615 | |
|
616 | 0 | toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, socket->bytesAvailable())); |
617 | 0 | } |
618 | 0 | return bytes; |
619 | |
|
620 | 0 | } |
621 | | |
622 | | qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *socket, QByteDataBuffer *out) |
623 | 0 | { |
624 | 0 | qint64 bytes = 0; |
625 | 0 | while (socket->bytesAvailable()) { |
626 | |
|
627 | 0 | if (readBufferMaxSize && (bytes > readBufferMaxSize)) |
628 | 0 | break; |
629 | | |
630 | 0 | if (!lastChunkRead && currentChunkRead >= currentChunkSize) { |
631 | | // For the first chunk and when we're done with a chunk |
632 | 0 | currentChunkSize = 0; |
633 | 0 | currentChunkRead = 0; |
634 | 0 | if (bytes) { |
635 | | // After a chunk |
636 | 0 | char crlf[2]; |
637 | | // read the "\r\n" after the chunk |
638 | 0 | qint64 haveRead = socket->read(crlf, 2); |
639 | | // FIXME: This code is slightly broken and not optimal. What if the 2 bytes are not available yet?! |
640 | | // For nice reasons (the toLong in getChunkSize accepting \n at the beginning |
641 | | // it right now still works, but we should definitely fix this. |
642 | |
|
643 | 0 | if (haveRead != 2) |
644 | 0 | return bytes; // FIXME |
645 | 0 | bytes += haveRead; |
646 | 0 | } |
647 | | // Note that chunk size gets stored in currentChunkSize, what is returned is the bytes read |
648 | 0 | bytes += getChunkSize(socket, ¤tChunkSize); |
649 | 0 | if (currentChunkSize == -1) |
650 | 0 | break; |
651 | 0 | } |
652 | | // if the chunk size is 0, end of the stream |
653 | 0 | if (currentChunkSize == 0 || lastChunkRead) { |
654 | 0 | lastChunkRead = true; |
655 | | // try to read the "\r\n" after the chunk |
656 | 0 | char crlf[2]; |
657 | 0 | qint64 haveRead = socket->read(crlf, 2); |
658 | 0 | if (haveRead > 0) |
659 | 0 | bytes += haveRead; |
660 | |
|
661 | 0 | if ((haveRead == 2 && crlf[0] == '\r' && crlf[1] == '\n') || (haveRead == 1 && crlf[0] == '\n')) |
662 | 0 | state = AllDoneState; |
663 | 0 | else if (haveRead == 1 && crlf[0] == '\r') |
664 | 0 | break; // Still waiting for the last \n |
665 | 0 | else if (haveRead > 0) { |
666 | | // If we read something else then CRLF, we need to close the channel. |
667 | 0 | forceConnectionCloseEnabled = true; |
668 | 0 | state = AllDoneState; |
669 | 0 | } |
670 | 0 | break; |
671 | 0 | } |
672 | | |
673 | | // otherwise, try to begin reading this chunk / to read what is missing for this chunk |
674 | 0 | qint64 haveRead = readReplyBodyRaw (socket, out, currentChunkSize - currentChunkRead); |
675 | 0 | currentChunkRead += haveRead; |
676 | 0 | bytes += haveRead; |
677 | | |
678 | | // ### error checking here |
679 | |
|
680 | 0 | } |
681 | 0 | return bytes; |
682 | 0 | } |
683 | | |
684 | | qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *socket, qint64 *chunkSize) |
685 | 0 | { |
686 | 0 | qint64 bytes = 0; |
687 | 0 | char crlf[2]; |
688 | 0 | *chunkSize = -1; |
689 | |
|
690 | 0 | int bytesAvailable = socket->bytesAvailable(); |
691 | | // FIXME rewrite to permanent loop without bytesAvailable |
692 | 0 | while (bytesAvailable > bytes) { |
693 | 0 | qint64 sniffedBytes = socket->peek(crlf, 2); |
694 | 0 | int fragmentSize = fragment.size(); |
695 | | |
696 | | // check the next two bytes for a "\r\n", skip blank lines |
697 | 0 | if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n') |
698 | 0 | ||(fragmentSize > 1 && fragment.endsWith('\r') && crlf[0] == '\n')) |
699 | 0 | { |
700 | 0 | bytes += socket->read(crlf, 1); // read the \r or \n |
701 | 0 | if (crlf[0] == '\r') |
702 | 0 | bytes += socket->read(crlf, 1); // read the \n |
703 | 0 | bool ok = false; |
704 | | // ignore the chunk-extension |
705 | 0 | const auto fragmentView = QByteArrayView(fragment).mid(0, fragment.indexOf(';')).trimmed(); |
706 | 0 | *chunkSize = fragmentView.toLong(&ok, 16); |
707 | 0 | fragment.clear(); |
708 | 0 | break; // size done |
709 | 0 | } else { |
710 | | // read the fragment to the buffer |
711 | 0 | char c = 0; |
712 | 0 | qint64 haveRead = socket->read(&c, 1); |
713 | 0 | if (haveRead < 0) { |
714 | 0 | return -1; // FIXME |
715 | 0 | } |
716 | 0 | bytes += haveRead; |
717 | 0 | fragment.append(c); |
718 | 0 | } |
719 | 0 | } |
720 | | |
721 | 0 | return bytes; |
722 | 0 | } |
723 | | |
724 | | bool QHttpNetworkReplyPrivate::isRedirecting() const |
725 | 0 | { |
726 | | // We're in the process of redirecting - if the HTTP status code says so and |
727 | | // followRedirect is switched on |
728 | 0 | return (QHttpNetworkReply::isHttpRedirect(parser.getStatusCode()) |
729 | 0 | && request.isFollowRedirects()); |
730 | 0 | } |
731 | | |
732 | | bool QHttpNetworkReplyPrivate::shouldEmitSignals() |
733 | 0 | { |
734 | | // for 401 & 407 don't emit the data signals. Content along with these |
735 | | // responses are sent only if the authentication fails. |
736 | 0 | return parser.getStatusCode() != 401 && parser.getStatusCode() != 407; |
737 | 0 | } |
738 | | |
739 | | bool QHttpNetworkReplyPrivate::expectContent() |
740 | 0 | { |
741 | 0 | int statusCode = parser.getStatusCode(); |
742 | | // check whether we can expect content after the headers (rfc 2616, sec4.4) |
743 | 0 | if ((statusCode >= 100 && statusCode < 200) |
744 | 0 | || statusCode == 204 || statusCode == 304) |
745 | 0 | return false; |
746 | 0 | if (request.operation() == QHttpNetworkRequest::Head) |
747 | 0 | return false; // no body expected for HEAD request |
748 | 0 | qint64 expectedContentLength = contentLength(); |
749 | 0 | if (expectedContentLength == 0) |
750 | 0 | return false; |
751 | 0 | if (expectedContentLength == -1 && bodyLength == 0) { |
752 | | // The content-length header was stripped, but its value was 0. |
753 | | // This would be the case for an explicitly zero-length compressed response. |
754 | 0 | return false; |
755 | 0 | } |
756 | 0 | return true; |
757 | 0 | } |
758 | | |
759 | | void QHttpNetworkReplyPrivate::eraseData() |
760 | 0 | { |
761 | 0 | responseData.clear(); |
762 | 0 | } |
763 | | |
764 | | |
765 | | // SSL support below |
766 | | #ifndef QT_NO_SSL |
767 | | |
768 | | QSslConfiguration QHttpNetworkReply::sslConfiguration() const |
769 | 0 | { |
770 | 0 | Q_D(const QHttpNetworkReply); |
771 | |
|
772 | 0 | if (!d->connectionChannel) |
773 | 0 | return QSslConfiguration(); |
774 | | |
775 | 0 | QSslSocket *sslSocket = qobject_cast<QSslSocket*>(d->connectionChannel->socket); |
776 | 0 | if (!sslSocket) |
777 | 0 | return QSslConfiguration(); |
778 | | |
779 | 0 | return sslSocket->sslConfiguration(); |
780 | 0 | } |
781 | | |
782 | | void QHttpNetworkReply::setSslConfiguration(const QSslConfiguration &config) |
783 | 0 | { |
784 | 0 | Q_D(QHttpNetworkReply); |
785 | 0 | if (d->connection) |
786 | 0 | d->connection->setSslConfiguration(config); |
787 | 0 | } |
788 | | |
789 | | void QHttpNetworkReply::ignoreSslErrors() |
790 | 0 | { |
791 | 0 | Q_D(QHttpNetworkReply); |
792 | 0 | if (d->connection) |
793 | 0 | d->connection->ignoreSslErrors(); |
794 | 0 | } |
795 | | |
796 | | void QHttpNetworkReply::ignoreSslErrors(const QList<QSslError> &errors) |
797 | 0 | { |
798 | 0 | Q_D(QHttpNetworkReply); |
799 | 0 | if (d->connection) |
800 | 0 | d->connection->ignoreSslErrors(errors); |
801 | 0 | } |
802 | | |
803 | | |
804 | | #endif //QT_NO_SSL |
805 | | |
806 | | |
807 | | QT_END_NAMESPACE |
808 | | |
809 | | #include "moc_qhttpnetworkreply_p.cpp" |