|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtNetwork module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "qhttpnetworkreply_p.h" |
|
43 #include "qhttpnetworkconnection_p.h" |
|
44 |
|
45 #include <qbytearraymatcher.h> |
|
46 |
|
47 #ifndef QT_NO_HTTP |
|
48 |
|
49 #ifndef QT_NO_OPENSSL |
|
50 # include <QtNetwork/qsslkey.h> |
|
51 # include <QtNetwork/qsslcipher.h> |
|
52 # include <QtNetwork/qsslconfiguration.h> |
|
53 #endif |
|
54 |
|
55 QT_BEGIN_NAMESPACE |
|
56 |
|
57 QHttpNetworkReply::QHttpNetworkReply(const QUrl &url, QObject *parent) |
|
58 : QObject(*new QHttpNetworkReplyPrivate(url), parent) |
|
59 { |
|
60 } |
|
61 |
|
62 QHttpNetworkReply::~QHttpNetworkReply() |
|
63 { |
|
64 Q_D(QHttpNetworkReply); |
|
65 if (d->connection) { |
|
66 d->connection->d_func()->removeReply(this); |
|
67 } |
|
68 } |
|
69 |
|
70 QUrl QHttpNetworkReply::url() const |
|
71 { |
|
72 return d_func()->url; |
|
73 } |
|
74 void QHttpNetworkReply::setUrl(const QUrl &url) |
|
75 { |
|
76 Q_D(QHttpNetworkReply); |
|
77 d->url = url; |
|
78 } |
|
79 |
|
80 qint64 QHttpNetworkReply::contentLength() const |
|
81 { |
|
82 return d_func()->contentLength(); |
|
83 } |
|
84 |
|
85 void QHttpNetworkReply::setContentLength(qint64 length) |
|
86 { |
|
87 Q_D(QHttpNetworkReply); |
|
88 d->setContentLength(length); |
|
89 } |
|
90 |
|
91 QList<QPair<QByteArray, QByteArray> > QHttpNetworkReply::header() const |
|
92 { |
|
93 return d_func()->fields; |
|
94 } |
|
95 |
|
96 QByteArray QHttpNetworkReply::headerField(const QByteArray &name, const QByteArray &defaultValue) const |
|
97 { |
|
98 return d_func()->headerField(name, defaultValue); |
|
99 } |
|
100 |
|
101 void QHttpNetworkReply::setHeaderField(const QByteArray &name, const QByteArray &data) |
|
102 { |
|
103 Q_D(QHttpNetworkReply); |
|
104 d->setHeaderField(name, data); |
|
105 } |
|
106 |
|
107 void QHttpNetworkReply::parseHeader(const QByteArray &header) |
|
108 { |
|
109 Q_D(QHttpNetworkReply); |
|
110 d->parseHeader(header); |
|
111 } |
|
112 |
|
113 QHttpNetworkRequest QHttpNetworkReply::request() const |
|
114 { |
|
115 return d_func()->request; |
|
116 } |
|
117 |
|
118 void QHttpNetworkReply::setRequest(const QHttpNetworkRequest &request) |
|
119 { |
|
120 Q_D(QHttpNetworkReply); |
|
121 d->request = request; |
|
122 } |
|
123 |
|
124 int QHttpNetworkReply::statusCode() const |
|
125 { |
|
126 return d_func()->statusCode; |
|
127 } |
|
128 |
|
129 void QHttpNetworkReply::setStatusCode(int code) |
|
130 { |
|
131 Q_D(QHttpNetworkReply); |
|
132 d->statusCode = code; |
|
133 } |
|
134 |
|
135 QString QHttpNetworkReply::errorString() const |
|
136 { |
|
137 return d_func()->errorString; |
|
138 } |
|
139 |
|
140 QString QHttpNetworkReply::reasonPhrase() const |
|
141 { |
|
142 return d_func()->reasonPhrase; |
|
143 } |
|
144 |
|
145 void QHttpNetworkReply::setErrorString(const QString &error) |
|
146 { |
|
147 Q_D(QHttpNetworkReply); |
|
148 d->errorString = error; |
|
149 } |
|
150 |
|
151 int QHttpNetworkReply::majorVersion() const |
|
152 { |
|
153 return d_func()->majorVersion; |
|
154 } |
|
155 |
|
156 int QHttpNetworkReply::minorVersion() const |
|
157 { |
|
158 return d_func()->minorVersion; |
|
159 } |
|
160 |
|
161 qint64 QHttpNetworkReply::bytesAvailable() const |
|
162 { |
|
163 Q_D(const QHttpNetworkReply); |
|
164 if (d->connection) |
|
165 return d->connection->d_func()->uncompressedBytesAvailable(*this); |
|
166 else |
|
167 return -1; |
|
168 } |
|
169 |
|
170 qint64 QHttpNetworkReply::bytesAvailableNextBlock() const |
|
171 { |
|
172 Q_D(const QHttpNetworkReply); |
|
173 if (d->connection) |
|
174 return d->connection->d_func()->uncompressedBytesAvailableNextBlock(*this); |
|
175 else |
|
176 return -1; |
|
177 } |
|
178 |
|
179 QByteArray QHttpNetworkReply::readAny() |
|
180 { |
|
181 Q_D(QHttpNetworkReply); |
|
182 return d->responseData.read(); |
|
183 } |
|
184 |
|
185 bool QHttpNetworkReply::isFinished() const |
|
186 { |
|
187 return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState; |
|
188 } |
|
189 |
|
190 bool QHttpNetworkReply::isPipeliningUsed() const |
|
191 { |
|
192 return d_func()->pipeliningUsed; |
|
193 } |
|
194 |
|
195 |
|
196 QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) |
|
197 : QHttpNetworkHeaderPrivate(newUrl), state(NothingDoneState), statusCode(100), |
|
198 majorVersion(0), minorVersion(0), bodyLength(0), contentRead(0), totalProgress(0), |
|
199 chunkedTransferEncoding(false), |
|
200 connectionCloseEnabled(true), |
|
201 forceConnectionCloseEnabled(false), |
|
202 currentChunkSize(0), currentChunkRead(0), connection(0), initInflate(false), |
|
203 autoDecompress(false), responseData(), requestIsPrepared(false) |
|
204 ,pipeliningUsed(false) |
|
205 { |
|
206 } |
|
207 |
|
208 QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate() |
|
209 { |
|
210 } |
|
211 |
|
212 void QHttpNetworkReplyPrivate::clear() |
|
213 { |
|
214 state = NothingDoneState; |
|
215 statusCode = 100; |
|
216 bodyLength = 0; |
|
217 contentRead = 0; |
|
218 totalProgress = 0; |
|
219 currentChunkSize = 0; |
|
220 currentChunkRead = 0; |
|
221 connectionCloseEnabled = true; |
|
222 connection = 0; |
|
223 #ifndef QT_NO_COMPRESS |
|
224 if (initInflate) |
|
225 inflateEnd(&inflateStrm); |
|
226 #endif |
|
227 initInflate = false; |
|
228 streamEnd = false; |
|
229 autoDecompress = false; |
|
230 fields.clear(); |
|
231 } |
|
232 |
|
233 // QHttpNetworkReplyPrivate |
|
234 qint64 QHttpNetworkReplyPrivate::bytesAvailable() const |
|
235 { |
|
236 return (state != ReadingDataState ? 0 : fragment.size()); |
|
237 } |
|
238 |
|
239 bool QHttpNetworkReplyPrivate::isGzipped() |
|
240 { |
|
241 QByteArray encoding = headerField("content-encoding"); |
|
242 return encoding.toLower() == "gzip"; |
|
243 } |
|
244 |
|
245 void QHttpNetworkReplyPrivate::removeAutoDecompressHeader() |
|
246 { |
|
247 // The header "Content-Encoding = gzip" is retained. |
|
248 // Content-Length is removed since the actual one send by the server is for compressed data |
|
249 QByteArray name("content-length"); |
|
250 QByteArray lowerName = name.toLower(); |
|
251 QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(), |
|
252 end = fields.end(); |
|
253 while (it != end) { |
|
254 if (name == it->first.toLower()) { |
|
255 fields.erase(it); |
|
256 break; |
|
257 } |
|
258 ++it; |
|
259 } |
|
260 |
|
261 } |
|
262 |
|
263 bool QHttpNetworkReplyPrivate::findChallenge(bool forProxy, QByteArray &challenge) const |
|
264 { |
|
265 challenge.clear(); |
|
266 // find out the type of authentication protocol requested. |
|
267 QByteArray header = forProxy ? "proxy-authenticate" : "www-authenticate"; |
|
268 // pick the best protocol (has to match parsing in QAuthenticatorPrivate) |
|
269 QList<QByteArray> challenges = headerFieldValues(header); |
|
270 for (int i = 0; i<challenges.size(); i++) { |
|
271 QByteArray line = challenges.at(i); |
|
272 if (!line.toLower().startsWith("negotiate")) |
|
273 challenge = line; |
|
274 } |
|
275 return !challenge.isEmpty(); |
|
276 } |
|
277 |
|
278 QAuthenticatorPrivate::Method QHttpNetworkReplyPrivate::authenticationMethod(bool isProxy) const |
|
279 { |
|
280 // The logic is same as the one used in void QAuthenticatorPrivate::parseHttpResponse() |
|
281 QAuthenticatorPrivate::Method method = QAuthenticatorPrivate::None; |
|
282 QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate"; |
|
283 QList<QByteArray> challenges = headerFieldValues(header); |
|
284 for (int i = 0; i<challenges.size(); i++) { |
|
285 QByteArray line = challenges.at(i).trimmed().toLower(); |
|
286 if (method < QAuthenticatorPrivate::Basic |
|
287 && line.startsWith("basic")) { |
|
288 method = QAuthenticatorPrivate::Basic; |
|
289 } else if (method < QAuthenticatorPrivate::Ntlm |
|
290 && line.startsWith("ntlm")) { |
|
291 method = QAuthenticatorPrivate::Ntlm; |
|
292 } else if (method < QAuthenticatorPrivate::DigestMd5 |
|
293 && line.startsWith("digest")) { |
|
294 method = QAuthenticatorPrivate::DigestMd5; |
|
295 } |
|
296 } |
|
297 return method; |
|
298 } |
|
299 |
|
300 #ifndef QT_NO_COMPRESS |
|
301 bool QHttpNetworkReplyPrivate::gzipCheckHeader(QByteArray &content, int &pos) |
|
302 { |
|
303 int method = 0; // method byte |
|
304 int flags = 0; // flags byte |
|
305 bool ret = false; |
|
306 |
|
307 // Assure two bytes in the buffer so we can peek ahead -- handle case |
|
308 // where first byte of header is at the end of the buffer after the last |
|
309 // gzip segment |
|
310 pos = -1; |
|
311 QByteArray &body = content; |
|
312 int maxPos = body.size()-1; |
|
313 if (maxPos < 1) { |
|
314 return ret; |
|
315 } |
|
316 |
|
317 // Peek ahead to check the gzip magic header |
|
318 if (body[0] != char(gz_magic[0]) || |
|
319 body[1] != char(gz_magic[1])) { |
|
320 return ret; |
|
321 } |
|
322 pos += 2; |
|
323 // Check the rest of the gzip header |
|
324 if (++pos <= maxPos) |
|
325 method = body[pos]; |
|
326 if (pos++ <= maxPos) |
|
327 flags = body[pos]; |
|
328 if (method != Z_DEFLATED || (flags & RESERVED) != 0) { |
|
329 return ret; |
|
330 } |
|
331 |
|
332 // Discard time, xflags and OS code: |
|
333 pos += 6; |
|
334 if (pos > maxPos) |
|
335 return ret; |
|
336 if ((flags & EXTRA_FIELD) && ((pos+2) <= maxPos)) { // skip the extra field |
|
337 unsigned len = (unsigned)body[++pos]; |
|
338 len += ((unsigned)body[++pos])<<8; |
|
339 pos += len; |
|
340 if (pos > maxPos) |
|
341 return ret; |
|
342 } |
|
343 if ((flags & ORIG_NAME) != 0) { // skip the original file name |
|
344 while(++pos <= maxPos && body[pos]) {} |
|
345 } |
|
346 if ((flags & COMMENT) != 0) { // skip the .gz file comment |
|
347 while(++pos <= maxPos && body[pos]) {} |
|
348 } |
|
349 if ((flags & HEAD_CRC) != 0) { // skip the header crc |
|
350 pos += 2; |
|
351 if (pos > maxPos) |
|
352 return ret; |
|
353 } |
|
354 ret = (pos < maxPos); // return failed, if no more bytes left |
|
355 return ret; |
|
356 } |
|
357 |
|
358 int QHttpNetworkReplyPrivate::gunzipBodyPartially(QByteArray &compressed, QByteArray &inflated) |
|
359 { |
|
360 int ret = Z_DATA_ERROR; |
|
361 unsigned have; |
|
362 unsigned char out[CHUNK]; |
|
363 int pos = -1; |
|
364 |
|
365 if (!initInflate) { |
|
366 // check the header |
|
367 if (!gzipCheckHeader(compressed, pos)) |
|
368 return ret; |
|
369 // allocate inflate state |
|
370 inflateStrm.zalloc = Z_NULL; |
|
371 inflateStrm.zfree = Z_NULL; |
|
372 inflateStrm.opaque = Z_NULL; |
|
373 inflateStrm.avail_in = 0; |
|
374 inflateStrm.next_in = Z_NULL; |
|
375 ret = inflateInit2(&inflateStrm, -MAX_WBITS); |
|
376 if (ret != Z_OK) |
|
377 return ret; |
|
378 initInflate = true; |
|
379 streamEnd = false; |
|
380 } |
|
381 |
|
382 //remove the header. |
|
383 compressed.remove(0, pos+1); |
|
384 // expand until deflate stream ends |
|
385 inflateStrm.next_in = (unsigned char *)compressed.data(); |
|
386 inflateStrm.avail_in = compressed.size(); |
|
387 do { |
|
388 inflateStrm.avail_out = sizeof(out); |
|
389 inflateStrm.next_out = out; |
|
390 ret = inflate(&inflateStrm, Z_NO_FLUSH); |
|
391 switch (ret) { |
|
392 case Z_NEED_DICT: |
|
393 ret = Z_DATA_ERROR; |
|
394 // and fall through |
|
395 case Z_DATA_ERROR: |
|
396 case Z_MEM_ERROR: |
|
397 inflateEnd(&inflateStrm); |
|
398 initInflate = false; |
|
399 return ret; |
|
400 } |
|
401 have = sizeof(out) - inflateStrm.avail_out; |
|
402 inflated.append(QByteArray((const char *)out, have)); |
|
403 } while (inflateStrm.avail_out == 0); |
|
404 // clean up and return |
|
405 if (ret <= Z_ERRNO || ret == Z_STREAM_END) { |
|
406 inflateEnd(&inflateStrm); |
|
407 initInflate = false; |
|
408 } |
|
409 streamEnd = (ret == Z_STREAM_END); |
|
410 return ret; |
|
411 } |
|
412 #endif |
|
413 |
|
414 qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) |
|
415 { |
|
416 qint64 bytes = 0; |
|
417 char c; |
|
418 |
|
419 while (socket->bytesAvailable()) { |
|
420 // allow both CRLF & LF (only) line endings |
|
421 if (socket->peek(&c, 1) == 1 && c == '\n') { |
|
422 bytes += socket->read(&c, 1); // read the "n" |
|
423 // remove the CR at the end |
|
424 if (fragment.endsWith('\r')) { |
|
425 fragment.truncate(fragment.length()-1); |
|
426 } |
|
427 bool ok = parseStatus(fragment); |
|
428 state = ReadingHeaderState; |
|
429 fragment.clear(); |
|
430 if (!ok) { |
|
431 return -1; |
|
432 } |
|
433 break; |
|
434 } else { |
|
435 c = 0; |
|
436 int haveRead = socket->read(&c, 1); |
|
437 if (haveRead == -1) |
|
438 return -1; |
|
439 bytes += haveRead; |
|
440 fragment.append(c); |
|
441 } |
|
442 |
|
443 // is this a valid reply? |
|
444 if (fragment.length() >= 5 && !fragment.startsWith("HTTP/")) |
|
445 { |
|
446 fragment.clear(); |
|
447 return -1; |
|
448 } |
|
449 |
|
450 } |
|
451 |
|
452 return bytes; |
|
453 } |
|
454 |
|
455 bool QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status) |
|
456 { |
|
457 // from RFC 2616: |
|
458 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF |
|
459 // HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT |
|
460 // that makes: 'HTTP/n.n xxx Message' |
|
461 // byte count: 0123456789012 |
|
462 |
|
463 static const int minLength = 11; |
|
464 static const int dotPos = 6; |
|
465 static const int spacePos = 8; |
|
466 static const char httpMagic[] = "HTTP/"; |
|
467 |
|
468 if (status.length() < minLength |
|
469 || !status.startsWith(httpMagic) |
|
470 || status.at(dotPos) != '.' |
|
471 || status.at(spacePos) != ' ') { |
|
472 // I don't know how to parse this status line |
|
473 return false; |
|
474 } |
|
475 |
|
476 // optimize for the valid case: defer checking until the end |
|
477 majorVersion = status.at(dotPos - 1) - '0'; |
|
478 minorVersion = status.at(dotPos + 1) - '0'; |
|
479 |
|
480 int i = spacePos; |
|
481 int j = status.indexOf(' ', i + 1); // j == -1 || at(j) == ' ' so j+1 == 0 && j+1 <= length() |
|
482 const QByteArray code = status.mid(i + 1, j - i - 1); |
|
483 |
|
484 bool ok; |
|
485 statusCode = code.toInt(&ok); |
|
486 reasonPhrase = QString::fromLatin1(status.constData() + j + 1); |
|
487 |
|
488 return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9; |
|
489 } |
|
490 |
|
491 qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket) |
|
492 { |
|
493 qint64 bytes = 0; |
|
494 char c = 0; |
|
495 bool allHeaders = false; |
|
496 while (!allHeaders && socket->bytesAvailable()) { |
|
497 if (socket->peek(&c, 1) == 1 && c == '\n') { |
|
498 // check for possible header endings. As per HTTP rfc, |
|
499 // the header endings will be marked by CRLFCRLF. But |
|
500 // we will allow CRLFLF, LFLF & CRLFCRLF |
|
501 if (fragment.endsWith("\n\r") || fragment.endsWith('\n')) |
|
502 allHeaders = true; |
|
503 } |
|
504 bytes += socket->read(&c, 1); |
|
505 fragment.append(c); |
|
506 } |
|
507 // we received all headers now parse them |
|
508 if (allHeaders) { |
|
509 parseHeader(fragment); |
|
510 state = ReadingDataState; |
|
511 fragment.clear(); // next fragment |
|
512 bodyLength = contentLength(); // cache the length |
|
513 |
|
514 // cache isChunked() since it is called often |
|
515 chunkedTransferEncoding = headerField("transfer-encoding").toLower().contains("chunked"); |
|
516 |
|
517 // cache isConnectionCloseEnabled since it is called often |
|
518 QByteArray connectionHeaderField = headerField("connection"); |
|
519 // check for explicit indication of close or the implicit connection close of HTTP/1.0 |
|
520 connectionCloseEnabled = (connectionHeaderField.toLower().contains("close") || |
|
521 headerField("proxy-connection").toLower().contains("close")) || |
|
522 (majorVersion == 1 && minorVersion == 0 && connectionHeaderField.isEmpty()); |
|
523 } |
|
524 return bytes; |
|
525 } |
|
526 |
|
527 void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header) |
|
528 { |
|
529 // see rfc2616, sec 4 for information about HTTP/1.1 headers. |
|
530 // allows relaxed parsing here, accepts both CRLF & LF line endings |
|
531 const QByteArrayMatcher lf("\n"); |
|
532 const QByteArrayMatcher colon(":"); |
|
533 int i = 0; |
|
534 while (i < header.count()) { |
|
535 int j = colon.indexIn(header, i); // field-name |
|
536 if (j == -1) |
|
537 break; |
|
538 const QByteArray field = header.mid(i, j - i).trimmed(); |
|
539 j++; |
|
540 // any number of LWS is allowed before and after the value |
|
541 QByteArray value; |
|
542 do { |
|
543 i = lf.indexIn(header, j); |
|
544 if (i == -1) |
|
545 break; |
|
546 if (!value.isEmpty()) |
|
547 value += ' '; |
|
548 // check if we have CRLF or only LF |
|
549 bool hasCR = (i && header[i-1] == '\r'); |
|
550 int length = i -(hasCR ? 1: 0) - j; |
|
551 value += header.mid(j, length).trimmed(); |
|
552 j = ++i; |
|
553 } while (i < header.count() && (header.at(i) == ' ' || header.at(i) == '\t')); |
|
554 if (i == -1) |
|
555 break; // something is wrong |
|
556 |
|
557 fields.append(qMakePair(field, value)); |
|
558 } |
|
559 } |
|
560 |
|
561 bool QHttpNetworkReplyPrivate::isChunked() |
|
562 { |
|
563 return chunkedTransferEncoding; |
|
564 } |
|
565 |
|
566 bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled() |
|
567 { |
|
568 return connectionCloseEnabled || forceConnectionCloseEnabled; |
|
569 } |
|
570 |
|
571 // note this function can only be used for non-chunked, non-compressed with |
|
572 // known content length |
|
573 qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb) |
|
574 { |
|
575 qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead); |
|
576 QByteArray bd; |
|
577 bd.resize(toBeRead); |
|
578 qint64 haveRead = socket->read(bd.data(), bd.size()); |
|
579 if (haveRead == -1) { |
|
580 bd.clear(); |
|
581 return 0; // ### error checking here; |
|
582 } |
|
583 bd.resize(haveRead); |
|
584 |
|
585 rb->append(bd); |
|
586 |
|
587 if (contentRead + haveRead == bodyLength) { |
|
588 state = AllDoneState; |
|
589 } |
|
590 |
|
591 contentRead += haveRead; |
|
592 return haveRead; |
|
593 } |
|
594 |
|
595 |
|
596 qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuffer *out) |
|
597 { |
|
598 qint64 bytes = 0; |
|
599 if (isChunked()) { |
|
600 bytes += readReplyBodyChunked(socket, out); // chunked transfer encoding (rfc 2616, sec 3.6) |
|
601 } else if (bodyLength > 0) { // we have a Content-Length |
|
602 bytes += readReplyBodyRaw(socket, out, bodyLength - contentRead); |
|
603 if (contentRead + bytes == bodyLength) |
|
604 state = AllDoneState; |
|
605 } else { |
|
606 bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable()); |
|
607 } |
|
608 contentRead += bytes; |
|
609 return bytes; |
|
610 } |
|
611 |
|
612 qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QIODevice *in, QByteDataBuffer *out, qint64 size) |
|
613 { |
|
614 qint64 bytes = 0; |
|
615 Q_ASSERT(in); |
|
616 Q_ASSERT(out); |
|
617 |
|
618 int toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, in->bytesAvailable())); |
|
619 while (toBeRead > 0) { |
|
620 QByteArray byteData; |
|
621 byteData.resize(toBeRead); |
|
622 qint64 haveRead = in->read(byteData.data(), byteData.size()); |
|
623 if (haveRead <= 0) { |
|
624 // ### error checking here |
|
625 byteData.clear(); |
|
626 return bytes; |
|
627 } |
|
628 |
|
629 byteData.resize(haveRead); |
|
630 out->append(byteData); |
|
631 bytes += haveRead; |
|
632 size -= haveRead; |
|
633 |
|
634 toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, in->bytesAvailable())); |
|
635 } |
|
636 return bytes; |
|
637 |
|
638 } |
|
639 |
|
640 qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *in, QByteDataBuffer *out) |
|
641 { |
|
642 qint64 bytes = 0; |
|
643 while (in->bytesAvailable()) { // while we can read from input |
|
644 // if we are done with the current chunk, get the size of the new chunk |
|
645 if (currentChunkRead >= currentChunkSize) { |
|
646 currentChunkSize = 0; |
|
647 currentChunkRead = 0; |
|
648 if (bytes) { |
|
649 char crlf[2]; |
|
650 bytes += in->read(crlf, 2); // read the "\r\n" after the chunk |
|
651 } |
|
652 bytes += getChunkSize(in, ¤tChunkSize); |
|
653 if (currentChunkSize == -1) |
|
654 break; |
|
655 } |
|
656 // if the chunk size is 0, end of the stream |
|
657 if (currentChunkSize == 0) { |
|
658 state = AllDoneState; |
|
659 break; |
|
660 } |
|
661 |
|
662 // otherwise, try to read what is missing for this chunk |
|
663 qint64 haveRead = readReplyBodyRaw (in, out, currentChunkSize - currentChunkRead); |
|
664 currentChunkRead += haveRead; |
|
665 bytes += haveRead; |
|
666 |
|
667 // ### error checking here |
|
668 |
|
669 } |
|
670 return bytes; |
|
671 } |
|
672 |
|
673 qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *in, qint64 *chunkSize) |
|
674 { |
|
675 qint64 bytes = 0; |
|
676 char crlf[2]; |
|
677 *chunkSize = -1; |
|
678 int bytesAvailable = in->bytesAvailable(); |
|
679 while (bytesAvailable > bytes) { |
|
680 qint64 sniffedBytes = in->peek(crlf, 2); |
|
681 int fragmentSize = fragment.size(); |
|
682 // check the next two bytes for a "\r\n", skip blank lines |
|
683 if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n') |
|
684 ||(fragmentSize > 1 && fragment.endsWith('\r') && crlf[0] == '\n')) |
|
685 { |
|
686 bytes += in->read(crlf, 1); // read the \r or \n |
|
687 if (crlf[0] == '\r') |
|
688 bytes += in->read(crlf, 1); // read the \n |
|
689 bool ok = false; |
|
690 // ignore the chunk-extension |
|
691 fragment = fragment.mid(0, fragment.indexOf(';')).trimmed(); |
|
692 *chunkSize = fragment.toLong(&ok, 16); |
|
693 fragment.clear(); |
|
694 break; // size done |
|
695 } else { |
|
696 // read the fragment to the buffer |
|
697 char c = 0; |
|
698 bytes += in->read(&c, 1); |
|
699 fragment.append(c); |
|
700 } |
|
701 } |
|
702 return bytes; |
|
703 } |
|
704 |
|
705 void QHttpNetworkReplyPrivate::appendUncompressedReplyData(QByteArray &qba) |
|
706 { |
|
707 responseData.append(qba); |
|
708 |
|
709 // clear the original! helps with implicit sharing and |
|
710 // avoiding memcpy when the user is reading the data |
|
711 qba.clear(); |
|
712 } |
|
713 |
|
714 void QHttpNetworkReplyPrivate::appendUncompressedReplyData(QByteDataBuffer &data) |
|
715 { |
|
716 responseData.append(data); |
|
717 |
|
718 // clear the original! helps with implicit sharing and |
|
719 // avoiding memcpy when the user is reading the data |
|
720 data.clear(); |
|
721 } |
|
722 |
|
723 void QHttpNetworkReplyPrivate::appendCompressedReplyData(QByteDataBuffer &data) |
|
724 { |
|
725 // Work in progress: Later we will directly use a list of QByteArray or a QRingBuffer |
|
726 // instead of one QByteArray. |
|
727 for(int i = 0; i < data.bufferCount(); i++) { |
|
728 QByteArray &byteData = data[i]; |
|
729 compressedData.append(byteData.constData(), byteData.size()); |
|
730 } |
|
731 data.clear(); |
|
732 } |
|
733 |
|
734 |
|
735 bool QHttpNetworkReplyPrivate::shouldEmitSignals() |
|
736 { |
|
737 // for 401 & 407 don't emit the data signals. Content along with these |
|
738 // responses are send only if the authentication fails. |
|
739 return (statusCode != 401 && statusCode != 407); |
|
740 } |
|
741 |
|
742 bool QHttpNetworkReplyPrivate::expectContent() |
|
743 { |
|
744 // check whether we can expect content after the headers (rfc 2616, sec4.4) |
|
745 if ((statusCode >= 100 && statusCode < 200) |
|
746 || statusCode == 204 || statusCode == 304) |
|
747 return false; |
|
748 if (request.operation() == QHttpNetworkRequest::Head) |
|
749 return !shouldEmitSignals(); |
|
750 if (contentLength() == 0) |
|
751 return false; |
|
752 return true; |
|
753 } |
|
754 |
|
755 void QHttpNetworkReplyPrivate::eraseData() |
|
756 { |
|
757 compressedData.clear(); |
|
758 responseData.clear(); |
|
759 } |
|
760 |
|
761 |
|
762 // SSL support below |
|
763 #ifndef QT_NO_OPENSSL |
|
764 |
|
765 QSslConfiguration QHttpNetworkReply::sslConfiguration() const |
|
766 { |
|
767 Q_D(const QHttpNetworkReply); |
|
768 if (d->connection) |
|
769 return d->connection->d_func()->sslConfiguration(*this); |
|
770 return QSslConfiguration(); |
|
771 } |
|
772 |
|
773 void QHttpNetworkReply::setSslConfiguration(const QSslConfiguration &config) |
|
774 { |
|
775 Q_D(QHttpNetworkReply); |
|
776 if (d->connection) |
|
777 d->connection->setSslConfiguration(config); |
|
778 } |
|
779 |
|
780 void QHttpNetworkReply::ignoreSslErrors() |
|
781 { |
|
782 Q_D(QHttpNetworkReply); |
|
783 if (d->connection) |
|
784 d->connection->ignoreSslErrors(); |
|
785 } |
|
786 |
|
787 void QHttpNetworkReply::ignoreSslErrors(const QList<QSslError> &errors) |
|
788 { |
|
789 Q_D(QHttpNetworkReply); |
|
790 if (d->connection) |
|
791 d->connection->ignoreSslErrors(errors); |
|
792 } |
|
793 |
|
794 |
|
795 #endif //QT_NO_OPENSSL |
|
796 |
|
797 |
|
798 QT_END_NAMESPACE |
|
799 |
|
800 #endif // QT_NO_HTTP |