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

Last change on this file since 157 was 2, checked in by Dmitry A. Kuminov, 17 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 35.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** 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 are unsure which license is appropriate for your use, please
37** contact the sales department 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 break;
216
217 default:
218 // no, we can't handle this request
219 return 0;
220 }
221
222 QUrl url = request.url();
223 QString scheme = url.scheme().toLower();
224 if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
225 return new QNetworkAccessHttpBackend;
226
227 return 0;
228}
229
230static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url)
231{
232 QNetworkReply::NetworkError code;
233 // we've got an error
234 switch (httpStatusCode) {
235 case 401: // Authorization required
236 code = QNetworkReply::AuthenticationRequiredError;
237 break;
238
239 case 403: // Access denied
240 code = QNetworkReply::ContentOperationNotPermittedError;
241 break;
242
243 case 404: // Not Found
244 code = QNetworkReply::ContentNotFoundError;
245 break;
246
247 case 407:
248 code = QNetworkReply::ProxyAuthenticationRequiredError;
249 break;
250
251 default:
252 if (httpStatusCode > 500) {
253 // some kind of server error
254 code = QNetworkReply::ProtocolUnknownError;
255 } else if (httpStatusCode >= 400) {
256 // content error we did not handle above
257 code = QNetworkReply::UnknownContentError;
258 } else {
259 qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
260 httpStatusCode, qPrintable(url.toString()));
261 code = QNetworkReply::ProtocolFailure;
262 }
263 }
264
265 return code;
266}
267
268class QNetworkAccessHttpBackendCache: public QHttpNetworkConnection,
269 public QNetworkAccessCache::CacheableObject
270{
271 // Q_OBJECT
272public:
273 QNetworkAccessHttpBackendCache(const QString &hostName, quint16 port, bool encrypt)
274 : QHttpNetworkConnection(hostName, port, encrypt)
275 {
276 setExpires(true);
277 setShareable(true);
278 }
279
280 virtual void dispose()
281 {
282#if 0 // sample code; do this right with the API
283 Q_ASSERT(!isWorking());
284#endif
285 delete this;
286 }
287};
288
289class QNetworkAccessHttpBackendIODevice: public QIODevice
290{
291 // Q_OBJECT
292public:
293 bool eof;
294 QNetworkAccessHttpBackendIODevice(QNetworkAccessHttpBackend *parent)
295 : QIODevice(parent), eof(false)
296 {
297 setOpenMode(ReadOnly);
298 }
299 bool isSequential() const { return true; }
300 qint64 bytesAvailable() const
301 { return static_cast<QNetworkAccessHttpBackend *>(parent())->upstreamBytesAvailable(); }
302
303protected:
304 virtual qint64 readData(char *buffer, qint64 maxlen)
305 {
306 qint64 ret = static_cast<QNetworkAccessHttpBackend *>(parent())->deviceReadData(buffer, maxlen);
307 if (!ret && eof)
308 return -1;
309 return ret;
310 }
311
312 virtual qint64 writeData(const char *, qint64)
313 {
314 return -1; // cannot write
315 }
316
317 friend class QNetworkAccessHttpBackend;
318};
319
320QNetworkAccessHttpBackend::QNetworkAccessHttpBackend()
321 : QNetworkAccessBackend(), httpReply(0), http(0), uploadDevice(0)
322#ifndef QT_NO_OPENSSL
323 , pendingSslConfiguration(0), pendingIgnoreSslErrors(false)
324#endif
325{
326}
327
328QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend()
329{
330 if (http)
331 disconnectFromHttp();
332#ifndef QT_NO_OPENSSL
333 delete pendingSslConfiguration;
334#endif
335}
336
337void QNetworkAccessHttpBackend::disconnectFromHttp()
338{
339 if (http) {
340 disconnect(http, 0, this, 0);
341 QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getCache(this);
342 cache->releaseEntry(cacheKey);
343 }
344
345 if (httpReply)
346 disconnect(httpReply, 0, this, 0);
347
348 http = 0;
349 httpReply = 0;
350 cacheKey.clear();
351}
352
353void QNetworkAccessHttpBackend::finished()
354{
355 if (http)
356 disconnectFromHttp();
357 // call parent
358 QNetworkAccessBackend::finished();
359}
360
361void QNetworkAccessHttpBackend::setupConnection()
362{
363#ifndef QT_NO_NETWORKPROXY
364 connect(http, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
365 SLOT(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)));
366#endif
367 connect(http, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
368 SLOT(httpAuthenticationRequired(QHttpNetworkRequest,QAuthenticator*)));
369 connect(http, SIGNAL(error(QNetworkReply::NetworkError,QString)),
370 SLOT(httpError(QNetworkReply::NetworkError,QString)));
371#ifndef QT_NO_OPENSSL
372 connect(http, SIGNAL(sslErrors(QList<QSslError>)),
373 SLOT(sslErrors(QList<QSslError>)));
374#endif
375}
376
377/*
378 For a given httpRequest
379 1) If AlwaysNetwork, return
380 2) If we have a cache entry for this url populate headers so the server can return 304
381 3) Calculate if response_is_fresh and if so send the cache and set loadedFromCache to true
382 */
383void QNetworkAccessHttpBackend::validateCache(QHttpNetworkRequest &httpRequest, bool &loadedFromCache)
384{
385 QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
386 (QNetworkRequest::CacheLoadControl)request().attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
387 if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
388 // forced reload from the network
389 // tell any caching proxy servers to reload too
390 httpRequest.setHeaderField("Cache-Control", "no-cache");
391 httpRequest.setHeaderField("Pragma", "no-cache");
392 return;
393 }
394
395 QAbstractNetworkCache *nc = networkCache();
396 if (!nc)
397 return; // no local cache
398
399 QNetworkCacheMetaData metaData = nc->metaData(url());
400 if (!metaData.isValid())
401 return; // not in cache
402
403 if (!metaData.saveToDisk())
404 return;
405
406 QNetworkHeadersPrivate cacheHeaders;
407 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
408 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
409
410 it = cacheHeaders.findRawHeader("etag");
411 if (it != cacheHeaders.rawHeaders.constEnd())
412 httpRequest.setHeaderField("If-None-Match", it->second);
413
414 QDateTime lastModified = metaData.lastModified();
415 if (lastModified.isValid())
416 httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified));
417
418 it = cacheHeaders.findRawHeader("Cache-Control");
419 if (it != cacheHeaders.rawHeaders.constEnd()) {
420 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
421 if (cacheControl.contains("must-revalidate"))
422 return;
423 }
424
425 /*
426 * age_value
427 * is the value of Age: header received by the cache with
428 * this response.
429 * date_value
430 * is the value of the origin server's Date: header
431 * request_time
432 * is the (local) time when the cache made the request
433 * that resulted in this cached response
434 * response_time
435 * is the (local) time when the cache received the
436 * response
437 * now
438 * is the current (local) time
439 */
440 QDateTime currentDateTime = QDateTime::currentDateTime();
441 int age_value = 0;
442 it = cacheHeaders.findRawHeader("age");
443 if (it != cacheHeaders.rawHeaders.constEnd())
444 age_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t();
445
446 int date_value = 0;
447 it = cacheHeaders.findRawHeader("date");
448 if (it != cacheHeaders.rawHeaders.constEnd())
449 date_value = QNetworkHeadersPrivate::fromHttpDate(it->second).toTime_t();
450
451 int now = currentDateTime.toUTC().toTime_t();
452 int request_time = now;
453 int response_time = now;
454
455 int apparent_age = qMax(0, response_time - date_value);
456 int corrected_received_age = qMax(apparent_age, age_value);
457 int response_delay = response_time - request_time;
458 int corrected_initial_age = corrected_received_age + response_delay;
459 int resident_time = now - response_time;
460 int current_age = corrected_initial_age + resident_time;
461
462 // RFC 2616 13.2.4 Expiration Calculations
463 QDateTime expirationDate = metaData.expirationDate();
464 if (!expirationDate.isValid()) {
465 if (lastModified.isValid()) {
466 int diff = currentDateTime.secsTo(lastModified);
467 expirationDate = lastModified;
468 expirationDate.addSecs(diff / 10);
469 if (httpRequest.headerField("Warning").isEmpty()) {
470 QDateTime dt;
471 dt.setTime_t(current_age);
472 if (dt.daysTo(currentDateTime) > 1)
473 httpRequest.setHeaderField("Warning", "113");
474 }
475 }
476 }
477
478 int freshness_lifetime = currentDateTime.secsTo(expirationDate);
479 bool response_is_fresh = (freshness_lifetime > current_age);
480
481 if (!response_is_fresh && CacheLoadControlAttribute == QNetworkRequest::PreferNetwork)
482 return;
483
484 loadedFromCache = true;
485#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
486 qDebug() << "response_is_fresh" << CacheLoadControlAttribute;
487#endif
488 if (!sendCacheContents(metaData))
489 loadedFromCache = false;
490}
491
492void QNetworkAccessHttpBackend::postRequest()
493{
494 bool loadedFromCache = false;
495 QHttpNetworkRequest httpRequest;
496 switch (operation()) {
497 case QNetworkAccessManager::GetOperation:
498 httpRequest.setOperation(QHttpNetworkRequest::Get);
499 validateCache(httpRequest, loadedFromCache);
500 break;
501
502 case QNetworkAccessManager::HeadOperation:
503 httpRequest.setOperation(QHttpNetworkRequest::Head);
504 validateCache(httpRequest, loadedFromCache);
505 break;
506
507 case QNetworkAccessManager::PostOperation:
508 invalidateCache();
509 httpRequest.setOperation(QHttpNetworkRequest::Post);
510 uploadDevice = new QNetworkAccessHttpBackendIODevice(this);
511 break;
512
513 case QNetworkAccessManager::PutOperation:
514 invalidateCache();
515 httpRequest.setOperation(QHttpNetworkRequest::Put);
516 uploadDevice = new QNetworkAccessHttpBackendIODevice(this);
517 break;
518
519 default:
520 break; // can't happen
521 }
522
523 httpRequest.setData(uploadDevice);
524 httpRequest.setUrl(url());
525
526 QList<QByteArray> headers = request().rawHeaderList();
527 foreach (const QByteArray &header, headers)
528 httpRequest.setHeaderField(header, request().rawHeader(header));
529
530 if (loadedFromCache) {
531 QNetworkAccessBackend::finished();
532 return; // no need to send the request! :)
533 }
534
535 httpReply = http->sendRequest(httpRequest);
536 httpReply->setParent(this);
537#ifndef QT_NO_OPENSSL
538 if (pendingSslConfiguration)
539 httpReply->setSslConfiguration(*pendingSslConfiguration);
540 if (pendingIgnoreSslErrors)
541 httpReply->ignoreSslErrors();
542#endif
543
544 connect(httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
545 connect(httpReply, SIGNAL(finished()), SLOT(replyFinished()));
546 connect(httpReply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
547 SLOT(httpError(QNetworkReply::NetworkError,QString)));
548 connect(httpReply, SIGNAL(headerChanged()), SLOT(replyHeaderChanged()));
549}
550
551void QNetworkAccessHttpBackend::invalidateCache()
552{
553 QAbstractNetworkCache *nc = networkCache();
554 if (nc)
555 nc->remove(url());
556}
557
558void QNetworkAccessHttpBackend::open()
559{
560 QUrl url = request().url();
561 bool encrypt = url.scheme().toLower() == QLatin1String("https");
562 setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, encrypt);
563
564 // set the port number in the reply if it wasn't set
565 url.setPort(url.port(encrypt ? DefaultHttpsPort : DefaultHttpPort));
566
567 QNetworkProxy *theProxy = 0;
568#ifndef QT_NO_NETWORKPROXY
569 QNetworkProxy transparentProxy, cacheProxy;
570
571 foreach (const QNetworkProxy &p, proxyList()) {
572 // use the first proxy that works
573 // for non-encrypted connections, any transparent or HTTP proxy
574 // for encrypted, only transparent proxies
575 if (!encrypt
576 && (p.capabilities() & QNetworkProxy::CachingCapability)
577 && (p.type() == QNetworkProxy::HttpProxy ||
578 p.type() == QNetworkProxy::HttpCachingProxy)) {
579 cacheProxy = p;
580 transparentProxy = QNetworkProxy::NoProxy;
581 theProxy = &cacheProxy;
582 break;
583 }
584 if (p.isTransparentProxy()) {
585 transparentProxy = p;
586 cacheProxy = QNetworkProxy::NoProxy;
587 theProxy = &transparentProxy;
588 break;
589 }
590 }
591
592 // check if at least one of the proxies
593 if (transparentProxy.type() == QNetworkProxy::DefaultProxy &&
594 cacheProxy.type() == QNetworkProxy::DefaultProxy) {
595 // unsuitable proxies
596 error(QNetworkReply::ProxyNotFoundError,
597 tr("No suitable proxy found"));
598 finished();
599 return;
600 }
601#endif
602
603 // check if we have an open connection to this host
604 cacheKey = makeCacheKey(this, theProxy);
605 QNetworkAccessCache *cache = QNetworkAccessManagerPrivate::getCache(this);
606 if ((http = static_cast<QNetworkAccessHttpBackendCache *>(cache->requestEntryNow(cacheKey))) == 0) {
607 // no entry in cache; create an object
608 http = new QNetworkAccessHttpBackendCache(url.host(), url.port(), encrypt);
609
610#ifndef QT_NO_NETWORKPROXY
611 http->setTransparentProxy(transparentProxy);
612 http->setCacheProxy(cacheProxy);
613#endif
614
615 cache->addEntry(cacheKey, http);
616 }
617
618 setupConnection();
619 postRequest();
620}
621
622void QNetworkAccessHttpBackend::closeDownstreamChannel()
623{
624 // this indicates that the user closed the stream while the reply isn't finished yet
625}
626
627void QNetworkAccessHttpBackend::closeUpstreamChannel()
628{
629 // this indicates that the user finished uploading the data for POST
630 Q_ASSERT(uploadDevice);
631 uploadDevice->eof = true;
632 emit uploadDevice->readChannelFinished();
633}
634
635bool QNetworkAccessHttpBackend::waitForDownstreamReadyRead(int msecs)
636{
637 Q_ASSERT(http);
638
639 if (httpReply->bytesAvailable()) {
640 readFromHttp();
641 return true;
642 }
643
644 if (msecs == 0) {
645 // no bytes available in the socket and no waiting
646 return false;
647 }
648
649 // ### FIXME
650 qCritical("QNetworkAccess: HTTP backend does not support waitForReadyRead()");
651 return false;
652}
653
654bool QNetworkAccessHttpBackend::waitForUpstreamBytesWritten(int msecs)
655{
656
657 // ### FIXME: not implemented in QHttpNetworkAccess
658 Q_UNUSED(msecs);
659 qCritical("QNetworkAccess: HTTP backend does not support waitForBytesWritten()");
660 return false;
661}
662
663void QNetworkAccessHttpBackend::upstreamReadyRead()
664{
665 // There is more data available from the user to be uploaded
666 // QHttpNetworkAccess implements the upload rate control:
667 // we simply tell QHttpNetworkAccess that there is more data available
668 // it'll pull from us when it can (through uploadDevice)
669
670 Q_ASSERT(uploadDevice);
671 emit uploadDevice->readyRead();
672}
673
674qint64 QNetworkAccessHttpBackend::deviceReadData(char *buffer, qint64 maxlen)
675{
676 QByteArray toBeUploaded = readUpstream();
677 if (toBeUploaded.isEmpty())
678 return 0; // nothing to be uploaded
679
680 maxlen = qMin<qint64>(maxlen, toBeUploaded.length());
681
682 memcpy(buffer, toBeUploaded.constData(), maxlen);
683 upstreamBytesConsumed(maxlen);
684 return maxlen;
685}
686
687void QNetworkAccessHttpBackend::downstreamReadyWrite()
688{
689 readFromHttp();
690 if (httpReply && httpReply->bytesAvailable() == 0 && httpReply->isFinished())
691 replyFinished();
692}
693
694void QNetworkAccessHttpBackend::replyReadyRead()
695{
696 readFromHttp();
697}
698
699void QNetworkAccessHttpBackend::readFromHttp()
700{
701 if (!httpReply)
702 return;
703
704 // We implement the download rate control
705 // Don't read from QHttpNetworkAccess more than QNetworkAccessBackend wants
706 // One of the two functions above will be called when we can read again
707
708 qint64 bytesToRead = qBound<qint64>(0, httpReply->bytesAvailable(), nextDownstreamBlockSize());
709 if (!bytesToRead)
710 return;
711
712 QByteArray data = httpReply->read(bytesToRead);
713 writeDownstreamData(data);
714}
715
716void QNetworkAccessHttpBackend::replyFinished()
717{
718 if (httpReply->bytesAvailable())
719 // we haven't read everything yet. Wait some more.
720 return;
721
722 int statusCode = httpReply->statusCode();
723 if (statusCode >= 400) {
724 // it's an error reply
725 QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
726 "Error downloading %1 - server replied: %2"));
727 msg = msg.arg(url().toString(), httpReply->reasonPhrase());
728 error(statusCodeFromHttp(httpReply->statusCode(), httpReply->url()), msg);
729 }
730
731#ifndef QT_NO_OPENSSL
732 // store the SSL configuration now
733 // once we call finished(), we won't have access to httpReply anymore
734 QSslConfiguration sslConfig = httpReply->sslConfiguration();
735 if (pendingSslConfiguration)
736 *pendingSslConfiguration = sslConfig;
737 else if (!sslConfig.isNull())
738 pendingSslConfiguration = new QSslConfiguration(sslConfig);
739#endif
740
741 finished();
742}
743
744void QNetworkAccessHttpBackend::checkForRedirect(const int statusCode)
745{
746 switch (statusCode) {
747 case 301: // Moved Permanently
748 case 302: // Found
749 case 303: // See Other
750 case 307: // Temporary Redirect
751 // What do we do about the caching of the HTML note?
752 // The response to a 303 MUST NOT be cached, while the response to
753 // all of the others is cacheable if the headers indicate it to be
754 QByteArray header = rawHeader("location");
755 QUrl url = QUrl::fromEncoded(header);
756 if (!url.isValid())
757 url = QUrl(QLatin1String(header));
758 redirectionRequested(url);
759 }
760}
761
762void QNetworkAccessHttpBackend::replyHeaderChanged()
763{
764 // reconstruct the HTTP header
765 QList<QPair<QByteArray, QByteArray> > headerMap = httpReply->header();
766 QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
767 end = headerMap.constEnd();
768 QByteArray header;
769
770 for (; it != end; ++it) {
771 QByteArray value = rawHeader(it->first);
772 if (!value.isEmpty()) {
773 if (it->first.toLower() == "set-cookie")
774 value += "\n";
775 else
776 value += ", ";
777 }
778 value += it->second;
779 setRawHeader(it->first, value);
780 }
781
782 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpReply->statusCode());
783 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
784
785 // is it a redirection?
786 const int statusCode = httpReply->statusCode();
787 checkForRedirect(statusCode);
788
789 if (statusCode >= 500 && statusCode < 600) {
790 QAbstractNetworkCache *nc = networkCache();
791 if (nc) {
792 QNetworkCacheMetaData metaData = nc->metaData(url());
793 QNetworkHeadersPrivate cacheHeaders;
794 cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
795 QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
796 it = cacheHeaders.findRawHeader("Cache-Control");
797 bool mustReValidate = false;
798 if (it != cacheHeaders.rawHeaders.constEnd()) {
799 QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
800 if (cacheControl.contains("must-revalidate"))
801 mustReValidate = true;
802 }
803 if (!mustReValidate && sendCacheContents(metaData))
804 return;
805 }
806 }
807
808 if (statusCode == 304) {
809#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
810 qDebug() << "Received a 304 from" << url();
811#endif
812 QAbstractNetworkCache *nc = networkCache();
813 if (nc) {
814 QNetworkCacheMetaData oldMetaData = nc->metaData(url());
815 QNetworkCacheMetaData metaData = fetchCacheMetaData(oldMetaData);
816 if (oldMetaData != metaData)
817 nc->updateMetaData(metaData);
818 if (sendCacheContents(metaData))
819 return;
820 }
821 }
822
823
824 if (statusCode != 304 && statusCode != 303) {
825 if (!isCachingEnabled())
826 setCachingEnabled(true);
827 }
828 metaDataChanged();
829}
830
831void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &,
832 QAuthenticator *auth)
833{
834 authenticationRequired(auth);
835}
836
837void QNetworkAccessHttpBackend::httpError(QNetworkReply::NetworkError errorCode,
838 const QString &errorString)
839{
840#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
841 qDebug() << "http error!" << errorCode << errorString;
842#endif
843#if 0
844 static const QNetworkReply::NetworkError conversionTable[] = {
845 QNetworkReply::ConnectionRefusedError,
846 QNetworkReply::RemoteHostClosedError,
847 QNetworkReply::HostNotFoundError,
848 QNetworkReply::UnknownNetworkError, // SocketAccessError
849 QNetworkReply::UnknownNetworkError, // SocketResourceError
850 QNetworkReply::TimeoutError, // SocketTimeoutError
851 QNetworkReply::UnknownNetworkError, // DatagramTooLargeError
852 QNetworkReply::UnknownNetworkError, // NetworkError
853 QNetworkReply::UnknownNetworkError, // AddressInUseError
854 QNetworkReply::UnknownNetworkError, // SocketAddressNotAvailableError
855 QNetworkReply::UnknownNetworkError, // UnsupportedSocketOperationError
856 QNetworkReply::UnknownNetworkError, // UnfinishedSocketOperationError
857 QNetworkReply::ProxyAuthenticationRequiredError
858 };
859 QNetworkReply::NetworkError code;
860 if (int(errorCode) >= 0 &&
861 uint(errorCode) < (sizeof conversionTable / sizeof conversionTable[0]))
862 code = conversionTable[errorCode];
863 else
864 code = QNetworkReply::UnknownNetworkError;
865#endif
866 error(errorCode, errorString);
867 finished();
868}
869
870/*
871 A simple web page that can be used to test us: http://www.procata.com/cachetest/
872 */
873bool QNetworkAccessHttpBackend::sendCacheContents(const QNetworkCacheMetaData &metaData)
874{
875 setCachingEnabled(false);
876 if (!metaData.isValid())
877 return false;
878
879 QAbstractNetworkCache *nc = networkCache();
880 Q_ASSERT(nc);
881 QIODevice *contents = nc->data(url());
882 if (!contents) {
883#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
884 qDebug() << "Can not send cache, the contents are 0" << url();
885#endif
886 return false;
887 }
888 contents->setParent(this);
889
890 QNetworkCacheMetaData::AttributesMap attributes = metaData.attributes();
891 int status = attributes.value(QNetworkRequest::HttpStatusCodeAttribute).toInt();
892 if (status < 100)
893 status = 200; // fake it
894
895 setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
896 setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
897 setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
898
899 QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
900 QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
901 end = rawHeaders.constEnd();
902 for ( ; it != end; ++it)
903 setRawHeader(it->first, it->second);
904
905 checkForRedirect(status);
906
907 writeDownstreamData(contents);
908#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
909 qDebug() << "Successfully sent cache:" << url() << contents->size() << "bytes";
910#endif
911 if (httpReply)
912 disconnect(httpReply, SIGNAL(finished()), this, SLOT(replyFinished()));
913 return true;
914}
915
916void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev)
917{
918 delete dev;
919 finished();
920}
921
922#ifndef QT_NO_OPENSSL
923void QNetworkAccessHttpBackend::ignoreSslErrors()
924{
925 if (httpReply)
926 httpReply->ignoreSslErrors();
927 else
928 pendingIgnoreSslErrors = true;
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 // Don't store Warning 1xx headers
975 if (header == "warning") {
976 QByteArray v = rawHeader(header);
977 if (v.length() == 3
978 && v[0] == '1'
979 && v[1] >= '0' && v[1] <= '9'
980 && v[2] >= '0' && v[2] <= '9')
981 continue;
982 }
983
984 it = cacheHeaders.findRawHeader(header);
985 if (it != cacheHeaders.rawHeaders.constEnd()) {
986 // Match the behavior of Firefox and assume Cache-Control: "no-transform"
987 if (header == "content-encoding"
988 || header == "content-range"
989 || header == "content-type")
990 continue;
991
992 // For MS servers that send "Content-Length: 0" on 304 responses
993 // ignore this too
994 if (header == "content-length")
995 continue;
996 }
997
998#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
999 QByteArray n = rawHeader(header);
1000 QByteArray o;
1001 if (it != cacheHeaders.rawHeaders.constEnd())
1002 o = (*it).second;
1003 if (n != o && header != "date") {
1004 qDebug() << "replacing" << header;
1005 qDebug() << "new" << n;
1006 qDebug() << "old" << o;
1007 }
1008#endif
1009 cacheHeaders.setRawHeader(originalHeader, rawHeader(header));
1010 }
1011 metaData.setRawHeaders(cacheHeaders.rawHeaders);
1012
1013 bool checkExpired = true;
1014
1015 QHash<QByteArray, QByteArray> cacheControl;
1016 it = cacheHeaders.findRawHeader("Cache-Control");
1017 if (it != cacheHeaders.rawHeaders.constEnd()) {
1018 cacheControl = parseHttpOptionHeader(it->second);
1019 QByteArray maxAge = cacheControl.value("max-age");
1020 if (!maxAge.isEmpty()) {
1021 checkExpired = false;
1022 QDateTime dt = QDateTime::currentDateTime();
1023 dt = dt.addSecs(maxAge.toInt());
1024 metaData.setExpirationDate(dt);
1025 }
1026 }
1027 if (checkExpired) {
1028 it = cacheHeaders.findRawHeader("expires");
1029 if (it != cacheHeaders.rawHeaders.constEnd()) {
1030 QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
1031 metaData.setExpirationDate(expiredDateTime);
1032 }
1033 }
1034
1035 it = cacheHeaders.findRawHeader("last-modified");
1036 if (it != cacheHeaders.rawHeaders.constEnd())
1037 metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
1038
1039 bool canDiskCache = true; // Everything defaults to being cacheable on disk
1040
1041 // 14.32
1042 // HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client
1043 // had sent "Cache-Control: no-cache".
1044 it = cacheHeaders.findRawHeader("pragma");
1045 if (it != cacheHeaders.rawHeaders.constEnd()
1046 && it->second == "no-cache")
1047 canDiskCache = false;
1048
1049 // HTTP/1.1. Check the Cache-Control header
1050 if (cacheControl.contains("no-cache"))
1051 canDiskCache = false;
1052 else if (cacheControl.contains("no-store"))
1053 canDiskCache = false;
1054
1055 metaData.setSaveToDisk(canDiskCache);
1056 int statusCode = httpReply->statusCode();
1057 QNetworkCacheMetaData::AttributesMap attributes;
1058 if (statusCode != 304) {
1059 // update the status code
1060 attributes.insert(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
1061 attributes.insert(QNetworkRequest::HttpReasonPhraseAttribute, httpReply->reasonPhrase());
1062 } else {
1063 // this is a redirection, keep the attributes intact
1064 attributes = oldMetaData.attributes();
1065 }
1066 metaData.setAttributes(attributes);
1067 return metaData;
1068}
1069
1070QT_END_NAMESPACE
1071
1072#endif // QT_NO_HTTP
Note: See TracBrowser for help on using the repository browser.