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

Last change on this file since 788 was 769, checked in by Dmitry A. Kuminov, 15 years ago

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

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