diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d0ac3ff9..4062cfe2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,9 @@ endif() # Mandatory: OpenSSL find_package(OpenSSL REQUIRED) +# Mandatory: KF5 +find_package(KF5 REQUIRED COMPONENTS Archive) + # KF5I18n: Mandatory with downloaded translations (only for ki18n_install) if (EXISTS "${CMAKE_SOURCE_DIR}/po") find_package(KF5I18n REQUIRED) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index b4aff598d..74775e351 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -156,6 +156,7 @@ set(SRCS ${SRCS} plugins/pluginproxy.cpp plugins/plugins.cpp plugins/speeddial.cpp + plugins/ocssupport.cpp plugins/qml/qmlpluginloader.cpp plugins/qml/qmlplugin.cpp plugins/qml/qmlplugins.cpp @@ -348,6 +349,7 @@ target_link_libraries(FalkonPrivate Qt5::PrintSupport Qt5::QuickWidgets Qt5::WebChannel + KF5::Archive ${OPENSSL_CRYPTO_LIBRARY} ) diff --git a/src/lib/notifications/desktopnotificationsfactory.cpp b/src/lib/notifications/desktopnotificationsfactory.cpp index 1dc5887fc..66db67ac2 100644 --- a/src/lib/notifications/desktopnotificationsfactory.cpp +++ b/src/lib/notifications/desktopnotificationsfactory.cpp @@ -61,6 +61,11 @@ bool DesktopNotificationsFactory::supportsNativeNotifications() const #endif } +void DesktopNotificationsFactory::showNotification(const QString &heading, const QString &text) +{ + showNotification(QPixmap(), heading, text); +} + void DesktopNotificationsFactory::showNotification(const QPixmap &icon, const QString &heading, const QString &text) { if (!m_enabled) { diff --git a/src/lib/notifications/desktopnotificationsfactory.h b/src/lib/notifications/desktopnotificationsfactory.h index b37ee7d74..64c3d6a0b 100644 --- a/src/lib/notifications/desktopnotificationsfactory.h +++ b/src/lib/notifications/desktopnotificationsfactory.h @@ -43,6 +43,7 @@ public: bool supportsNativeNotifications() const; + void showNotification(const QString &heading, const QString &text); void showNotification(const QPixmap &icon, const QString &heading, const QString &text); void nativeNotificationPreview(); diff --git a/src/lib/plugins/ocssupport.cpp b/src/lib/plugins/ocssupport.cpp new file mode 100644 index 000000000..138c15f1d --- /dev/null +++ b/src/lib/plugins/ocssupport.cpp @@ -0,0 +1,206 @@ +/* ============================================================ +* Falkon - Qt web browser +* Copyright (C) 2019 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "ocssupport.h" +#include "pluginproxy.h" +#include "datapaths.h" +#include "networkmanager.h" +#include "desktopnotificationsfactory.h" +#include "mainapplication.h" + +#include +#include +#include +#include + +#include + +Q_GLOBAL_STATIC(OcsSupport, qz_ocs_support) + +OcsSupport::OcsSupport(QObject *parent) + : QObject(parent) +{ +} + +bool OcsSupport::handleUrl(const QUrl &url) +{ + if (url.host() != QL1S("install")) { + return false; + } + + QUrl fileUrl; + QString fileType; + QString fileName; + + const auto items = QUrlQuery(url).queryItems(QUrl::FullyDecoded); + for (const auto &item : items) { + if (item.first == QL1S("url")) { + fileUrl = QUrl(item.second); + } else if (item.first == QL1S("type")) { + fileType = item.second; + } else if (item.first == QL1S("filename")) { + fileName = item.second; + } + } + + if (!fileType.startsWith(QL1S("falkon_"))) { + return false; + } + + if (fileType != QL1S("falkon_themes") && fileType != QL1S("falkon_extensions")) { + qWarning() << "Unsupported type" << fileType; + return false; + } + + if (!fileUrl.isValid()) { + qWarning() << "Invalid url" << fileUrl << url; + return false; + } + + qInfo() << "Downloading" << fileUrl; + + QNetworkReply *reply = mApp->networkManager()->get(QNetworkRequest(fileUrl)); + connect(reply, &QNetworkReply::finished, this, [=]() { + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "Error downloading" << fileUrl << reply->error() << reply->errorString(); + return; + } + QBuffer buf; + buf.setData(reply->readAll()); + KZip zip(&buf); + if (!zip.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open archive"; + return; + } + QString notifyMessage; + if (fileType == QL1S("falkon_themes")) { + installTheme(zip.directory()); + } else if (fileType == QL1S("falkon_extensions")) { + installExtension(zip.directory()); + } + }); + + return true; +} + +// static +OcsSupport *OcsSupport::instance() +{ + return qz_ocs_support(); +} + +void OcsSupport::installTheme(const KArchiveDirectory *directory) +{ + auto showError = []() { + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Failed to install theme")); + }; + + if (directory->entries().size() != 1) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + const QString name = directory->entries().at(0); + const KArchiveEntry *entry = directory->entry(name); + if (!entry || !entry->isDirectory()) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + const QString targetDir = DataPaths::path(DataPaths::Config) + QL1S("/themes"); + QDir().mkpath(targetDir); + + if (QFileInfo::exists(targetDir + QL1C('/') + name)) { + qWarning() << "Theme" << name << "already exists"; + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Theme is already installed")); + return; + } + + if (!directory->copyTo(targetDir)) { + qWarning() << "Failed to copy theme to" << targetDir; + showError(); + return; + } + + qInfo() << "Theme installed to" << targetDir; + + mApp->desktopNotifications()->showNotification(tr("Theme installed"), tr("Theme was successfully installed")); +} + +void OcsSupport::installExtension(const KArchiveDirectory *directory) +{ + auto showError = []() { + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Failed to install extension")); + }; + + if (directory->entries().size() != 1) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + const QString name = directory->entries().at(0); + const KArchiveEntry *entry = directory->entry(name); + if (!entry || !entry->isDirectory()) { + qWarning() << "Invalid archive format"; + showError(); + return; + } + + QString type; + const QStringList files = static_cast(entry)->entries(); + if (files.contains(QL1S("__init__.py"))) { + type = QSL("python"); + } else if (files.contains(QL1S("main.qml"))) { + type = QSL("qml"); + } + + if (type.isEmpty()) { + qWarning() << "Unsupported extension type"; + showError(); + return; + } + + const QString targetDir = DataPaths::path(DataPaths::Config) + QL1S("/plugins/") + type; + QDir().mkpath(targetDir); + + if (QFileInfo::exists(targetDir + QL1S("/") + name)) { + qWarning() << "Extension" << name << "already exists"; + mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Extension is already installed")); + return; + } + + if (!directory->copyTo(targetDir)) { + qWarning() << "Failed to copy extension to" << targetDir; + showError(); + return; + } + + qInfo() << "Extension installed to" << targetDir; + + const QString fullId = QSL("%1:%2/%3").arg(type, targetDir, name); + if (!mApp->plugins()->addPlugin(fullId)) { + qWarning() << "Failed to add plugin" << fullId; + showError(); + return; + } + + mApp->desktopNotifications()->showNotification(tr("Extension installed"), tr("Extension was successfully installed")); +} diff --git a/src/lib/plugins/ocssupport.h b/src/lib/plugins/ocssupport.h new file mode 100644 index 000000000..a91e9ab7e --- /dev/null +++ b/src/lib/plugins/ocssupport.h @@ -0,0 +1,40 @@ +/* ============================================================ +* Falkon - Qt web browser +* Copyright (C) 2019 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#pragma once + +#include "qzcommon.h" + +#include + +class KArchiveDirectory; + +class OcsSupport : public QObject +{ + Q_OBJECT + +public: + explicit OcsSupport(QObject *parent = nullptr); + + bool handleUrl(const QUrl &url); + + static OcsSupport *instance(); + +private: + void installTheme(const KArchiveDirectory *directory); + void installExtension(const KArchiveDirectory *directory); +}; diff --git a/src/lib/plugins/plugins.cpp b/src/lib/plugins/plugins.cpp index 39be4a0d8..77aa86bed 100644 --- a/src/lib/plugins/plugins.cpp +++ b/src/lib/plugins/plugins.cpp @@ -47,8 +47,7 @@ bool Plugins::Plugin::isRemovable() const bool Plugins::Plugin::operator==(const Plugin &other) const { - return type == other.type && - pluginId == other.pluginId; + return type == other.type && pluginId == other.pluginId; } Plugins::Plugins(QObject* parent) @@ -131,6 +130,21 @@ void Plugins::removePlugin(Plugins::Plugin *plugin) emit availablePluginsChanged(); } +bool Plugins::addPlugin(const QString &id) +{ + Plugin plugin = loadPlugin(id); + if (plugin.type == Plugin::Invalid) { + return false; + } + if (plugin.pluginSpec.name.isEmpty()) { + qWarning() << "Invalid plugin spec of" << id << "plugin"; + return false; + } + registerAvailablePlugin(plugin); + emit availablePluginsChanged(); + return true; +} + void Plugins::loadSettings() { QStringList defaultAllowedPlugins = { diff --git a/src/lib/plugins/plugins.h b/src/lib/plugins/plugins.h index 434139755..45b61a893 100644 --- a/src/lib/plugins/plugins.h +++ b/src/lib/plugins/plugins.h @@ -86,6 +86,8 @@ public: void unloadPlugin(Plugin* plugin); void removePlugin(Plugin *plugin); + bool addPlugin(const QString &id); + void shutdown(); // SpeedDial diff --git a/src/lib/webengine/webpage.cpp b/src/lib/webengine/webpage.cpp index af1461f8b..5037b1cba 100644 --- a/src/lib/webengine/webpage.cpp +++ b/src/lib/webengine/webpage.cpp @@ -42,6 +42,7 @@ #include "ui_jsprompt.h" #include "passwordmanager.h" #include "scripts.h" +#include "ocssupport.h" #include @@ -453,6 +454,10 @@ bool WebPage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::Navigatio } } + if (url.scheme() == QL1S("ocs") && OcsSupport::instance()->handleUrl(url)) { + return false; + } + const bool result = QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); if (result) {