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

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

trunk: Merged in qt 4.6.1 sources.

File size: 36.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 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 (equal != -1) {
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 it = cacheHeaders.findRawHeader("Cache-Control");
397 if (it != cacheHeaders.rawHeaders.constEnd()) {
398 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
399 if (cacheControl.contains("must-revalidate"))
400 return;
401 }
402
403 /*
404 * age_value
405 * is the value of Age: header received by the cache with
406 * this response.
407 * date_value
408 * is the value of the origin server's Date: header
409 * request_time
410 * is the (local) time when the cache made the request
411 * that resulted in this cached response
412 * response_time
413 * is the (local) time when the cache received the
414 * response
415 * now
416 * is the current (local) time
417 */
418 QDateTime currentDateTime = QDateTime::currentDateTime();
419 int age_value = 0;
420 it = cacheHeaders.findRawHeader("age");
421 if (it != cacheHeaders.rawHeaders.constEnd())
422 age_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t();
423
424 int date_value = 0;
425 it = cacheHeaders.findRawHeader("date");
426 if (it != cacheHeaders.rawHeaders.constEnd())
427 date_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t();
428
429 int now = currentDateTime.toUTC().toTime_t();
430 int request_time = now;
431 int response_time = now;
432
433 int apparent_age = qMax(0, response_time - date_value);
434 int corrected_received_age = qMax(apparent_age, age_value);
435 int response_delay = response_time - request_time;
436 int corrected_initial_age = corrected_received_age + response_delay;
437 int resident_time = now - response_time;
438 int current_age = corrected_initial_age + resident_time;
439
440 // RFC 2616 13.2.4 Expiration Calculations
441 QDateTime expirationDate = metaData.expirationDate();
442 if (!expirationDate.isValid()) {
443 if (lastModified.isValid()) {
444 int diff = currentDateTime.secsTo(lastModified);
445 expirationDate = lastModified;
446 expirationDate.addSecs(diff / 10);
447 if (httpRequest.headerField("Warning").isEmpty()) {
448 QDateTime dt;
449 dt.setTime_t(current_age);
450 if (dt.daysTo(currentDateTime) > 1)
451 httpRequest.setHeaderField("Warning", "113");
452 }
453 }
454 }
455
456 int freshness_lifetime = currentDateTime.secsTo(expirationDate);
457 bool response_is_fresh = (freshness_lifetime > current_age);
458
459 if (!response_is_fresh && CacheLoadControlAttribute == QNetworkRequest::PreferNetwork)
460 return;
461
462 loadedFromCache = true;
463#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
464 qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
465#endif
466 if (!sendCacheContents(metaData))
467 loadedFromCache = false;
468}
469
470void QNetworkAccessHttpBackend::postRequest()
471{
472 bool loadedFromCache = false;
473 QHttpNetworkRequest httpRequest;
474 switch (operation()) {
475 case QNetworkAccessManager::GetOperation:
476 httpRequest.setOperation(QHttpNetworkRequest::Get);
477 validateCache(httpRequest, loadedFromCache);
478 break;
479
480 case QNetworkAccessManager::HeadOperation:
481 httpRequest.setOperation(QHttpNetworkRequest::Head);
482 validateCache(httpRequest, loadedFromCache);
483 break;
484
485 case QNetworkAccessManager::PostOperation:
486 invalidateCache();
487 httpRequest.setOperation(QHttpNetworkRequest::Post);
488 httpRequest.setUploadByteDevice(createUploadByteDevice());
489 break;
490
491 case QNetworkAccessManager::PutOperation:
492 invalidateCache();
493 httpRequest.setOperation(QHttpNetworkRequest::Put);
494 httpRequest.setUploadByteDevice(createUploadByteDevice());
495 break;
496
497 case QNetworkAccessManager::DeleteOperation:
498 invalidateCache();
499 httpRequest.setOperation(QHttpNetworkRequest::Delete);
500 break;
501
502 default:
503 break; // can't happen
504 }
505
506 httpRequest.setUrl(url());
507
508 QList<QByteArray> headers = request().rawHeaderList();
509 foreach (const QByteArray &header, headers)
510 httpRequest.setHeaderField(header, request().rawHeader(header));
511
512 if (loadedFromCache) {
513 // commented this out since it will be called later anyway
514 // by copyFinished()
515 //QNetworkAccessBackend::finished();
516 return; // no need to send the request! :)
517 }
518
519 if (request().attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
520 httpRequest.setPipeliningAllowed(true);
521
522 httpReply = http->sendRequest(httpRequest);
523 httpReply->setParent(this);
524#ifndef QT_NO_OPENSSL
525 if (pendingSslConfiguration)
526 httpReply->setSslConfiguration(*pendingSslConfiguration);
527 if (pendingIgnoreAllSslErrors)
528 httpReply->ignoreSslErrors();
529 httpReply->ignoreSslErrors(pendingIgnoreSslErrorsList);
530#endif
531
532 connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
533 connect(httpReply, SIGNAL(finished()), SLOT(replyFinished()));
534 connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
535 SLOT(httpError(QNetworkReply::NetworkError,QString)));
536 connect(httpReply, SIGNAL(headerChanged()), SLOT(replyHeaderChanged()));
537}
538
539void QNetworkAccessHttpBackend::invalidateCache()
540{
541 QAbstractNetworkCache *nc = networkCache();
542 if (nc)
543 nc->remove(url());
544}
545
546void QNetworkAccessHttpBackend::open()
547{
548 QUrl url = request().url();
549 bool encrypt = url.scheme().toLower() == QLatin1String("https");
550 setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, encrypt);
551
552 // set the port number in the reply if it wasn't set
553 url.setPort(url.port(encrypt ? DefaultHttpsPort : DefaultHttpPort));
554
555 QNetworkProxy *theProxy = 0;
556#ifndef QT_NO_NETWORKPROXY
557 QNetworkProxy transparentProxy, cacheProxy;
558
559 foreach (const QNetworkProxy &p, proxyList()) {
560 // use the first proxy that works
561 // for non-encrypted connections, any transparent or HTTP proxy
562 // for encrypted, only transparent proxies
563 if (!encrypt
564 && (p.capabilities() & QNetworkProxy::CachingCapability)
565 && (p.type() == QNetworkProxy::HttpProxy ||
566 p.type() == QNetworkProxy::HttpCachingProxy)) {
567 cacheProxy = p;
568 transparentProxy = QNetworkProxy::NoProxy;
569 theProxy = &cacheProxy;
570 break;
571 }
572 if (p.isTransparentProxy()) {
573 transparentProxy = p;
574 cacheProxy = QNetworkProxy::NoProxy;
575 theProxy = &transparentProxy;
576 break;
577 }
578 }
579
580 // check if at least one of the proxies
581 if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
582 cacheProxy.type() == QNetworkProxy::DefaultProxy) {
583 // unsuitable proxies
584 error(QNetworkReply::ProxyNotFoundError,
585 tr("No suitable proxy found"));
586 finished();
587 return;
588 }
589#endif
590
591 // check if we have an open connection to this host
592 cacheKey = makeCacheKey(this, theProxy);
593 QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getObjectCache(this);
594 // the http object is actually a QHttpNetworkConnection
595 http = static_cast<QNetworkAccessCachedHttpConnection *>(cache->requestEntryNow(cacheKey));
596 if (http == 0) {
597 // no entry in cache; create an object
598 // the http object is actually a QHttpNetworkConnection
599 http = new QNetworkAccessCachedHttpConnection(url.host(), url.port(), encrypt);
600
601#ifndef QT_NO_NETWORKPROXY
602 http->setTransparentProxy(transparentProxy);
603 http->setCacheProxy(cacheProxy);
604#endif
605
606 // cache the QHttpNetworkConnection corresponding to this cache key
607 cache->addEntry(cacheKey, http);
608 }
609
610 setupConnection();
611 postRequest();
612}
613
614void QNetworkAccessHttpBackend::closeDownstreamChannel()
615{
616 // this indicates that the user closed the stream while the reply isn't finished yet
617}
618
619bool QNetworkAccessHttpBackend::waitForDownstreamReadyRead(int msecs)
620{
621 Q_ASSERT(http);
622
623 if (httpReply->bytesAvailable()) {
624 readFromHttp();
625 return true;
626 }
627
628 if (msecs == 0) {
629 // no bytes available in the socket and no waiting
630 return false;
631 }
632
633 // ### FIXME
634 qCritical("QNetworkAccess: HTTP backend does not support waitForReadyRead()");
635 return false;
636}
637
638
639void QNetworkAccessHttpBackend::downstreamReadyWrite()
640{
641 readFromHttp();
642 if (httpReply && httpReply->bytesAvailable() == 0 && httpReply->isFinished())
643 replyFinished();
644}
645
646void QNetworkAccessHttpBackend::replyReadyRead()
647{
648 readFromHttp();
649}
650
651void QNetworkAccessHttpBackend::readFromHttp()
652{
653 if (!httpReply)
654 return;
655
656 // We read possibly more than nextDownstreamBlockSize(), but
657 // this is not a critical thing since it is already in the
658 // memory anyway
659
660 QByteDataBuffer list;
661
662 while (httpReply->bytesAvailable() != 0 && nextDownstreamBlockSize() != 0 && nextDownstreamBlockSize() > list.byteAmount()) {
663 QByteArray data = httpReply->readAny();
664 list.append(data);
665 }
666
667 if (!list.isEmpty())
668 writeDownstreamData(list);
669}
670
671void QNetworkAccessHttpBackend::replyFinished()
672{
673 if (httpReply->bytesAvailable())
674 // we haven't read everything yet. Wait some more.
675 return;
676
677 int statusCode = httpReply->statusCode();
678 if (statusCode >= 400) {
679 // it's an error reply
680 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
681 "Error downloading %1 - server replied: %2"));
682 msg = msg.arg(url().toString(), httpReply->reasonPhrase());
683 error(statusCodeFromHttp(httpReply->statusCode(), httpReply->url()), msg);
684 }
685
686#ifndef QT_NO_OPENSSL
687 // store the SSL configuration now
688 // once we call finished(), we won't have access to httpReply anymore
689 QSslConfiguration sslConfig = httpReply->sslConfiguration();
690 if (pendingSslConfiguration) {
691 *pendingSslConfiguration = sslConfig;
692 } else if (!sslConfig.isNull()) {
693 QT_TRY {
694 pendingSslConfiguration = new QSslConfiguration(sslConfig);
695 } QT_CATCH(...) {
696 qWarning("QNetworkAccess: could not allocate a QSslConfiguration object for a SSL connection.");
697 }
698 }
699#endif
700
701 finished();
702}
703
704void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode)
705{
706 switch (statusCode) {
707 case 301: // Moved Permanently
708 case 302: // Found
709 case 303: // See Other
710 case 307: // Temporary Redirect
711 // What do we do about the caching of the HTML note?
712 // The response to a 303 MUST NOT be cached, while the response to
713 // all of the others is cacheable if the headers indicate it to be
714 QByteArray header = rawHeader("location");
715 QUrl url = QUrl::fromEncoded(header);
716 if (!url.isValid())
717 url = QUrl(QLatin1String(header));
718 redirectionRequested(url);
719 }
720}
721
722void QNetworkAccessHttpBackend::replyHeaderChanged()
723{
724 setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, httpReply->isPipeliningUsed());
725
726 // reconstruct the HTTP header
727 QList<QPair<QByteArray, QByteArray> > headerMap = httpReply->header();
728 QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
729 end = headerMap.constEnd();
730 QByteArray header;
731
732 for (; it != end; ++it) {
733 QByteArray value = rawHeader(it->first);
734 if (!value.isEmpty()) {
735 if (qstricmp(it->first.constData(), "set-cookie") == 0)
736 value += "\n";
737 else
738 value += ", ";
739 }
740 value += it->second;
741 setRawHeader(it->first, value);
742 }
743
744 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpReply->statusCode());
745 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
746
747 // is it a redirection?
748 const int statusCode = httpReply->statusCode();
749 checkForRedirect(statusCode);
750
751 if (statusCode >= 500 && statusCode < 600) {
752 QAbstractNetworkCache *nc = networkCache();
753 if (nc) {
754 QNetworkCacheMetaData metaData = nc->metaData(url());
755 QNetworkHeadersPrivate cacheHeaders;
756 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
757 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
758 it = cacheHeaders.findRawHeader("Cache-Control");
759 bool mustReValidate = false;
760 if (it != cacheHeaders.rawHeaders.constEnd()) {
761 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
762 if (cacheControl.contains("must-revalidate"))
763 mustReValidate = true;
764 }
765 if (!mustReValidate && sendCacheContents(metaData))
766 return;
767 }
768 }
769
770 if (statusCode == 304) {
771#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
772 qDebug() << "Received a 304 from" << url();
773#endif
774 QAbstractNetworkCache *nc = networkCache();
775 if (nc) {
776 QNetworkCacheMetaData oldMetaData = nc->metaData(url());
777 QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
778 if (oldMetaData != metaData)
779 nc->updateMetaData(metaData);
780 if (sendCacheContents(metaData))
781 return;
782 }
783 }
784
785
786 if (statusCode != 304 && statusCode != 303) {
787 if (!isCachingEnabled())
788 setCachingEnabled(true);
789 }
790 metaDataChanged();
791}
792
793void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &,
794 QAuthenticator *auth)
795{
796 authenticationRequired(auth);
797}
798
799void QNetworkAccessHttpBackend::httpError(QNetworkReply::NetworkError errorCode,
800 const QString &errorString)
801{
802#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
803 qDebug() << "http error!" << errorCode << errorString;
804#endif
805#if 0
806 static const QNetworkReply::NetworkError conversionTable[] = {
807 QNetworkReply::ConnectionRefusedError,
808 QNetworkReply::RemoteHostClosedError,
809 QNetworkReply::HostNotFoundError,
810 QNetworkReply::UnknownNetworkError, // SocketAccessError
811 QNetworkReply::UnknownNetworkError, // SocketResourceError
812 QNetworkReply::TimeoutError, // SocketTimeoutError
813 QNetworkReply::UnknownNetworkError, // DatagramTooLargeError
814 QNetworkReply::UnknownNetworkError, // NetworkError
815 QNetworkReply::UnknownNetworkError, // AddressInUseError
816 QNetworkReply::UnknownNetworkError, // SocketAddressNotAvailableError
817 QNetworkReply::UnknownNetworkError, // UnsupportedSocketOperationError
818 QNetworkReply::UnknownNetworkError, // UnfinishedSocketOperationError
819 QNetworkReply::ProxyAuthenticationRequiredError
820 };
821 QNetworkReply::NetworkError code;
822 if (int(errorCode) >= 0 &&
823 uint(errorCode) < (sizeof conversionTable / sizeof conversionTable[0]))
824 code = conversionTable[errorCode];
825 else
826 code = QNetworkReply::UnknownNetworkError;
827#endif
828 error(errorCode, errorString);
829 finished();
830}
831
832/*
833 A simple web page that can be used to test us: http://www.procata.com/cachetest/
834 */
835bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &metaData)
836{
837 setCachingEnabled(false);
838 if (!metaData.isValid())
839 return false;
840
841 QAbstractNetworkCache *nc = networkCache();
842 Q_ASSERT(nc);
843 QIODevice *contents = nc->data(url());
844 if (!contents) {
845#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
846 qDebug() << "Can not send cache, the contents are 0" << url();
847#endif
848 return false;
849 }
850 contents->setParent(this);
851
852 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
853 int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
854 if (status < 100)
855 status = 200; // fake it
856
857 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
858 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
859 setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
860
861 QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
862 QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
863 end = rawHeaders.constEnd();
864 for ( ; it != end; ++it)
865 setRawHeader(it->first, it->second);
866
867 checkForRedirect(status);
868
869 emit metaDataChanged();
870
871 // invoke this asynchronously, else Arora/QtDemoBrowser don't like cached downloads
872 // see task 250221 / 251801
873 qRegisterMetaType<QIODevice*>("QIODevice*");
874 QMetaObject::invokeMethod(this, "writeDownstreamData", Qt::QueuedConnection, Q_ARG(QIODevice*, contents));
875
876
877#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
878 qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes";
879#endif
880 if (httpReply)
881 disconnect(httpReply, SIGNAL(finished()), this, SLOT(replyFinished()));
882 return true;
883}
884
885void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev)
886{
887 delete dev;
888 finished();
889}
890
891#ifndef QT_NO_OPENSSL
892void QNetworkAccessHttpBackend::ignoreSslErrors()
893{
894 if (httpReply)
895 httpReply->ignoreSslErrors();
896 else
897 pendingIgnoreAllSslErrors = true;
898}
899
900void QNetworkAccessHttpBackend::ignoreSslErrors(const QList<QSslError> &errors)
901{
902 if (httpReply) {
903 httpReply->ignoreSslErrors(errors);
904 } else {
905 // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
906 // is called before QNetworkAccessManager::get() (or post(), etc.)
907 pendingIgnoreSslErrorsList = errors;
908 }
909}
910
911void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const
912{
913 if (httpReply)
914 config = httpReply->sslConfiguration();
915 else if (pendingSslConfiguration)
916 config = *pendingSslConfiguration;
917}
918
919void QNetworkAccessHttpBackend::setSslConfiguration(const QSslConfiguration &newconfig)
920{
921 if (httpReply)
922 httpReply->setSslConfiguration(newconfig);
923 else if (pendingSslConfiguration)
924 *pendingSslConfiguration = newconfig;
925 else
926 pendingSslConfiguration = new QSslConfiguration(newconfig);
927}
928#endif
929
930QNetworkCacheMetaData QNetworkAccessHttpBackend::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
931{
932 QNetworkCacheMetaData metaData = oldMetaData;
933
934 QNetworkHeadersPrivate cacheHeaders;
935 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
936 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
937
938 QList<QByteArray> newHeaders = rawHeaderList();
939 foreach (QByteArray header, newHeaders) {
940 QByteArray originalHeader = header;
941 header = header.toLower();
942 bool hop_by_hop =
943 (header == "connection"
944 || header == "keep-alive"
945 || header == "proxy-authenticate"
946 || header == "proxy-authorization"
947 || header == "te"
948 || header == "trailers"
949 || header == "transfer-encoding"
950 || header == "upgrade");
951 if (hop_by_hop)
952 continue;
953
954 // for 4.6.0, we were planning to not store the date header in the
955 // cached resource; through that we planned to reduce the number
956 // of writes to disk when using a QNetworkDiskCache (i.e. don't
957 // write to disk when only the date changes).
958 // However, without the date we cannot calculate the age of the page
959 // anymore. Consider a proper fix of that problem for 4.6.1.
960 //if (header == "date")
961 //continue;
962
963 // Don't store Warning 1xx headers
964 if (header == "warning") {
965 QByteArray v = rawHeader(header);
966 if (v.length() == 3
967 && v[0] == '1'
968 && v[1] >= '0' && v[1] <= '9'
969 && v[2] >= '0' && v[2] <= '9')
970 continue;
971 }
972
973 it = cacheHeaders.findRawHeader(header);
974 if (it != cacheHeaders.rawHeaders.constEnd()) {
975 // Match the behavior of Firefox and assume Cache-Control: "no-transform"
976 if (header == "content-encoding"
977 || header == "content-range"
978 || header == "content-type")
979 continue;
980
981 // For MS servers that send "Content-Length: 0" on 304 responses
982 // ignore this too
983 if (header == "content-length")
984 continue;
985 }
986
987#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
988 QByteArray n = rawHeader(header);
989 QByteArray o;
990 if (it != cacheHeaders.rawHeaders.constEnd())
991 o = (*it).second;
992 if (n != o && header != "date") {
993 qDebug() << "replacing" << header;
994 qDebug() << "new" << n;
995 qDebug() << "old" << o;
996 }
997#endif
998 cacheHeaders.setRawHeader(originalHeader, rawHeader(header));
999 }
1000 metaData.setRawHeaders(cacheHeaders.rawHeaders);
1001
1002 bool checkExpired = true;
1003
1004 QHash<QByteArray, QByteArray> cacheControl;
1005 it = cacheHeaders.findRawHeader("Cache-Control");
1006 if (it != cacheHeaders.rawHeaders.constEnd()) {
1007 cacheControl = parseHttpOptionHeader(it->second);
1008 QByteArray maxAge = cacheControl.value("max-age");
1009 if (!maxAge.isEmpty()) {
1010 checkExpired = false;
1011 QDateTime dt = QDateTime::currentDateTime();
1012 dt = dt.addSecs(maxAge.toInt());
1013 metaData.setExpirationDate(dt);
1014 }
1015 }
1016 if (checkExpired) {
1017 it = cacheHeaders.findRawHeader("expires");
1018 if (it != cacheHeaders.rawHeaders.constEnd()) {
1019 QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
1020 metaData.setExpirationDate(expiredDateTime);
1021 }
1022 }
1023
1024 it = cacheHeaders.findRawHeader("last-modified");
1025 if (it != cacheHeaders.rawHeaders.constEnd())
1026 metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
1027
1028 bool canDiskCache;
1029 // only cache GET replies by default, all other replies (POST, PUT, DELETE)
1030 // are not cacheable by default (according to RFC 2616 section 9)
1031 if (httpReply->request().operation() == QHttpNetworkRequest::Get) {
1032
1033 canDiskCache = true;
1034 // 14.32
1035 // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client
1036 // had sent "Cache-Control: no-cache".
1037 it = cacheHeaders.findRawHeader("pragma");
1038 if (it != cacheHeaders.rawHeaders.constEnd()
1039 && it->second == "no-cache")
1040 canDiskCache = false;
1041
1042 // HTTP/1.1. Check the Cache-Control header
1043 if (cacheControl.contains("no-cache"))
1044 canDiskCache = false;
1045 else if (cacheControl.contains("no-store"))
1046 canDiskCache = false;
1047
1048 // responses to POST might be cacheable
1049 } else if (httpReply->request().operation() == QHttpNetworkRequest::Post) {
1050
1051 canDiskCache = false;
1052 // some pages contain "expires:" and "cache-control: no-cache" field,
1053 // so we only might cache POST requests if we get "cache-control: max-age ..."
1054 if (cacheControl.contains("max-age"))
1055 canDiskCache = true;
1056
1057 // responses to PUT and DELETE are not cacheable
1058 } else {
1059 canDiskCache = false;
1060 }
1061
1062 metaData.setSaveToDisk(canDiskCache);
1063 int statusCode = httpReply->statusCode();
1064 QNetworkCacheMetaData::AttributesMap attributes;
1065 if (statusCode != 304) {
1066 // update the status code
1067 attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1068 attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
1069 } else {
1070 // this is a redirection, keep the attributes intact
1071 attributes = oldMetaData.attributes();
1072 }
1073 metaData.setAttributes(attributes);
1074 return metaData;
1075}
1076
1077QT_END_NAMESPACE
1078
1079#endif // QT_NO_HTTP
Note: See TracBrowser for help on using the repository browser.