1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the QtNetwork module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include <private/qabstractsocket_p.h> |
43 | #include "qhttpnetworkconnection_p.h" |
44 | #include "qhttpnetworkconnectionchannel_p.h" |
45 | #include "private/qnoncontiguousbytedevice_p.h" |
46 | #include <private/qnetworkrequest_p.h> |
47 | #include <private/qobject_p.h> |
48 | #include <private/qauthenticator_p.h> |
49 | #include <qnetworkproxy.h> |
50 | #include <qauthenticator.h> |
51 | #include <qcoreapplication.h> |
52 | |
53 | #include <qbuffer.h> |
54 | #include <qpair.h> |
55 | #include <qhttp.h> |
56 | #include <qdebug.h> |
57 | |
58 | #ifndef QT_NO_HTTP |
59 | |
60 | #ifndef QT_NO_OPENSSL |
61 | # include <private/qsslsocket_p.h> |
62 | # include <QtNetwork/qsslkey.h> |
63 | # include <QtNetwork/qsslcipher.h> |
64 | # include <QtNetwork/qsslconfiguration.h> |
65 | #endif |
66 | |
67 | |
68 | |
69 | QT_BEGIN_NAMESPACE |
70 | |
71 | #ifdef Q_OS_SYMBIAN |
72 | const int QHttpNetworkConnectionPrivate::defaultChannelCount = 3; |
73 | #else |
74 | const int QHttpNetworkConnectionPrivate::defaultChannelCount = 6; |
75 | #endif |
76 | |
77 | // The pipeline length. So there will be 4 requests in flight. |
78 | const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3; |
79 | // Only re-fill the pipeline if there's defaultRePipelineLength slots free in the pipeline. |
80 | // This means that there are 2 requests in flight and 2 slots free that will be re-filled. |
81 | const int QHttpNetworkConnectionPrivate::defaultRePipelineLength = 2; |
82 | |
83 | |
84 | QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt) |
85 | : state(RunningState), |
86 | hostName(hostName), port(port), encrypt(encrypt), |
87 | channelCount(defaultChannelCount) |
88 | #ifndef QT_NO_NETWORKPROXY |
89 | , networkProxy(QNetworkProxy::NoProxy) |
90 | #endif |
91 | { |
92 | channels = new QHttpNetworkConnectionChannel[channelCount]; |
93 | } |
94 | |
95 | QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt) |
96 | : state(RunningState), |
97 | hostName(hostName), port(port), encrypt(encrypt), |
98 | channelCount(channelCount) |
99 | #ifndef QT_NO_NETWORKPROXY |
100 | , networkProxy(QNetworkProxy::NoProxy) |
101 | #endif |
102 | { |
103 | channels = new QHttpNetworkConnectionChannel[channelCount]; |
104 | } |
105 | |
106 | |
107 | |
108 | QHttpNetworkConnectionPrivate::~QHttpNetworkConnectionPrivate() |
109 | { |
110 | for (int i = 0; i < channelCount; ++i) { |
111 | if (channels[i].socket) { |
112 | channels[i].socket->close(); |
113 | delete channels[i].socket; |
114 | } |
115 | } |
116 | delete []channels; |
117 | } |
118 | |
119 | void QHttpNetworkConnectionPrivate::init() |
120 | { |
121 | for (int i = 0; i < channelCount; i++) { |
122 | channels[i].setConnection(this->q_func()); |
123 | channels[i].ssl = encrypt; |
124 | #ifndef QT_NO_BEARERMANAGEMENT |
125 | //push session down to channels |
126 | channels[i].networkSession = networkSession; |
127 | #endif |
128 | channels[i].init(); |
129 | } |
130 | } |
131 | |
132 | void QHttpNetworkConnectionPrivate::pauseConnection() |
133 | { |
134 | state = PausedState; |
135 | |
136 | // Disable all socket notifiers |
137 | for (int i = 0; i < channelCount; i++) { |
138 | #ifndef QT_NO_OPENSSL |
139 | if (encrypt) |
140 | QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket)); |
141 | else |
142 | #endif |
143 | QAbstractSocketPrivate::pauseSocketNotifiers(channels[i].socket); |
144 | } |
145 | } |
146 | |
147 | void QHttpNetworkConnectionPrivate::resumeConnection() |
148 | { |
149 | state = RunningState; |
150 | // Enable all socket notifiers |
151 | for (int i = 0; i < channelCount; i++) { |
152 | #ifndef QT_NO_OPENSSL |
153 | if (encrypt) |
154 | QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket)); |
155 | else |
156 | #endif |
157 | QAbstractSocketPrivate::resumeSocketNotifiers(channels[i].socket); |
158 | |
159 | // Resume pending upload if needed |
160 | if (channels[i].state == QHttpNetworkConnectionChannel::WritingState) |
161 | QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead" , Qt::QueuedConnection); |
162 | } |
163 | |
164 | // queue _q_startNextRequest |
165 | QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest" , Qt::QueuedConnection); |
166 | } |
167 | |
168 | int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const |
169 | { |
170 | for (int i = 0; i < channelCount; ++i) |
171 | if (channels[i].socket == socket) |
172 | return i; |
173 | |
174 | qFatal("Called with unknown socket object." ); |
175 | return 0; |
176 | } |
177 | |
178 | qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailable(const QHttpNetworkReply &reply) const |
179 | { |
180 | return reply.d_func()->responseData.byteAmount(); |
181 | } |
182 | |
183 | qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) const |
184 | { |
185 | return reply.d_func()->responseData.sizeNextBlock(); |
186 | } |
187 | |
188 | void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair) |
189 | { |
190 | QHttpNetworkRequest &request = messagePair.first; |
191 | QHttpNetworkReply *reply = messagePair.second; |
192 | |
193 | // add missing fields for the request |
194 | QByteArray value; |
195 | // check if Content-Length is provided |
196 | QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice(); |
197 | if (uploadByteDevice) { |
198 | if (request.contentLength() != -1 && uploadByteDevice->size() != -1) { |
199 | // both values known, take the smaller one. |
200 | request.setContentLength(qMin(uploadByteDevice->size(), request.contentLength())); |
201 | } else if (request.contentLength() == -1 && uploadByteDevice->size() != -1) { |
202 | // content length not supplied by user, but the upload device knows it |
203 | request.setContentLength(uploadByteDevice->size()); |
204 | } else if (request.contentLength() != -1 && uploadByteDevice->size() == -1) { |
205 | // everything OK, the user supplied us the contentLength |
206 | } else if (request.contentLength() == -1 && uploadByteDevice->size() == -1) { |
207 | qFatal("QHttpNetworkConnectionPrivate: Neither content-length nor upload device size were given" ); |
208 | } |
209 | } |
210 | // set the Connection/Proxy-Connection: Keep-Alive headers |
211 | #ifndef QT_NO_NETWORKPROXY |
212 | if (networkProxy.type() == QNetworkProxy::HttpCachingProxy) { |
213 | value = request.headerField("proxy-connection" ); |
214 | if (value.isEmpty()) |
215 | request.setHeaderField("Proxy-Connection" , "Keep-Alive" ); |
216 | } else { |
217 | #endif |
218 | value = request.headerField("connection" ); |
219 | if (value.isEmpty()) |
220 | request.setHeaderField("Connection" , "Keep-Alive" ); |
221 | #ifndef QT_NO_NETWORKPROXY |
222 | } |
223 | #endif |
224 | |
225 | // If the request had a accept-encoding set, we better not mess |
226 | // with it. If it was not set, we announce that we understand gzip |
227 | // and remember this fact in request.d->autoDecompress so that |
228 | // we can later decompress the HTTP reply if it has such an |
229 | // encoding. |
230 | value = request.headerField("accept-encoding" ); |
231 | if (value.isEmpty()) { |
232 | #ifndef QT_NO_COMPRESS |
233 | request.setHeaderField("Accept-Encoding" , "gzip" ); |
234 | request.d->autoDecompress = true; |
235 | #else |
236 | // if zlib is not available set this to false always |
237 | request.d->autoDecompress = false; |
238 | #endif |
239 | } |
240 | |
241 | // some websites mandate an accept-language header and fail |
242 | // if it is not sent. This is a problem with the website and |
243 | // not with us, but we work around this by setting |
244 | // one always. |
245 | value = request.headerField("accept-language" ); |
246 | if (value.isEmpty()) { |
247 | QString systemLocale = QLocale::system().name().replace(QChar::fromAscii('_'),QChar::fromAscii('-')); |
248 | QString acceptLanguage; |
249 | if (systemLocale == QLatin1String("C" )) |
250 | acceptLanguage = QString::fromAscii("en,*" ); |
251 | else if (systemLocale.startsWith(QLatin1String("en-" ))) |
252 | acceptLanguage = QString::fromAscii("%1,*" ).arg(systemLocale); |
253 | else |
254 | acceptLanguage = QString::fromAscii("%1,en,*" ).arg(systemLocale); |
255 | request.setHeaderField("Accept-Language" , acceptLanguage.toAscii()); |
256 | } |
257 | |
258 | // set the User Agent |
259 | value = request.headerField("user-agent" ); |
260 | if (value.isEmpty()) |
261 | request.setHeaderField("User-Agent" , "Mozilla/5.0" ); |
262 | // set the host |
263 | value = request.headerField("host" ); |
264 | if (value.isEmpty()) { |
265 | QHostAddress add; |
266 | QByteArray host; |
267 | if(add.setAddress(hostName)) { |
268 | if(add.protocol() == QAbstractSocket::IPv6Protocol) { |
269 | host = "[" + hostName.toAscii() + "]" ;//format the ipv6 in the standard way |
270 | } else { |
271 | host = QUrl::toAce(hostName); |
272 | } |
273 | } else { |
274 | host = QUrl::toAce(hostName); |
275 | } |
276 | |
277 | int port = request.url().port(); |
278 | if (port != -1) { |
279 | host += ':'; |
280 | host += QByteArray::number(port); |
281 | } |
282 | |
283 | request.setHeaderField("Host" , host); |
284 | } |
285 | |
286 | reply->d_func()->requestIsPrepared = true; |
287 | } |
288 | |
289 | |
290 | |
291 | |
292 | void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket, |
293 | QHttpNetworkReply *reply, |
294 | QNetworkReply::NetworkError errorCode) |
295 | { |
296 | Q_Q(QHttpNetworkConnection); |
297 | if (socket && reply) { |
298 | // this error matters only to this reply |
299 | reply->d_func()->errorString = errorDetail(errorCode, socket); |
300 | emit reply->finishedWithError(errorCode, reply->d_func()->errorString); |
301 | int i = indexOf(socket); |
302 | // remove the corrupt data if any |
303 | reply->d_func()->eraseData(); |
304 | |
305 | // Clean the channel |
306 | channels[i].close(); |
307 | channels[i].reply = 0; |
308 | channels[i].request = QHttpNetworkRequest(); |
309 | channels[i].requeueCurrentlyPipelinedRequests(); |
310 | |
311 | // send the next request |
312 | QMetaObject::invokeMethod(q, "_q_startNextRequest" , Qt::QueuedConnection); |
313 | } |
314 | } |
315 | |
316 | void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthenticator *auth, bool isProxy) |
317 | { |
318 | Q_ASSERT(auth); |
319 | |
320 | // NTLM is a multi phase authentication. Copying credentials between authenticators would mess things up. |
321 | if (!isProxy && channels[fromChannel].authMethod == QAuthenticatorPrivate::Ntlm) |
322 | return; |
323 | if (isProxy && channels[fromChannel].proxyAuthMethod == QAuthenticatorPrivate::Ntlm) |
324 | return; |
325 | |
326 | |
327 | // select another channel |
328 | QAuthenticator* otherAuth = 0; |
329 | for (int i = 0; i < channelCount; ++i) { |
330 | if (i == fromChannel) |
331 | continue; |
332 | if (isProxy) |
333 | otherAuth = &channels[i].proxyAuthenticator; |
334 | else |
335 | otherAuth = &channels[i].authenticator; |
336 | // if the credentials are different, copy them |
337 | if (otherAuth->user().compare(auth->user())) |
338 | otherAuth->setUser(auth->user()); |
339 | if (otherAuth->password().compare(auth->password())) |
340 | otherAuth->setPassword(auth->password()); |
341 | } |
342 | } |
343 | |
344 | |
345 | // handles the authentication for one channel and eventually re-starts the other channels |
346 | bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, |
347 | bool isProxy, bool &resend) |
348 | { |
349 | Q_ASSERT(socket); |
350 | Q_ASSERT(reply); |
351 | |
352 | resend = false; |
353 | //create the response header to be used with QAuthenticatorPrivate. |
354 | QList<QPair<QByteArray, QByteArray> > fields = reply->header(); |
355 | |
356 | //find out the type of authentication protocol requested. |
357 | QAuthenticatorPrivate::Method authMethod = reply->d_func()->authenticationMethod(isProxy); |
358 | if (authMethod != QAuthenticatorPrivate::None) { |
359 | int i = indexOf(socket); |
360 | //Use a single authenticator for all domains. ### change later to use domain/realm |
361 | QAuthenticator* auth = 0; |
362 | if (isProxy) { |
363 | auth = &channels[i].proxyAuthenticator; |
364 | channels[i].proxyAuthMethod = authMethod; |
365 | } else { |
366 | auth = &channels[i].authenticator; |
367 | channels[i].authMethod = authMethod; |
368 | } |
369 | //proceed with the authentication. |
370 | if (auth->isNull()) |
371 | auth->detach(); |
372 | QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth); |
373 | priv->parseHttpResponse(fields, isProxy); |
374 | |
375 | if (priv->phase == QAuthenticatorPrivate::Done) { |
376 | pauseConnection(); |
377 | if (!isProxy) { |
378 | if (channels[i].authenticationCredentialsSent) { |
379 | auth->detach(); |
380 | priv = QAuthenticatorPrivate::getPrivate(*auth); |
381 | priv->hasFailed = true; |
382 | priv->phase = QAuthenticatorPrivate::Done; |
383 | channels[i].authenticationCredentialsSent = false; |
384 | } |
385 | emit reply->authenticationRequired(reply->request(), auth); |
386 | #ifndef QT_NO_NETWORKPROXY |
387 | } else { |
388 | if (channels[i].proxyCredentialsSent) { |
389 | auth->detach(); |
390 | priv = QAuthenticatorPrivate::getPrivate(*auth); |
391 | priv->hasFailed = true; |
392 | priv->phase = QAuthenticatorPrivate::Done; |
393 | channels[i].proxyCredentialsSent = false; |
394 | } |
395 | emit reply->proxyAuthenticationRequired(networkProxy, auth); |
396 | #endif |
397 | } |
398 | resumeConnection(); |
399 | |
400 | if (priv->phase != QAuthenticatorPrivate::Done) { |
401 | // send any pending requests |
402 | copyCredentials(i, auth, isProxy); |
403 | } |
404 | } else if (priv->phase == QAuthenticatorPrivate::Start) { |
405 | // If the url's authenticator has a 'user' set we will end up here (phase is only set to 'Done' by |
406 | // parseHttpResponse above if 'user' is empty). So if credentials were supplied with the request, |
407 | // such as in the case of an XMLHttpRequest, this is our only opportunity to cache them. |
408 | emit reply->cacheCredentials(reply->request(), auth); |
409 | } |
410 | // - Changing values in QAuthenticator will reset the 'phase'. Therefore if it is still "Done" |
411 | // then nothing was filled in by the user or the cache |
412 | // - If withCredentials has been set to false (e.g. by QtWebKit for a cross-origin XMLHttpRequest) then |
413 | // we need to bail out if authentication is required. |
414 | if (priv->phase == QAuthenticatorPrivate::Done || !reply->request().withCredentials()) { |
415 | // Reset authenticator so the next request on that channel does not get messed up |
416 | auth = 0; |
417 | if (isProxy) |
418 | channels[i].proxyAuthenticator = QAuthenticator(); |
419 | else |
420 | channels[i].authenticator = QAuthenticator(); |
421 | |
422 | // authentication is cancelled, send the current contents to the user. |
423 | emit channels[i].reply->headerChanged(); |
424 | emit channels[i].reply->readyRead(); |
425 | QNetworkReply::NetworkError errorCode = |
426 | isProxy |
427 | ? QNetworkReply::ProxyAuthenticationRequiredError |
428 | : QNetworkReply::AuthenticationRequiredError; |
429 | reply->d_func()->errorString = errorDetail(errorCode, socket); |
430 | emit reply->finishedWithError(errorCode, reply->d_func()->errorString); |
431 | // ### at this point the reply could be deleted |
432 | return true; |
433 | } |
434 | //resend the request |
435 | resend = true; |
436 | return true; |
437 | } |
438 | return false; |
439 | } |
440 | |
441 | void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request) |
442 | { |
443 | Q_ASSERT(socket); |
444 | |
445 | int i = indexOf(socket); |
446 | |
447 | // Send "Authorization" header, but not if it's NTLM and the socket is already authenticated. |
448 | if (channels[i].authMethod != QAuthenticatorPrivate::None) { |
449 | if (!(channels[i].authMethod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 401)) { |
450 | QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].authenticator); |
451 | if (priv && priv->method != QAuthenticatorPrivate::None) { |
452 | QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false)); |
453 | request.setHeaderField("Authorization" , response); |
454 | channels[i].authenticationCredentialsSent = true; |
455 | } |
456 | } |
457 | } |
458 | |
459 | // Send "Proxy-Authorization" header, but not if it's NTLM and the socket is already authenticated. |
460 | if (channels[i].proxyAuthMethod != QAuthenticatorPrivate::None) { |
461 | if (!(channels[i].proxyAuthMethod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 407)) { |
462 | QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].proxyAuthenticator); |
463 | if (priv && priv->method != QAuthenticatorPrivate::None) { |
464 | QByteArray response = priv->calculateResponse(request.d->methodName(), request.d->uri(false)); |
465 | request.setHeaderField("Proxy-Authorization" , response); |
466 | channels[i].proxyCredentialsSent = true; |
467 | } |
468 | } |
469 | } |
470 | } |
471 | |
472 | QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetworkRequest &request) |
473 | { |
474 | Q_Q(QHttpNetworkConnection); |
475 | |
476 | // The reply component of the pair is created initially. |
477 | QHttpNetworkReply *reply = new QHttpNetworkReply(request.url()); |
478 | reply->setRequest(request); |
479 | reply->d_func()->connection = q; |
480 | reply->d_func()->connectionChannel = &channels[0]; // will have the correct one set later |
481 | HttpMessagePair pair = qMakePair(request, reply); |
482 | |
483 | switch (request.priority()) { |
484 | case QHttpNetworkRequest::HighPriority: |
485 | highPriorityQueue.prepend(pair); |
486 | break; |
487 | case QHttpNetworkRequest::NormalPriority: |
488 | case QHttpNetworkRequest::LowPriority: |
489 | lowPriorityQueue.prepend(pair); |
490 | break; |
491 | } |
492 | |
493 | // this used to be called via invokeMethod and a QueuedConnection |
494 | // It is the only place _q_startNextRequest is called directly without going |
495 | // through the event loop using a QueuedConnection. |
496 | // This is dangerous because of recursion that might occur when emitting |
497 | // signals as DirectConnection from this code path. Therefore all signal |
498 | // emissions that can come out from this code path need to |
499 | // be QueuedConnection. |
500 | // We are currently trying to fine-tune this. |
501 | _q_startNextRequest(); |
502 | |
503 | |
504 | return reply; |
505 | } |
506 | |
507 | void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair) |
508 | { |
509 | Q_Q(QHttpNetworkConnection); |
510 | |
511 | QHttpNetworkRequest request = pair.first; |
512 | switch (request.priority()) { |
513 | case QHttpNetworkRequest::HighPriority: |
514 | highPriorityQueue.prepend(pair); |
515 | break; |
516 | case QHttpNetworkRequest::NormalPriority: |
517 | case QHttpNetworkRequest::LowPriority: |
518 | lowPriorityQueue.prepend(pair); |
519 | break; |
520 | } |
521 | |
522 | QMetaObject::invokeMethod(q, "_q_startNextRequest" , Qt::QueuedConnection); |
523 | } |
524 | |
525 | bool QHttpNetworkConnectionPrivate::dequeueRequest(QAbstractSocket *socket) |
526 | { |
527 | Q_ASSERT(socket); |
528 | |
529 | int i = indexOf(socket); |
530 | |
531 | if (!highPriorityQueue.isEmpty()) { |
532 | // remove from queue before sendRequest! else we might pipeline the same request again |
533 | HttpMessagePair messagePair = highPriorityQueue.takeLast(); |
534 | if (!messagePair.second->d_func()->requestIsPrepared) |
535 | prepareRequest(messagePair); |
536 | channels[i].request = messagePair.first; |
537 | channels[i].reply = messagePair.second; |
538 | return true; |
539 | } |
540 | |
541 | if (!lowPriorityQueue.isEmpty()) { |
542 | // remove from queue before sendRequest! else we might pipeline the same request again |
543 | HttpMessagePair messagePair = lowPriorityQueue.takeLast(); |
544 | if (!messagePair.second->d_func()->requestIsPrepared) |
545 | prepareRequest(messagePair); |
546 | channels[i].request = messagePair.first; |
547 | channels[i].reply = messagePair.second; |
548 | return true; |
549 | } |
550 | return false; |
551 | } |
552 | |
553 | QHttpNetworkRequest QHttpNetworkConnectionPrivate::predictNextRequest() |
554 | { |
555 | if (!highPriorityQueue.isEmpty()) |
556 | return highPriorityQueue.last().first; |
557 | if (!lowPriorityQueue.isEmpty()) |
558 | return lowPriorityQueue.last().first; |
559 | return QHttpNetworkRequest(); |
560 | } |
561 | |
562 | // this is called from _q_startNextRequest and when a request has been sent down a socket from the channel |
563 | void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket) |
564 | { |
565 | // return fast if there is nothing to pipeline |
566 | if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) |
567 | return; |
568 | |
569 | int i = indexOf(socket); |
570 | |
571 | // return fast if there was no reply right now processed |
572 | if (channels[i].reply == 0) |
573 | return; |
574 | |
575 | if (! (defaultPipelineLength - channels[i].alreadyPipelinedRequests.length() >= defaultRePipelineLength)) { |
576 | return; |
577 | } |
578 | |
579 | if (channels[i].pipeliningSupported != QHttpNetworkConnectionChannel::PipeliningProbablySupported) |
580 | return; |
581 | |
582 | // the current request that is in must already support pipelining |
583 | if (!channels[i].request.isPipeliningAllowed()) |
584 | return; |
585 | |
586 | // the current request must be a idempotent (right now we only check GET) |
587 | if (channels[i].request.operation() != QHttpNetworkRequest::Get) |
588 | return; |
589 | |
590 | // check if socket is connected |
591 | if (socket->state() != QAbstractSocket::ConnectedState) |
592 | return; |
593 | |
594 | // check for resendCurrent |
595 | if (channels[i].resendCurrent) |
596 | return; |
597 | |
598 | // we do not like authentication stuff |
599 | // ### make sure to be OK with this in later releases |
600 | if (!channels[i].authenticator.isNull() |
601 | && (!channels[i].authenticator.user().isEmpty() |
602 | || !channels[i].authenticator.password().isEmpty())) |
603 | return; |
604 | if (!channels[i].proxyAuthenticator.isNull() |
605 | && (!channels[i].proxyAuthenticator.user().isEmpty() |
606 | || !channels[i].proxyAuthenticator.password().isEmpty())) |
607 | return; |
608 | |
609 | // must be in ReadingState or WaitingState |
610 | if (! (channels[i].state == QHttpNetworkConnectionChannel::WaitingState |
611 | || channels[i].state == QHttpNetworkConnectionChannel::ReadingState)) |
612 | return; |
613 | |
614 | int lengthBefore; |
615 | while (!highPriorityQueue.isEmpty()) { |
616 | lengthBefore = channels[i].alreadyPipelinedRequests.length(); |
617 | fillPipeline(highPriorityQueue, channels[i]); |
618 | |
619 | if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) { |
620 | channels[i].pipelineFlush(); |
621 | return; |
622 | } |
623 | |
624 | if (lengthBefore == channels[i].alreadyPipelinedRequests.length()) |
625 | break; // did not process anything, now do the low prio queue |
626 | } |
627 | |
628 | while (!lowPriorityQueue.isEmpty()) { |
629 | lengthBefore = channels[i].alreadyPipelinedRequests.length(); |
630 | fillPipeline(lowPriorityQueue, channels[i]); |
631 | |
632 | if (channels[i].alreadyPipelinedRequests.length() >= defaultPipelineLength) { |
633 | channels[i].pipelineFlush(); |
634 | return; |
635 | } |
636 | |
637 | if (lengthBefore == channels[i].alreadyPipelinedRequests.length()) |
638 | break; // did not process anything |
639 | } |
640 | |
641 | |
642 | channels[i].pipelineFlush(); |
643 | } |
644 | |
645 | // returns true when the processing of a queue has been done |
646 | bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel) |
647 | { |
648 | if (queue.isEmpty()) |
649 | return true; |
650 | |
651 | for (int i = queue.count() - 1; i >= 0; --i) { |
652 | HttpMessagePair messagePair = queue.at(i); |
653 | const QHttpNetworkRequest &request = messagePair.first; |
654 | |
655 | // we currently do not support pipelining if HTTP authentication is used |
656 | if (!request.url().userInfo().isEmpty()) |
657 | continue; |
658 | |
659 | // take only GET requests |
660 | if (request.operation() != QHttpNetworkRequest::Get) |
661 | continue; |
662 | |
663 | if (!request.isPipeliningAllowed()) |
664 | continue; |
665 | |
666 | // remove it from the queue |
667 | queue.takeAt(i); |
668 | // we modify the queue we iterate over here, but since we return from the function |
669 | // afterwards this is fine. |
670 | |
671 | // actually send it |
672 | if (!messagePair.second->d_func()->requestIsPrepared) |
673 | prepareRequest(messagePair); |
674 | channel.pipelineInto(messagePair); |
675 | |
676 | // return false because we processed something and need to process again |
677 | return false; |
678 | } |
679 | |
680 | // return true, the queue has been processed and not changed |
681 | return true; |
682 | } |
683 | |
684 | |
685 | QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket* socket, |
686 | const QString &) |
687 | { |
688 | Q_ASSERT(socket); |
689 | |
690 | QString errorString; |
691 | switch (errorCode) { |
692 | case QNetworkReply::HostNotFoundError: |
693 | errorString = QCoreApplication::translate("QHttp" , "Host %1 not found" ).arg(socket->peerName()); |
694 | break; |
695 | case QNetworkReply::ConnectionRefusedError: |
696 | errorString = QCoreApplication::translate("QHttp" , "Connection refused" ); |
697 | break; |
698 | case QNetworkReply::RemoteHostClosedError: |
699 | errorString = QCoreApplication::translate("QHttp" , "Connection closed" ); |
700 | break; |
701 | case QNetworkReply::TimeoutError: |
702 | errorString = QCoreApplication::translate("QAbstractSocket" , "Socket operation timed out" ); |
703 | break; |
704 | case QNetworkReply::ProxyAuthenticationRequiredError: |
705 | errorString = QCoreApplication::translate("QHttp" , "Proxy requires authentication" ); |
706 | break; |
707 | case QNetworkReply::AuthenticationRequiredError: |
708 | errorString = QCoreApplication::translate("QHttp" , "Host requires authentication" ); |
709 | break; |
710 | case QNetworkReply::ProtocolFailure: |
711 | errorString = QCoreApplication::translate("QHttp" , "Data corrupted" ); |
712 | break; |
713 | case QNetworkReply::ProtocolUnknownError: |
714 | errorString = QCoreApplication::translate("QHttp" , "Unknown protocol specified" ); |
715 | break; |
716 | case QNetworkReply::SslHandshakeFailedError: |
717 | errorString = QCoreApplication::translate("QHttp" , "SSL handshake failed" ); |
718 | break; |
719 | default: |
720 | // all other errors are treated as QNetworkReply::UnknownNetworkError |
721 | errorString = extraDetail; |
722 | break; |
723 | } |
724 | return errorString; |
725 | } |
726 | |
727 | // this is called from the destructor of QHttpNetworkReply. It is called when |
728 | // the reply was finished correctly or when it was aborted. |
729 | void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply) |
730 | { |
731 | Q_Q(QHttpNetworkConnection); |
732 | |
733 | // check if the reply is currently being processed or it is pipelined in |
734 | for (int i = 0; i < channelCount; ++i) { |
735 | // is the reply associated the currently processing of this channel? |
736 | if (channels[i].reply == reply) { |
737 | channels[i].reply = 0; |
738 | channels[i].request = QHttpNetworkRequest(); |
739 | channels[i].resendCurrent = false; |
740 | |
741 | if (!reply->isFinished() && !channels[i].alreadyPipelinedRequests.isEmpty()) { |
742 | // the reply had to be prematurely removed, e.g. it was not finished |
743 | // therefore we have to requeue the already pipelined requests. |
744 | channels[i].requeueCurrentlyPipelinedRequests(); |
745 | } |
746 | |
747 | // if HTTP mandates we should close |
748 | // or the reply is not finished yet, e.g. it was aborted |
749 | // we have to close that connection |
750 | if (reply->d_func()->isConnectionCloseEnabled() || !reply->isFinished()) |
751 | channels[i].close(); |
752 | |
753 | QMetaObject::invokeMethod(q, "_q_startNextRequest" , Qt::QueuedConnection); |
754 | return; |
755 | } |
756 | |
757 | // is the reply inside the pipeline of this channel already? |
758 | for (int j = 0; j < channels[i].alreadyPipelinedRequests.length(); j++) { |
759 | if (channels[i].alreadyPipelinedRequests.at(j).second == reply) { |
760 | // Remove that HttpMessagePair |
761 | channels[i].alreadyPipelinedRequests.removeAt(j); |
762 | |
763 | channels[i].requeueCurrentlyPipelinedRequests(); |
764 | |
765 | // Since some requests had already been pipelined, but we removed |
766 | // one and re-queued the others |
767 | // we must force a connection close after the request that is |
768 | // currently in processing has been finished. |
769 | if (channels[i].reply) |
770 | channels[i].reply->d_func()->forceConnectionCloseEnabled = true; |
771 | |
772 | QMetaObject::invokeMethod(q, "_q_startNextRequest" , Qt::QueuedConnection); |
773 | return; |
774 | } |
775 | } |
776 | } |
777 | // remove from the high priority queue |
778 | if (!highPriorityQueue.isEmpty()) { |
779 | for (int j = highPriorityQueue.count() - 1; j >= 0; --j) { |
780 | HttpMessagePair messagePair = highPriorityQueue.at(j); |
781 | if (messagePair.second == reply) { |
782 | highPriorityQueue.removeAt(j); |
783 | QMetaObject::invokeMethod(q, "_q_startNextRequest" , Qt::QueuedConnection); |
784 | return; |
785 | } |
786 | } |
787 | } |
788 | // remove from the low priority queue |
789 | if (!lowPriorityQueue.isEmpty()) { |
790 | for (int j = lowPriorityQueue.count() - 1; j >= 0; --j) { |
791 | HttpMessagePair messagePair = lowPriorityQueue.at(j); |
792 | if (messagePair.second == reply) { |
793 | lowPriorityQueue.removeAt(j); |
794 | QMetaObject::invokeMethod(q, "_q_startNextRequest" , Qt::QueuedConnection); |
795 | return; |
796 | } |
797 | } |
798 | } |
799 | } |
800 | |
801 | |
802 | |
803 | // This function must be called from the event loop. The only |
804 | // exception is documented in QHttpNetworkConnectionPrivate::queueRequest |
805 | // although it is called _q_startNextRequest, it will actually start multiple requests when possible |
806 | void QHttpNetworkConnectionPrivate::_q_startNextRequest() |
807 | { |
808 | // If the QHttpNetworkConnection is currently paused then bail out immediately |
809 | if (state == PausedState) |
810 | return; |
811 | |
812 | //resend the necessary ones. |
813 | for (int i = 0; i < channelCount; ++i) { |
814 | if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) { |
815 | channels[i].resendCurrent = false; |
816 | channels[i].state = QHttpNetworkConnectionChannel::IdleState; |
817 | |
818 | // if this is not possible, error will be emitted and connection terminated |
819 | if (!channels[i].resetUploadData()) |
820 | continue; |
821 | channels[i].sendRequest(); |
822 | } |
823 | } |
824 | |
825 | // dequeue new ones |
826 | |
827 | // return fast if there is nothing to do |
828 | if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) |
829 | return; |
830 | // try to get a free AND connected socket |
831 | for (int i = 0; i < channelCount; ++i) { |
832 | if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) { |
833 | if (dequeueRequest(channels[i].socket)) |
834 | channels[i].sendRequest(); |
835 | } |
836 | } |
837 | |
838 | // try to push more into all sockets |
839 | // ### FIXME we should move this to the beginning of the function |
840 | // as soon as QtWebkit is properly using the pipelining |
841 | // (e.g. not for XMLHttpRequest or the first page load) |
842 | // ### FIXME we should also divide the requests more even |
843 | // on the connected sockets |
844 | //tryToFillPipeline(socket); |
845 | // return fast if there is nothing to pipeline |
846 | if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) |
847 | return; |
848 | for (int i = 0; i < channelCount; i++) |
849 | if (channels[i].socket->state() == QAbstractSocket::ConnectedState) |
850 | fillPipeline(channels[i].socket); |
851 | |
852 | // If there is not already any connected channels we need to connect a new one. |
853 | // We do not pair the channel with the request until we know if it is |
854 | // connected or not. This is to reuse connected channels before we connect new once. |
855 | int queuedRequest = highPriorityQueue.count() + lowPriorityQueue.count(); |
856 | for (int i = 0; i < channelCount; ++i) { |
857 | if (channels[i].socket->state() == QAbstractSocket::ConnectingState) |
858 | queuedRequest--; |
859 | if ( queuedRequest <=0 ) |
860 | break; |
861 | if (!channels[i].reply && !channels[i].isSocketBusy() && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) { |
862 | channels[i].ensureConnection(); |
863 | queuedRequest--; |
864 | } |
865 | } |
866 | } |
867 | |
868 | |
869 | void QHttpNetworkConnectionPrivate::readMoreLater(QHttpNetworkReply *reply) |
870 | { |
871 | for (int i = 0 ; i < channelCount; ++i) { |
872 | if (channels[i].reply == reply) { |
873 | // emulate a readyRead() from the socket |
874 | QMetaObject::invokeMethod(&channels[i], "_q_readyRead" , Qt::QueuedConnection); |
875 | return; |
876 | } |
877 | } |
878 | } |
879 | |
880 | #ifndef QT_NO_BEARERMANAGEMENT |
881 | QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession) |
882 | : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent) |
883 | { |
884 | Q_D(QHttpNetworkConnection); |
885 | d->networkSession = networkSession; |
886 | d->init(); |
887 | } |
888 | |
889 | QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession) |
890 | : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent) |
891 | { |
892 | Q_D(QHttpNetworkConnection); |
893 | d->networkSession = networkSession; |
894 | d->init(); |
895 | } |
896 | #else |
897 | QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent) |
898 | : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent) |
899 | { |
900 | Q_D(QHttpNetworkConnection); |
901 | d->init(); |
902 | } |
903 | |
904 | QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent) |
905 | : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent) |
906 | { |
907 | Q_D(QHttpNetworkConnection); |
908 | d->init(); |
909 | } |
910 | #endif |
911 | |
912 | QHttpNetworkConnection::~QHttpNetworkConnection() |
913 | { |
914 | } |
915 | |
916 | QString QHttpNetworkConnection::hostName() const |
917 | { |
918 | Q_D(const QHttpNetworkConnection); |
919 | return d->hostName; |
920 | } |
921 | |
922 | quint16 QHttpNetworkConnection::port() const |
923 | { |
924 | Q_D(const QHttpNetworkConnection); |
925 | return d->port; |
926 | } |
927 | |
928 | QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest &request) |
929 | { |
930 | Q_D(QHttpNetworkConnection); |
931 | return d->queueRequest(request); |
932 | } |
933 | |
934 | bool QHttpNetworkConnection::isSsl() const |
935 | { |
936 | Q_D(const QHttpNetworkConnection); |
937 | return d->encrypt; |
938 | } |
939 | |
940 | QHttpNetworkConnectionChannel *QHttpNetworkConnection::channels() const |
941 | { |
942 | return d_func()->channels; |
943 | } |
944 | |
945 | #ifndef QT_NO_NETWORKPROXY |
946 | void QHttpNetworkConnection::setCacheProxy(const QNetworkProxy &networkProxy) |
947 | { |
948 | Q_D(QHttpNetworkConnection); |
949 | d->networkProxy = networkProxy; |
950 | // update the authenticator |
951 | if (!d->networkProxy.user().isEmpty()) { |
952 | for (int i = 0; i < d->channelCount; ++i) { |
953 | d->channels[i].proxyAuthenticator.setUser(d->networkProxy.user()); |
954 | d->channels[i].proxyAuthenticator.setPassword(d->networkProxy.password()); |
955 | } |
956 | } |
957 | } |
958 | |
959 | QNetworkProxy QHttpNetworkConnection::cacheProxy() const |
960 | { |
961 | Q_D(const QHttpNetworkConnection); |
962 | return d->networkProxy; |
963 | } |
964 | |
965 | void QHttpNetworkConnection::setTransparentProxy(const QNetworkProxy &networkProxy) |
966 | { |
967 | Q_D(QHttpNetworkConnection); |
968 | for (int i = 0; i < d->channelCount; ++i) |
969 | d->channels[i].socket->setProxy(networkProxy); |
970 | } |
971 | |
972 | QNetworkProxy QHttpNetworkConnection::transparentProxy() const |
973 | { |
974 | Q_D(const QHttpNetworkConnection); |
975 | return d->channels[0].socket->proxy(); |
976 | } |
977 | #endif |
978 | |
979 | |
980 | // SSL support below |
981 | #ifndef QT_NO_OPENSSL |
982 | void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config) |
983 | { |
984 | Q_D(QHttpNetworkConnection); |
985 | if (!d->encrypt) |
986 | return; |
987 | |
988 | // set the config on all channels |
989 | for (int i = 0; i < d->channelCount; ++i) |
990 | static_cast<QSslSocket *>(d->channels[i].socket)->setSslConfiguration(config); |
991 | } |
992 | |
993 | void QHttpNetworkConnection::ignoreSslErrors(int channel) |
994 | { |
995 | Q_D(QHttpNetworkConnection); |
996 | if (!d->encrypt) |
997 | return; |
998 | |
999 | if (channel == -1) { // ignore for all channels |
1000 | for (int i = 0; i < d->channelCount; ++i) { |
1001 | static_cast<QSslSocket *>(d->channels[i].socket)->ignoreSslErrors(); |
1002 | d->channels[i].ignoreAllSslErrors = true; |
1003 | } |
1004 | |
1005 | } else { |
1006 | static_cast<QSslSocket *>(d->channels[channel].socket)->ignoreSslErrors(); |
1007 | d->channels[channel].ignoreAllSslErrors = true; |
1008 | } |
1009 | } |
1010 | |
1011 | void QHttpNetworkConnection::ignoreSslErrors(const QList<QSslError> &errors, int channel) |
1012 | { |
1013 | Q_D(QHttpNetworkConnection); |
1014 | if (!d->encrypt) |
1015 | return; |
1016 | |
1017 | if (channel == -1) { // ignore for all channels |
1018 | for (int i = 0; i < d->channelCount; ++i) { |
1019 | static_cast<QSslSocket *>(d->channels[i].socket)->ignoreSslErrors(errors); |
1020 | d->channels[i].ignoreSslErrorsList = errors; |
1021 | } |
1022 | |
1023 | } else { |
1024 | static_cast<QSslSocket *>(d->channels[channel].socket)->ignoreSslErrors(errors); |
1025 | d->channels[channel].ignoreSslErrorsList = errors; |
1026 | } |
1027 | } |
1028 | |
1029 | #endif //QT_NO_OPENSSL |
1030 | |
1031 | #ifndef QT_NO_NETWORKPROXY |
1032 | // only called from QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, not |
1033 | // from QHttpNetworkConnectionChannel::handleAuthenticationChallenge |
1034 | // e.g. it is for SOCKS proxies which require authentication. |
1035 | void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpNetworkConnectionChannel *chan, const QNetworkProxy &proxy, QAuthenticator* auth) |
1036 | { |
1037 | // Also pause the connection because socket notifiers may fire while an user |
1038 | // dialog is displaying |
1039 | pauseConnection(); |
1040 | emit chan->reply->proxyAuthenticationRequired(proxy, auth); |
1041 | resumeConnection(); |
1042 | int i = indexOf(chan->socket); |
1043 | copyCredentials(i, auth, true); |
1044 | } |
1045 | #endif |
1046 | |
1047 | |
1048 | QT_END_NAMESPACE |
1049 | |
1050 | #include "moc_qhttpnetworkconnection_p.cpp" |
1051 | |
1052 | #endif // QT_NO_HTTP |
1053 | |