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 class KWebPage::KWebPagePrivate 00150 { 00151 public: 00152 KWebPagePrivate() : inPrivateBrowsingMode(false) {} 00153 void _k_copyResultToTempFile(KJob * job) 00154 { 00155 if ( job->error() ) 00156 job->uiDelegate()->showErrorMessage(); 00157 else // Same as KRun::foundMimeType but with a different URL 00158 (void)KRun::runUrl(static_cast<KIO::FileCopyJob *>(job)->destUrl(), mimeType, window); 00159 } 00160 00161 QPointer<QWidget> window; 00162 QString mimeType; 00163 QPointer<KWebWallet> wallet; 00164 bool inPrivateBrowsingMode; 00165 }; 00166 00167 00168 KWebPage::KWebPage(QObject *parent, Integration flags) 00169 :QWebPage(parent), d(new KWebPagePrivate) 00170 { 00171 // KDE KParts integration for <embed> tag... 00172 if (!flags || (flags & KPartsIntegration)) 00173 setPluginFactory(new KWebPluginFactory(this)); 00174 00175 WId windowId = 0; 00176 QWidget *widget = qobject_cast<QWidget*>(parent); 00177 if (widget && widget->window()) 00178 windowId = widget->window()->winId(); 00179 00180 // KDE IO (KIO) integration... 00181 if (!flags || (flags & KIOIntegration)) { 00182 KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(this); 00183 // Disable QtWebKit's internal cache to avoid duplication with the one in KIO... 00184 manager->setCache(0); 00185 manager->setCookieJarWindowId(windowId); 00186 setNetworkAccessManager(manager); 00187 } 00188 00189 // KWallet integration... 00190 if (!flags || (flags & KWalletIntegration)) 00191 setWallet(new KWebWallet(0, windowId)); 00192 00193 action(Back)->setIcon(KIcon("go-previous")); 00194 action(Forward)->setIcon(KIcon("go-next")); 00195 action(Reload)->setIcon(KIcon("view-refresh")); 00196 action(Stop)->setIcon(KIcon("process-stop")); 00197 action(Cut)->setIcon(KIcon("edit-cut")); 00198 action(Copy)->setIcon(KIcon("edit-copy")); 00199 action(Paste)->setIcon(KIcon("edit-paste")); 00200 action(Undo)->setIcon(KIcon("edit-undo")); 00201 action(Redo)->setIcon(KIcon("edit-redo")); 00202 action(InspectElement)->setIcon(KIcon("view-process-all")); 00203 action(OpenLinkInNewWindow)->setIcon(KIcon("window-new")); 00204 action(OpenFrameInNewWindow)->setIcon(KIcon("window-new")); 00205 action(OpenImageInNewWindow)->setIcon(KIcon("window-new")); 00206 action(CopyLinkToClipboard)->setIcon(KIcon("edit-copy")); 00207 action(CopyImageToClipboard)->setIcon(KIcon("edit-copy")); 00208 action(ToggleBold)->setIcon(KIcon("format-text-bold")); 00209 action(ToggleItalic)->setIcon(KIcon("format-text-italic")); 00210 action(ToggleUnderline)->setIcon(KIcon("format-text-underline")); 00211 action(DownloadLinkToDisk)->setIcon(KIcon("document-save")); 00212 action(DownloadImageToDisk)->setIcon(KIcon("document-save")); 00213 00214 settings()->setWebGraphic(QWebSettings::MissingPluginGraphic, KIcon("preferences-plugin").pixmap(32, 32)); 00215 settings()->setWebGraphic(QWebSettings::MissingImageGraphic, KIcon("image-missing").pixmap(32, 32)); 00216 settings()->setWebGraphic(QWebSettings::DefaultFrameIconGraphic, KIcon("applications-internet").pixmap(32, 32)); 00217 00218 action(Back)->setShortcut(KStandardShortcut::back().primary()); 00219 action(Forward)->setShortcut(KStandardShortcut::forward().primary()); 00220 action(Reload)->setShortcut(KStandardShortcut::reload().primary()); 00221 action(Stop)->setShortcut(QKeySequence(Qt::Key_Escape)); 00222 action(Cut)->setShortcut(KStandardShortcut::cut().primary()); 00223 action(Copy)->setShortcut(KStandardShortcut::copy().primary()); 00224 action(Paste)->setShortcut(KStandardShortcut::paste().primary()); 00225 action(Undo)->setShortcut(KStandardShortcut::undo().primary()); 00226 action(Redo)->setShortcut(KStandardShortcut::redo().primary()); 00227 action(SelectAll)->setShortcut(KStandardShortcut::selectAll().primary()); 00228 } 00229 00230 KWebPage::~KWebPage() 00231 { 00232 delete d; 00233 } 00234 00235 bool KWebPage::isExternalContentAllowed() const 00236 { 00237 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00238 if (manager) 00239 return manager->isExternalContentAllowed(); 00240 return true; 00241 } 00242 00243 KWebWallet *KWebPage::wallet() const 00244 { 00245 return d->wallet; 00246 } 00247 00248 void KWebPage::setAllowExternalContent(bool allow) 00249 { 00250 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00251 if (manager) 00252 manager->setExternalContentAllowed(allow); 00253 } 00254 00255 void KWebPage::setWallet(KWebWallet* wallet) 00256 { 00257 // Delete the current wallet if this object is its parent... 00258 if (d->wallet && this == d->wallet->parent()) 00259 delete d->wallet; 00260 00261 d->wallet = wallet; 00262 00263 if (d->wallet) 00264 d->wallet->setParent(this); 00265 } 00266 00267 void KWebPage::downloadRequest(const QNetworkRequest &request) 00268 { 00269 downloadResource(request.url(), QString(), view(), 00270 request.attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap()); 00271 } 00272 00273 void KWebPage::downloadUrl(const KUrl &url) 00274 { 00275 downloadResource(url, QString(), view()); 00276 } 00277 00278 static bool isReplyStatusOk(const QNetworkReply* reply) 00279 { 00280 if (!reply || reply->error() != QNetworkReply::NoError) 00281 return false; 00282 00283 bool ok = false; 00284 const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); 00285 00286 if (!ok || statusCode < 200 || statusCode > 299) 00287 return false; 00288 00289 return true; 00290 } 00291 00292 void KWebPage::downloadResponse(QNetworkReply *reply) 00293 { 00294 Q_ASSERT(reply); 00295 00296 if (!reply) 00297 return; 00298 00299 // Put the job on hold... 00300 KIO::Integration::AccessManager::putReplyOnHold(reply); 00301 00302 // Reply url... 00303 const KUrl requestUrl (reply->request().url()); 00304 00305 // Get the top level window... 00306 QWidget* topLevelWindow = view() ? view()->window() : 0; 00307 00308 // Get suggested file name... 00309 const KIO::MetaData& metaData = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap(); 00310 const QString suggestedFileName = metaData.value(QL1S("content-disposition-filename")); 00311 const QString contentDispositionType = metaData.value(QL1S("content-disposition-type")); 00312 00313 // Get the mime-type... 00314 QString mimeType; 00315 extractMimeType(reply, mimeType); 00316 // Convert executable text files to plain text... 00317 if (KParts::BrowserRun::isTextExecutable(mimeType)) 00318 mimeType = QL1S("text/plain"); 00319 00320 //kDebug(800) << "Content-disposition:" << contentDispositionType << suggestedFileName; 00321 //kDebug(800) << "Got unsupported content of type:" << mimeType << "URL:" << requestUrl; 00322 //kDebug(800) << "Error code:" << reply->error() << reply->errorString(); 00323 if (isReplyStatusOk(reply)) { 00324 KParts::BrowserOpenOrSaveQuestion::Result result; 00325 KParts::BrowserOpenOrSaveQuestion dlg(topLevelWindow, requestUrl, mimeType); 00326 dlg.setSuggestedFileName(suggestedFileName); 00327 dlg.setFeatures(KParts::BrowserOpenOrSaveQuestion::ServiceSelection); 00328 result = dlg.askOpenOrSave(); 00329 00330 switch (result) { 00331 case KParts::BrowserOpenOrSaveQuestion::Open: 00332 // Handle Post operations that return content... 00333 if (reply->operation() == QNetworkAccessManager::PostOperation) { 00334 d->mimeType = mimeType; 00335 d->window = topLevelWindow; 00336 QFileInfo finfo (suggestedFileName.isEmpty() ? requestUrl.fileName() : suggestedFileName); 00337 KTemporaryFile tempFile; 00338 tempFile.setSuffix(QL1C('.') + finfo.suffix()); 00339 tempFile.setAutoRemove(false); 00340 tempFile.open(); 00341 KUrl destUrl; 00342 destUrl.setPath(tempFile.fileName()); 00343 KIO::Job *job = KIO::file_copy(requestUrl, destUrl, 0600, KIO::Overwrite); 00344 job->ui()->setWindow(topLevelWindow); 00345 connect(job, SIGNAL(result(KJob *)), 00346 this, SLOT(_k_copyResultToTempFile(KJob*))); 00347 return; 00348 } 00349 00350 // Ask before running any executables... 00351 if (KParts::BrowserRun::allowExecution(mimeType, requestUrl)) { 00352 KService::Ptr offer = dlg.selectedService(); 00353 // HACK: The check below is necessary to break an infinite 00354 // recursion that occurs whenever this function is called as a result 00355 // of receiving content that can be rendered by the app using this engine. 00356 // For example a text/html header that containing a content-disposition 00357 // header is received by the app using this class. 00358 if (isMimeTypeAssociatedWithSelf(mimeType, offer)) { 00359 QNetworkRequest req (reply->request()); 00360 req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); 00361 currentFrame()->load(req); 00362 return; 00363 } 00364 00365 if (offer) { 00366 KUrl::List list; 00367 list.append(requestUrl); 00368 //kDebug(800) << "Suggested file name:" << suggestedFileName; 00369 if (offer->categories().contains(QL1S("KDE"), Qt::CaseInsensitive)) { 00370 KIO::Scheduler::publishSlaveOnHold(); 00371 KRun::run(*offer, list, topLevelWindow , false, suggestedFileName); 00372 return; 00373 } 00374 // For non KDE applications, we launch and kill the slave-on-hold... 00375 KRun::run(*offer, list, topLevelWindow , false, suggestedFileName); 00376 } 00377 } 00378 break; 00379 case KParts::BrowserOpenOrSaveQuestion::Save: 00380 // Do not attempt to download directories and local files... 00381 if (mimeType == QL1S("inode/directory") || requestUrl.isLocalFile()) 00382 break; 00383 00384 downloadResource(requestUrl, suggestedFileName, topLevelWindow); 00385 return; 00386 case KParts::BrowserOpenOrSaveQuestion::Cancel: 00387 default: 00388 break; 00389 } 00390 } else { 00391 KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimeType); 00392 if (isMimeTypeAssociatedWithSelf(mimeType, offer)) { 00393 QNetworkRequest req (reply->request()); 00394 req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); 00395 currentFrame()->load(req); 00396 return; 00397 } 00398 } 00399 00400 // Remove any ioslave that was put on hold... 00401 KIO::Scheduler::removeSlaveOnHold(); 00402 } 00403 00404 QString KWebPage::sessionMetaData(const QString &key) const 00405 { 00406 QString value; 00407 00408 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00409 if (manager) 00410 value = manager->sessionMetaData().value(key); 00411 00412 return value; 00413 } 00414 00415 QString KWebPage::requestMetaData(const QString &key) const 00416 { 00417 QString value; 00418 00419 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00420 if (manager) 00421 value = manager->requestMetaData().value(key); 00422 00423 return value; 00424 } 00425 00426 void KWebPage::setSessionMetaData(const QString &key, const QString &value) 00427 { 00428 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00429 if (manager) 00430 manager->sessionMetaData()[key] = value; 00431 } 00432 00433 void KWebPage::setRequestMetaData(const QString &key, const QString &value) 00434 { 00435 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00436 if (manager) 00437 manager->requestMetaData()[key] = value; 00438 } 00439 00440 void KWebPage::removeSessionMetaData(const QString &key) 00441 { 00442 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00443 if (manager) 00444 manager->sessionMetaData().remove(key); 00445 } 00446 00447 void KWebPage::removeRequestMetaData(const QString &key) 00448 { 00449 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00450 if (manager) 00451 manager->requestMetaData().remove(key); 00452 } 00453 00454 QString KWebPage::userAgentForUrl(const QUrl& _url) const 00455 { 00456 const KUrl url(_url); 00457 const QString userAgent = KProtocolManager::userAgentForHost((url.isLocalFile() ? QL1S("localhost") : url.host())); 00458 00459 if (userAgent == KProtocolManager::defaultUserAgent()) 00460 return QWebPage::userAgentForUrl(_url); 00461 00462 return userAgent; 00463 } 00464 00465 static void setDisableCookieJarStorage(QNetworkAccessManager* manager, bool status) 00466 { 00467 if (manager) { 00468 KIO::Integration::CookieJar *cookieJar = manager ? qobject_cast<KIO::Integration::CookieJar*>(manager->cookieJar()) : 0; 00469 if (cookieJar) { 00470 //kDebug(800) << "Store cookies ?" << !status; 00471 cookieJar->setDisableCookieStorage(status); 00472 } 00473 } 00474 } 00475 00476 bool KWebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type) 00477 { 00478 kDebug(800) << "url:" << request.url() << ", type:" << type << ", frame:" << frame; 00479 00480 if (frame && d->wallet && type == QWebPage::NavigationTypeFormSubmitted) 00481 d->wallet->saveFormData(frame); 00482 00483 // Make sure nothing is cached when private browsing mode is enabled... 00484 if (settings()->testAttribute(QWebSettings::PrivateBrowsingEnabled)) { 00485 if (!d->inPrivateBrowsingMode) { 00486 setDisableCookieJarStorage(networkAccessManager(), true); 00487 setSessionMetaData(QL1S("no-cache"), QL1S("true")); 00488 d->inPrivateBrowsingMode = true; 00489 } 00490 } else { 00491 if (d->inPrivateBrowsingMode) { 00492 setDisableCookieJarStorage(networkAccessManager(), false); 00493 removeSessionMetaData(QL1S("no-cache")); 00494 d->inPrivateBrowsingMode = false; 00495 } 00496 } 00497 00498 /* 00499 If the navigation request is from the main frame, set the cross-domain 00500 meta-data value to the current url for proper integration with KCookieJar... 00501 */ 00502 if (frame == mainFrame() && type != QWebPage::NavigationTypeReload) 00503 setSessionMetaData(QL1S("cross-domain"), request.url().toString()); 00504 00505 return QWebPage::acceptNavigationRequest(frame, request, type); 00506 } 00507 00508 #include "kwebpage.moc"
KDE 4.6 API Reference