KDEWebKit
kwebpage.cpp
Go to the documentation of this file.
00001 /* 00002 * This file is part of the KDE project. 00003 * 00004 * Copyright (C) 2008 Dirk Mueller <mueller@kde.org> 00005 * Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org> 00006 * Copyright (C) 2008 Michael Howell <mhowell123@gmail.com> 00007 * Copyright (C) 2009,2010 Dawit Alemayehu <adawit@kde.org> 00008 * 00009 * This library is free software; you can redistribute it and/or 00010 * modify it under the terms of the GNU Library General Public 00011 * License as published by the Free Software Foundation; either 00012 * version 2 of the License, or (at your option) any later version. 00013 * 00014 * This library is distributed in the hope that it will be useful, 00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00017 * Library General Public License for more details. 00018 * 00019 * You should have received a copy of the GNU Library General Public License 00020 * along with this library; see the file COPYING.LIB. If not, write to 00021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00022 * Boston, MA 02110-1301, USA. 00023 * 00024 */ 00025 00026 // Own 00027 #include "kwebpage.h" 00028 #include "kwebwallet.h" 00029 00030 // Local 00031 #include "kwebpluginfactory.h" 00032 00033 // KDE 00034 #include <kaction.h> 00035 #include <kfiledialog.h> 00036 #include <kprotocolmanager.h> 00037 #include <kjobuidelegate.h> 00038 #include <krun.h> 00039 #include <kstandarddirs.h> 00040 #include <kstandardshortcut.h> 00041 #include <kurl.h> 00042 #include <kdebug.h> 00043 #include <kmimetypetrader.h> 00044 #include <klocalizedstring.h> 00045 #include <ktemporaryfile.h> 00046 #include <kio/accessmanager.h> 00047 #include <kio/job.h> 00048 #include <kio/jobuidelegate.h> 00049 #include <kio/renamedialog.h> 00050 #include <kio/scheduler.h> 00051 #include <kparts/browseropenorsavequestion.h> 00052 00053 // Qt 00054 #include <QtCore/QPointer> 00055 #include <QtCore/QFileInfo> 00056 #include <QtCore/QCoreApplication> 00057 #include <QtWebKit/QWebFrame> 00058 #include <QtNetwork/QNetworkReply> 00059 00060 00061 #define QL1S(x) QLatin1String(x) 00062 #define QL1C(x) QLatin1Char(x) 00063 00064 static bool isMimeTypeAssociatedWithSelf(const QString& mimeType, const KService::Ptr &offer) 00065 { 00066 Q_UNUSED(mimeType); 00067 00068 if (!offer) 00069 return false; 00070 00071 kDebug(800) << offer->desktopEntryName(); 00072 00073 const QString& appName = QCoreApplication::applicationName(); 00074 00075 if (appName == offer->desktopEntryName() || offer->exec().trimmed().startsWith(appName)) 00076 return true; 00077 00078 // konqueror exception since it uses kfmclient to open html content... 00079 if (appName == QL1S("konqueror") && offer->exec().trimmed().startsWith(QL1S("kfmclient"))) 00080 return true; 00081 00082 return false; 00083 } 00084 00085 static void extractMimeType(const QNetworkReply* reply, QString& mimeType) 00086 { 00087 mimeType.clear(); 00088 const KIO::MetaData& metaData = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap(); 00089 if (metaData.contains(QL1S("content-type"))) 00090 mimeType = metaData.value(QL1S("content-type")); 00091 00092 if (!mimeType.isEmpty()) 00093 return; 00094 00095 if (!reply->hasRawHeader("Content-Type")) 00096 return; 00097 00098 const QString value (QL1S(reply->rawHeader("Content-Type").simplified().constData())); 00099 const int index = value.indexOf(QL1C(';')); 00100 mimeType = ((index == -1) ? value : value.left(index)); 00101 } 00102 00103 static KUrl promptUser (QWidget *parent, const KUrl& url, const QString& suggestedName) 00104 { 00105 KUrl destUrl; 00106 int result = KIO::R_OVERWRITE; 00107 const QString fileName ((suggestedName.isEmpty() ? url.fileName() : suggestedName)); 00108 00109 do { 00110 // convert filename to URL using fromPath to avoid trouble with ':' in filenames (#184202) 00111 destUrl = KFileDialog::getSaveFileName(KUrl::fromPath(fileName), QString(), parent); 00112 00113 if (destUrl.isLocalFile()) { 00114 QFileInfo finfo (destUrl.toLocalFile()); 00115 if (finfo.exists()) { 00116 QDateTime now = QDateTime::currentDateTime(); 00117 KIO::RenameDialog dlg (parent, i18n("Overwrite File?"), url, destUrl, 00118 KIO::RenameDialog_Mode(KIO::M_OVERWRITE | KIO::M_SKIP), 00119 -1, finfo.size(), 00120 now.toTime_t(), finfo.created().toTime_t(), 00121 now.toTime_t(), finfo.lastModified().toTime_t()); 00122 result = dlg.exec(); 00123 } 00124 } 00125 } while (result == KIO::R_CANCEL && destUrl.isValid()); 00126 00127 return destUrl; 00128 } 00129 00130 static bool downloadResource (const KUrl& srcUrl, const QString& suggestedName = QString(), 00131 QWidget* parent = 0, const KIO::MetaData& metaData = KIO::MetaData()) 00132 { 00133 const KUrl& destUrl = promptUser(parent, srcUrl, suggestedName); 00134 00135 if (!destUrl.isValid()) 00136 return false; 00137 00138 KIO::Job *job = KIO::file_copy(srcUrl, destUrl, -1, KIO::Overwrite); 00139 00140 if (!metaData.isEmpty()) 00141 job->setMetaData(metaData); 00142 00143 job->addMetaData(QL1S("MaxCacheSize"), QL1S("0")); // Don't store in http cache. 00144 job->addMetaData(QL1S("cache"), QL1S("cache")); // Use entry from cache if available. 00145 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 00146 return true; 00147 } 00148 00149 static bool isReplyStatusOk(const QNetworkReply* reply) 00150 { 00151 if (!reply || reply->error() != QNetworkReply::NoError) 00152 return false; 00153 00154 // Check HTTP status code only for http and webdav protocols... 00155 const QString scheme = reply->url().scheme(); 00156 if (scheme.startsWith(QLatin1String("http"), Qt::CaseInsensitive) || 00157 scheme.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive)) { 00158 bool ok = false; 00159 const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); 00160 if (!ok || statusCode < 200 || statusCode > 299) 00161 return false; 00162 } 00163 00164 return true; 00165 } 00166 00167 class KWebPage::KWebPagePrivate 00168 { 00169 public: 00170 KWebPagePrivate() : inPrivateBrowsingMode(false) {} 00171 void _k_copyResultToTempFile(KJob * job) 00172 { 00173 if ( job->error() ) 00174 job->uiDelegate()->showErrorMessage(); 00175 else // Same as KRun::foundMimeType but with a different URL 00176 (void)KRun::runUrl(static_cast<KIO::FileCopyJob *>(job)->destUrl(), mimeType, window); 00177 } 00178 00179 QPointer<QWidget> window; 00180 QString mimeType; 00181 QPointer<KWebWallet> wallet; 00182 bool inPrivateBrowsingMode; 00183 }; 00184 00185 00186 KWebPage::KWebPage(QObject *parent, Integration flags) 00187 :QWebPage(parent), d(new KWebPagePrivate) 00188 { 00189 // KDE KParts integration for <embed> tag... 00190 if (!flags || (flags & KPartsIntegration)) 00191 setPluginFactory(new KWebPluginFactory(this)); 00192 00193 WId windowId = 0; 00194 QWidget *widget = qobject_cast<QWidget*>(parent); 00195 if (widget && widget->window()) 00196 windowId = widget->window()->winId(); 00197 00198 // KDE IO (KIO) integration... 00199 if (!flags || (flags & KIOIntegration)) { 00200 KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(this); 00201 // Disable QtWebKit's internal cache to avoid duplication with the one in KIO... 00202 manager->setCache(0); 00203 manager->setCookieJarWindowId(windowId); 00204 setNetworkAccessManager(manager); 00205 } 00206 00207 // KWallet integration... 00208 if (!flags || (flags & KWalletIntegration)) 00209 setWallet(new KWebWallet(0, windowId)); 00210 00211 action(Back)->setIcon(KIcon("go-previous")); 00212 action(Forward)->setIcon(KIcon("go-next")); 00213 action(Reload)->setIcon(KIcon("view-refresh")); 00214 action(Stop)->setIcon(KIcon("process-stop")); 00215 action(Cut)->setIcon(KIcon("edit-cut")); 00216 action(Copy)->setIcon(KIcon("edit-copy")); 00217 action(Paste)->setIcon(KIcon("edit-paste")); 00218 action(Undo)->setIcon(KIcon("edit-undo")); 00219 action(Redo)->setIcon(KIcon("edit-redo")); 00220 action(InspectElement)->setIcon(KIcon("view-process-all")); 00221 action(OpenLinkInNewWindow)->setIcon(KIcon("window-new")); 00222 action(OpenFrameInNewWindow)->setIcon(KIcon("window-new")); 00223 action(OpenImageInNewWindow)->setIcon(KIcon("window-new")); 00224 action(CopyLinkToClipboard)->setIcon(KIcon("edit-copy")); 00225 action(CopyImageToClipboard)->setIcon(KIcon("edit-copy")); 00226 action(ToggleBold)->setIcon(KIcon("format-text-bold")); 00227 action(ToggleItalic)->setIcon(KIcon("format-text-italic")); 00228 action(ToggleUnderline)->setIcon(KIcon("format-text-underline")); 00229 action(DownloadLinkToDisk)->setIcon(KIcon("document-save")); 00230 action(DownloadImageToDisk)->setIcon(KIcon("document-save")); 00231 00232 settings()->setWebGraphic(QWebSettings::MissingPluginGraphic, KIcon("preferences-plugin").pixmap(32, 32)); 00233 settings()->setWebGraphic(QWebSettings::MissingImageGraphic, KIcon("image-missing").pixmap(32, 32)); 00234 settings()->setWebGraphic(QWebSettings::DefaultFrameIconGraphic, KIcon("applications-internet").pixmap(32, 32)); 00235 00236 action(Back)->setShortcut(KStandardShortcut::back().primary()); 00237 action(Forward)->setShortcut(KStandardShortcut::forward().primary()); 00238 action(Reload)->setShortcut(KStandardShortcut::reload().primary()); 00239 action(Stop)->setShortcut(QKeySequence(Qt::Key_Escape)); 00240 action(Cut)->setShortcut(KStandardShortcut::cut().primary()); 00241 action(Copy)->setShortcut(KStandardShortcut::copy().primary()); 00242 action(Paste)->setShortcut(KStandardShortcut::paste().primary()); 00243 action(Undo)->setShortcut(KStandardShortcut::undo().primary()); 00244 action(Redo)->setShortcut(KStandardShortcut::redo().primary()); 00245 action(SelectAll)->setShortcut(KStandardShortcut::selectAll().primary()); 00246 } 00247 00248 KWebPage::~KWebPage() 00249 { 00250 delete d; 00251 } 00252 00253 bool KWebPage::isExternalContentAllowed() const 00254 { 00255 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00256 if (manager) 00257 return manager->isExternalContentAllowed(); 00258 return true; 00259 } 00260 00261 KWebWallet *KWebPage::wallet() const 00262 { 00263 return d->wallet; 00264 } 00265 00266 void KWebPage::setAllowExternalContent(bool allow) 00267 { 00268 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00269 if (manager) 00270 manager->setExternalContentAllowed(allow); 00271 } 00272 00273 void KWebPage::setWallet(KWebWallet* wallet) 00274 { 00275 // Delete the current wallet if this object is its parent... 00276 if (d->wallet && this == d->wallet->parent()) 00277 delete d->wallet; 00278 00279 d->wallet = wallet; 00280 00281 if (d->wallet) 00282 d->wallet->setParent(this); 00283 } 00284 00285 void KWebPage::downloadRequest(const QNetworkRequest &request) 00286 { 00287 downloadResource(request.url(), QString(), view(), 00288 request.attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap()); 00289 } 00290 00291 void KWebPage::downloadUrl(const KUrl &url) 00292 { 00293 downloadResource(url, QString(), view()); 00294 } 00295 00296 void KWebPage::downloadResponse(QNetworkReply *reply) 00297 { 00298 Q_ASSERT(reply); 00299 00300 if (!reply) 00301 return; 00302 00303 // Put the job on hold only for the protocols we know about (read: http). 00304 KIO::Integration::AccessManager::putReplyOnHold(reply); 00305 00306 // Reply url... 00307 const KUrl requestUrl (reply->request().url()); 00308 00309 // Get the top level window... 00310 QWidget* topLevelWindow = view() ? view()->window() : 0; 00311 00312 // Get suggested file name... 00313 const KIO::MetaData& metaData = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap(); 00314 const QString suggestedFileName = metaData.value(QL1S("content-disposition-filename")); 00315 const QString contentDispositionType = metaData.value(QL1S("content-disposition-type")); 00316 00317 // Get the mime-type... 00318 QString mimeType; 00319 extractMimeType(reply, mimeType); 00320 // Convert executable text files to plain text... 00321 if (KParts::BrowserRun::isTextExecutable(mimeType)) 00322 mimeType = QL1S("text/plain"); 00323 00324 //kDebug(800) << "Content-disposition:" << contentDispositionType << suggestedFileName; 00325 //kDebug(800) << "Got unsupported content of type:" << mimeType << "URL:" << requestUrl; 00326 //kDebug(800) << "Error code:" << reply->error() << reply->errorString(); 00327 00328 if (isReplyStatusOk(reply)) { 00329 KParts::BrowserOpenOrSaveQuestion::Result result; 00330 KParts::BrowserOpenOrSaveQuestion dlg(topLevelWindow, requestUrl, mimeType); 00331 dlg.setSuggestedFileName(suggestedFileName); 00332 dlg.setFeatures(KParts::BrowserOpenOrSaveQuestion::ServiceSelection); 00333 result = dlg.askOpenOrSave(); 00334 00335 switch (result) { 00336 case KParts::BrowserOpenOrSaveQuestion::Open: 00337 // Handle Post operations that return content... 00338 if (reply->operation() == QNetworkAccessManager::PostOperation) { 00339 d->mimeType = mimeType; 00340 d->window = topLevelWindow; 00341 QFileInfo finfo (suggestedFileName.isEmpty() ? requestUrl.fileName() : suggestedFileName); 00342 KTemporaryFile tempFile; 00343 tempFile.setSuffix(QL1C('.') + finfo.suffix()); 00344 tempFile.setAutoRemove(false); 00345 tempFile.open(); 00346 KUrl destUrl; 00347 destUrl.setPath(tempFile.fileName()); 00348 KIO::Job *job = KIO::file_copy(requestUrl, destUrl, 0600, KIO::Overwrite); 00349 job->ui()->setWindow(topLevelWindow); 00350 connect(job, SIGNAL(result(KJob *)), 00351 this, SLOT(_k_copyResultToTempFile(KJob*))); 00352 return; 00353 } 00354 00355 // Ask before running any executables... 00356 if (KParts::BrowserRun::allowExecution(mimeType, requestUrl)) { 00357 KService::Ptr offer = dlg.selectedService(); 00358 // HACK: The check below is necessary to break an infinite 00359 // recursion that occurs whenever this function is called as a result 00360 // of receiving content that can be rendered by the app using this engine. 00361 // For example a text/html header that containing a content-disposition 00362 // header is received by the app using this class. 00363 if (isMimeTypeAssociatedWithSelf(mimeType, offer)) { 00364 QNetworkRequest req (reply->request()); 00365 req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); 00366 currentFrame()->load(req); 00367 return; 00368 } 00369 00370 if (offer) { 00371 KUrl::List list; 00372 list.append(requestUrl); 00373 //kDebug(800) << "Suggested file name:" << suggestedFileName; 00374 if (offer->categories().contains(QL1S("KDE"), Qt::CaseInsensitive)) { 00375 KIO::Scheduler::publishSlaveOnHold(); 00376 KRun::run(*offer, list, topLevelWindow , false, suggestedFileName); 00377 return; 00378 } 00379 // For non KDE applications, we launch and kill the slave-on-hold... 00380 KRun::run(*offer, list, topLevelWindow , false, suggestedFileName); 00381 } else { 00382 (void)new KRun(requestUrl, topLevelWindow); 00383 } 00384 } 00385 break; 00386 case KParts::BrowserOpenOrSaveQuestion::Save: 00387 // Do not attempt to download directories and local files... 00388 if (mimeType == QL1S("inode/directory") || requestUrl.isLocalFile()) 00389 break; 00390 00391 downloadResource(requestUrl, suggestedFileName, topLevelWindow); 00392 return; 00393 case KParts::BrowserOpenOrSaveQuestion::Cancel: 00394 default: 00395 break; 00396 } 00397 } else { 00398 KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimeType); 00399 if (isMimeTypeAssociatedWithSelf(mimeType, offer)) { 00400 QNetworkRequest req (reply->request()); 00401 req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); 00402 currentFrame()->load(req); 00403 return; 00404 } 00405 } 00406 00407 // Remove any ioslave that was put on hold... 00408 KIO::Scheduler::removeSlaveOnHold(); 00409 } 00410 00411 QString KWebPage::sessionMetaData(const QString &key) const 00412 { 00413 QString value; 00414 00415 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00416 if (manager) 00417 value = manager->sessionMetaData().value(key); 00418 00419 return value; 00420 } 00421 00422 QString KWebPage::requestMetaData(const QString &key) const 00423 { 00424 QString value; 00425 00426 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00427 if (manager) 00428 value = manager->requestMetaData().value(key); 00429 00430 return value; 00431 } 00432 00433 void KWebPage::setSessionMetaData(const QString &key, const QString &value) 00434 { 00435 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00436 if (manager) 00437 manager->sessionMetaData()[key] = value; 00438 } 00439 00440 void KWebPage::setRequestMetaData(const QString &key, const QString &value) 00441 { 00442 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00443 if (manager) 00444 manager->requestMetaData()[key] = value; 00445 } 00446 00447 void KWebPage::removeSessionMetaData(const QString &key) 00448 { 00449 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00450 if (manager) 00451 manager->sessionMetaData().remove(key); 00452 } 00453 00454 void KWebPage::removeRequestMetaData(const QString &key) 00455 { 00456 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00457 if (manager) 00458 manager->requestMetaData().remove(key); 00459 } 00460 00461 QString KWebPage::userAgentForUrl(const QUrl& _url) const 00462 { 00463 const KUrl url(_url); 00464 const QString userAgent = KProtocolManager::userAgentForHost((url.isLocalFile() ? QL1S("localhost") : url.host())); 00465 00466 if (userAgent == KProtocolManager::defaultUserAgent()) 00467 return QWebPage::userAgentForUrl(_url); 00468 00469 return userAgent; 00470 } 00471 00472 static void setDisableCookieJarStorage(QNetworkAccessManager* manager, bool status) 00473 { 00474 if (manager) { 00475 KIO::Integration::CookieJar *cookieJar = manager ? qobject_cast<KIO::Integration::CookieJar*>(manager->cookieJar()) : 0; 00476 if (cookieJar) { 00477 //kDebug(800) << "Store cookies ?" << !status; 00478 cookieJar->setDisableCookieStorage(status); 00479 } 00480 } 00481 } 00482 00483 bool KWebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type) 00484 { 00485 kDebug(800) << "url:" << request.url() << ", type:" << type << ", frame:" << frame; 00486 00487 if (frame && d->wallet && type == QWebPage::NavigationTypeFormSubmitted) 00488 d->wallet->saveFormData(frame); 00489 00490 // Make sure nothing is cached when private browsing mode is enabled... 00491 if (settings()->testAttribute(QWebSettings::PrivateBrowsingEnabled)) { 00492 if (!d->inPrivateBrowsingMode) { 00493 setDisableCookieJarStorage(networkAccessManager(), true); 00494 setSessionMetaData(QL1S("no-cache"), QL1S("true")); 00495 d->inPrivateBrowsingMode = true; 00496 } 00497 } else { 00498 if (d->inPrivateBrowsingMode) { 00499 setDisableCookieJarStorage(networkAccessManager(), false); 00500 removeSessionMetaData(QL1S("no-cache")); 00501 d->inPrivateBrowsingMode = false; 00502 } 00503 } 00504 00505 /* 00506 If the navigation request is from the main frame, set the cross-domain 00507 meta-data value to the current url for proper integration with KCookieJar... 00508 */ 00509 if (frame == mainFrame() && type != QWebPage::NavigationTypeReload) 00510 setSessionMetaData(QL1S("cross-domain"), request.url().toString()); 00511 00512 return QWebPage::acceptNavigationRequest(frame, request, type); 00513 } 00514 00515 #include "kwebpage.moc"
KDE 4.6 API Reference