author | Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com> |
Fri, 19 Feb 2010 23:40:16 +0200 | |
branch | RCL_3 |
changeset 4 | 3b1da2848fc7 |
parent 3 | 41300fa6a67c |
child 8 | 3f74d0d4af4c |
permissions | -rw-r--r-- |
0 | 1 |
/**************************************************************************** |
2 |
** |
|
4
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
3 |
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
0 | 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); |
|
4
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
182 |
// we'll take the last buffer, so schedule another read from http |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
183 |
if (d->downstreamLimited && d->responseData.bufferCount() == 1) |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
184 |
d->connection->d_func()->readMoreLater(this); |
0 | 185 |
return d->responseData.read(); |
186 |
} |
|
187 |
||
4
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
188 |
void QHttpNetworkReply::setDownstreamLimited(bool dsl) |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
189 |
{ |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
190 |
Q_D(QHttpNetworkReply); |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
191 |
d->downstreamLimited = dsl; |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
192 |
d->connection->d_func()->readMoreLater(this); |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
193 |
} |
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
194 |
|
0 | 195 |
bool QHttpNetworkReply::isFinished() const |
196 |
{ |
|
197 |
return d_func()->state == QHttpNetworkReplyPrivate::AllDoneState; |
|
198 |
} |
|
199 |
||
200 |
bool QHttpNetworkReply::isPipeliningUsed() const |
|
201 |
{ |
|
202 |
return d_func()->pipeliningUsed; |
|
203 |
} |
|
204 |
||
205 |
||
206 |
QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl) |
|
207 |
: QHttpNetworkHeaderPrivate(newUrl), state(NothingDoneState), statusCode(100), |
|
208 |
majorVersion(0), minorVersion(0), bodyLength(0), contentRead(0), totalProgress(0), |
|
209 |
chunkedTransferEncoding(false), |
|
210 |
connectionCloseEnabled(true), |
|
211 |
forceConnectionCloseEnabled(false), |
|
212 |
currentChunkSize(0), currentChunkRead(0), connection(0), initInflate(false), |
|
213 |
autoDecompress(false), responseData(), requestIsPrepared(false) |
|
4
3b1da2848fc7
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
3
diff
changeset
|
214 |
,pipeliningUsed(false), downstreamLimited(false) |
0 | 215 |
{ |
216 |
} |
|
217 |
||
218 |
QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate() |
|
219 |
{ |
|
220 |
} |
|
221 |
||
222 |
void QHttpNetworkReplyPrivate::clear() |
|
223 |
{ |
|
224 |
state = NothingDoneState; |
|
225 |
statusCode = 100; |
|
226 |
bodyLength = 0; |
|
227 |
contentRead = 0; |
|
228 |
totalProgress = 0; |
|
229 |
currentChunkSize = 0; |
|
230 |
currentChunkRead = 0; |
|
231 |
connectionCloseEnabled = true; |
|
232 |
connection = 0; |
|
233 |
#ifndef QT_NO_COMPRESS |
|
234 |
if (initInflate) |
|
235 |
inflateEnd(&inflateStrm); |
|
236 |
#endif |
|
237 |
initInflate = false; |
|
238 |
streamEnd = false; |
|
239 |
autoDecompress = false; |
|
240 |
fields.clear(); |
|
241 |
} |
|
242 |
||
243 |
// QHttpNetworkReplyPrivate |
|
244 |
qint64 QHttpNetworkReplyPrivate::bytesAvailable() const |
|
245 |
{ |
|
246 |
return (state != ReadingDataState ? 0 : fragment.size()); |
|
247 |
} |
|
248 |
||
249 |
bool QHttpNetworkReplyPrivate::isGzipped() |
|
250 |
{ |
|
251 |
QByteArray encoding = headerField("content-encoding"); |
|
3
41300fa6a67c
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
0
diff
changeset
|
252 |
return qstricmp(encoding.constData(), "gzip") == 0; |
0 | 253 |
} |
254 |
||
255 |
void QHttpNetworkReplyPrivate::removeAutoDecompressHeader() |
|
256 |
{ |
|
257 |
// The header "Content-Encoding = gzip" is retained. |
|
258 |
// Content-Length is removed since the actual one send by the server is for compressed data |
|
259 |
QByteArray name("content-length"); |
|
260 |
QList<QPair<QByteArray, QByteArray> >::Iterator it = fields.begin(), |
|
261 |
end = fields.end(); |
|
262 |
while (it != end) { |
|
3
41300fa6a67c
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
0
diff
changeset
|
263 |
if (qstricmp(name.constData(), it->first.constData()) == 0) { |
0 | 264 |
fields.erase(it); |
265 |
break; |
|
266 |
} |
|
267 |
++it; |
|
268 |
} |
|
269 |
||
270 |
} |
|
271 |
||
272 |
bool QHttpNetworkReplyPrivate::findChallenge(bool forProxy, QByteArray &challenge) const |
|
273 |
{ |
|
274 |
challenge.clear(); |
|
275 |
// find out the type of authentication protocol requested. |
|
276 |
QByteArray header = forProxy ? "proxy-authenticate" : "www-authenticate"; |
|
277 |
// pick the best protocol (has to match parsing in QAuthenticatorPrivate) |
|
278 |
QList<QByteArray> challenges = headerFieldValues(header); |
|
279 |
for (int i = 0; i<challenges.size(); i++) { |
|
280 |
QByteArray line = challenges.at(i); |
|
3
41300fa6a67c
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
0
diff
changeset
|
281 |
// todo use qstrincmp |
0 | 282 |
if (!line.toLower().startsWith("negotiate")) |
283 |
challenge = line; |
|
284 |
} |
|
285 |
return !challenge.isEmpty(); |
|
286 |
} |
|
287 |
||
288 |
QAuthenticatorPrivate::Method QHttpNetworkReplyPrivate::authenticationMethod(bool isProxy) const |
|
289 |
{ |
|
290 |
// The logic is same as the one used in void QAuthenticatorPrivate::parseHttpResponse() |
|
291 |
QAuthenticatorPrivate::Method method = QAuthenticatorPrivate::None; |
|
292 |
QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate"; |
|
293 |
QList<QByteArray> challenges = headerFieldValues(header); |
|
294 |
for (int i = 0; i<challenges.size(); i++) { |
|
295 |
QByteArray line = challenges.at(i).trimmed().toLower(); |
|
296 |
if (method < QAuthenticatorPrivate::Basic |
|
297 |
&& line.startsWith("basic")) { |
|
298 |
method = QAuthenticatorPrivate::Basic; |
|
299 |
} else if (method < QAuthenticatorPrivate::Ntlm |
|
300 |
&& line.startsWith("ntlm")) { |
|
301 |
method = QAuthenticatorPrivate::Ntlm; |
|
302 |
} else if (method < QAuthenticatorPrivate::DigestMd5 |
|
303 |
&& line.startsWith("digest")) { |
|
304 |
method = QAuthenticatorPrivate::DigestMd5; |
|
305 |
} |
|
306 |
} |
|
307 |
return method; |
|
308 |
} |
|
309 |
||
310 |
#ifndef QT_NO_COMPRESS |
|
311 |
bool QHttpNetworkReplyPrivate::gzipCheckHeader(QByteArray &content, int &pos) |
|
312 |
{ |
|
313 |
int method = 0; // method byte |
|
314 |
int flags = 0; // flags byte |
|
315 |
bool ret = false; |
|
316 |
||
317 |
// Assure two bytes in the buffer so we can peek ahead -- handle case |
|
318 |
// where first byte of header is at the end of the buffer after the last |
|
319 |
// gzip segment |
|
320 |
pos = -1; |
|
321 |
QByteArray &body = content; |
|
322 |
int maxPos = body.size()-1; |
|
323 |
if (maxPos < 1) { |
|
324 |
return ret; |
|
325 |
} |
|
326 |
||
327 |
// Peek ahead to check the gzip magic header |
|
328 |
if (body[0] != char(gz_magic[0]) || |
|
329 |
body[1] != char(gz_magic[1])) { |
|
330 |
return ret; |
|
331 |
} |
|
332 |
pos += 2; |
|
333 |
// Check the rest of the gzip header |
|
334 |
if (++pos <= maxPos) |
|
335 |
method = body[pos]; |
|
336 |
if (pos++ <= maxPos) |
|
337 |
flags = body[pos]; |
|
338 |
if (method != Z_DEFLATED || (flags & RESERVED) != 0) { |
|
339 |
return ret; |
|
340 |
} |
|
341 |
||
342 |
// Discard time, xflags and OS code: |
|
343 |
pos += 6; |
|
344 |
if (pos > maxPos) |
|
345 |
return ret; |
|
346 |
if ((flags & EXTRA_FIELD) && ((pos+2) <= maxPos)) { // skip the extra field |
|
347 |
unsigned len = (unsigned)body[++pos]; |
|
348 |
len += ((unsigned)body[++pos])<<8; |
|
349 |
pos += len; |
|
350 |
if (pos > maxPos) |
|
351 |
return ret; |
|
352 |
} |
|
353 |
if ((flags & ORIG_NAME) != 0) { // skip the original file name |
|
354 |
while(++pos <= maxPos && body[pos]) {} |
|
355 |
} |
|
356 |
if ((flags & COMMENT) != 0) { // skip the .gz file comment |
|
357 |
while(++pos <= maxPos && body[pos]) {} |
|
358 |
} |
|
359 |
if ((flags & HEAD_CRC) != 0) { // skip the header crc |
|
360 |
pos += 2; |
|
361 |
if (pos > maxPos) |
|
362 |
return ret; |
|
363 |
} |
|
364 |
ret = (pos < maxPos); // return failed, if no more bytes left |
|
365 |
return ret; |
|
366 |
} |
|
367 |
||
368 |
int QHttpNetworkReplyPrivate::gunzipBodyPartially(QByteArray &compressed, QByteArray &inflated) |
|
369 |
{ |
|
370 |
int ret = Z_DATA_ERROR; |
|
371 |
unsigned have; |
|
372 |
unsigned char out[CHUNK]; |
|
373 |
int pos = -1; |
|
374 |
||
375 |
if (!initInflate) { |
|
376 |
// check the header |
|
377 |
if (!gzipCheckHeader(compressed, pos)) |
|
378 |
return ret; |
|
379 |
// allocate inflate state |
|
380 |
inflateStrm.zalloc = Z_NULL; |
|
381 |
inflateStrm.zfree = Z_NULL; |
|
382 |
inflateStrm.opaque = Z_NULL; |
|
383 |
inflateStrm.avail_in = 0; |
|
384 |
inflateStrm.next_in = Z_NULL; |
|
385 |
ret = inflateInit2(&inflateStrm, -MAX_WBITS); |
|
386 |
if (ret != Z_OK) |
|
387 |
return ret; |
|
388 |
initInflate = true; |
|
389 |
streamEnd = false; |
|
390 |
} |
|
391 |
||
392 |
//remove the header. |
|
393 |
compressed.remove(0, pos+1); |
|
394 |
// expand until deflate stream ends |
|
395 |
inflateStrm.next_in = (unsigned char *)compressed.data(); |
|
396 |
inflateStrm.avail_in = compressed.size(); |
|
397 |
do { |
|
398 |
inflateStrm.avail_out = sizeof(out); |
|
399 |
inflateStrm.next_out = out; |
|
400 |
ret = inflate(&inflateStrm, Z_NO_FLUSH); |
|
401 |
switch (ret) { |
|
402 |
case Z_NEED_DICT: |
|
403 |
ret = Z_DATA_ERROR; |
|
404 |
// and fall through |
|
405 |
case Z_DATA_ERROR: |
|
406 |
case Z_MEM_ERROR: |
|
407 |
inflateEnd(&inflateStrm); |
|
408 |
initInflate = false; |
|
409 |
return ret; |
|
410 |
} |
|
411 |
have = sizeof(out) - inflateStrm.avail_out; |
|
412 |
inflated.append(QByteArray((const char *)out, have)); |
|
413 |
} while (inflateStrm.avail_out == 0); |
|
414 |
// clean up and return |
|
415 |
if (ret <= Z_ERRNO || ret == Z_STREAM_END) { |
|
416 |
inflateEnd(&inflateStrm); |
|
417 |
initInflate = false; |
|
418 |
} |
|
419 |
streamEnd = (ret == Z_STREAM_END); |
|
420 |
return ret; |
|
421 |
} |
|
422 |
#endif |
|
423 |
||
424 |
qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket) |
|
425 |
{ |
|
426 |
qint64 bytes = 0; |
|
427 |
char c; |
|
428 |
||
429 |
while (socket->bytesAvailable()) { |
|
430 |
// allow both CRLF & LF (only) line endings |
|
431 |
if (socket->peek(&c, 1) == 1 && c == '\n') { |
|
432 |
bytes += socket->read(&c, 1); // read the "n" |
|
433 |
// remove the CR at the end |
|
434 |
if (fragment.endsWith('\r')) { |
|
435 |
fragment.truncate(fragment.length()-1); |
|
436 |
} |
|
437 |
bool ok = parseStatus(fragment); |
|
438 |
state = ReadingHeaderState; |
|
439 |
fragment.clear(); |
|
440 |
if (!ok) { |
|
441 |
return -1; |
|
442 |
} |
|
443 |
break; |
|
444 |
} else { |
|
445 |
c = 0; |
|
446 |
int haveRead = socket->read(&c, 1); |
|
447 |
if (haveRead == -1) |
|
448 |
return -1; |
|
449 |
bytes += haveRead; |
|
450 |
fragment.append(c); |
|
451 |
} |
|
452 |
||
453 |
// is this a valid reply? |
|
454 |
if (fragment.length() >= 5 && !fragment.startsWith("HTTP/")) |
|
455 |
{ |
|
456 |
fragment.clear(); |
|
457 |
return -1; |
|
458 |
} |
|
459 |
||
460 |
} |
|
461 |
||
462 |
return bytes; |
|
463 |
} |
|
464 |
||
465 |
bool QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status) |
|
466 |
{ |
|
467 |
// from RFC 2616: |
|
468 |
// Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF |
|
469 |
// HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT |
|
470 |
// that makes: 'HTTP/n.n xxx Message' |
|
471 |
// byte count: 0123456789012 |
|
472 |
||
473 |
static const int minLength = 11; |
|
474 |
static const int dotPos = 6; |
|
475 |
static const int spacePos = 8; |
|
476 |
static const char httpMagic[] = "HTTP/"; |
|
477 |
||
478 |
if (status.length() < minLength |
|
479 |
|| !status.startsWith(httpMagic) |
|
480 |
|| status.at(dotPos) != '.' |
|
481 |
|| status.at(spacePos) != ' ') { |
|
482 |
// I don't know how to parse this status line |
|
483 |
return false; |
|
484 |
} |
|
485 |
||
486 |
// optimize for the valid case: defer checking until the end |
|
487 |
majorVersion = status.at(dotPos - 1) - '0'; |
|
488 |
minorVersion = status.at(dotPos + 1) - '0'; |
|
489 |
||
490 |
int i = spacePos; |
|
491 |
int j = status.indexOf(' ', i + 1); // j == -1 || at(j) == ' ' so j+1 == 0 && j+1 <= length() |
|
492 |
const QByteArray code = status.mid(i + 1, j - i - 1); |
|
493 |
||
494 |
bool ok; |
|
495 |
statusCode = code.toInt(&ok); |
|
496 |
reasonPhrase = QString::fromLatin1(status.constData() + j + 1); |
|
497 |
||
498 |
return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9; |
|
499 |
} |
|
500 |
||
501 |
qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket) |
|
502 |
{ |
|
503 |
qint64 bytes = 0; |
|
504 |
char c = 0; |
|
505 |
bool allHeaders = false; |
|
506 |
while (!allHeaders && socket->bytesAvailable()) { |
|
507 |
if (socket->peek(&c, 1) == 1 && c == '\n') { |
|
508 |
// check for possible header endings. As per HTTP rfc, |
|
509 |
// the header endings will be marked by CRLFCRLF. But |
|
510 |
// we will allow CRLFLF, LFLF & CRLFCRLF |
|
511 |
if (fragment.endsWith("\n\r") || fragment.endsWith('\n')) |
|
512 |
allHeaders = true; |
|
513 |
} |
|
514 |
bytes += socket->read(&c, 1); |
|
515 |
fragment.append(c); |
|
516 |
} |
|
517 |
// we received all headers now parse them |
|
518 |
if (allHeaders) { |
|
519 |
parseHeader(fragment); |
|
520 |
state = ReadingDataState; |
|
521 |
fragment.clear(); // next fragment |
|
522 |
bodyLength = contentLength(); // cache the length |
|
523 |
||
524 |
// cache isChunked() since it is called often |
|
525 |
chunkedTransferEncoding = headerField("transfer-encoding").toLower().contains("chunked"); |
|
526 |
||
527 |
// cache isConnectionCloseEnabled since it is called often |
|
528 |
QByteArray connectionHeaderField = headerField("connection"); |
|
529 |
// check for explicit indication of close or the implicit connection close of HTTP/1.0 |
|
530 |
connectionCloseEnabled = (connectionHeaderField.toLower().contains("close") || |
|
531 |
headerField("proxy-connection").toLower().contains("close")) || |
|
532 |
(majorVersion == 1 && minorVersion == 0 && connectionHeaderField.isEmpty()); |
|
533 |
} |
|
534 |
return bytes; |
|
535 |
} |
|
536 |
||
537 |
void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header) |
|
538 |
{ |
|
539 |
// see rfc2616, sec 4 for information about HTTP/1.1 headers. |
|
540 |
// allows relaxed parsing here, accepts both CRLF & LF line endings |
|
541 |
const QByteArrayMatcher lf("\n"); |
|
542 |
const QByteArrayMatcher colon(":"); |
|
543 |
int i = 0; |
|
544 |
while (i < header.count()) { |
|
545 |
int j = colon.indexIn(header, i); // field-name |
|
546 |
if (j == -1) |
|
547 |
break; |
|
548 |
const QByteArray field = header.mid(i, j - i).trimmed(); |
|
549 |
j++; |
|
550 |
// any number of LWS is allowed before and after the value |
|
551 |
QByteArray value; |
|
552 |
do { |
|
553 |
i = lf.indexIn(header, j); |
|
554 |
if (i == -1) |
|
555 |
break; |
|
556 |
if (!value.isEmpty()) |
|
557 |
value += ' '; |
|
558 |
// check if we have CRLF or only LF |
|
559 |
bool hasCR = (i && header[i-1] == '\r'); |
|
560 |
int length = i -(hasCR ? 1: 0) - j; |
|
561 |
value += header.mid(j, length).trimmed(); |
|
562 |
j = ++i; |
|
563 |
} while (i < header.count() && (header.at(i) == ' ' || header.at(i) == '\t')); |
|
564 |
if (i == -1) |
|
565 |
break; // something is wrong |
|
566 |
||
567 |
fields.append(qMakePair(field, value)); |
|
568 |
} |
|
569 |
} |
|
570 |
||
571 |
bool QHttpNetworkReplyPrivate::isChunked() |
|
572 |
{ |
|
573 |
return chunkedTransferEncoding; |
|
574 |
} |
|
575 |
||
576 |
bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled() |
|
577 |
{ |
|
578 |
return connectionCloseEnabled || forceConnectionCloseEnabled; |
|
579 |
} |
|
580 |
||
581 |
// note this function can only be used for non-chunked, non-compressed with |
|
582 |
// known content length |
|
583 |
qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb) |
|
584 |
{ |
|
585 |
qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead); |
|
586 |
QByteArray bd; |
|
587 |
bd.resize(toBeRead); |
|
588 |
qint64 haveRead = socket->read(bd.data(), bd.size()); |
|
589 |
if (haveRead == -1) { |
|
590 |
bd.clear(); |
|
591 |
return 0; // ### error checking here; |
|
592 |
} |
|
593 |
bd.resize(haveRead); |
|
594 |
||
595 |
rb->append(bd); |
|
596 |
||
597 |
if (contentRead + haveRead == bodyLength) { |
|
598 |
state = AllDoneState; |
|
599 |
} |
|
600 |
||
601 |
contentRead += haveRead; |
|
602 |
return haveRead; |
|
603 |
} |
|
604 |
||
605 |
||
606 |
qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuffer *out) |
|
607 |
{ |
|
608 |
qint64 bytes = 0; |
|
609 |
if (isChunked()) { |
|
610 |
bytes += readReplyBodyChunked(socket, out); // chunked transfer encoding (rfc 2616, sec 3.6) |
|
611 |
} else if (bodyLength > 0) { // we have a Content-Length |
|
612 |
bytes += readReplyBodyRaw(socket, out, bodyLength - contentRead); |
|
613 |
if (contentRead + bytes == bodyLength) |
|
614 |
state = AllDoneState; |
|
615 |
} else { |
|
616 |
bytes += readReplyBodyRaw(socket, out, socket->bytesAvailable()); |
|
617 |
} |
|
618 |
contentRead += bytes; |
|
619 |
return bytes; |
|
620 |
} |
|
621 |
||
622 |
qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QIODevice *in, QByteDataBuffer *out, qint64 size) |
|
623 |
{ |
|
624 |
qint64 bytes = 0; |
|
625 |
Q_ASSERT(in); |
|
626 |
Q_ASSERT(out); |
|
627 |
||
628 |
int toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, in->bytesAvailable())); |
|
629 |
while (toBeRead > 0) { |
|
630 |
QByteArray byteData; |
|
631 |
byteData.resize(toBeRead); |
|
632 |
qint64 haveRead = in->read(byteData.data(), byteData.size()); |
|
633 |
if (haveRead <= 0) { |
|
634 |
// ### error checking here |
|
635 |
byteData.clear(); |
|
636 |
return bytes; |
|
637 |
} |
|
638 |
||
639 |
byteData.resize(haveRead); |
|
640 |
out->append(byteData); |
|
641 |
bytes += haveRead; |
|
642 |
size -= haveRead; |
|
643 |
||
644 |
toBeRead = qMin<qint64>(128*1024, qMin<qint64>(size, in->bytesAvailable())); |
|
645 |
} |
|
646 |
return bytes; |
|
647 |
||
648 |
} |
|
649 |
||
650 |
qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *in, QByteDataBuffer *out) |
|
651 |
{ |
|
652 |
qint64 bytes = 0; |
|
653 |
while (in->bytesAvailable()) { // while we can read from input |
|
654 |
// if we are done with the current chunk, get the size of the new chunk |
|
655 |
if (currentChunkRead >= currentChunkSize) { |
|
656 |
currentChunkSize = 0; |
|
657 |
currentChunkRead = 0; |
|
658 |
if (bytes) { |
|
659 |
char crlf[2]; |
|
660 |
bytes += in->read(crlf, 2); // read the "\r\n" after the chunk |
|
661 |
} |
|
662 |
bytes += getChunkSize(in, ¤tChunkSize); |
|
663 |
if (currentChunkSize == -1) |
|
664 |
break; |
|
665 |
} |
|
666 |
// if the chunk size is 0, end of the stream |
|
667 |
if (currentChunkSize == 0) { |
|
668 |
state = AllDoneState; |
|
669 |
break; |
|
670 |
} |
|
671 |
||
672 |
// otherwise, try to read what is missing for this chunk |
|
673 |
qint64 haveRead = readReplyBodyRaw (in, out, currentChunkSize - currentChunkRead); |
|
674 |
currentChunkRead += haveRead; |
|
675 |
bytes += haveRead; |
|
676 |
||
677 |
// ### error checking here |
|
678 |
||
679 |
} |
|
680 |
return bytes; |
|
681 |
} |
|
682 |
||
683 |
qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *in, qint64 *chunkSize) |
|
684 |
{ |
|
685 |
qint64 bytes = 0; |
|
686 |
char crlf[2]; |
|
687 |
*chunkSize = -1; |
|
688 |
int bytesAvailable = in->bytesAvailable(); |
|
689 |
while (bytesAvailable > bytes) { |
|
690 |
qint64 sniffedBytes = in->peek(crlf, 2); |
|
691 |
int fragmentSize = fragment.size(); |
|
692 |
// check the next two bytes for a "\r\n", skip blank lines |
|
693 |
if ((fragmentSize && sniffedBytes == 2 && crlf[0] == '\r' && crlf[1] == '\n') |
|
694 |
||(fragmentSize > 1 && fragment.endsWith('\r') && crlf[0] == '\n')) |
|
695 |
{ |
|
696 |
bytes += in->read(crlf, 1); // read the \r or \n |
|
697 |
if (crlf[0] == '\r') |
|
698 |
bytes += in->read(crlf, 1); // read the \n |
|
699 |
bool ok = false; |
|
700 |
// ignore the chunk-extension |
|
701 |
fragment = fragment.mid(0, fragment.indexOf(';')).trimmed(); |
|
702 |
*chunkSize = fragment.toLong(&ok, 16); |
|
703 |
fragment.clear(); |
|
704 |
break; // size done |
|
705 |
} else { |
|
706 |
// read the fragment to the buffer |
|
707 |
char c = 0; |
|
708 |
bytes += in->read(&c, 1); |
|
709 |
fragment.append(c); |
|
710 |
} |
|
711 |
} |
|
712 |
return bytes; |
|
713 |
} |
|
714 |
||
715 |
void QHttpNetworkReplyPrivate::appendUncompressedReplyData(QByteArray &qba) |
|
716 |
{ |
|
717 |
responseData.append(qba); |
|
718 |
||
719 |
// clear the original! helps with implicit sharing and |
|
720 |
// avoiding memcpy when the user is reading the data |
|
721 |
qba.clear(); |
|
722 |
} |
|
723 |
||
724 |
void QHttpNetworkReplyPrivate::appendUncompressedReplyData(QByteDataBuffer &data) |
|
725 |
{ |
|
726 |
responseData.append(data); |
|
727 |
||
728 |
// clear the original! helps with implicit sharing and |
|
729 |
// avoiding memcpy when the user is reading the data |
|
730 |
data.clear(); |
|
731 |
} |
|
732 |
||
733 |
void QHttpNetworkReplyPrivate::appendCompressedReplyData(QByteDataBuffer &data) |
|
734 |
{ |
|
735 |
// Work in progress: Later we will directly use a list of QByteArray or a QRingBuffer |
|
736 |
// instead of one QByteArray. |
|
737 |
for(int i = 0; i < data.bufferCount(); i++) { |
|
738 |
QByteArray &byteData = data[i]; |
|
739 |
compressedData.append(byteData.constData(), byteData.size()); |
|
740 |
} |
|
741 |
data.clear(); |
|
742 |
} |
|
743 |
||
744 |
||
745 |
bool QHttpNetworkReplyPrivate::shouldEmitSignals() |
|
746 |
{ |
|
747 |
// for 401 & 407 don't emit the data signals. Content along with these |
|
748 |
// responses are send only if the authentication fails. |
|
749 |
return (statusCode != 401 && statusCode != 407); |
|
750 |
} |
|
751 |
||
752 |
bool QHttpNetworkReplyPrivate::expectContent() |
|
753 |
{ |
|
754 |
// check whether we can expect content after the headers (rfc 2616, sec4.4) |
|
755 |
if ((statusCode >= 100 && statusCode < 200) |
|
756 |
|| statusCode == 204 || statusCode == 304) |
|
757 |
return false; |
|
758 |
if (request.operation() == QHttpNetworkRequest::Head) |
|
759 |
return !shouldEmitSignals(); |
|
760 |
if (contentLength() == 0) |
|
761 |
return false; |
|
762 |
return true; |
|
763 |
} |
|
764 |
||
765 |
void QHttpNetworkReplyPrivate::eraseData() |
|
766 |
{ |
|
767 |
compressedData.clear(); |
|
768 |
responseData.clear(); |
|
769 |
} |
|
770 |
||
771 |
||
772 |
// SSL support below |
|
773 |
#ifndef QT_NO_OPENSSL |
|
774 |
||
775 |
QSslConfiguration QHttpNetworkReply::sslConfiguration() const |
|
776 |
{ |
|
777 |
Q_D(const QHttpNetworkReply); |
|
778 |
if (d->connection) |
|
779 |
return d->connection->d_func()->sslConfiguration(*this); |
|
780 |
return QSslConfiguration(); |
|
781 |
} |
|
782 |
||
783 |
void QHttpNetworkReply::setSslConfiguration(const QSslConfiguration &config) |
|
784 |
{ |
|
785 |
Q_D(QHttpNetworkReply); |
|
786 |
if (d->connection) |
|
787 |
d->connection->setSslConfiguration(config); |
|
788 |
} |
|
789 |
||
790 |
void QHttpNetworkReply::ignoreSslErrors() |
|
791 |
{ |
|
792 |
Q_D(QHttpNetworkReply); |
|
793 |
if (d->connection) |
|
794 |
d->connection->ignoreSslErrors(); |
|
795 |
} |
|
796 |
||
797 |
void QHttpNetworkReply::ignoreSslErrors(const QList<QSslError> &errors) |
|
798 |
{ |
|
799 |
Q_D(QHttpNetworkReply); |
|
800 |
if (d->connection) |
|
801 |
d->connection->ignoreSslErrors(errors); |
|
802 |
} |
|
803 |
||
804 |
||
805 |
#endif //QT_NO_OPENSSL |
|
806 |
||
807 |
||
808 |
QT_END_NAMESPACE |
|
809 |
||
810 |
#endif // QT_NO_HTTP |