source: trunk/src/network/access/qhttpnetworkconnectionchannel.cpp@ 890

Last change on this file since 890 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 41.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtNetwork module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
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** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qhttpnetworkconnection_p.h"
43#include "qhttpnetworkconnectionchannel_p.h"
44#include "private/qnoncontiguousbytedevice_p.h"
45
46#include <qpair.h>
47#include <qdebug.h>
48
49#ifndef QT_NO_HTTP
50
51#ifndef QT_NO_OPENSSL
52# include <QtNetwork/qsslkey.h>
53# include <QtNetwork/qsslcipher.h>
54# include <QtNetwork/qsslconfiguration.h>
55#endif
56
57QT_BEGIN_NAMESPACE
58
59// TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp
60
61QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
62 : socket(0)
63 , state(IdleState)
64 , reply(0)
65 , written(0)
66 , bytesTotal(0)
67 , resendCurrent(false)
68 , lastStatus(0)
69 , pendingEncrypt(false)
70 , reconnectAttempts(2)
71 , authMethod(QAuthenticatorPrivate::None)
72 , proxyAuthMethod(QAuthenticatorPrivate::None)
73#ifndef QT_NO_OPENSSL
74 , ignoreAllSslErrors(false)
75#endif
76 , pipeliningSupported(PipeliningSupportUnknown)
77 , connection(0)
78{
79 // Inlining this function in the header leads to compiler error on
80 // release-armv5, on at least timebox 9.2 and 10.1.
81}
82
83void QHttpNetworkConnectionChannel::init()
84{
85#ifndef QT_NO_OPENSSL
86 if (connection->d_func()->encrypt)
87 socket = new QSslSocket;
88 else
89 socket = new QTcpSocket;
90#else
91 socket = new QTcpSocket;
92#endif
93
94 // limit the socket read buffer size. we will read everything into
95 // the QHttpNetworkReply anyway, so let's grow only that and not
96 // here and there.
97 socket->setReadBufferSize(64*1024);
98
99 QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
100 this, SLOT(_q_bytesWritten(qint64)),
101 Qt::DirectConnection);
102 QObject::connect(socket, SIGNAL(connected()),
103 this, SLOT(_q_connected()),
104 Qt::DirectConnection);
105 QObject::connect(socket, SIGNAL(readyRead()),
106 this, SLOT(_q_readyRead()),
107 Qt::DirectConnection);
108
109 // The disconnected() and error() signals may already come
110 // while calling connectToHost().
111 // In case of a cached hostname or an IP this
112 // will then emit a signal to the user of QNetworkReply
113 // but cannot be caught because the user did not have a chance yet
114 // to connect to QNetworkReply's signals.
115 qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
116 QObject::connect(socket, SIGNAL(disconnected()),
117 this, SLOT(_q_disconnected()),
118 Qt::QueuedConnection);
119 QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
120 this, SLOT(_q_error(QAbstractSocket::SocketError)),
121 Qt::QueuedConnection);
122
123
124#ifndef QT_NO_NETWORKPROXY
125 QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
126 this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
127 Qt::DirectConnection);
128#endif
129
130#ifndef QT_NO_OPENSSL
131 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
132 if (sslSocket) {
133 // won't be a sslSocket if encrypt is false
134 QObject::connect(sslSocket, SIGNAL(encrypted()),
135 this, SLOT(_q_encrypted()),
136 Qt::DirectConnection);
137 QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
138 this, SLOT(_q_sslErrors(QList<QSslError>)),
139 Qt::DirectConnection);
140 QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
141 this, SLOT(_q_encryptedBytesWritten(qint64)),
142 Qt::DirectConnection);
143 }
144#endif
145}
146
147
148void QHttpNetworkConnectionChannel::close()
149{
150 socket->blockSignals(true);
151 socket->close();
152 socket->blockSignals(false);
153 state = QHttpNetworkConnectionChannel::IdleState;
154}
155
156
157bool QHttpNetworkConnectionChannel::sendRequest()
158{
159 if (!reply) {
160 // heh, how should that happen!
161 qWarning() << "QHttpNetworkConnectionChannel::sendRequest() called without QHttpNetworkReply";
162 state = QHttpNetworkConnectionChannel::IdleState;
163 return false;
164 }
165
166 switch (state) {
167 case QHttpNetworkConnectionChannel::IdleState: { // write the header
168 if (!ensureConnection()) {
169 // wait for the connection (and encryption) to be done
170 // sendRequest will be called again from either
171 // _q_connected or _q_encrypted
172 return false;
173 }
174 written = 0; // excluding the header
175 bytesTotal = 0;
176
177 reply->d_func()->clear();
178 reply->d_func()->connection = connection;
179 reply->d_func()->connectionChannel = this;
180 reply->d_func()->autoDecompress = request.d->autoDecompress;
181 reply->d_func()->pipeliningUsed = false;
182
183 // if the url contains authentication parameters, use the new ones
184 // both channels will use the new authentication parameters
185 if (!request.url().userInfo().isEmpty() && request.withCredentials()) {
186 QUrl url = request.url();
187 QAuthenticator &auth = authenticator;
188 if (url.userName() != auth.user()
189 || (!url.password().isEmpty() && url.password() != auth.password())) {
190 auth.setUser(url.userName());
191 auth.setPassword(url.password());
192 emit reply->cacheCredentials(request, &auth);
193 connection->d_func()->copyCredentials(connection->d_func()->indexOf(socket), &auth, false);
194 }
195 // clear the userinfo, since we use the same request for resending
196 // userinfo in url can conflict with the one in the authenticator
197 url.setUserInfo(QString());
198 request.setUrl(url);
199 }
200 // Will only be false if QtWebKit is performing a cross-origin XMLHttpRequest
201 // and withCredentials has not been set to true.
202 if (request.withCredentials())
203 connection->d_func()->createAuthorization(socket, request);
204#ifndef QT_NO_NETWORKPROXY
205 QByteArray header = QHttpNetworkRequestPrivate::header(request,
206 (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
207#else
208 QByteArray header = QHttpNetworkRequestPrivate::header(request, false);
209#endif
210 socket->write(header);
211 // flushing is dangerous (QSslSocket calls transmit which might read or error)
212// socket->flush();
213 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
214 if (uploadByteDevice) {
215 // connect the signals so this function gets called again
216 QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead()));
217
218 bytesTotal = request.contentLength();
219
220 state = QHttpNetworkConnectionChannel::WritingState; // start writing data
221 sendRequest(); //recurse
222 } else {
223 state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
224 sendRequest(); //recurse
225 }
226
227 break;
228 }
229 case QHttpNetworkConnectionChannel::WritingState:
230 {
231 // write the data
232 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
233 if (!uploadByteDevice || bytesTotal == written) {
234 if (uploadByteDevice)
235 emit reply->dataSendProgress(written, bytesTotal);
236 state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
237 sendRequest(); // recurse
238 break;
239 }
240
241 // only feed the QTcpSocket buffer when there is less than 32 kB in it
242 const qint64 socketBufferFill = 32*1024;
243 const qint64 socketWriteMaxSize = 16*1024;
244
245
246#ifndef QT_NO_OPENSSL
247 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
248 // if it is really an ssl socket, check more than just bytesToWrite()
249 while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0))
250 <= socketBufferFill && bytesTotal != written)
251#else
252 while (socket->bytesToWrite() <= socketBufferFill
253 && bytesTotal != written)
254#endif
255 {
256 // get pointer to upload data
257 qint64 currentReadSize;
258 qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written);
259 const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize);
260
261 if (currentReadSize == -1) {
262 // premature eof happened
263 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
264 return false;
265 break;
266 } else if (readPointer == 0 || currentReadSize == 0) {
267 // nothing to read currently, break the loop
268 break;
269 } else {
270 qint64 currentWriteSize = socket->write(readPointer, currentReadSize);
271 if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
272 // socket broke down
273 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
274 return false;
275 } else {
276 written += currentWriteSize;
277 uploadByteDevice->advanceReadPointer(currentWriteSize);
278
279 emit reply->dataSendProgress(written, bytesTotal);
280
281 if (written == bytesTotal) {
282 // make sure this function is called once again
283 state = QHttpNetworkConnectionChannel::WaitingState;
284 sendRequest();
285 break;
286 }
287 }
288 }
289 }
290 break;
291 }
292
293 case QHttpNetworkConnectionChannel::WaitingState:
294 {
295 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
296 if (uploadByteDevice) {
297 QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead()));
298 }
299
300 // HTTP pipelining
301 //connection->d_func()->fillPipeline(socket);
302 //socket->flush();
303
304 // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called
305 // this is needed if the sends an reply before we have finished sending the request. In that
306 // case receiveReply had been called before but ignored the server reply
307 if (socket->bytesAvailable())
308 QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
309 break;
310 }
311 case QHttpNetworkConnectionChannel::ReadingState:
312 // ignore _q_bytesWritten in these states
313 // fall through
314 default:
315 break;
316 }
317 return true;
318}
319
320
321void QHttpNetworkConnectionChannel::_q_receiveReply()
322{
323 Q_ASSERT(socket);
324
325 if (!reply) {
326 // heh, how should that happen!
327 qWarning() << "QHttpNetworkConnectionChannel::_q_receiveReply() called without QHttpNetworkReply,"
328 << socket->bytesAvailable() << "bytes on socket.";
329 close();
330 return;
331 }
332
333 // only run when the QHttpNetworkConnection is not currently being destructed, e.g.
334 // this function is called from _q_disconnected which is called because
335 // of ~QHttpNetworkConnectionPrivate
336 if (!qobject_cast<QHttpNetworkConnection*>(connection)) {
337 return;
338 }
339
340 qint64 bytes = 0;
341 QAbstractSocket::SocketState socketState = socket->state();
342
343 // connection might be closed to signal the end of data
344 if (socketState == QAbstractSocket::UnconnectedState) {
345 if (socket->bytesAvailable() <= 0) {
346 if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
347 // finish this reply. this case happens when the server did not send a content length
348 reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
349 allDone();
350 return;
351 } else {
352 handleUnexpectedEOF();
353 return;
354 }
355 } else {
356 // socket not connected but still bytes for reading.. just continue in this function
357 }
358 }
359
360 // read loop for the response
361 while (socket->bytesAvailable()) {
362 QHttpNetworkReplyPrivate::ReplyState state = reply->d_func()->state;
363 switch (state) {
364 case QHttpNetworkReplyPrivate::NothingDoneState: {
365 // only eat whitespace on the first call
366 eatWhitespace();
367 state = reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
368 // fallthrough
369 }
370 case QHttpNetworkReplyPrivate::ReadingStatusState: {
371 qint64 statusBytes = reply->d_func()->readStatus(socket);
372 if (statusBytes == -1) {
373 // connection broke while reading status. also handled if later _q_disconnected is called
374 handleUnexpectedEOF();
375 return;
376 }
377 bytes += statusBytes;
378 lastStatus = reply->d_func()->statusCode;
379 break;
380 }
381 case QHttpNetworkReplyPrivate::ReadingHeaderState: {
382 QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
383 qint64 headerBytes = replyPrivate->readHeader(socket);
384 if (headerBytes == -1) {
385 // connection broke while reading headers. also handled if later _q_disconnected is called
386 handleUnexpectedEOF();
387 return;
388 }
389 bytes += headerBytes;
390 if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState) {
391 if (replyPrivate->isGzipped() && replyPrivate->autoDecompress) {
392 // remove the Content-Length from header
393 replyPrivate->removeAutoDecompressHeader();
394 } else {
395 replyPrivate->autoDecompress = false;
396 }
397 if (replyPrivate->statusCode == 100) {
398 replyPrivate->clearHttpLayerInformation();
399 replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState;
400 break; // ignore
401 }
402 if (replyPrivate->shouldEmitSignals())
403 emit reply->headerChanged();
404 if (!replyPrivate->expectContent()) {
405 replyPrivate->state = QHttpNetworkReplyPrivate::AllDoneState;
406 allDone();
407 break;
408 }
409 }
410 break;
411 }
412 case QHttpNetworkReplyPrivate::ReadingDataState: {
413 QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
414 if (socket->state() == QAbstractSocket::ConnectedState &&
415 replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) {
416 // (only do the following when still connected, not when we have already been disconnected and there is still data)
417 // We already have some HTTP body data. We don't read more from the socket until
418 // this is fetched by QHttpNetworkAccessHttpBackend. If we would read more,
419 // we could not limit our read buffer usage.
420 // We only do this when shouldEmitSignals==true because our HTTP parsing
421 // always needs to parse the 401/407 replies. Therefore they don't really obey
422 // to the read buffer maximum size, but we don't care since they should be small.
423 return;
424 }
425 if (!replyPrivate->isChunked() && !replyPrivate->autoDecompress
426 && replyPrivate->bodyLength > 0) {
427 // bulk files like images should fulfill these properties and
428 // we can therefore save on memory copying
429 bytes = replyPrivate->readBodyFast(socket, &replyPrivate->responseData);
430 replyPrivate->totalProgress += bytes;
431 if (replyPrivate->shouldEmitSignals()) {
432 QPointer<QHttpNetworkReply> replyPointer = reply;
433 emit reply->readyRead();
434 // make sure that the reply is valid
435 if (replyPointer.isNull())
436 return;
437 emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
438 // make sure that the reply is valid
439 if (replyPointer.isNull())
440 return;
441 }
442 }
443 else
444 {
445 // use the traditional slower reading (for compressed encoding, chunked encoding,
446 // no content-length etc)
447 QByteDataBuffer byteDatas;
448 bytes = replyPrivate->readBody(socket, &byteDatas);
449 if (bytes) {
450 if (replyPrivate->autoDecompress)
451 replyPrivate->appendCompressedReplyData(byteDatas);
452 else
453 replyPrivate->appendUncompressedReplyData(byteDatas);
454
455 if (!replyPrivate->autoDecompress) {
456 replyPrivate->totalProgress += bytes;
457 if (replyPrivate->shouldEmitSignals()) {
458 QPointer<QHttpNetworkReply> replyPointer = reply;
459 // important: At the point of this readyRead(), the byteDatas list must be empty,
460 // else implicit sharing will trigger memcpy when the user is reading data!
461 emit reply->readyRead();
462 // make sure that the reply is valid
463 if (replyPointer.isNull())
464 return;
465 emit reply->dataReadProgress(replyPrivate->totalProgress, replyPrivate->bodyLength);
466 // make sure that the reply is valid
467 if (replyPointer.isNull())
468 return;
469 }
470 }
471#ifndef QT_NO_COMPRESS
472 else if (!expand(false)) { // expand a chunk if possible
473 return; // ### expand failed
474 }
475#endif
476 }
477 }
478 // still in ReadingDataState? This function will be called again by the socket's readyRead
479 if (replyPrivate->state == QHttpNetworkReplyPrivate::ReadingDataState)
480 break;
481
482 // everything done, fall through
483 }
484 case QHttpNetworkReplyPrivate::AllDoneState:
485 allDone();
486 break;
487 default:
488 break;
489 }
490 }
491}
492
493// called when unexpectedly reading a -1 or when data is expected but socket is closed
494void QHttpNetworkConnectionChannel::handleUnexpectedEOF()
495{
496 if (reconnectAttempts <= 0) {
497 // too many errors reading/receiving/parsing the status, close the socket and emit error
498 requeueCurrentlyPipelinedRequests();
499 close();
500 reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::RemoteHostClosedError, socket);
501 emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString);
502 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
503 } else {
504 reconnectAttempts--;
505 reply->d_func()->clear();
506 reply->d_func()->connection = connection;
507 reply->d_func()->connectionChannel = this;
508 closeAndResendCurrentRequest();
509 }
510}
511
512bool QHttpNetworkConnectionChannel::ensureConnection()
513{
514 QAbstractSocket::SocketState socketState = socket->state();
515
516 // resend this request after we receive the disconnected signal
517 if (socketState == QAbstractSocket::ClosingState) {
518 resendCurrent = true;
519 return false;
520 }
521
522 // already trying to connect?
523 if (socketState == QAbstractSocket::HostLookupState ||
524 socketState == QAbstractSocket::ConnectingState) {
525 return false;
526 }
527
528 // make sure that this socket is in a connected state, if not initiate
529 // connection to the host.
530 if (socketState != QAbstractSocket::ConnectedState) {
531 // connect to the host if not already connected.
532 state = QHttpNetworkConnectionChannel::ConnectingState;
533 pendingEncrypt = connection->d_func()->encrypt;
534
535 // reset state
536 pipeliningSupported = PipeliningSupportUnknown;
537
538 // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done"
539 // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the
540 // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not
541 // check the "phase" for generating the Authorization header. NTLM authentication is a two stage
542 // process & needs the "phase". To make sure the QAuthenticator uses the current username/password
543 // the phase is reset to Start.
544 QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(authenticator);
545 if (priv && priv->phase == QAuthenticatorPrivate::Done)
546 priv->phase = QAuthenticatorPrivate::Start;
547 priv = QAuthenticatorPrivate::getPrivate(proxyAuthenticator);
548 if (priv && priv->phase == QAuthenticatorPrivate::Done)
549 priv->phase = QAuthenticatorPrivate::Start;
550
551 QString connectHost = connection->d_func()->hostName;
552 qint16 connectPort = connection->d_func()->port;
553
554#ifndef QT_NO_NETWORKPROXY
555 // HTTPS always use transparent proxy.
556 if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !connection->d_func()->encrypt) {
557 connectHost = connection->d_func()->networkProxy.hostName();
558 connectPort = connection->d_func()->networkProxy.port();
559 }
560#endif
561 if (connection->d_func()->encrypt) {
562#ifndef QT_NO_OPENSSL
563 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
564 sslSocket->connectToHostEncrypted(connectHost, connectPort);
565 if (ignoreAllSslErrors)
566 sslSocket->ignoreSslErrors();
567 sslSocket->ignoreSslErrors(ignoreSslErrorsList);
568#else
569 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
570#endif
571 } else {
572 socket->connectToHost(connectHost, connectPort);
573 }
574 return false;
575 }
576 return true;
577}
578
579
580#ifndef QT_NO_COMPRESS
581bool QHttpNetworkConnectionChannel::expand(bool dataComplete)
582{
583 Q_ASSERT(socket);
584 Q_ASSERT(reply);
585
586 qint64 total = reply->d_func()->compressedData.size();
587 if (total >= CHUNK || dataComplete) {
588 // uncompress the data
589 QByteArray content, inflated;
590 content = reply->d_func()->compressedData;
591 reply->d_func()->compressedData.clear();
592
593 int ret = Z_OK;
594 if (content.size())
595 ret = reply->d_func()->gunzipBodyPartially(content, inflated);
596 int retCheck = (dataComplete) ? Z_STREAM_END : Z_OK;
597 if (ret >= retCheck) {
598 if (inflated.size()) {
599 reply->d_func()->totalProgress += inflated.size();
600 reply->d_func()->appendUncompressedReplyData(inflated);
601 if (reply->d_func()->shouldEmitSignals()) {
602 QPointer<QHttpNetworkReply> replyPointer = reply;
603 // important: At the point of this readyRead(), inflated must be cleared,
604 // else implicit sharing will trigger memcpy when the user is reading data!
605 emit reply->readyRead();
606 // make sure that the reply is valid
607 if (replyPointer.isNull())
608 return true;
609 emit reply->dataReadProgress(reply->d_func()->totalProgress, 0);
610 // make sure that the reply is valid
611 if (replyPointer.isNull())
612 return true;
613
614 }
615 }
616 } else {
617 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure);
618 return false;
619 }
620 }
621 return true;
622}
623#endif
624
625
626void QHttpNetworkConnectionChannel::allDone()
627{
628#ifndef QT_NO_COMPRESS
629 // expand the whole data.
630 if (reply->d_func()->expectContent() && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd)
631 expand(true); // ### if expand returns false, its an error
632#endif
633 // while handling 401 & 407, we might reset the status code, so save this.
634 bool emitFinished = reply->d_func()->shouldEmitSignals();
635 handleStatus();
636 // ### at this point there should be no more data on the socket
637 // close if server requested
638 bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled();
639 if (connectionCloseEnabled)
640 close();
641 // queue the finished signal, this is required since we might send new requests from
642 // slot connected to it. The socket will not fire readyRead signal, if we are already
643 // in the slot connected to readyRead
644 if (emitFinished)
645 QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection);
646 // reset the reconnection attempts after we receive a complete reply.
647 // in case of failures, each channel will attempt two reconnects before emitting error.
648 reconnectAttempts = 2;
649
650 detectPipeliningSupport();
651
652 // now the channel can be seen as free/idle again, all signal emissions for the reply have been done
653 this->state = QHttpNetworkConnectionChannel::IdleState;
654
655 // if it does not need to be sent again we can set it to 0
656 // the previous code did not do that and we had problems with accidental re-sending of a
657 // finished request.
658 // Note that this may trigger a segfault at some other point. But then we can fix the underlying
659 // problem.
660 if (!resendCurrent) {
661 request = QHttpNetworkRequest();
662 reply = 0;
663 }
664
665 // move next from pipeline to current request
666 if (!alreadyPipelinedRequests.isEmpty()) {
667 if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) {
668 // move the pipelined ones back to the main queue
669 requeueCurrentlyPipelinedRequests();
670 close();
671 } else {
672 // there were requests pipelined in and we can continue
673 HttpMessagePair messagePair = alreadyPipelinedRequests.takeFirst();
674
675 request = messagePair.first;
676 reply = messagePair.second;
677 state = QHttpNetworkConnectionChannel::ReadingState;
678 resendCurrent = false;
679
680 written = 0; // message body, excluding the header, irrelevant here
681 bytesTotal = 0; // message body total, excluding the header, irrelevant here
682
683 // pipeline even more
684 connection->d_func()->fillPipeline(socket);
685
686 // continue reading
687 //_q_receiveReply();
688 // this was wrong, allDone gets called from that function anyway.
689 }
690 } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) {
691 eatWhitespace();
692 // this is weird. we had nothing pipelined but still bytes available. better close it.
693 if (socket->bytesAvailable() > 0)
694 close();
695 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
696 } else if (alreadyPipelinedRequests.isEmpty()) {
697 if (qobject_cast<QHttpNetworkConnection*>(connection))
698 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
699 }
700}
701
702void QHttpNetworkConnectionChannel::detectPipeliningSupport()
703{
704 // detect HTTP Pipelining support
705 QByteArray serverHeaderField;
706 if (
707 // check for HTTP/1.1
708 (reply->d_func()->majorVersion == 1 && reply->d_func()->minorVersion == 1)
709 // check for not having connection close
710 && (!reply->d_func()->isConnectionCloseEnabled())
711 // check if it is still connected
712 && (socket->state() == QAbstractSocket::ConnectedState)
713 // check for broken servers in server reply header
714 // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
715 && (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4."))
716 && (!serverHeaderField.contains("Microsoft-IIS/5."))
717 && (!serverHeaderField.contains("Netscape-Enterprise/3."))
718 // this is adpoted from the knowledge of the Nokia 7.x browser team (DEF143319)
719 && (!serverHeaderField.contains("WebLogic"))
720 ) {
721 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningProbablySupported;
722 } else {
723 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
724 }
725}
726
727// called when the connection broke and we need to queue some pipelined requests again
728void QHttpNetworkConnectionChannel::requeueCurrentlyPipelinedRequests()
729{
730 for (int i = 0; i < alreadyPipelinedRequests.length(); i++)
731 connection->d_func()->requeueRequest(alreadyPipelinedRequests.at(i));
732 alreadyPipelinedRequests.clear();
733
734 // only run when the QHttpNetworkConnection is not currently being destructed, e.g.
735 // this function is called from _q_disconnected which is called because
736 // of ~QHttpNetworkConnectionPrivate
737 if (qobject_cast<QHttpNetworkConnection*>(connection))
738 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
739}
740
741void QHttpNetworkConnectionChannel::eatWhitespace()
742{
743 char c;
744 do {
745 qint64 ret = socket->peek(&c, 1);
746
747 // nothing read, fine.
748 if (ret == 0)
749 return;
750
751 // EOF from socket?
752 if (ret == -1)
753 return; // FIXME, we need to stop processing. however the next stuff done will also do that.
754
755 // read all whitespace and line endings
756 if (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31) {
757 socket->read(&c, 1);
758 continue;
759 } else {
760 break;
761 }
762 } while(true);
763}
764
765void QHttpNetworkConnectionChannel::handleStatus()
766{
767 Q_ASSERT(socket);
768 Q_ASSERT(reply);
769
770 int statusCode = reply->statusCode();
771 bool resend = false;
772
773 switch (statusCode) {
774 case 401: // auth required
775 case 407: // proxy auth required
776 if (connection->d_func()->handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) {
777 if (resend) {
778 if (!resetUploadData())
779 break;
780
781 reply->d_func()->eraseData();
782
783 if (alreadyPipelinedRequests.isEmpty()) {
784 // this does a re-send without closing the connection
785 resendCurrent = true;
786 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
787 } else {
788 // we had requests pipelined.. better close the connection in closeAndResendCurrentRequest
789 closeAndResendCurrentRequest();
790 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
791 }
792 }
793 } else {
794 emit reply->headerChanged();
795 emit reply->readyRead();
796 QNetworkReply::NetworkError errorCode = (statusCode == 407)
797 ? QNetworkReply::ProxyAuthenticationRequiredError
798 : QNetworkReply::AuthenticationRequiredError;
799 reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode, socket);
800 emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
801 }
802 break;
803 default:
804 if (qobject_cast<QHttpNetworkConnection*>(connection))
805 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
806 }
807}
808
809bool QHttpNetworkConnectionChannel::resetUploadData()
810{
811 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
812 if (!uploadByteDevice)
813 return true;
814
815 if (uploadByteDevice->reset()) {
816 written = 0;
817 return true;
818 } else {
819 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ContentReSendError);
820 return false;
821 }
822}
823
824
825void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair)
826{
827 // this is only called for simple GET
828
829 QHttpNetworkRequest &request = pair.first;
830 QHttpNetworkReply *reply = pair.second;
831 reply->d_func()->clear();
832 reply->d_func()->connection = connection;
833 reply->d_func()->connectionChannel = this;
834 reply->d_func()->autoDecompress = request.d->autoDecompress;
835 reply->d_func()->pipeliningUsed = true;
836
837#ifndef QT_NO_NETWORKPROXY
838 QByteArray header = QHttpNetworkRequestPrivate::header(request,
839 (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
840#else
841 QByteArray header = QHttpNetworkRequestPrivate::header(request, false);
842#endif
843 socket->write(header);
844
845 alreadyPipelinedRequests.append(pair);
846}
847
848void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest()
849{
850 requeueCurrentlyPipelinedRequests();
851 close();
852 resendCurrent = true;
853 if (qobject_cast<QHttpNetworkConnection*>(connection))
854 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
855}
856
857bool QHttpNetworkConnectionChannel::isSocketBusy() const
858{
859 return (state & QHttpNetworkConnectionChannel::BusyState);
860}
861
862bool QHttpNetworkConnectionChannel::isSocketWriting() const
863{
864 return (state & QHttpNetworkConnectionChannel::WritingState);
865}
866
867bool QHttpNetworkConnectionChannel::isSocketWaiting() const
868{
869 return (state & QHttpNetworkConnectionChannel::WaitingState);
870}
871
872bool QHttpNetworkConnectionChannel::isSocketReading() const
873{
874 return (state & QHttpNetworkConnectionChannel::ReadingState);
875}
876
877//private slots
878void QHttpNetworkConnectionChannel::_q_readyRead()
879{
880 if (isSocketWaiting() || isSocketReading()) {
881 state = QHttpNetworkConnectionChannel::ReadingState;
882 if (reply)
883 _q_receiveReply();
884 }
885}
886
887void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
888{
889 Q_UNUSED(bytes);
890 // bytes have been written to the socket. write even more of them :)
891 if (isSocketWriting())
892 sendRequest();
893 // otherwise we do nothing
894}
895
896void QHttpNetworkConnectionChannel::_q_disconnected()
897{
898 // read the available data before closing
899 if (isSocketWaiting() || isSocketReading()) {
900 if (reply) {
901 state = QHttpNetworkConnectionChannel::ReadingState;
902 _q_receiveReply();
903 }
904 } else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) {
905 // re-sending request because the socket was in ClosingState
906 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
907 }
908 state = QHttpNetworkConnectionChannel::IdleState;
909
910 requeueCurrentlyPipelinedRequests();
911 close();
912}
913
914
915void QHttpNetworkConnectionChannel::_q_connected()
916{
917 // improve performance since we get the request sent by the kernel ASAP
918 //socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
919 // We have this commented out now. It did not have the effect we wanted. If we want to
920 // do this properly, Qt has to combine multiple HTTP requests into one buffer
921 // and send this to the kernel in one syscall and then the kernel immediately sends
922 // it as one TCP packet because of TCP_NODELAY.
923 // However, this code is currently not in Qt, so we rely on the kernel combining
924 // the requests into one TCP packet.
925
926 // not sure yet if it helps, but it makes sense
927 socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
928
929 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
930
931 // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
932 //channels[i].reconnectAttempts = 2;
933 if (!pendingEncrypt) {
934 state = QHttpNetworkConnectionChannel::IdleState;
935 if (reply)
936 sendRequest();
937 else
938 close();
939 }
940}
941
942
943void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError)
944{
945 if (!socket)
946 return;
947 QNetworkReply::NetworkError errorCode = QNetworkReply::UnknownNetworkError;
948
949 switch (socketError) {
950 case QAbstractSocket::HostNotFoundError:
951 errorCode = QNetworkReply::HostNotFoundError;
952 break;
953 case QAbstractSocket::ConnectionRefusedError:
954 errorCode = QNetworkReply::ConnectionRefusedError;
955 break;
956 case QAbstractSocket::RemoteHostClosedError:
957 // try to reconnect/resend before sending an error.
958 // while "Reading" the _q_disconnected() will handle this.
959 if (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
960 if (reconnectAttempts-- > 0) {
961 closeAndResendCurrentRequest();
962 return;
963 } else {
964 errorCode = QNetworkReply::RemoteHostClosedError;
965 }
966 } else {
967 return;
968 }
969 break;
970 case QAbstractSocket::SocketTimeoutError:
971 // try to reconnect/resend before sending an error.
972 if (state == QHttpNetworkConnectionChannel::WritingState && (reconnectAttempts-- > 0)) {
973 closeAndResendCurrentRequest();
974 return;
975 }
976 errorCode = QNetworkReply::TimeoutError;
977 break;
978 case QAbstractSocket::ProxyAuthenticationRequiredError:
979 errorCode = QNetworkReply::ProxyAuthenticationRequiredError;
980 break;
981 case QAbstractSocket::SslHandshakeFailedError:
982 errorCode = QNetworkReply::SslHandshakeFailedError;
983 break;
984 default:
985 // all other errors are treated as NetworkError
986 errorCode = QNetworkReply::UnknownNetworkError;
987 break;
988 }
989 QPointer<QHttpNetworkConnection> that = connection;
990 QString errorString = connection->d_func()->errorDetail(errorCode, socket, socket->errorString());
991
992 if (reply) {
993 reply->d_func()->errorString = errorString;
994 emit reply->finishedWithError(errorCode, errorString);
995 }
996 // send the next request
997 QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
998
999 if (that) //signal emission triggered event loop
1000 close();
1001}
1002
1003#ifndef QT_NO_NETWORKPROXY
1004void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
1005{
1006 connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
1007}
1008#endif
1009
1010void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
1011{
1012 sendRequest();
1013}
1014
1015#ifndef QT_NO_OPENSSL
1016void QHttpNetworkConnectionChannel::_q_encrypted()
1017{
1018 if (!socket)
1019 return; // ### error
1020 state = QHttpNetworkConnectionChannel::IdleState;
1021 pendingEncrypt = false;
1022 sendRequest();
1023}
1024
1025void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
1026{
1027 if (!socket)
1028 return;
1029 //QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure;
1030 // Also pause the connection because socket notifiers may fire while an user
1031 // dialog is displaying
1032 connection->d_func()->pauseConnection();
1033 emit reply->sslErrors(errors);
1034 connection->d_func()->resumeConnection();
1035}
1036
1037void QHttpNetworkConnectionChannel::_q_encryptedBytesWritten(qint64 bytes)
1038{
1039 Q_UNUSED(bytes);
1040 // bytes have been written to the socket. write even more of them :)
1041 if (isSocketWriting())
1042 sendRequest();
1043 // otherwise we do nothing
1044}
1045
1046#endif
1047
1048void QHttpNetworkConnectionChannel::setConnection(QHttpNetworkConnection *c)
1049{
1050 // Inlining this function in the header leads to compiler error on
1051 // release-armv5, on at least timebox 9.2 and 10.1.
1052 connection = c;
1053}
1054
1055QT_END_NAMESPACE
1056
1057#include "moc_qhttpnetworkconnectionchannel_p.cpp"
1058
1059#endif // QT_NO_HTTP
Note: See TracBrowser for help on using the repository browser.