| 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 |
|
|---|
| 59 | QT_BEGIN_NAMESPACE
|
|---|
| 60 |
|
|---|
| 61 | enum {
|
|---|
| 62 | DefaultHttpPort = 80,
|
|---|
| 63 | DefaultHttpsPort = 443
|
|---|
| 64 | };
|
|---|
| 65 |
|
|---|
| 66 | class QNetworkProxy;
|
|---|
| 67 |
|
|---|
| 68 | static 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 |
|
|---|
| 108 | static 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
|
|---|
| 115 | static 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 |
|
|---|
| 205 | QNetworkAccessBackend *
|
|---|
| 206 | QNetworkAccessHttpBackendFactory::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 |
|
|---|
| 230 | static 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 |
|
|---|
| 268 | class QNetworkAccessHttpBackendCache: public QHttpNetworkConnection,
|
|---|
| 269 | public QNetworkAccessCache::CacheableObject
|
|---|
| 270 | {
|
|---|
| 271 | // Q_OBJECT
|
|---|
| 272 | public:
|
|---|
| 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 |
|
|---|
| 289 | class QNetworkAccessHttpBackendIODevice: public QIODevice
|
|---|
| 290 | {
|
|---|
| 291 | // Q_OBJECT
|
|---|
| 292 | public:
|
|---|
| 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 |
|
|---|
| 303 | protected:
|
|---|
| 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 |
|
|---|
| 320 | QNetworkAccessHttpBackend::QNetworkAccessHttpBackend()
|
|---|
| 321 | : QNetworkAccessBackend(), httpReply(0), http(0), uploadDevice(0)
|
|---|
| 322 | #ifndef QT_NO_OPENSSL
|
|---|
| 323 | , pendingSslConfiguration(0), pendingIgnoreSslErrors(false)
|
|---|
| 324 | #endif
|
|---|
| 325 | {
|
|---|
| 326 | }
|
|---|
| 327 |
|
|---|
| 328 | QNetworkAccessHttpBackend::~QNetworkAccessHttpBackend()
|
|---|
| 329 | {
|
|---|
| 330 | if (http)
|
|---|
| 331 | disconnectFromHttp();
|
|---|
| 332 | #ifndef QT_NO_OPENSSL
|
|---|
| 333 | delete pendingSslConfiguration;
|
|---|
| 334 | #endif
|
|---|
| 335 | }
|
|---|
| 336 |
|
|---|
| 337 | void 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 |
|
|---|
| 353 | void QNetworkAccessHttpBackend::finished()
|
|---|
| 354 | {
|
|---|
| 355 | if (http)
|
|---|
| 356 | disconnectFromHttp();
|
|---|
| 357 | // call parent
|
|---|
| 358 | QNetworkAccessBackend::finished();
|
|---|
| 359 | }
|
|---|
| 360 |
|
|---|
| 361 | void 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 | */
|
|---|
| 383 | void 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 |
|
|---|
| 492 | void 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 |
|
|---|
| 551 | void QNetworkAccessHttpBackend::invalidateCache()
|
|---|
| 552 | {
|
|---|
| 553 | QAbstractNetworkCache *nc = networkCache();
|
|---|
| 554 | if (nc)
|
|---|
| 555 | nc->remove(url());
|
|---|
| 556 | }
|
|---|
| 557 |
|
|---|
| 558 | void 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 |
|
|---|
| 622 | void QNetworkAccessHttpBackend::closeDownstreamChannel()
|
|---|
| 623 | {
|
|---|
| 624 | // this indicates that the user closed the stream while the reply isn't finished yet
|
|---|
| 625 | }
|
|---|
| 626 |
|
|---|
| 627 | void 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 |
|
|---|
| 635 | bool 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 |
|
|---|
| 654 | bool 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 |
|
|---|
| 663 | void 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 |
|
|---|
| 674 | qint64 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 |
|
|---|
| 687 | void QNetworkAccessHttpBackend::downstreamReadyWrite()
|
|---|
| 688 | {
|
|---|
| 689 | readFromHttp();
|
|---|
| 690 | if (httpReply && httpReply->bytesAvailable() == 0 && httpReply->isFinished())
|
|---|
| 691 | replyFinished();
|
|---|
| 692 | }
|
|---|
| 693 |
|
|---|
| 694 | void QNetworkAccessHttpBackend::replyReadyRead()
|
|---|
| 695 | {
|
|---|
| 696 | readFromHttp();
|
|---|
| 697 | }
|
|---|
| 698 |
|
|---|
| 699 | void 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 |
|
|---|
| 716 | void 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 |
|
|---|
| 744 | void 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 |
|
|---|
| 762 | void 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 |
|
|---|
| 831 | void QNetworkAccessHttpBackend::httpAuthenticationRequired(const QHttpNetworkRequest &,
|
|---|
| 832 | QAuthenticator *auth)
|
|---|
| 833 | {
|
|---|
| 834 | authenticationRequired(auth);
|
|---|
| 835 | }
|
|---|
| 836 |
|
|---|
| 837 | void 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 | */
|
|---|
| 873 | bool 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 |
|
|---|
| 916 | void QNetworkAccessHttpBackend::copyFinished(QIODevice *dev)
|
|---|
| 917 | {
|
|---|
| 918 | delete dev;
|
|---|
| 919 | finished();
|
|---|
| 920 | }
|
|---|
| 921 |
|
|---|
| 922 | #ifndef QT_NO_OPENSSL
|
|---|
| 923 | void QNetworkAccessHttpBackend::ignoreSslErrors()
|
|---|
| 924 | {
|
|---|
| 925 | if (httpReply)
|
|---|
| 926 | httpReply->ignoreSslErrors();
|
|---|
| 927 | else
|
|---|
| 928 | pendingIgnoreSslErrors = true;
|
|---|
| 929 | }
|
|---|
| 930 |
|
|---|
| 931 | void QNetworkAccessHttpBackend::fetchSslConfiguration(QSslConfiguration &config) const
|
|---|
| 932 | {
|
|---|
| 933 | if (httpReply)
|
|---|
| 934 | config = httpReply->sslConfiguration();
|
|---|
| 935 | else if (pendingSslConfiguration)
|
|---|
| 936 | config = *pendingSslConfiguration;
|
|---|
| 937 | }
|
|---|
| 938 |
|
|---|
| 939 | void 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 |
|
|---|
| 950 | QNetworkCacheMetaData 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 |
|
|---|
| 1070 | QT_END_NAMESPACE
|
|---|
| 1071 |
|
|---|
| 1072 | #endif // QT_NO_HTTP
|
|---|