kdelibs/kdewebkit/kwebpage.cpp
Ivailo Monev 36072e6f92 generic: remove now redundant QT_KATIE guards
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2016-09-13 22:43:04 +00:00

606 lines
22 KiB
C++

/*
* This file is part of the KDE project.
*
* Copyright (C) 2008 Dirk Mueller <mueller@kde.org>
* Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org>
* Copyright (C) 2008 Michael Howell <mhowell123@gmail.com>
* Copyright (C) 2009,2010 Dawit Alemayehu <adawit@kde.org>
*
* 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 <kaction.h>
#include <kfiledialog.h>
#include <kprotocolmanager.h>
#include <kjobuidelegate.h>
#include <krun.h>
#include <kstandarddirs.h>
#include <kstandardshortcut.h>
#include <kurl.h>
#include <kdebug.h>
#include <kshell.h>
#include <kmimetypetrader.h>
#include <klocalizedstring.h>
#include <ktemporaryfile.h>
#include <kio/accessmanager.h>
#include <kio/job.h>
#include <kio/copyjob.h>
#include <kio/jobuidelegate.h>
#include <kio/renamedialog.h>
#include <kio/scheduler.h>
#include <kparts/browseropenorsavequestion.h>
// Qt
#include <QtCore/QPointer>
#include <QtCore/QFileInfo>
#include <QtCore/QCoreApplication>
#include <QtWebKit/QWebFrame>
#include <QtNetwork/QNetworkReply>
#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<QWebFrame*> (req.originatingObject());
if (!frame)
return;
frame->load(req);
}
static bool isMimeTypeAssociatedWithSelf(const KService::Ptr &offer)
{
if (!offer)
return false;
kDebug(800) << 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<QNetworkRequest::Attribute>(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 KUrl& srcUrl, const QString& suggestedName = QString(),
QWidget* parent = 0, 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)
KUrl destUrl = KFileDialog::getSaveFileName(KUrl::fromPath(fileName), QString(), parent);
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.
job->ui()->setWindow((parent ? parent->window() : 0));
job->ui()->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<KIO::FileCopyJob *>(job);
if (cJob && !cJob->error() ) {
// Same as KRun::foundMimeType but with a different URL
(void)KRun::runUrl(cJob->destUrl(), mimeType, window);
}
}
void _k_receivedContentType(KIO::Job* job, const QString& mimetype)
{
KIO::TransferJob* tJob = qobject_cast<KIO::TransferJob*>(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")));
// kDebug(800) << "suggested filename:" << suggestedFileName << ", mimetype:" << mimetype;
(void) downloadResource(tJob->url(), suggestedFileName, window, tJob->metaData());
}
}
void _k_contentTypeCheckFailed(KJob* job)
{
KIO::TransferJob* tJob = qobject_cast<KIO::TransferJob*>(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<QWidget> window;
QString mimeType;
QPointer<KWebWallet> wallet;
bool inPrivateBrowsingMode;
};
static void setActionIcon(QAction* action, const QIcon& icon)
{
if (action) {
action->setIcon(icon);
}
}
static void setActionShortcut(QAction* action, const KShortcut& shortcut)
{
if (action) {
action->setShortcuts(shortcut.toList());
}
}
KWebPage::KWebPage(QObject *parent, Integration flags)
:QWebPage(parent), d(new KWebPagePrivate(this))
{
// KDE KParts integration for <embed> tag...
if (!flags || (flags & KPartsIntegration))
setPluginFactory(new KWebPluginFactory(this));
QWidget *parentWidget = qobject_cast<QWidget*>(parent);
d->window = (parentWidget ? parentWidget->window() : 0);
// 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(0);
manager->setWindow(d->window);
manager->setEmitReadyReadOnMetaDataChange(true);
setNetworkAccessManager(manager);
}
// KWallet integration...
if (!flags || (flags & KWalletIntegration)) {
setWallet(new KWebWallet(0, (d->window ? d->window->winId() : 0) ));
}
setActionIcon(action(Back), KIcon("go-previous"));
setActionIcon(action(Forward), KIcon("go-next"));
setActionIcon(action(Reload), KIcon("view-refresh"));
setActionIcon(action(Stop), KIcon("process-stop"));
setActionIcon(action(Cut), KIcon("edit-cut"));
setActionIcon(action(Copy), KIcon("edit-copy"));
setActionIcon(action(Paste), KIcon("edit-paste"));
setActionIcon(action(Undo), KIcon("edit-undo"));
setActionIcon(action(Redo), KIcon("edit-redo"));
setActionIcon(action(SelectAll), KIcon("edit-select-all"));
setActionIcon(action(InspectElement), KIcon("view-process-all"));
setActionIcon(action(OpenLinkInNewWindow), KIcon("window-new"));
setActionIcon(action(OpenFrameInNewWindow), KIcon("window-new"));
setActionIcon(action(OpenImageInNewWindow), KIcon("window-new"));
setActionIcon(action(CopyLinkToClipboard), KIcon("edit-copy"));
setActionIcon(action(CopyImageToClipboard), KIcon("edit-copy"));
setActionIcon(action(ToggleBold), KIcon("format-text-bold"));
setActionIcon(action(ToggleItalic), KIcon("format-text-italic"));
setActionIcon(action(ToggleUnderline), KIcon("format-text-underline"));
setActionIcon(action(DownloadLinkToDisk), KIcon("document-save"));
setActionIcon(action(DownloadImageToDisk), KIcon("document-save"));
settings()->setWebGraphic(QWebSettings::MissingPluginGraphic, KIcon("preferences-plugin").pixmap(32, 32));
settings()->setWebGraphic(QWebSettings::MissingImageGraphic, KIcon("image-missing").pixmap(32, 32));
settings()->setWebGraphic(QWebSettings::DefaultFrameIconGraphic, KIcon("applications-internet").pixmap(32, 32));
setActionShortcut(action(Back), KStandardShortcut::back());
setActionShortcut(action(Forward), KStandardShortcut::forward());
setActionShortcut(action(Reload), KStandardShortcut::reload());
setActionShortcut(action(Stop), KShortcut(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<KIO::AccessManager*>(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<KIO::AccessManager*>(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<QNetworkRequest::Attribute>(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.
job->ui()->setWindow(d->windowWidget());
}
void KWebPage::downloadUrl(const KUrl &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 KUrl replyUrl (reply->url());
// Ask KRun to handle the response when mimetype is unknown
if (mimeType.isEmpty()) {
(void)new KRun(replyUrl, d->windowWidget(), 0 , replyUrl.isLocalFile());
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,
metaData.value(QL1S("content-disposition-filename")))) {
return;
}
}
QString KWebPage::sessionMetaData(const QString &key) const
{
QString value;
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(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<KIO::Integration::AccessManager *>(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<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->sessionMetaData()[key] = value;
}
void KWebPage::setRequestMetaData(const QString &key, const QString &value)
{
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->requestMetaData()[key] = value;
}
void KWebPage::removeSessionMetaData(const QString &key)
{
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->sessionMetaData().remove(key);
}
void KWebPage::removeRequestMetaData(const QString &key)
{
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->requestMetaData().remove(key);
}
QString KWebPage::userAgentForUrl(const QUrl& _url) const
{
const KUrl 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<KIO::Integration::CookieJar*>(manager->cookieJar()) : 0;
if (cookieJar) {
//kDebug(800) << "Store cookies ?" << !status;
cookieJar->setDisableCookieStorage(status);
}
}
}
bool KWebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type)
{
kDebug(800) << "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 KUrl replyUrl (reply->url());
// Get suggested file name...
const KIO::MetaData& data = reply->attribute(static_cast<QNetworkRequest::Attribute>(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");
//kDebug(800) << "Content-disposition:" << suggestedFileName;
//kDebug(800) << "Got unsupported content of type:" << mimeType << "URL:" << replyUrl;
//kDebug(800) << "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);
KTemporaryFile tempFile;
tempFile.setSuffix(QL1C('.') + finfo.suffix());
tempFile.setAutoRemove(false);
tempFile.open();
KUrl destUrl;
destUrl.setPath(tempFile.fileName());
KIO::Job *job = KIO::file_copy(replyUrl, destUrl, 0600, KIO::Overwrite);
job->ui()->setWindow(d->windowWidget());
job->ui()->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 {
KUrl::List list;
list.append(replyUrl);
bool success = false;
// kDebug(800) << "Suggested file name:" << suggestedFileName;
if (offer) {
success = KRun::run(*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);
}
// kDebug(800) << "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"