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 <kparts/browseropenorsavequestion.h> 00051 00052 // Qt 00053 #include <QtCore/QPointer> 00054 #include <QtCore/QFileInfo> 00055 #include <QtCore/QCoreApplication> 00056 #include <QtWebKit/QWebFrame> 00057 #include <QtNetwork/QNetworkReply> 00058 00059 00060 #define QL1S(x) QLatin1String(x) 00061 #define QL1C(x) QLatin1Char(x) 00062 00063 static void reloadRequestWithoutDisposition (QNetworkReply* reply) 00064 { 00065 QNetworkRequest req (reply->request()); 00066 req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); 00067 00068 QWebFrame* frame = qobject_cast<QWebFrame*> (req.originatingObject()); 00069 if (!frame) 00070 return; 00071 00072 frame->load(req); 00073 } 00074 00075 static bool isMimeTypeAssociatedWithSelf(const KService::Ptr &offer) 00076 { 00077 if (!offer) 00078 return false; 00079 00080 kDebug(800) << offer->desktopEntryName(); 00081 00082 const QString& appName = QCoreApplication::applicationName(); 00083 00084 if (appName == offer->desktopEntryName() || offer->exec().trimmed().startsWith(appName)) 00085 return true; 00086 00087 // konqueror exception since it uses kfmclient to open html content... 00088 if (appName == QL1S("konqueror") && offer->exec().trimmed().startsWith(QL1S("kfmclient"))) 00089 return true; 00090 00091 return false; 00092 } 00093 00094 static void extractMimeType(const QNetworkReply* reply, QString& mimeType) 00095 { 00096 mimeType.clear(); 00097 const KIO::MetaData& metaData = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap(); 00098 if (metaData.contains(QL1S("content-type"))) 00099 mimeType = metaData.value(QL1S("content-type")); 00100 00101 if (!mimeType.isEmpty()) 00102 return; 00103 00104 if (!reply->hasRawHeader("Content-Type")) 00105 return; 00106 00107 const QString value (QL1S(reply->rawHeader("Content-Type").simplified().constData())); 00108 const int index = value.indexOf(QL1C(';')); 00109 mimeType = ((index == -1) ? value : value.left(index)); 00110 } 00111 00112 static KUrl promptUser (QWidget *parent, const KUrl& url, const QString& suggestedName) 00113 { 00114 KUrl destUrl; 00115 int result = KIO::R_OVERWRITE; 00116 const QString fileName ((suggestedName.isEmpty() ? url.fileName() : suggestedName)); 00117 00118 do { 00119 // convert filename to URL using fromPath to avoid trouble with ':' in filenames (#184202) 00120 destUrl = KFileDialog::getSaveFileName(KUrl::fromPath(fileName), QString(), parent); 00121 00122 if (destUrl.isLocalFile()) { 00123 QFileInfo finfo (destUrl.toLocalFile()); 00124 if (finfo.exists()) { 00125 QDateTime now = QDateTime::currentDateTime(); 00126 KIO::RenameDialog dlg (parent, i18n("Overwrite File?"), url, destUrl, 00127 KIO::RenameDialog_Mode(KIO::M_OVERWRITE | KIO::M_SKIP), 00128 -1, finfo.size(), 00129 now.toTime_t(), finfo.created().toTime_t(), 00130 now.toTime_t(), finfo.lastModified().toTime_t()); 00131 result = dlg.exec(); 00132 } 00133 } 00134 } while (result == KIO::R_CANCEL && destUrl.isValid()); 00135 00136 return destUrl; 00137 } 00138 00139 static bool downloadResource (const KUrl& srcUrl, const QString& suggestedName = QString(), 00140 QWidget* parent = 0, const KIO::MetaData& metaData = KIO::MetaData()) 00141 { 00142 const KUrl& destUrl = promptUser(parent, srcUrl, suggestedName); 00143 00144 if (!destUrl.isValid()) 00145 return false; 00146 00147 KIO::Job *job = KIO::file_copy(srcUrl, destUrl); 00148 00149 if (!metaData.isEmpty()) 00150 job->setMetaData(metaData); 00151 00152 job->addMetaData(QL1S("MaxCacheSize"), QL1S("0")); // Don't store in http cache. 00153 job->addMetaData(QL1S("cache"), QL1S("cache")); // Use entry from cache if available. 00154 job->ui()->setWindow((parent ? parent->window() : 0)); 00155 job->ui()->setAutoErrorHandlingEnabled(true); 00156 return true; 00157 } 00158 00159 static bool isReplyStatusOk(const QNetworkReply* reply) 00160 { 00161 if (!reply || reply->error() != QNetworkReply::NoError) 00162 return false; 00163 00164 // Check HTTP status code only for http and webdav protocols... 00165 const QString scheme = reply->url().scheme(); 00166 if (scheme.startsWith(QLatin1String("http"), Qt::CaseInsensitive) || 00167 scheme.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive)) { 00168 bool ok = false; 00169 const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); 00170 if (!ok || statusCode < 200 || statusCode > 299) 00171 return false; 00172 } 00173 00174 return true; 00175 } 00176 00177 class KWebPage::KWebPagePrivate 00178 { 00179 public: 00180 KWebPagePrivate() : inPrivateBrowsingMode(false) {} 00181 void _k_copyResultToTempFile(KJob * job) 00182 { 00183 if ( job->error() ) { 00184 job->uiDelegate()->showErrorMessage(); 00185 return; 00186 } 00187 // Same as KRun::foundMimeType but with a different URL 00188 (void)KRun::runUrl(static_cast<KIO::FileCopyJob *>(job)->destUrl(), mimeType, window); 00189 } 00190 00191 QPointer<QWidget> window; 00192 QString mimeType; 00193 QPointer<KWebWallet> wallet; 00194 bool inPrivateBrowsingMode; 00195 }; 00196 00197 static void setActionIcon(QAction* action, const QIcon& icon) 00198 { 00199 if (action) { 00200 action->setIcon(icon); 00201 } 00202 } 00203 00204 static void setActionShortcut(QAction* action, const KShortcut& shortcut) 00205 { 00206 if (action) { 00207 action->setShortcuts(shortcut.toList()); 00208 } 00209 } 00210 00211 KWebPage::KWebPage(QObject *parent, Integration flags) 00212 :QWebPage(parent), d(new KWebPagePrivate) 00213 { 00214 // KDE KParts integration for <embed> tag... 00215 if (!flags || (flags & KPartsIntegration)) 00216 setPluginFactory(new KWebPluginFactory(this)); 00217 00218 QWidget *parentWidget = qobject_cast<QWidget*>(parent); 00219 QWidget *window = parentWidget ? parentWidget->window() : 0; 00220 00221 // KDE IO (KIO) integration... 00222 if (!flags || (flags & KIOIntegration)) { 00223 KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(this); 00224 // Disable QtWebKit's internal cache to avoid duplication with the one in KIO... 00225 manager->setCache(0); 00226 manager->setWindow(window); 00227 manager->setEmitReadyReadOnMetaDataChange(true); 00228 setNetworkAccessManager(manager); 00229 } 00230 00231 // KWallet integration... 00232 if (!flags || (flags & KWalletIntegration)) { 00233 setWallet(new KWebWallet(0, (window ? window->winId() : 0) )); 00234 } 00235 00236 setActionIcon(action(Back), KIcon("go-previous")); 00237 setActionIcon(action(Forward), KIcon("go-next")); 00238 setActionIcon(action(Reload), KIcon("view-refresh")); 00239 setActionIcon(action(Stop), KIcon("process-stop")); 00240 setActionIcon(action(Cut), KIcon("edit-cut")); 00241 setActionIcon(action(Copy), KIcon("edit-copy")); 00242 setActionIcon(action(Paste), KIcon("edit-paste")); 00243 setActionIcon(action(Undo), KIcon("edit-undo")); 00244 setActionIcon(action(Redo), KIcon("edit-redo")); 00245 setActionIcon(action(InspectElement), KIcon("view-process-all")); 00246 setActionIcon(action(OpenLinkInNewWindow), KIcon("window-new")); 00247 setActionIcon(action(OpenFrameInNewWindow), KIcon("window-new")); 00248 setActionIcon(action(OpenImageInNewWindow), KIcon("window-new")); 00249 setActionIcon(action(CopyLinkToClipboard), KIcon("edit-copy")); 00250 setActionIcon(action(CopyImageToClipboard), KIcon("edit-copy")); 00251 setActionIcon(action(ToggleBold), KIcon("format-text-bold")); 00252 setActionIcon(action(ToggleItalic), KIcon("format-text-italic")); 00253 setActionIcon(action(ToggleUnderline), KIcon("format-text-underline")); 00254 setActionIcon(action(DownloadLinkToDisk), KIcon("document-save")); 00255 setActionIcon(action(DownloadImageToDisk), KIcon("document-save")); 00256 00257 settings()->setWebGraphic(QWebSettings::MissingPluginGraphic, KIcon("preferences-plugin").pixmap(32, 32)); 00258 settings()->setWebGraphic(QWebSettings::MissingImageGraphic, KIcon("image-missing").pixmap(32, 32)); 00259 settings()->setWebGraphic(QWebSettings::DefaultFrameIconGraphic, KIcon("applications-internet").pixmap(32, 32)); 00260 00261 setActionShortcut(action(Back), KStandardShortcut::back()); 00262 setActionShortcut(action(Forward), KStandardShortcut::forward()); 00263 setActionShortcut(action(Reload), KStandardShortcut::reload()); 00264 setActionShortcut(action(Stop), KShortcut(QKeySequence(Qt::Key_Escape))); 00265 setActionShortcut(action(Cut), KStandardShortcut::cut()); 00266 setActionShortcut(action(Copy), KStandardShortcut::copy()); 00267 setActionShortcut(action(Paste), KStandardShortcut::paste()); 00268 setActionShortcut(action(Undo), KStandardShortcut::undo()); 00269 setActionShortcut(action(Redo), KStandardShortcut::redo()); 00270 setActionShortcut(action(SelectAll), KStandardShortcut::selectAll()); 00271 } 00272 00273 KWebPage::~KWebPage() 00274 { 00275 delete d; 00276 } 00277 00278 bool KWebPage::isExternalContentAllowed() const 00279 { 00280 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00281 if (manager) 00282 return manager->isExternalContentAllowed(); 00283 return true; 00284 } 00285 00286 KWebWallet *KWebPage::wallet() const 00287 { 00288 return d->wallet; 00289 } 00290 00291 void KWebPage::setAllowExternalContent(bool allow) 00292 { 00293 KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager()); 00294 if (manager) 00295 manager->setExternalContentAllowed(allow); 00296 } 00297 00298 void KWebPage::setWallet(KWebWallet* wallet) 00299 { 00300 // Delete the current wallet if this object is its parent... 00301 if (d->wallet && this == d->wallet->parent()) 00302 delete d->wallet; 00303 00304 d->wallet = wallet; 00305 00306 if (d->wallet) 00307 d->wallet->setParent(this); 00308 } 00309 00310 void KWebPage::downloadRequest(const QNetworkRequest &request) 00311 { 00312 downloadResource(request.url(), QString(), view(), 00313 request.attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap()); 00314 } 00315 00316 void KWebPage::downloadUrl(const KUrl &url) 00317 { 00318 downloadResource(url, QString(), view()); 00319 } 00320 00321 void KWebPage::downloadResponse(QNetworkReply *reply) 00322 { 00323 Q_ASSERT(reply); 00324 00325 if (!reply) 00326 return; 00327 00328 // Put the job on hold only for the protocols we know about (read: http). 00329 KIO::Integration::AccessManager::putReplyOnHold(reply); 00330 00331 QString mimeType; 00332 KIO::MetaData metaData; 00333 00334 if (handleReply(reply, &mimeType, &metaData)) { 00335 return; 00336 } 00337 00338 const KUrl replyUrl (reply->url()); 00339 QWidget* topLevelWindow = view() ? view()->window() : 0; 00340 00341 // Ask KRun to handle the response when mimetype is unknown 00342 if (mimeType.isEmpty()) { 00343 (void)new KRun(replyUrl, topLevelWindow, 0 , replyUrl.isLocalFile()); 00344 return; 00345 } 00346 00347 // Ask KRun::runUrl to handle the response when mimetype is inode/* 00348 if (mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive) && 00349 KRun::runUrl(replyUrl, mimeType, topLevelWindow, false, false, 00350 metaData.value(QL1S("content-disposition-filename")))) { 00351 return; 00352 } 00353 } 00354 00355 QString KWebPage::sessionMetaData(const QString &key) const 00356 { 00357 QString value; 00358 00359 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00360 if (manager) 00361 value = manager->sessionMetaData().value(key); 00362 00363 return value; 00364 } 00365 00366 QString KWebPage::requestMetaData(const QString &key) const 00367 { 00368 QString value; 00369 00370 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00371 if (manager) 00372 value = manager->requestMetaData().value(key); 00373 00374 return value; 00375 } 00376 00377 void KWebPage::setSessionMetaData(const QString &key, const QString &value) 00378 { 00379 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00380 if (manager) 00381 manager->sessionMetaData()[key] = value; 00382 } 00383 00384 void KWebPage::setRequestMetaData(const QString &key, const QString &value) 00385 { 00386 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00387 if (manager) 00388 manager->requestMetaData()[key] = value; 00389 } 00390 00391 void KWebPage::removeSessionMetaData(const QString &key) 00392 { 00393 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00394 if (manager) 00395 manager->sessionMetaData().remove(key); 00396 } 00397 00398 void KWebPage::removeRequestMetaData(const QString &key) 00399 { 00400 KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager()); 00401 if (manager) 00402 manager->requestMetaData().remove(key); 00403 } 00404 00405 QString KWebPage::userAgentForUrl(const QUrl& _url) const 00406 { 00407 const KUrl url(_url); 00408 const QString userAgent = KProtocolManager::userAgentForHost((url.isLocalFile() ? QL1S("localhost") : url.host())); 00409 00410 if (userAgent == KProtocolManager::defaultUserAgent()) 00411 return QWebPage::userAgentForUrl(_url); 00412 00413 return userAgent; 00414 } 00415 00416 static void setDisableCookieJarStorage(QNetworkAccessManager* manager, bool status) 00417 { 00418 if (manager) { 00419 KIO::Integration::CookieJar *cookieJar = manager ? qobject_cast<KIO::Integration::CookieJar*>(manager->cookieJar()) : 0; 00420 if (cookieJar) { 00421 //kDebug(800) << "Store cookies ?" << !status; 00422 cookieJar->setDisableCookieStorage(status); 00423 } 00424 } 00425 } 00426 00427 bool KWebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type) 00428 { 00429 kDebug(800) << "url:" << request.url() << ", type:" << type << ", frame:" << frame; 00430 00431 if (frame && d->wallet && type == QWebPage::NavigationTypeFormSubmitted) 00432 d->wallet->saveFormData(frame); 00433 00434 // Make sure nothing is cached when private browsing mode is enabled... 00435 if (settings()->testAttribute(QWebSettings::PrivateBrowsingEnabled)) { 00436 if (!d->inPrivateBrowsingMode) { 00437 setDisableCookieJarStorage(networkAccessManager(), true); 00438 setSessionMetaData(QL1S("no-cache"), QL1S("true")); 00439 d->inPrivateBrowsingMode = true; 00440 } 00441 } else { 00442 if (d->inPrivateBrowsingMode) { 00443 setDisableCookieJarStorage(networkAccessManager(), false); 00444 removeSessionMetaData(QL1S("no-cache")); 00445 d->inPrivateBrowsingMode = false; 00446 } 00447 } 00448 00449 /* 00450 If the navigation request is from the main frame, set the cross-domain 00451 meta-data value to the current url for proper integration with KCookieJar... 00452 */ 00453 if (frame == mainFrame() && type != QWebPage::NavigationTypeReload) 00454 setSessionMetaData(QL1S("cross-domain"), request.url().toString()); 00455 00456 return QWebPage::acceptNavigationRequest(frame, request, type); 00457 } 00458 00459 bool KWebPage::handleReply(QNetworkReply* reply, QString* contentType, KIO::MetaData* metaData) 00460 { 00461 // Reply url... 00462 const KUrl replyUrl (reply->url()); 00463 00464 // Get the top level window... 00465 QWidget* topLevelWindow = view() ? view()->window() : 0; 00466 00467 // Get suggested file name... 00468 const KIO::MetaData& data = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap(); 00469 const QString suggestedFileName = data.value(QL1S("content-disposition-filename")); 00470 if (metaData) { 00471 *metaData = data; 00472 } 00473 00474 // Get the mime-type... 00475 QString mimeType; 00476 extractMimeType(reply, mimeType); 00477 if (contentType) { 00478 *contentType = mimeType; 00479 } 00480 00481 // Let the calling function deal with handling empty or inode/* mimetypes... 00482 if (mimeType.isEmpty() || mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive)) { 00483 return false; 00484 } 00485 00486 // Convert executable text files to plain text... 00487 if (KParts::BrowserRun::isTextExecutable(mimeType)) 00488 mimeType = QL1S("text/plain"); 00489 00490 //kDebug(800) << "Content-disposition:" << suggestedFileName; 00491 //kDebug(800) << "Got unsupported content of type:" << mimeType << "URL:" << replyUrl; 00492 //kDebug(800) << "Error code:" << reply->error() << reply->errorString(); 00493 00494 if (isReplyStatusOk(reply)) { 00495 KParts::BrowserOpenOrSaveQuestion::Result result; 00496 KParts::BrowserOpenOrSaveQuestion dlg(topLevelWindow, replyUrl, mimeType); 00497 dlg.setSuggestedFileName(suggestedFileName); 00498 dlg.setFeatures(KParts::BrowserOpenOrSaveQuestion::ServiceSelection); 00499 result = dlg.askOpenOrSave(); 00500 00501 switch (result) { 00502 case KParts::BrowserOpenOrSaveQuestion::Open: 00503 // Handle Post operations that return content... 00504 if (reply->operation() == QNetworkAccessManager::PostOperation) { 00505 d->mimeType = mimeType; 00506 d->window = topLevelWindow; 00507 QFileInfo finfo (suggestedFileName.isEmpty() ? replyUrl.fileName() : suggestedFileName); 00508 KTemporaryFile tempFile; 00509 tempFile.setSuffix(QL1C('.') + finfo.suffix()); 00510 tempFile.setAutoRemove(false); 00511 tempFile.open(); 00512 KUrl destUrl; 00513 destUrl.setPath(tempFile.fileName()); 00514 KIO::Job *job = KIO::file_copy(replyUrl, destUrl, 0600, KIO::Overwrite); 00515 job->ui()->setWindow(topLevelWindow); 00516 job->ui()->setAutoErrorHandlingEnabled(true); 00517 connect(job, SIGNAL(result(KJob *)), 00518 this, SLOT(_k_copyResultToTempFile(KJob*))); 00519 return true; 00520 } 00521 00522 // Ask before running any executables... 00523 if (KParts::BrowserRun::allowExecution(mimeType, replyUrl)) { 00524 KService::Ptr offer = dlg.selectedService(); 00525 // HACK: The check below is necessary to break an infinite 00526 // recursion that occurs whenever this function is called as a result 00527 // of receiving content that can be rendered by the app using this engine. 00528 // For example a text/html header that containing a content-disposition 00529 // header is received by the app using this class. 00530 if (isMimeTypeAssociatedWithSelf(offer)) { 00531 reloadRequestWithoutDisposition(reply); 00532 } else { 00533 KUrl::List list; 00534 list.append(replyUrl); 00535 bool success = false; 00536 // kDebug(800) << "Suggested file name:" << suggestedFileName; 00537 if (offer) { 00538 success = KRun::run(*offer, list, topLevelWindow , false, suggestedFileName); 00539 } else { 00540 success = KRun::displayOpenWithDialog(list, topLevelWindow, false, suggestedFileName); 00541 } 00542 // For non KIO apps and cancelled Open With dialog, remove slave on hold. 00543 if (!success || (offer && !offer->categories().contains(QL1S("KDE")))) { 00544 KIO::SimpleJob::removeOnHold(); // Remove any slave-on-hold... 00545 } 00546 } 00547 return true; 00548 } 00549 // TODO: Instead of silently failing when allowExecution fails, notify 00550 // the user why the requested action cannot be fulfilled... 00551 break; 00552 case KParts::BrowserOpenOrSaveQuestion::Save: 00553 // Do not download local files... 00554 if (!replyUrl.isLocalFile()) { 00555 return downloadResource(replyUrl, suggestedFileName, topLevelWindow); 00556 } 00557 return true; 00558 case KParts::BrowserOpenOrSaveQuestion::Cancel: 00559 default: 00560 return true; 00561 } 00562 } else { 00563 KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimeType); 00564 if (isMimeTypeAssociatedWithSelf(offer)) { 00565 reloadRequestWithoutDisposition(reply); 00566 return true; 00567 } 00568 } 00569 00570 return false; 00571 } 00572 00573 #include "kwebpage.moc" 00574
KDE 4.7 API Reference