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

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

trunk: Merged in qt 4.6.2 sources.

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