KIO
accessmanager.cpp
Go to the documentation of this file.
00001 /* 00002 * This file is part of the KDE project. 00003 * 00004 * Copyright (C) 2009,2010 Dawit Alemayehu <adawit @ kde.org> 00005 * Copyright (C) 2008 - 2009 Urs Wolfer <uwolfer @ kde.org> 00006 * Copyright (C) 2007 Trolltech ASA 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Library General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Library General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Library General Public License 00019 * along with this library; see the file COPYING.LIB. If not, write to 00020 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00021 * Boston, MA 02110-1301, USA. 00022 * 00023 */ 00024 00025 #include "accessmanager.h" 00026 00027 #include "accessmanagerreply_p.h" 00028 #include "job.h" 00029 #include "scheduler.h" 00030 #include "jobuidelegate.h" 00031 00032 #include <kdebug.h> 00033 #include <kconfiggroup.h> 00034 #include <ksharedconfig.h> 00035 #include <kprotocolinfo.h> 00036 #include <klocalizedstring.h> 00037 00038 #include <QtCore/QUrl> 00039 #include <QtGui/QWidget> 00040 #include <QtCore/QEventLoop> 00041 #include <QtCore/QWeakPointer> 00042 #include <QtDBus/QDBusInterface> 00043 #include <QtDBus/QDBusConnection> 00044 #include <QtDBus/QDBusReply> 00045 #include <QtNetwork/QNetworkReply> 00046 #include <QtNetwork/QNetworkRequest> 00047 #include <QtNetwork/QSslCipher> 00048 #include <QtNetwork/QSslCertificate> 00049 #include <QtNetwork/QSslConfiguration> 00050 00051 00052 #define QL1S(x) QLatin1String(x) 00053 #define QL1C(x) QLatin1Char(x) 00054 00055 #if QT_VERSION >= 0x040800 00056 static QNetworkRequest::Attribute gSynchronousNetworkRequestAttribute = QNetworkRequest::SynchronousRequestAttribute; 00057 #else // QtWebkit hack to use the internal attribute 00058 static QNetworkRequest::Attribute gSynchronousNetworkRequestAttribute = static_cast<QNetworkRequest::Attribute>(QNetworkRequest::HttpPipeliningWasUsedAttribute + 7); 00059 #endif 00060 00061 static qint64 sizeFromRequest(const QNetworkRequest& req) 00062 { 00063 const QVariant size = req.header(QNetworkRequest::ContentLengthHeader); 00064 if (!size.isValid()) 00065 return -1; 00066 return size.toLongLong(); 00067 } 00068 00069 namespace KIO { 00070 00071 class AccessManager::AccessManagerPrivate 00072 { 00073 public: 00074 AccessManagerPrivate() 00075 : externalContentAllowed(true), 00076 emitReadReadOnMetaDataChange(false), 00077 window(0) 00078 {} 00079 00080 void setMetaDataForRequest(QNetworkRequest request, KIO::MetaData& metaData); 00081 00082 bool externalContentAllowed; 00083 bool emitReadReadOnMetaDataChange; 00084 KIO::MetaData requestMetaData; 00085 KIO::MetaData sessionMetaData; 00086 QWidget* window; 00087 }; 00088 00089 namespace Integration { 00090 00091 class CookieJar::CookieJarPrivate 00092 { 00093 public: 00094 CookieJarPrivate() 00095 : windowId((WId)-1), 00096 isEnabled(true), 00097 isStorageDisabled(false) 00098 {} 00099 00100 WId windowId; 00101 bool isEnabled; 00102 bool isStorageDisabled; 00103 }; 00104 00105 } 00106 00107 } 00108 00109 using namespace KIO; 00110 00111 AccessManager::AccessManager(QObject *parent) 00112 :QNetworkAccessManager(parent), d(new AccessManager::AccessManagerPrivate()) 00113 { 00114 // KDE Cookiejar (KCookieJar) integration... 00115 setCookieJar(new KIO::Integration::CookieJar); 00116 } 00117 00118 AccessManager::~AccessManager() 00119 { 00120 delete d; 00121 } 00122 00123 void AccessManager::setExternalContentAllowed(bool allowed) 00124 { 00125 d->externalContentAllowed = allowed; 00126 } 00127 00128 bool AccessManager::isExternalContentAllowed() const 00129 { 00130 return d->externalContentAllowed; 00131 } 00132 00133 #ifndef KDE_NO_DEPRECATED 00134 void AccessManager::setCookieJarWindowId(WId id) 00135 { 00136 QWidget* window = QWidget::find(id); 00137 if (!window) { 00138 return; 00139 } 00140 00141 KIO::Integration::CookieJar *jar = qobject_cast<KIO::Integration::CookieJar *> (cookieJar()); 00142 if (jar) { 00143 jar->setWindowId(id); 00144 } 00145 00146 d->window = window->isWindow() ? window : window->window(); 00147 } 00148 #endif 00149 00150 void AccessManager::setWindow(QWidget* widget) 00151 { 00152 if (!widget) { 00153 return; 00154 } 00155 00156 d->window = widget->isWindow() ? widget : widget->window(); 00157 00158 if (!d->window) { 00159 return; 00160 } 00161 00162 KIO::Integration::CookieJar *jar = qobject_cast<KIO::Integration::CookieJar *> (cookieJar()); 00163 if (jar) { 00164 jar->setWindowId(d->window->winId()); 00165 } 00166 } 00167 00168 #ifndef KDE_NO_DEPRECATED 00169 WId AccessManager::cookieJarWindowid() const 00170 { 00171 KIO::Integration::CookieJar *jar = qobject_cast<KIO::Integration::CookieJar *> (cookieJar()); 00172 if (jar) 00173 return jar->windowId(); 00174 00175 return 0; 00176 } 00177 #endif 00178 00179 QWidget* AccessManager::window() const 00180 { 00181 return d->window; 00182 } 00183 00184 KIO::MetaData& AccessManager::requestMetaData() 00185 { 00186 return d->requestMetaData; 00187 } 00188 00189 KIO::MetaData& AccessManager::sessionMetaData() 00190 { 00191 return d->sessionMetaData; 00192 } 00193 00194 void AccessManager::putReplyOnHold(QNetworkReply* reply) 00195 { 00196 KDEPrivate::AccessManagerReply* r = qobject_cast<KDEPrivate::AccessManagerReply*>(reply); 00197 if (!r) { 00198 return; 00199 } 00200 00201 r->putOnHold(); 00202 } 00203 00204 void AccessManager::setEmitReadyReadOnMetaDataChange(bool enable) 00205 { 00206 d->emitReadReadOnMetaDataChange = enable; 00207 } 00208 00209 QNetworkReply *AccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData) 00210 { 00211 KIO::SimpleJob *kioJob = 0; 00212 const KUrl reqUrl (req.url()); 00213 00214 if (!d->externalContentAllowed && !KDEPrivate::AccessManager_isLocalRequest(reqUrl) && reqUrl.scheme() != QL1S("data")) { 00215 kDebug( 7044 ) << "Blocked: " << reqUrl; 00216 KDEPrivate::AccessManagerReply* reply = new KDEPrivate::AccessManagerReply(op, req, kioJob, d->emitReadReadOnMetaDataChange, this); 00217 reply->setStatus(i18n("Blocked request."),QNetworkReply::ContentAccessDenied); 00218 return reply; 00219 } 00220 00221 // Check if the internal ignore content disposition header is set. 00222 const bool ignoreContentDisposition = req.hasRawHeader("x-kdewebkit-ignore-disposition"); 00223 00224 // Retrieve the KIO meta data... 00225 KIO::MetaData metaData; 00226 d->setMetaDataForRequest(req, metaData); 00227 00228 switch (op) { 00229 case HeadOperation: { 00230 //kDebug( 7044 ) << "HeadOperation:" << reqUrl; 00231 kioJob = KIO::mimetype(reqUrl, KIO::HideProgressInfo); 00232 break; 00233 } 00234 case GetOperation: { 00235 //kDebug( 7044 ) << "GetOperation:" << reqUrl; 00236 if (!reqUrl.path().isEmpty() || reqUrl.host().isEmpty()) 00237 kioJob = KIO::get(reqUrl, KIO::NoReload, KIO::HideProgressInfo); 00238 else 00239 kioJob = KIO::stat(reqUrl, KIO::HideProgressInfo); 00240 00241 // WORKAROUND: Avoid the brain damaged stuff QtWebKit does when a POST 00242 // operation is redirected! See BR# 268694. 00243 metaData.remove(QL1S("content-type")); // Remove the content-type from a GET/HEAD request! 00244 break; 00245 } 00246 case PutOperation: { 00247 //kDebug( 7044 ) << "PutOperation:" << reqUrl; 00248 if (outgoingData) 00249 kioJob = KIO::storedPut(outgoingData->readAll(), reqUrl, -1, KIO::HideProgressInfo); 00250 else 00251 kioJob = KIO::put(reqUrl, -1, KIO::HideProgressInfo); 00252 break; 00253 } 00254 case PostOperation: { 00255 //kDebug( 7044 ) << "PostOperation:" << reqUrl; 00256 const qint64 size = sizeFromRequest(req); 00257 //kDebug(7044) << "PostOperation: data size=" << size; 00258 kioJob = KIO::http_post(reqUrl, outgoingData, size, KIO::HideProgressInfo); 00259 if (!metaData.contains(QL1S("content-type"))) { 00260 const QVariant header = req.header(QNetworkRequest::ContentTypeHeader); 00261 if (header.isValid()) { 00262 metaData.insert(QL1S("content-type"), 00263 (QL1S("Content-Type: ") + header.toString())); 00264 } else { 00265 metaData.insert(QL1S("content-type"), 00266 QL1S("Content-Type: application/x-www-form-urlencoded")); 00267 } 00268 } 00269 break; 00270 } 00271 case DeleteOperation: { 00272 //kDebug(7044) << "DeleteOperation:" << reqUrl; 00273 kioJob = KIO::file_delete(reqUrl, KIO::HideProgressInfo); 00274 break; 00275 } 00276 case CustomOperation: { 00277 const QByteArray& method = req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray(); 00278 //kDebug(7044) << "CustomOperation:" << reqUrl << "method:" << method << "outgoing data:" << outgoingData; 00279 00280 if (method.isEmpty()) { 00281 KDEPrivate::AccessManagerReply* reply = new KDEPrivate::AccessManagerReply(op, req, kioJob, d->emitReadReadOnMetaDataChange, this); 00282 reply->setStatus(i18n("Unknown HTTP verb."), QNetworkReply::ProtocolUnknownError); 00283 return reply; 00284 } 00285 00286 if (outgoingData) 00287 kioJob = KIO::http_post(reqUrl, outgoingData, sizeFromRequest(req), KIO::HideProgressInfo); 00288 else 00289 kioJob = KIO::get(reqUrl, KIO::NoReload, KIO::HideProgressInfo); 00290 00291 metaData.insert(QL1S("CustomHTTPMethod"), method); 00292 break; 00293 } 00294 default: { 00295 kWarning(7044) << "Unsupported KIO operation requested! Defering to QNetworkAccessManager..."; 00296 return QNetworkAccessManager::createRequest(op, req, outgoingData); 00297 } 00298 } 00299 00300 // Set the window on the the KIO ui delegate 00301 if (d->window) { 00302 kioJob->ui()->setWindow(d->window); 00303 } 00304 00305 // Disable internal automatic redirection handling 00306 kioJob->setRedirectionHandlingEnabled(false); 00307 00308 // Set the job priority 00309 switch (req.priority()) { 00310 case QNetworkRequest::HighPriority: 00311 KIO::Scheduler::setJobPriority(kioJob, -5); 00312 break; 00313 case QNetworkRequest::LowPriority: 00314 KIO::Scheduler::setJobPriority(kioJob, 5); 00315 break; 00316 default: 00317 break; 00318 } 00319 00320 // Set the meta data for this job... 00321 kioJob->setMetaData(metaData); 00322 00323 // Create the reply... 00324 KDEPrivate::AccessManagerReply *reply = new KDEPrivate::AccessManagerReply(op, req, kioJob, d->emitReadReadOnMetaDataChange, this); 00325 00326 /* 00327 * NOTE: Since QtWebkit >= v2.2 no longer spins in its own even loop, we 00328 * are forced to create our own local event loop here to handle the very 00329 * rare but still in use synchronous XHR calls, e.g. http://webchat.freenode.net/ 00330 */ 00331 if (req.attribute(gSynchronousNetworkRequestAttribute).toBool()) { 00332 QEventLoop eventLoop; 00333 connect (reply, SIGNAL(finished()), &eventLoop, SLOT(quit())); 00334 eventLoop.exec(); 00335 } 00336 00337 if (ignoreContentDisposition) { 00338 kDebug(7044) << "Content-Disposition WILL BE IGNORED!"; 00339 reply->setIgnoreContentDisposition(ignoreContentDisposition); 00340 } 00341 00342 return reply; 00343 } 00344 00345 void AccessManager::AccessManagerPrivate::setMetaDataForRequest(QNetworkRequest request, KIO::MetaData& metaData) 00346 { 00347 // Add any meta data specified within request... 00348 QVariant userMetaData = request.attribute (static_cast<QNetworkRequest::Attribute>(MetaData)); 00349 if (userMetaData.isValid() && userMetaData.type() == QVariant::Map) 00350 metaData += userMetaData.toMap(); 00351 00352 metaData.insert(QL1S("PropagateHttpHeader"), QL1S("true")); 00353 00354 if (request.hasRawHeader("User-Agent")) { 00355 metaData.insert(QL1S("UserAgent"), request.rawHeader("User-Agent")); 00356 request.setRawHeader("User-Agent", QByteArray()); 00357 } 00358 00359 if (request.hasRawHeader("Accept")) { 00360 metaData.insert(QL1S("accept"), request.rawHeader("Accept")); 00361 request.setRawHeader("Accept", QByteArray()); 00362 } 00363 00364 if (request.hasRawHeader("Accept-Charset")) { 00365 metaData.insert(QL1S("Charsets"), request.rawHeader("Accept-Charset")); 00366 request.setRawHeader("Accept-Charset", QByteArray()); 00367 } 00368 00369 if (request.hasRawHeader("Accept-Language")) { 00370 metaData.insert(QL1S("Languages"), request.rawHeader("Accept-Language")); 00371 request.setRawHeader("Accept-Language", QByteArray()); 00372 } 00373 00374 if (request.hasRawHeader("Referer")) { 00375 metaData.insert(QL1S("referrer"), request.rawHeader("Referer")); 00376 request.setRawHeader("Referer", QByteArray()); 00377 } 00378 00379 if (request.hasRawHeader("Content-Type")) { 00380 metaData.insert(QL1S("content-type"), request.rawHeader("Content-Type")); 00381 request.setRawHeader("Content-Type", QByteArray()); 00382 } 00383 00384 if (request.attribute(QNetworkRequest::AuthenticationReuseAttribute) == QNetworkRequest::Manual) { 00385 metaData.insert(QL1S("no-preemptive-auth-reuse"), QL1S("true")); 00386 } 00387 00388 request.setRawHeader("Content-Length", QByteArray()); 00389 request.setRawHeader("Connection", QByteArray()); 00390 request.setRawHeader("If-None-Match", QByteArray()); 00391 request.setRawHeader("If-Modified-Since", QByteArray()); 00392 request.setRawHeader("x-kdewebkit-ignore-disposition", QByteArray()); 00393 00394 QStringList customHeaders; 00395 Q_FOREACH(const QByteArray &key, request.rawHeaderList()) { 00396 const QByteArray value = request.rawHeader(key); 00397 if (value.length()) 00398 customHeaders << (key + QL1S(": ") + value); 00399 } 00400 00401 if (!customHeaders.isEmpty()) { 00402 metaData.insert(QL1S("customHTTPHeader"), customHeaders.join("\r\n")); 00403 } 00404 00405 // Append per request meta data, if any... 00406 if (!requestMetaData.isEmpty()) { 00407 metaData += requestMetaData; 00408 // Clear per request meta data... 00409 requestMetaData.clear(); 00410 } 00411 00412 // Append per session meta data, if any... 00413 if (!sessionMetaData.isEmpty()) { 00414 metaData += sessionMetaData; 00415 } 00416 } 00417 00418 00419 using namespace KIO::Integration; 00420 00421 static QSsl::SslProtocol qSslProtocolFromString(const QString& str) 00422 { 00423 if (str.compare(QLatin1String("SSLv3"), Qt::CaseInsensitive) == 0) { 00424 return QSsl::SslV3; 00425 } 00426 00427 if (str.compare(QLatin1String("SSLv2"), Qt::CaseInsensitive) == 0) { 00428 return QSsl::SslV2; 00429 } 00430 00431 if (str.compare(QLatin1String("TLSv1"), Qt::CaseInsensitive) == 0) { 00432 return QSsl::TlsV1; 00433 } 00434 00435 return QSsl::AnyProtocol; 00436 } 00437 00438 bool KIO::Integration::sslConfigFromMetaData(const KIO::MetaData& metadata, QSslConfiguration& sslconfig) 00439 { 00440 bool success = false; 00441 00442 if (metadata.contains(QL1S("ssl_in_use"))) { 00443 const QSsl::SslProtocol sslProto = qSslProtocolFromString(metadata.value(QL1S("ssl_protocol_version"))); 00444 QList<QSslCipher> cipherList; 00445 cipherList << QSslCipher(metadata.value(QL1S("ssl_cipher_name")), sslProto); 00446 sslconfig.setCaCertificates(QSslCertificate::fromData(metadata.value(QL1S("ssl_peer_chain")).toUtf8())); 00447 sslconfig.setCiphers(cipherList); 00448 sslconfig.setProtocol(sslProto); 00449 success = sslconfig.isNull(); 00450 } 00451 00452 return success; 00453 } 00454 00455 CookieJar::CookieJar(QObject* parent) 00456 :QNetworkCookieJar(parent), d(new CookieJar::CookieJarPrivate) 00457 { 00458 reparseConfiguration(); 00459 } 00460 00461 CookieJar::~CookieJar() 00462 { 00463 delete d; 00464 } 00465 00466 WId CookieJar::windowId() const 00467 { 00468 return d->windowId; 00469 } 00470 00471 bool CookieJar::isCookieStorageDisabled() const 00472 { 00473 return d->isStorageDisabled; 00474 } 00475 00476 QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl &url) const 00477 { 00478 QList<QNetworkCookie> cookieList; 00479 00480 if (!d->isEnabled) { 00481 return cookieList; 00482 } 00483 QDBusInterface kcookiejar("org.kde.kded", "/modules/kcookiejar", "org.kde.KCookieServer"); 00484 QDBusReply<QString> reply = kcookiejar.call("findDOMCookies", url.toString(QUrl::RemoveUserInfo), (qlonglong)d->windowId); 00485 00486 if (!reply.isValid()) { 00487 kWarning(7044) << "Unable to communicate with the cookiejar!"; 00488 return cookieList; 00489 } 00490 00491 const QString cookieStr = reply.value(); 00492 const QStringList cookies = cookieStr.split(QL1S("; "), QString::SkipEmptyParts); 00493 Q_FOREACH(const QString& cookie, cookies) { 00494 const int index = cookie.indexOf(QL1C('=')); 00495 const QString name = cookie.left(index); 00496 const QString value = cookie.right((cookie.length() - index - 1)); 00497 cookieList << QNetworkCookie(name.toUtf8(), value.toUtf8()); 00498 //kDebug(7044) << "cookie: name=" << name << ", value=" << value; 00499 } 00500 00501 return cookieList; 00502 } 00503 00504 bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url) 00505 { 00506 if (!d->isEnabled) { 00507 return false; 00508 } 00509 00510 QDBusInterface kcookiejar("org.kde.kded", "/modules/kcookiejar", "org.kde.KCookieServer"); 00511 Q_FOREACH(const QNetworkCookie &cookie, cookieList) { 00512 QByteArray cookieHeader ("Set-Cookie: "); 00513 if (d->isStorageDisabled && !cookie.isSessionCookie()) { 00514 QNetworkCookie sessionCookie(cookie); 00515 sessionCookie.setExpirationDate(QDateTime()); 00516 cookieHeader += sessionCookie.toRawForm(); 00517 } else { 00518 cookieHeader += cookie.toRawForm(); 00519 } 00520 kcookiejar.call("addCookies", url.toString(QUrl::RemoveUserInfo), cookieHeader, (qlonglong)d->windowId); 00521 //kDebug(7044) << "[" << d->windowId << "]" << cookieHeader << " from " << url; 00522 } 00523 00524 return !kcookiejar.lastError().isValid(); 00525 } 00526 00527 void CookieJar::setDisableCookieStorage(bool disable) 00528 { 00529 d->isStorageDisabled = disable; 00530 } 00531 00532 void CookieJar::setWindowId(WId id) 00533 { 00534 d->windowId = id; 00535 } 00536 00537 void CookieJar::reparseConfiguration() 00538 { 00539 KConfigGroup cfg = KSharedConfig::openConfig("kcookiejarrc", KConfig::NoGlobals)->group("Cookie Policy"); 00540 d->isEnabled = cfg.readEntry("Cookies", true); 00541 } 00542 00543 #include "accessmanager.moc" 00544
KDE 4.7 API Reference