source: trunk/src/network/access/qnetworkaccesshttpbackend.cpp

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

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

File size: 42.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtNetwork module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** 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 qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42//#define QNETWORKACCESSHTTPBACKEND_DEBUG
43
44#include "qnetworkaccesshttpbackend_p.h"
45#include "qnetworkaccessmanager_p.h"
46#include "qnetworkaccesscache_p.h"
47#include "qabstractnetworkcache.h"
48#include "qnetworkrequest.h"
49#include "qnetworkreply.h"
50#include "qnetworkrequest_p.h"
51#include "qnetworkcookie_p.h"
52#include "QtCore/qdatetime.h"
53#include "QtCore/qelapsedtimer.h"
54#include "QtNetwork/qsslconfiguration.h"
55
56#ifndef QT_NO_HTTP
57
58#include <string.h> // for strchr
59
60QT_BEGIN_NAMESPACE
61
62enum {
63 DefaultHttpPort = 80,
64 DefaultHttpsPort = 443
65};
66
67class QNetworkProxy;
68
69static QByteArray makeCacheKey(QNetworkAccessHttpBackend *backend, QNetworkProxy *proxy)
70{
71 QByteArray result;
72 QUrl copy = backend->url();
73 bool isEncrypted = copy.scheme().toLower() == QLatin1String("https");
74 copy.setPort(copy.port(isEncrypted ? DefaultHttpsPort : DefaultHttpPort));
75 result = copy.toEncoded(QUrl::RemovePassword | QUrl::RemovePath |
76 QUrl::RemoveQuery | QUrl::RemoveFragment);
77
78#ifndef QT_NO_NETWORKPROXY
79 if (proxy->type() != QNetworkProxy::NoProxy) {
80 QUrl key;
81
82 switch (proxy->type()) {
83 case QNetworkProxy::Socks5Proxy:
84 key.setScheme(QLatin1String("proxy-socks5"));
85 break;
86
87 case QNetworkProxy::HttpProxy:
88 case QNetworkProxy::HttpCachingProxy:
89 key.setScheme(QLatin1String("proxy-http"));
90 break;
91
92 default:
93 break;
94 }
95
96 if (!key.scheme().isEmpty()) {
97 key.setUserName(proxy->user());
98 key.setHost(proxy->hostName());
99 key.setPort(proxy->port());
100 key.setEncodedQuery(result);
101 result = key.toEncoded();
102 }
103 }
104#endif
105
106 return "http-connection:" + result;
107}
108
109static inline bool isSeparator(register char c)
110{
111 static const char separators[] = "()<>@,;:\\\"/[]?={}";
112 return isLWS(c) || strchr(separators, c) != 0;
113}
114
115// ### merge with nextField in cookiejar.cpp
116static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &header)
117{
118 // The HTTP header is of the form:
119 // header = #1(directives)
120 // directives = token | value-directive
121 // value-directive = token "=" (token | quoted-string)
122 QHash<QByteArray, QByteArray> result;
123
124 int pos = 0;
125 while (true) {
126 // skip spaces
127 pos = nextNonWhitespace(header, pos);
128 if (pos == header.length())
129 return result; // end of parsing
130
131 // pos points to a non-whitespace
132 int comma = header.indexOf(',', pos);
133 int equal = header.indexOf('=', pos);
134 if (comma == pos || equal == pos)
135 // huh? Broken header.
136 return result;
137
138 // The key name is delimited by either a comma, an equal sign or the end
139 // of the header, whichever comes first
140 int end = comma;
141 if (end == -1)
142 end = header.length();
143 if (equal != -1 && end > equal)
144 end = equal; // equal sign comes before comma/end
145 QByteArray key = QByteArray(header.constData() + pos, end - pos).trimmed().toLower();
146 pos = end + 1;
147
148 if (uint(equal) < uint(comma)) {
149 // case: token "=" (token | quoted-string)
150 // skip spaces
151 pos = nextNonWhitespace(header, pos);
152 if (pos == header.length())
153 // huh? Broken header
154 return result;
155
156 QByteArray value;
157 value.reserve(header.length() - pos);
158 if (header.at(pos) == '"') {
159 // case: quoted-string
160 // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
161 // qdtext = <any TEXT except <">>
162 // quoted-pair = "\" CHAR
163 ++pos;
164 while (pos < header.length()) {
165 register char c = header.at(pos);
166 if (c == '"') {
167 // end of quoted text
168 break;
169 } else if (c == '\\') {
170 ++pos;
171 if (pos >= header.length())
172 // broken header
173 return result;
174 c = header.at(pos);
175 }
176
177 value += c;
178 ++pos;
179 }
180 } else {
181 // case: token
182 while (pos < header.length()) {
183 register char c = header.at(pos);
184 if (isSeparator(c))
185 break;
186 value += c;
187 ++pos;
188 }
189 }
190
191 result.insert(key, value);
192
193 // find the comma now:
194 comma = header.indexOf(',', pos);
195 if (comma == -1)
196 return result; // end of parsing
197 pos = comma + 1;
198 } else {
199 // case: token
200 // key is already set
201 result.insert(key, QByteArray());
202 }
203 }
204}
205
206QNetworkAccessBackend *
207QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op,
208 const QNetworkRequest &request) const
209{
210 // check the operation
211 switch (op) {
212 case QNetworkAccessManager::GetOperation:
213 case QNetworkAccessManager::PostOperation:
214 case QNetworkAccessManager::HeadOperation:
215 case QNetworkAccessManager::PutOperation:
216 case QNetworkAccessManager::DeleteOperation:
217 case QNetworkAccessManager::CustomOperation:
218 break;
219
220 default:
221 // no, we can't handle this request
222 return 0;
223 }
224
225 QUrl url = request.url();
226 QString scheme = url.scheme().toLower();
227 if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
228 return new QNetworkAccessHttpBackend;
229
230 return 0;
231}
232
233static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
234{
235 QNetworkReply::NetworkError code;
236 // we've got an error
237 switch (httpStatusCode) {
238 case 401: // Authorization required
239 code = QNetworkReply::AuthenticationRequiredError;
240 break;
241
242 case 403: // Access denied
243 code = QNetworkReply::ContentOperationNotPermittedError;
244 break;
245
246 case 404: // Not Found
247 code = QNetworkReply::ContentNotFoundError;
248 break;
249
250 case 405: // Method Not Allowed
251 code = QNetworkReply::ContentOperationNotPermittedError;
252 break;
253
254 case 407:
255 code = QNetworkReply::ProxyAuthenticationRequiredError;
256 break;
257
258 default:
259 if (httpStatusCode > 500) {
260 // some kind of server error
261 code = QNetworkReply::ProtocolUnknownError;
262 } else if (httpStatusCode >= 400) {
263 // content error we did not handle above
264 code = QNetworkReply::UnknownContentError;
265 } else {
266 qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
267 httpStatusCode, qPrintable(url.toString()));
268 code = QNetworkReply::ProtocolFailure;
269 }
270 }
271
272 return code;
273}
274
275class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
276 public QNetworkAccessCache::CacheableObject
277{
278 // Q_OBJECT
279public:
280 QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt)
281 : QHttpNetworkConnection(hostName, port, encrypt)
282 {
283 setExpires(true);
284 setShareable(true);
285 }
286
287 virtual void dispose()
288 {
289#if 0 // sample code; do this right with the API
290 Q_ASSERT(!isWorking());
291#endif
292 delete this;
293 }
294};
295
296QNetworkAccessHttpBackend::QNetworkAccessHttpBackend()
297 : QNetworkAccessBackend(), httpReply(0), http(0), uploadDevice(0)
298#ifndef QT_NO_OPENSSL
299 , pendingSslConfiguration(0), pendingIgnoreAllSslErrors(false)
300#endif
301 , resumeOffset(0)
302{
303}
304
305QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend()
306{
307 if (http)
308 disconnectFromHttp();
309#ifndef QT_NO_OPENSSL
310 delete pendingSslConfiguration;
311#endif
312}
313
314void QNetworkAccessHttpBackend::disconnectFromHttp()
315{
316 if (http) {
317 // This is abut disconnecting signals, not about disconnecting TCP connections
318 disconnect(http, 0, this, 0);
319
320 // Get the object cache that stores our QHttpNetworkConnection objects
321 QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this);
322
323 // synchronous calls are not put into the cache, so for them the key is empty
324 if (!cacheKey.isEmpty())
325 cache->releaseEntry(cacheKey);
326 }
327
328 // This is abut disconnecting signals, not about disconnecting TCP connections
329 if (httpReply)
330 disconnect(httpReply, 0, this, 0);
331
332 http = 0;
333 httpReply = 0;
334 cacheKey.clear();
335}
336
337void QNetworkAccessHttpBackend::finished()
338{
339 if (http)
340 disconnectFromHttp();
341 // call parent
342 QNetworkAccessBackend::finished();
343}
344
345/*
346 For a given httpRequest
347 1) If AlwaysNetwork, return
348 2) If we have a cache entry for this url populate headers so the server can return 304
349 3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true
350 */
351void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache)
352{
353 QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
354 (QNetworkRequest::CacheLoadControl)request().attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
355 if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
356 // forced reload from the network
357 // tell any caching proxy servers to reload too
358 httpRequest.setHeaderField("Cache-Control", "no-cache");
359 httpRequest.setHeaderField("Pragma", "no-cache");
360 return;
361 }
362
363 QAbstractNetworkCache *nc = networkCache();
364 if (!nc)
365 return; // no local cache
366
367 QNetworkCacheMetaData metaData = nc->metaData(url());
368 if (!metaData.isValid())
369 return; // not in cache
370
371 if (!metaData.saveToDisk())
372 return;
373
374 QNetworkHeadersPrivate cacheHeaders;
375 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
376 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
377
378 it = cacheHeaders.findRawHeader("etag");
379 if (it != cacheHeaders.rawHeaders.constEnd())
380 httpRequest.setHeaderField("If-None-Match", it->second);
381
382 QDateTime lastModified = metaData.lastModified();
383 if (lastModified.isValid())
384 httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified));
385
386 if (CacheLoadControlAttribute == QNetworkRequest::PreferNetwork) {
387 it = cacheHeaders.findRawHeader("Cache-Control");
388 if (it != cacheHeaders.rawHeaders.constEnd()) {
389 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
390 if (cacheControl.contains("must-revalidate"))
391 return;
392 }
393 }
394
395 QDateTime currentDateTime = QDateTime::currentDateTime();
396 QDateTime expirationDate = metaData.expirationDate();
397
398#if 0
399 /*
400 * age_value
401 * is the value of Age: header received by the cache with
402 * this response.
403 * date_value
404 * is the value of the origin server's Date: header
405 * request_time
406 * is the (local) time when the cache made the request
407 * that resulted in this cached response
408 * response_time
409 * is the (local) time when the cache received the
410 * response
411 * now
412 * is the current (local) time
413 */
414 int age_value = 0;
415 it = cacheHeaders.findRawHeader("age");
416 if (it != cacheHeaders.rawHeaders.constEnd())
417 age_value = it->second.toInt();
418
419 QDateTime dateHeader;
420 int date_value = 0;
421 it = cacheHeaders.findRawHeader("date");
422 if (it != cacheHeaders.rawHeaders.constEnd()) {
423 dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second);
424 date_value = dateHeader.toTime_t();
425 }
426
427 int now = currentDateTime.toUTC().toTime_t();
428 int request_time = now;
429 int response_time = now;
430
431 // Algorithm from RFC 2616 section 13.2.3
432 int apparent_age = qMax(0, response_time - date_value);
433 int corrected_received_age = qMax(apparent_age, age_value);
434 int response_delay = response_time - request_time;
435 int corrected_initial_age = corrected_received_age + response_delay;
436 int resident_time = now - response_time;
437 int current_age = corrected_initial_age + resident_time;
438
439 // RFC 2616 13.2.4 Expiration Calculations
440 if (!expirationDate.isValid()) {
441 if (lastModified.isValid()) {
442 int diff = currentDateTime.secsTo(lastModified);
443 expirationDate = lastModified;
444 expirationDate.addSecs(diff / 10);
445 if (httpRequest.headerField("Warning").isEmpty()) {
446 QDateTime dt;
447 dt.setTime_t(current_age);
448 if (dt.daysTo(currentDateTime) > 1)
449 httpRequest.setHeaderField("Warning", "113");
450 }
451 }
452 }
453
454 // the cache-saving code below sets the expirationDate with date+max_age
455 // if "max-age" is present, or to Expires otherwise
456 int freshness_lifetime = dateHeader.secsTo(expirationDate);
457 bool response_is_fresh = (freshness_lifetime > current_age);
458#else
459 bool response_is_fresh = currentDateTime.secsTo(expirationDate) >= 0;
460#endif
461
462 if (!response_is_fresh)
463 return;
464
465 loadedFromCache = true;
466#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
467 qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
468#endif
469 if (!sendCacheContents(metaData))
470 loadedFromCache = false;
471}
472
473static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& prio)
474{
475 switch (prio) {
476 case QNetworkRequest::LowPriority:
477 return QHttpNetworkRequest::LowPriority;
478 case QNetworkRequest::HighPriority:
479 return QHttpNetworkRequest::HighPriority;
480 case QNetworkRequest::NormalPriority:
481 default:
482 return QHttpNetworkRequest::NormalPriority;
483 }
484}
485
486void QNetworkAccessHttpBackend::postRequest()
487{
488 bool loadedFromCache = false;
489 QHttpNetworkRequest httpRequest;
490 httpRequest.setPriority(convert(request().priority()));
491 switch (operation()) {
492 case QNetworkAccessManager::GetOperation:
493 httpRequest.setOperation(QHttpNetworkRequest::Get);
494 validateCache(httpRequest, loadedFromCache);
495 break;
496
497 case QNetworkAccessManager::HeadOperation:
498 httpRequest.setOperation(QHttpNetworkRequest::Head);
499 validateCache(httpRequest, loadedFromCache);
500 break;
501
502 case QNetworkAccessManager::PostOperation:
503 invalidateCache();
504 httpRequest.setOperation(QHttpNetworkRequest::Post);
505 httpRequest.setUploadByteDevice(createUploadByteDevice());
506 break;
507
508 case QNetworkAccessManager::PutOperation:
509 invalidateCache();
510 httpRequest.setOperation(QHttpNetworkRequest::Put);
511 httpRequest.setUploadByteDevice(createUploadByteDevice());
512 break;
513
514 case QNetworkAccessManager::DeleteOperation:
515 invalidateCache();
516 httpRequest.setOperation(QHttpNetworkRequest::Delete);
517 break;
518
519 case QNetworkAccessManager::CustomOperation:
520 invalidateCache(); // for safety reasons, we don't know what the operation does
521 httpRequest.setOperation(QHttpNetworkRequest::Custom);
522 httpRequest.setUploadByteDevice(createUploadByteDevice());
523 httpRequest.setCustomVerb(request().attribute(
524 QNetworkRequest::CustomVerbAttribute).toByteArray());
525 break;
526
527 default:
528 break; // can't happen
529 }
530
531 httpRequest.setUrl(url());
532
533 QList<QByteArray> headers = request().rawHeaderList();
534 if (resumeOffset != 0) {
535 if (headers.contains("Range")) {
536 // Need to adjust resume offset for user specified range
537
538 headers.removeOne("Range");
539
540 // We've already verified that requestRange starts with "bytes=", see canResume.
541 QByteArray requestRange = request().rawHeader("Range").mid(6);
542
543 int index = requestRange.indexOf('-');
544
545 quint64 requestStartOffset = requestRange.left(index).toULongLong();
546 quint64 requestEndOffset = requestRange.mid(index + 1).toULongLong();
547
548 requestRange = "bytes=" + QByteArray::number(resumeOffset + requestStartOffset) +
549 '-' + QByteArray::number(requestEndOffset);
550
551 httpRequest.setHeaderField("Range", requestRange);
552 } else {
553 httpRequest.setHeaderField("Range", "bytes=" + QByteArray::number(resumeOffset) + '-');
554 }
555 }
556 foreach (const QByteArray &header, headers)
557 httpRequest.setHeaderField(header, request().rawHeader(header));
558
559 if (loadedFromCache) {
560 // commented this out since it will be called later anyway
561 // by copyFinished()
562 //QNetworkAccessBackend::finished();
563 return; // no need to send the request! :)
564 }
565
566 if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
567 httpRequest.setPipeliningAllowed(true);
568
569 if (static_cast<QNetworkRequest::LoadControl>
570 (request().attribute(QNetworkRequest::AuthenticationReuseAttribute,
571 QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
572 httpRequest.setWithCredentials(false);
573
574 httpReply = http->sendRequest(httpRequest);
575 httpReply->setParent(this);
576#ifndef QT_NO_OPENSSL
577 if (pendingSslConfiguration)
578 httpReply->setSslConfiguration(*pendingSslConfiguration);
579 if (pendingIgnoreAllSslErrors)
580 httpReply->ignoreSslErrors();
581 httpReply->ignoreSslErrors(pendingIgnoreSslErrorsList);
582 connect(httpReply, SIGNAL(sslErrors(QList<QSslError>)),
583 SLOT(sslErrors(QList<QSslError>)));
584#endif
585
586 connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
587 connect(httpReply, SIGNAL(finished()), SLOT(replyFinished()));
588 connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
589 SLOT(httpError(QNetworkReply::NetworkError,QString)));
590 connect(httpReply, SIGNAL(headerChanged()), SLOT(replyHeaderChanged()));
591 connect(httpReply, SIGNAL(cacheCredentials(QHttpNetworkRequest,QAuthenticator*)),
592 SLOT(httpCacheCredentials(QHttpNetworkRequest,QAuthenticator*)));
593#ifndef QT_NO_NETWORKPROXY
594 connect(httpReply, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
595 SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
596#endif
597 connect(httpReply, SIGNAL(authenticationRequired(const QHttpNetworkRequest,QAuthenticator*)),
598 SLOT(httpAuthenticationRequired(const QHttpNetworkRequest,QAuthenticator*)));
599}
600
601void QNetworkAccessHttpBackend::invalidateCache()
602{
603 QAbstractNetworkCache *nc = networkCache();
604 if (nc)
605 nc->remove(url());
606}
607
608void QNetworkAccessHttpBackend::open()
609{
610 QUrl url = request().url();
611 bool encrypt = url.scheme().toLower() == QLatin1String("https");
612 setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, encrypt);
613
614 // set the port number in the reply if it wasn't set
615 url.setPort(url.port(encrypt ? DefaultHttpsPort : DefaultHttpPort));
616
617 QNetworkProxy *theProxy = 0;
618#ifndef QT_NO_NETWORKPROXY
619 QNetworkProxy transparentProxy, cacheProxy;
620
621 foreach (const QNetworkProxy &p, proxyList()) {
622 // use the first proxy that works
623 // for non-encrypted connections, any transparent or HTTP proxy
624 // for encrypted, only transparent proxies
625 if (!encrypt
626 && (p.capabilities() & QNetworkProxy::CachingCapability)
627 && (p.type() == QNetworkProxy::HttpProxy ||
628 p.type() == QNetworkProxy::HttpCachingProxy)) {
629 cacheProxy = p;
630 transparentProxy = QNetworkProxy::NoProxy;
631 theProxy = &cacheProxy;
632 break;
633 }
634 if (p.isTransparentProxy()) {
635 transparentProxy = p;
636 cacheProxy = QNetworkProxy::NoProxy;
637 theProxy = &transparentProxy;
638 break;
639 }
640 }
641
642 // check if at least one of the proxies
643 if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
644 cacheProxy.type() == QNetworkProxy::DefaultProxy) {
645 // unsuitable proxies
646 if (isSynchronous()) {
647 error(QNetworkReply::ProxyNotFoundError, tr("No suitable proxy found"));
648 finished();
649 } else {
650 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
651 Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProxyNotFoundError),
652 Q_ARG(QString, tr("No suitable proxy found")));
653 QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
654 }
655 return;
656 }
657#endif
658
659 if (isSynchronous()) {
660 // for synchronous requests, we just create a new connection
661 http = new QHttpNetworkConnection(1, url.host(), url.port(), encrypt, this);
662#ifndef QT_NO_NETWORKPROXY
663 http->setTransparentProxy(transparentProxy);
664 http->setCacheProxy(cacheProxy);
665#endif
666 postRequest();
667 processRequestSynchronously();
668 } else {
669 // check if we have an open connection to this host
670 cacheKey = makeCacheKey(this, theProxy);
671 QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this);
672 // the http object is actually a QHttpNetworkConnection
673 http = static_cast<QNetworkAccessCachedHttpConnection *>(cache->requestEntryNow(cacheKey));
674 if (http == 0) {
675 // no entry in cache; create an object
676 // the http object is actually a QHttpNetworkConnection
677 http = new QNetworkAccessCachedHttpConnection(url.host(), url.port(), encrypt);
678
679#ifndef QT_NO_NETWORKPROXY
680 http->setTransparentProxy(transparentProxy);
681 http->setCacheProxy(cacheProxy);
682#endif
683
684 // cache the QHttpNetworkConnection corresponding to this cache key
685 cache->addEntry(cacheKey, static_cast<QNetworkAccessCachedHttpConnection *>(http.data()));
686 }
687 postRequest();
688 }
689}
690
691void QNetworkAccessHttpBackend::closeDownstreamChannel()
692{
693 // this indicates that the user closed the stream while the reply isn't finished yet
694}
695
696void QNetworkAccessHttpBackend::downstreamReadyWrite()
697{
698 readFromHttp();
699 if (httpReply && httpReply->bytesAvailable() == 0 && httpReply->isFinished())
700 replyFinished();
701}
702
703void QNetworkAccessHttpBackend::setDownstreamLimited(bool b)
704{
705 if (httpReply)
706 httpReply->setDownstreamLimited(b);
707}
708
709void QNetworkAccessHttpBackend::replyReadyRead()
710{
711 readFromHttp();
712}
713
714void QNetworkAccessHttpBackend::readFromHttp()
715{
716 if (!httpReply)
717 return;
718
719 // We read possibly more than nextDownstreamBlockSize(), but
720 // this is not a critical thing since it is already in the
721 // memory anyway
722
723 QByteDataBuffer list;
724
725 while (httpReply->bytesAvailable() != 0 && nextDownstreamBlockSize() != 0 && nextDownstreamBlockSize() > list.byteAmount()) {
726 list.append(httpReply->readAny());
727 }
728
729 if (!list.isEmpty())
730 writeDownstreamData(list);
731}
732
733void QNetworkAccessHttpBackend::replyFinished()
734{
735 if (httpReply->bytesAvailable())
736 // we haven't read everything yet. Wait some more.
737 return;
738
739 int statusCode = httpReply->statusCode();
740 if (statusCode >= 400) {
741 // it's an error reply
742 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
743 "Error downloading %1 - server replied: %2"));
744 msg = msg.arg(url().toString(), httpReply->reasonPhrase());
745 error(statusCodeFromHttp(httpReply->statusCode(), httpReply->url()), msg);
746 }
747
748#ifndef QT_NO_OPENSSL
749 // store the SSL configuration now
750 // once we call finished(), we won't have access to httpReply anymore
751 QSslConfiguration sslConfig = httpReply->sslConfiguration();
752 if (pendingSslConfiguration) {
753 *pendingSslConfiguration = sslConfig;
754 } else if (!sslConfig.isNull()) {
755 QT_TRY {
756 pendingSslConfiguration = new QSslConfiguration(sslConfig);
757 } QT_CATCH(...) {
758 qWarning("QNetworkAccess: could not allocate a QSslConfiguration object for a SSL connection.");
759 }
760 }
761#endif
762
763 finished();
764}
765
766void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode)
767{
768 switch (statusCode) {
769 case 301: // Moved Permanently
770 case 302: // Found
771 case 303: // See Other
772 case 307: // Temporary Redirect
773 // What do we do about the caching of the HTML note?
774 // The response to a 303 MUST NOT be cached, while the response to
775 // all of the others is cacheable if the headers indicate it to be
776 QByteArray header = rawHeader("location");
777 QUrl url = QUrl::fromEncoded(header);
778 if (!url.isValid())
779 url = QUrl(QLatin1String(header));
780 redirectionRequested(url);
781 }
782}
783
784void QNetworkAccessHttpBackend::replyHeaderChanged()
785{
786 setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, httpReply->isPipeliningUsed());
787
788 // reconstruct the HTTP header
789 QList<QPair<QByteArray, QByteArray> > headerMap = httpReply->header();
790 QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
791 end = headerMap.constEnd();
792 QByteArray header;
793
794 for (; it != end; ++it) {
795 QByteArray value = rawHeader(it->first);
796 if (!value.isEmpty()) {
797 if (qstricmp(it->first.constData(), "set-cookie") == 0)
798 value += '\n';
799 else
800 value += ", ";
801 }
802 value += it->second;
803 setRawHeader(it->first, value);
804 }
805
806 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpReply->statusCode());
807 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
808
809 // is it a redirection?
810 const int statusCode = httpReply->statusCode();
811 checkForRedirect(statusCode);
812
813 if (statusCode >= 500 && statusCode < 600) {
814 QAbstractNetworkCache *nc = networkCache();
815 if (nc) {
816 QNetworkCacheMetaData metaData = nc->metaData(url());
817 QNetworkHeadersPrivate cacheHeaders;
818 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
819 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
820 it = cacheHeaders.findRawHeader("Cache-Control");
821 bool mustReValidate = false;
822 if (it != cacheHeaders.rawHeaders.constEnd()) {
823 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
824 if (cacheControl.contains("must-revalidate"))
825 mustReValidate = true;
826 }
827 if (!mustReValidate && sendCacheContents(metaData))
828 return;
829 }
830 }
831
832 if (statusCode == 304) {
833#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
834 qDebug() << "Received a 304 from" << url();
835#endif
836 QAbstractNetworkCache *nc = networkCache();
837 if (nc) {
838 QNetworkCacheMetaData oldMetaData = nc->metaData(url());
839 QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
840 if (oldMetaData != metaData)
841 nc->updateMetaData(metaData);
842 if (sendCacheContents(metaData))
843 return;
844 }
845 }
846
847
848 if (statusCode != 304 && statusCode != 303) {
849 if (!isCachingEnabled())
850 setCachingEnabled(true);
851 }
852 metaDataChanged();
853}
854
855void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &,
856 QAuthenticator *auth)
857{
858 authenticationRequired(auth);
859}
860
861void QNetworkAccessHttpBackend::httpCacheCredentials(const QHttpNetworkRequest &,
862 QAuthenticator *auth)
863{
864 cacheCredentials(auth);
865}
866
867void QNetworkAccessHttpBackend::httpError(QNetworkReply::NetworkError errorCode,
868 const QString &errorString)
869{
870#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
871 qDebug() << "http error!" << errorCode << errorString;
872#endif
873 error(errorCode, errorString);
874 finished();
875}
876
877/*
878 A simple web page that can be used to test us: http://www.procata.com/cachetest/
879 */
880bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &metaData)
881{
882 setCachingEnabled(false);
883 if (!metaData.isValid())
884 return false;
885
886 QAbstractNetworkCache *nc = networkCache();
887 Q_ASSERT(nc);
888 QIODevice *contents = nc->data(url());
889 if (!contents) {
890#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
891 qDebug() << "Can not send cache, the contents are 0" << url();
892#endif
893 return false;
894 }
895 contents->setParent(this);
896
897 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
898 int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
899 if (status < 100)
900 status = 200; // fake it
901
902 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
903 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
904 setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
905
906 QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
907 QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
908 end = rawHeaders.constEnd();
909 for ( ; it != end; ++it)
910 setRawHeader(it->first, it->second);
911
912 checkForRedirect(status);
913
914 // This needs to be emitted in the event loop because it can be reached at
915 // the direct code path of qnam.get(...) before the user has a chance
916 // to connect any signals.
917 QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
918 qRegisterMetaType<QIODevice*>("QIODevice*");
919 QMetaObject::invokeMethod(this, "writeDownstreamData", Qt::QueuedConnection, Q_ARG(QIODevice*, contents));
920
921
922#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
923 qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes";
924#endif
925 if (httpReply)
926 disconnect(httpReply, SIGNAL(finished()), this, SLOT(replyFinished()));
927 return true;
928}
929
930void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev)
931{
932 delete dev;
933 finished();
934}
935
936#ifndef QT_NO_OPENSSL
937void QNetworkAccessHttpBackend::ignoreSslErrors()
938{
939 if (httpReply)
940 httpReply->ignoreSslErrors();
941 else
942 pendingIgnoreAllSslErrors = true;
943}
944
945void QNetworkAccessHttpBackend::ignoreSslErrors(const QList<QSslError> &errors)
946{
947 if (httpReply) {
948 httpReply->ignoreSslErrors(errors);
949 } else {
950 // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
951 // is called before QNetworkAccessManager::get() (or post(), etc.)
952 pendingIgnoreSslErrorsList = errors;
953 }
954}
955
956void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const
957{
958 if (httpReply)
959 config = httpReply->sslConfiguration();
960 else if (pendingSslConfiguration)
961 config = *pendingSslConfiguration;
962}
963
964void QNetworkAccessHttpBackend::setSslConfiguration(const QSslConfiguration &newconfig)
965{
966 if (httpReply)
967 httpReply->setSslConfiguration(newconfig);
968 else if (pendingSslConfiguration)
969 *pendingSslConfiguration = newconfig;
970 else
971 pendingSslConfiguration = new QSslConfiguration(newconfig);
972}
973#endif
974
975QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
976{
977 QNetworkCacheMetaData metaData = oldMetaData;
978
979 QNetworkHeadersPrivate cacheHeaders;
980 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
981 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
982
983 QList<QByteArray> newHeaders = rawHeaderList();
984 foreach (QByteArray header, newHeaders) {
985 QByteArray originalHeader = header;
986 header = header.toLower();
987 bool hop_by_hop =
988 (header == "connection"
989 || header == "keep-alive"
990 || header == "proxy-authenticate"
991 || header == "proxy-authorization"
992 || header == "te"
993 || header == "trailers"
994 || header == "transfer-encoding"
995 || header == "upgrade");
996 if (hop_by_hop)
997 continue;
998
999 // for 4.6.0, we were planning to not store the date header in the
1000 // cached resource; through that we planned to reduce the number
1001 // of writes to disk when using a QNetworkDiskCache (i.e. don't
1002 // write to disk when only the date changes).
1003 // However, without the date we cannot calculate the age of the page
1004 // anymore.
1005 //if (header == "date")
1006 //continue;
1007
1008 // Don't store Warning 1xx headers
1009 if (header == "warning") {
1010 QByteArray v = rawHeader(header);
1011 if (v.length() == 3
1012 && v[0] == '1'
1013 && v[1] >= '0' && v[1] <= '9'
1014 && v[2] >= '0' && v[2] <= '9')
1015 continue;
1016 }
1017
1018 it = cacheHeaders.findRawHeader(header);
1019 if (it != cacheHeaders.rawHeaders.constEnd()) {
1020 // Match the behavior of Firefox and assume Cache-Control: "no-transform"
1021 if (header == "content-encoding"
1022 || header == "content-range"
1023 || header == "content-type")
1024 continue;
1025
1026 // For MS servers that send "Content-Length: 0" on 304 responses
1027 // ignore this too
1028 if (header == "content-length")
1029 continue;
1030 }
1031
1032#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
1033 QByteArray n = rawHeader(header);
1034 QByteArray o;
1035 if (it != cacheHeaders.rawHeaders.constEnd())
1036 o = (*it).second;
1037 if (n != o && header != "date") {
1038 qDebug() << "replacing" << header;
1039 qDebug() << "new" << n;
1040 qDebug() << "old" << o;
1041 }
1042#endif
1043 cacheHeaders.setRawHeader(originalHeader, rawHeader(header));
1044 }
1045 metaData.setRawHeaders(cacheHeaders.rawHeaders);
1046
1047 bool checkExpired = true;
1048
1049 QHash<QByteArray, QByteArray> cacheControl;
1050 it = cacheHeaders.findRawHeader("Cache-Control");
1051 if (it != cacheHeaders.rawHeaders.constEnd()) {
1052 cacheControl = parseHttpOptionHeader(it->second);
1053 QByteArray maxAge = cacheControl.value("max-age");
1054 if (!maxAge.isEmpty()) {
1055 checkExpired = false;
1056 QDateTime dt = QDateTime::currentDateTime();
1057 dt = dt.addSecs(maxAge.toInt());
1058 metaData.setExpirationDate(dt);
1059 }
1060 }
1061 if (checkExpired) {
1062 it = cacheHeaders.findRawHeader("expires");
1063 if (it != cacheHeaders.rawHeaders.constEnd()) {
1064 QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
1065 metaData.setExpirationDate(expiredDateTime);
1066 }
1067 }
1068
1069 it = cacheHeaders.findRawHeader("last-modified");
1070 if (it != cacheHeaders.rawHeaders.constEnd())
1071 metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
1072
1073 bool canDiskCache;
1074 // only cache GET replies by default, all other replies (POST, PUT, DELETE)
1075 // are not cacheable by default (according to RFC 2616 section 9)
1076 if (httpReply->request().operation() == QHttpNetworkRequest::Get) {
1077
1078 canDiskCache = true;
1079 // 14.32
1080 // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client
1081 // had sent "Cache-Control: no-cache".
1082 it = cacheHeaders.findRawHeader("pragma");
1083 if (it != cacheHeaders.rawHeaders.constEnd()
1084 && it->second == "no-cache")
1085 canDiskCache = false;
1086
1087 // HTTP/1.1. Check the Cache-Control header
1088 if (cacheControl.contains("no-cache"))
1089 canDiskCache = false;
1090 else if (cacheControl.contains("no-store"))
1091 canDiskCache = false;
1092
1093 // responses to POST might be cacheable
1094 } else if (httpReply->request().operation() == QHttpNetworkRequest::Post) {
1095
1096 canDiskCache = false;
1097 // some pages contain "expires:" and "cache-control: no-cache" field,
1098 // so we only might cache POST requests if we get "cache-control: max-age ..."
1099 if (cacheControl.contains("max-age"))
1100 canDiskCache = true;
1101
1102 // responses to PUT and DELETE are not cacheable
1103 } else {
1104 canDiskCache = false;
1105 }
1106
1107 metaData.setSaveToDisk(canDiskCache);
1108 int statusCode = httpReply->statusCode();
1109 QNetworkCacheMetaData::AttributesMap attributes;
1110 if (statusCode != 304) {
1111 // update the status code
1112 attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1113 attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
1114 } else {
1115 // this is a redirection, keep the attributes intact
1116 attributes = oldMetaData.attributes();
1117 }
1118 metaData.setAttributes(attributes);
1119 return metaData;
1120}
1121
1122bool QNetworkAccessHttpBackend::canResume() const
1123{
1124 // Only GET operation supports resuming.
1125 if (operation() != QNetworkAccessManager::GetOperation)
1126 return false;
1127
1128 // Can only resume if server/resource supports Range header.
1129 if (httpReply->headerField("Accept-Ranges", "none") == "none")
1130 return false;
1131
1132 // We only support resuming for byte ranges.
1133 if (request().hasRawHeader("Range")) {
1134 QByteArray range = request().rawHeader("Range");
1135 if (!range.startsWith("bytes="))
1136 return false;
1137 }
1138
1139 return true;
1140}
1141
1142void QNetworkAccessHttpBackend::setResumeOffset(quint64 offset)
1143{
1144 resumeOffset = offset;
1145}
1146
1147bool QNetworkAccessHttpBackend::processRequestSynchronously()
1148{
1149 QHttpNetworkConnectionChannel *channel = &http->channels()[0];
1150
1151 // Disconnect all socket signals. They will only confuse us when using waitFor*
1152 QObject::disconnect(channel->socket, 0, 0, 0);
1153
1154 qint64 timeout = 20*1000; // 20 sec
1155 QElapsedTimer timeoutTimer;
1156
1157 bool waitResult = channel->socket->waitForConnected(timeout);
1158 timeoutTimer.start();
1159
1160 if (!waitResult || channel->socket->state() != QAbstractSocket::ConnectedState) {
1161 error(QNetworkReply::UnknownNetworkError, QLatin1String("could not connect"));
1162 return false;
1163 }
1164 channel->_q_connected(); // this will send the request (via sendRequest())
1165
1166#ifndef QT_NO_OPENSSL
1167 if (http->isSsl()) {
1168 qint64 remainingTimeEncrypted = timeout - timeoutTimer.elapsed();
1169 if (!static_cast<QSslSocket *>(channel->socket)->waitForEncrypted(remainingTimeEncrypted)) {
1170 error(QNetworkReply::SslHandshakeFailedError,
1171 QLatin1String("could not encrypt or timeout while encrypting"));
1172 return false;
1173 }
1174 channel->_q_encrypted();
1175 }
1176#endif
1177
1178 // if we get a 401 or 407, we might need to send the request twice, see below
1179 bool authenticating = false;
1180
1181 do {
1182 channel->sendRequest();
1183
1184 qint64 remainingTimeBytesWritten;
1185 while(channel->socket->bytesToWrite() > 0 ||
1186 channel->state == QHttpNetworkConnectionChannel::WritingState) {
1187 remainingTimeBytesWritten = timeout - timeoutTimer.elapsed();
1188 channel->sendRequest(); // triggers channel->socket->write()
1189 if (!channel->socket->waitForBytesWritten(remainingTimeBytesWritten)) {
1190 error(QNetworkReply::TimeoutError,
1191 QLatin1String("could not write bytes to socket or timeout while writing"));
1192 return false;
1193 }
1194 }
1195
1196 qint64 remainingTimeBytesRead = timeout - timeoutTimer.elapsed();
1197 // Loop for at most remainingTime until either the socket disconnects
1198 // or the reply is finished
1199 do {
1200 waitResult = channel->socket->waitForReadyRead(remainingTimeBytesRead);
1201 remainingTimeBytesRead = timeout - timeoutTimer.elapsed();
1202 if (!waitResult || remainingTimeBytesRead <= 0
1203 || channel->socket->state() != QAbstractSocket::ConnectedState) {
1204 error(QNetworkReply::TimeoutError,
1205 QLatin1String("could not read from socket or timeout while reading"));
1206 return false;
1207 }
1208
1209 if (channel->socket->bytesAvailable())
1210 channel->_q_readyRead();
1211
1212 if (!httpReply)
1213 return false; // we got a 401 or 407 and cannot handle it (it might happen that
1214 // disconnectFromHttp() was called, in that case the reply is zero)
1215 // ### I am quite sure this does not work for NTLM
1216 // ### how about uploading to an auth / proxyAuth site?
1217
1218 authenticating = (httpReply->statusCode() == 401 || httpReply->statusCode() == 407);
1219
1220 if (httpReply->isFinished())
1221 break;
1222 } while (remainingTimeBytesRead > 0);
1223 } while (authenticating);
1224
1225 return true;
1226}
1227
1228QT_END_NAMESPACE
1229
1230#endif // QT_NO_HTTP
Note: See TracBrowser for help on using the repository browser.