diff --git a/src/kwebpage.cpp b/src/kwebpage.cpp index 90239db..d4b04d9 100644 --- a/src/kwebpage.cpp +++ b/src/kwebpage.cpp @@ -1,631 +1,633 @@ /* * This file is part of the KDE project. * * Copyright (C) 2008 Dirk Mueller * Copyright (C) 2008 Urs Wolfer * Copyright (C) 2008 Michael Howell * Copyright (C) 2009,2010 Dawit Alemayehu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ // Own #include "kwebpage.h" #include "kwebwallet.h" // Local #include "kwebpluginfactory.h" // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) static void reloadRequestWithoutDisposition(QNetworkReply *reply) { QNetworkRequest req(reply->request()); req.setRawHeader("x-kdewebkit-ignore-disposition", "true"); QWebFrame *frame = qobject_cast (req.originatingObject()); if (!frame) { return; } frame->load(req); } static bool isMimeTypeAssociatedWithSelf(const KService::Ptr &offer) { if (!offer) { return false; } // qDebug() << offer->desktopEntryName(); const QString &appName = QCoreApplication::applicationName(); if (appName == offer->desktopEntryName() || offer->exec().trimmed().startsWith(appName)) { return true; } // konqueror exception since it uses kfmclient to open html content... if (appName == QL1S("konqueror") && offer->exec().trimmed().startsWith(QL1S("kfmclient"))) { return true; } return false; } static void extractMimeType(const QNetworkReply *reply, QString &mimeType) { mimeType.clear(); const KIO::MetaData &metaData = reply->attribute(static_cast(KIO::AccessManager::MetaData)).toMap(); if (metaData.contains(QL1S("content-type"))) { mimeType = metaData.value(QL1S("content-type")); } if (!mimeType.isEmpty()) { return; } if (!reply->hasRawHeader("Content-Type")) { return; } const QString value(QL1S(reply->rawHeader("Content-Type").simplified().constData())); const int index = value.indexOf(QL1C(';')); mimeType = ((index == -1) ? value : value.left(index)); } static bool downloadResource(const QUrl &srcUrl, const QString &suggestedName = QString(), QWidget *parent = nullptr, const KIO::MetaData &metaData = KIO::MetaData()) { const QString fileName = suggestedName.isEmpty() ? srcUrl.fileName() : suggestedName; // convert filename to URL using fromPath to avoid trouble with ':' in filenames (#184202) QUrl destUrl = QUrl::fromLocalFile(QFileDialog::getSaveFileName(parent, QString(), fileName)); if (!destUrl.isValid()) { return false; } // Using KIO::copy rather than file_copy, to benefit from "dest already exists" dialogs. KIO::Job *job = KIO::copy(srcUrl, destUrl); if (!metaData.isEmpty()) { job->setMetaData(metaData); } job->addMetaData(QL1S("MaxCacheSize"), QL1S("0")); // Don't store in http cache. job->addMetaData(QL1S("cache"), QL1S("cache")); // Use entry from cache if available. KJobWidgets::setWindow(job, parent ? parent->window() : nullptr); job->uiDelegate()->setAutoErrorHandlingEnabled(true); return true; } static bool isReplyStatusOk(const QNetworkReply *reply) { if (!reply || reply->error() != QNetworkReply::NoError) { return false; } // Check HTTP status code only for http and webdav protocols... const QString scheme = reply->url().scheme(); if (scheme.startsWith(QLatin1String("http"), Qt::CaseInsensitive) || scheme.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive)) { bool ok = false; const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok); if (!ok || statusCode < 200 || statusCode > 299) { return false; } } return true; } class KWebPage::KWebPagePrivate { public: KWebPagePrivate(KWebPage *page) : q(page) , inPrivateBrowsingMode(false) { } QWidget *windowWidget() { return (window ? window.data() : q->view()); } void _k_copyResultToTempFile(KJob *job) { KIO::FileCopyJob *cJob = qobject_cast(job); if (cJob && !cJob->error()) { // Same as KRun::foundMimeType but with a different URL - (void)KRun::runUrl(cJob->destUrl(), mimeType, window); + // TODO: check if KRun::RunExecutables is really wanted here + // old API call used default runExecutables=true flag + KRun::runUrl(cJob->destUrl(), mimeType, window, KRun::RunExecutables, QString(), QByteArray()); } } void _k_receivedContentType(KIO::Job *job, const QString &mimetype) { KIO::TransferJob *tJob = qobject_cast(job); if (tJob && !tJob->error()) { tJob->putOnHold(); KIO::Scheduler::publishSlaveOnHold(); // Get suggested file name... mimeType = mimetype; const QString suggestedFileName(tJob->queryMetaData(QL1S("content-disposition-filename"))); // qDebug() << "suggested filename:" << suggestedFileName << ", mimetype:" << mimetype; (void) downloadResource(tJob->url(), suggestedFileName, window, tJob->metaData()); } } void _k_contentTypeCheckFailed(KJob *job) { KIO::TransferJob *tJob = qobject_cast(job); // On error simply call downloadResource which will probably fail as well. if (tJob && tJob->error()) { (void)downloadResource(tJob->url(), QString(), window, tJob->metaData()); } } KWebPage *q; QPointer window; QString mimeType; QPointer wallet; bool inPrivateBrowsingMode; }; static void setActionIcon(QAction *action, const QIcon &icon) { if (action) { action->setIcon(icon); } } static void setActionShortcut(QAction *action, const QList &shortcut) { if (action) { action->setShortcuts(shortcut); } } KWebPage::KWebPage(QObject *parent, Integration flags) : QWebPage(parent), d(new KWebPagePrivate(this)) { // KDE KParts integration for tag... if (!flags || (flags & KPartsIntegration)) { setPluginFactory(new KWebPluginFactory(this)); } QWidget *parentWidget = qobject_cast(parent); d->window = (parentWidget ? parentWidget->window() : nullptr); // KDE IO (KIO) integration... if (!flags || (flags & KIOIntegration)) { KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(this); // Disable QtWebKit's internal cache to avoid duplication with the one in KIO... manager->setCache(nullptr); manager->setWindow(d->window); manager->setEmitReadyReadOnMetaDataChange(true); setNetworkAccessManager(manager); } // KWallet integration... if (!flags || (flags & KWalletIntegration)) { setWallet(new KWebWallet(nullptr, (d->window ? d->window->winId() : 0))); } setActionIcon(action(Back), QIcon::fromTheme("go-previous")); setActionIcon(action(Forward), QIcon::fromTheme("go-next")); setActionIcon(action(Reload), QIcon::fromTheme("view-refresh")); setActionIcon(action(Stop), QIcon::fromTheme("process-stop")); setActionIcon(action(Cut), QIcon::fromTheme("edit-cut")); setActionIcon(action(Copy), QIcon::fromTheme("edit-copy")); setActionIcon(action(Paste), QIcon::fromTheme("edit-paste")); setActionIcon(action(Undo), QIcon::fromTheme("edit-undo")); setActionIcon(action(Redo), QIcon::fromTheme("edit-redo")); setActionIcon(action(SelectAll), QIcon::fromTheme("edit-select-all")); setActionIcon(action(InspectElement), QIcon::fromTheme("view-process-all")); setActionIcon(action(OpenLinkInNewWindow), QIcon::fromTheme("window-new")); setActionIcon(action(OpenFrameInNewWindow), QIcon::fromTheme("window-new")); setActionIcon(action(OpenImageInNewWindow), QIcon::fromTheme("window-new")); setActionIcon(action(CopyLinkToClipboard), QIcon::fromTheme("edit-copy")); setActionIcon(action(CopyImageToClipboard), QIcon::fromTheme("edit-copy")); setActionIcon(action(ToggleBold), QIcon::fromTheme("format-text-bold")); setActionIcon(action(ToggleItalic), QIcon::fromTheme("format-text-italic")); setActionIcon(action(ToggleUnderline), QIcon::fromTheme("format-text-underline")); setActionIcon(action(DownloadLinkToDisk), QIcon::fromTheme("document-save")); setActionIcon(action(DownloadImageToDisk), QIcon::fromTheme("document-save")); settings()->setWebGraphic(QWebSettings::MissingPluginGraphic, QIcon::fromTheme("preferences-plugin").pixmap(32, 32)); settings()->setWebGraphic(QWebSettings::MissingImageGraphic, QIcon::fromTheme("image-missing").pixmap(32, 32)); settings()->setWebGraphic(QWebSettings::DefaultFrameIconGraphic, QIcon::fromTheme("applications-internet").pixmap(32, 32)); setActionShortcut(action(Back), KStandardShortcut::back()); setActionShortcut(action(Forward), KStandardShortcut::forward()); setActionShortcut(action(Reload), KStandardShortcut::reload()); setActionShortcut(action(Stop), QList() << QKeySequence(Qt::Key_Escape)); setActionShortcut(action(Cut), KStandardShortcut::cut()); setActionShortcut(action(Copy), KStandardShortcut::copy()); setActionShortcut(action(Paste), KStandardShortcut::paste()); setActionShortcut(action(Undo), KStandardShortcut::undo()); setActionShortcut(action(Redo), KStandardShortcut::redo()); setActionShortcut(action(SelectAll), KStandardShortcut::selectAll()); } KWebPage::~KWebPage() { delete d; } bool KWebPage::isExternalContentAllowed() const { KIO::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { return manager->isExternalContentAllowed(); } return true; } KWebWallet *KWebPage::wallet() const { return d->wallet; } void KWebPage::setAllowExternalContent(bool allow) { KIO::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { manager->setExternalContentAllowed(allow); } } void KWebPage::setWallet(KWebWallet *wallet) { // Delete the current wallet if this object is its parent... if (d->wallet && this == d->wallet->parent()) { delete d->wallet; } d->wallet = wallet; if (d->wallet) { d->wallet->setParent(this); } } void KWebPage::downloadRequest(const QNetworkRequest &request) { KIO::TransferJob *job = KIO::get(request.url()); connect(job, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(_k_receivedContentType(KIO::Job*,QString))); job->setMetaData(request.attribute(static_cast(KIO::AccessManager::MetaData)).toMap()); job->addMetaData(QL1S("MaxCacheSize"), QL1S("0")); // Don't store in http cache. job->addMetaData(QL1S("cache"), QL1S("cache")); // Use entry from cache if available. KJobWidgets::setWindow(job, d->windowWidget()); } void KWebPage::downloadUrl(const QUrl &url) { downloadRequest(QNetworkRequest(url)); } void KWebPage::downloadResponse(QNetworkReply *reply) { Q_ASSERT(reply); if (!reply) { return; } // Put the job on hold only for the protocols we know about (read: http). KIO::Integration::AccessManager::putReplyOnHold(reply); QString mimeType; KIO::MetaData metaData; if (handleReply(reply, &mimeType, &metaData)) { return; } const QUrl replyUrl(reply->url()); // Ask KRun to handle the response when mimetype is unknown if (mimeType.isEmpty()) { (void)new KRun(replyUrl, d->windowWidget()); return; } // Ask KRun::runUrl to handle the response when mimetype is inode/* if (mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive) && - KRun::runUrl(replyUrl, mimeType, d->windowWidget(), false, false, + KRun::runUrl(replyUrl, mimeType, d->windowWidget(), KRun::RunFlags(), metaData.value(QL1S("content-disposition-filename")))) { return; } } QString KWebPage::sessionMetaData(const QString &key) const { QString value; KIO::Integration::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { value = manager->sessionMetaData().value(key); } return value; } QString KWebPage::requestMetaData(const QString &key) const { QString value; KIO::Integration::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { value = manager->requestMetaData().value(key); } return value; } void KWebPage::setSessionMetaData(const QString &key, const QString &value) { KIO::Integration::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { manager->sessionMetaData()[key] = value; } } void KWebPage::setRequestMetaData(const QString &key, const QString &value) { KIO::Integration::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { manager->requestMetaData()[key] = value; } } void KWebPage::removeSessionMetaData(const QString &key) { KIO::Integration::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { manager->sessionMetaData().remove(key); } } void KWebPage::removeRequestMetaData(const QString &key) { KIO::Integration::AccessManager *manager = qobject_cast(networkAccessManager()); if (manager) { manager->requestMetaData().remove(key); } } QString KWebPage::userAgentForUrl(const QUrl &_url) const { const QUrl url(_url); const QString userAgent = KProtocolManager::userAgentForHost((url.isLocalFile() ? QL1S("localhost") : url.host())); if (userAgent == KProtocolManager::defaultUserAgent()) { return QWebPage::userAgentForUrl(_url); } return userAgent; } static void setDisableCookieJarStorage(QNetworkAccessManager *manager, bool status) { if (manager) { KIO::Integration::CookieJar *cookieJar = manager ? qobject_cast(manager->cookieJar()) : nullptr; if (cookieJar) { //qDebug() << "Store cookies ?" << !status; cookieJar->setDisableCookieStorage(status); } } } bool KWebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type) { // qDebug() << "url:" << request.url() << ", type:" << type << ", frame:" << frame; if (frame && d->wallet && type == QWebPage::NavigationTypeFormSubmitted) { d->wallet->saveFormData(frame); } // Make sure nothing is cached when private browsing mode is enabled... if (settings()->testAttribute(QWebSettings::PrivateBrowsingEnabled)) { if (!d->inPrivateBrowsingMode) { setDisableCookieJarStorage(networkAccessManager(), true); setSessionMetaData(QL1S("no-cache"), QL1S("true")); d->inPrivateBrowsingMode = true; } } else { if (d->inPrivateBrowsingMode) { setDisableCookieJarStorage(networkAccessManager(), false); removeSessionMetaData(QL1S("no-cache")); d->inPrivateBrowsingMode = false; } } /* If the navigation request is from the main frame, set the cross-domain meta-data value to the current url for proper integration with KCookieJar... */ if (frame == mainFrame() && type != QWebPage::NavigationTypeReload) { setSessionMetaData(QL1S("cross-domain"), request.url().toString()); } return QWebPage::acceptNavigationRequest(frame, request, type); } bool KWebPage::handleReply(QNetworkReply *reply, QString *contentType, KIO::MetaData *metaData) { // Reply url... const QUrl replyUrl(reply->url()); // Get suggested file name... const KIO::MetaData &data = reply->attribute(static_cast(KIO::AccessManager::MetaData)).toMap(); const QString suggestedFileName = data.value(QL1S("content-disposition-filename")); if (metaData) { *metaData = data; } // Get the mime-type... QString mimeType; extractMimeType(reply, mimeType); if (contentType) { *contentType = mimeType; } // Let the calling function deal with handling empty or inode/* mimetypes... if (mimeType.isEmpty() || mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive)) { return false; } // Convert executable text files to plain text... if (KParts::BrowserRun::isTextExecutable(mimeType)) { mimeType = QL1S("text/plain"); } //qDebug() << "Content-disposition:" << suggestedFileName; //qDebug() << "Got unsupported content of type:" << mimeType << "URL:" << replyUrl; //qDebug() << "Error code:" << reply->error() << reply->errorString(); if (isReplyStatusOk(reply)) { while (true) { KParts::BrowserOpenOrSaveQuestion::Result result; KParts::BrowserOpenOrSaveQuestion dlg(d->windowWidget(), replyUrl, mimeType); dlg.setSuggestedFileName(suggestedFileName); dlg.setFeatures(KParts::BrowserOpenOrSaveQuestion::ServiceSelection); result = dlg.askOpenOrSave(); switch (result) { case KParts::BrowserOpenOrSaveQuestion::Open: // Handle Post operations that return content... if (reply->operation() == QNetworkAccessManager::PostOperation) { d->mimeType = mimeType; QFileInfo finfo(suggestedFileName.isEmpty() ? replyUrl.fileName() : suggestedFileName); QTemporaryFile tempFile(QDir::tempPath() + QLatin1String("/kwebpage_XXXXXX.") + finfo.suffix()); tempFile.setAutoRemove(false); tempFile.open(); const QUrl destUrl = QUrl::fromLocalFile(tempFile.fileName()); KIO::Job *job = KIO::file_copy(replyUrl, destUrl, 0600, KIO::Overwrite); KJobWidgets::setWindow(job, d->windowWidget()); job->uiDelegate()->setAutoErrorHandlingEnabled(true); connect(job, SIGNAL(result(KJob*)), this, SLOT(_k_copyResultToTempFile(KJob*))); return true; } // Ask before running any executables... if (KParts::BrowserRun::allowExecution(mimeType, replyUrl)) { KService::Ptr offer = dlg.selectedService(); // HACK: The check below is necessary to break an infinite // recursion that occurs whenever this function is called as a result // of receiving content that can be rendered by the app using this engine. // For example a text/html header that containing a content-disposition // header is received by the app using this class. if (isMimeTypeAssociatedWithSelf(offer)) { reloadRequestWithoutDisposition(reply); } else { QList list; list.append(replyUrl); bool success = false; // qDebug() << "Suggested file name:" << suggestedFileName; if (offer) { - success = KRun::run(*offer, list, d->windowWidget(), false, suggestedFileName); + success = KRun::runService(*offer, list, d->windowWidget(), false, suggestedFileName); } else { success = KRun::displayOpenWithDialog(list, d->windowWidget(), false, suggestedFileName); if (!success) { break; } } // For non KIO apps and cancelled Open With dialog, remove slave on hold. if (!success || (offer && !offer->categories().contains(QL1S("KDE")))) { KIO::SimpleJob::removeOnHold(); // Remove any slave-on-hold... } } return true; } // TODO: Instead of silently failing when allowExecution fails, notify // the user why the requested action cannot be fulfilled... return false; case KParts::BrowserOpenOrSaveQuestion::Save: // Do not download local files... if (!replyUrl.isLocalFile()) { QString downloadCmd(reply->property("DownloadManagerExe").toString()); if (!downloadCmd.isEmpty()) { downloadCmd += QLatin1Char(' '); downloadCmd += KShell::quoteArg(replyUrl.url()); if (!suggestedFileName.isEmpty()) { downloadCmd += QLatin1Char(' '); downloadCmd += KShell::quoteArg(suggestedFileName); } // qDebug() << "download command:" << downloadCmd; if (KRun::runCommand(downloadCmd, view())) { return true; } } if (!downloadResource(replyUrl, suggestedFileName, d->windowWidget())) { return true; // file dialog was cancelled, stop here } } return true; case KParts::BrowserOpenOrSaveQuestion::Cancel: default: KIO::SimpleJob::removeOnHold(); // Remove any slave-on-hold... return true; } } } else { KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimeType); if (isMimeTypeAssociatedWithSelf(offer)) { reloadRequestWithoutDisposition(reply); return true; } } return false; } #include "moc_kwebpage.cpp"