Warning, file /network/falkon/src/lib/plugins/plugins.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "pluginproxy.h"
0019 #include "mainapplication.h"
0020 #include "speeddial.h"
0021 #include "settings.h"
0022 #include "datapaths.h"
0023 #include "adblock/adblockplugin.h"
0024 #include "../config.h"
0025 #include "desktopfile.h"
0026 #include "qml/qmlplugins.h"
0027 #include "qml/qmlplugin.h"
0028 
0029 #include <iostream>
0030 
0031 #include <QPluginLoader>
0032 #include <QDir>
0033 #include <QQmlEngine>
0034 #include <QQmlComponent>
0035 #include <QFileInfo>
0036 #include <QSettings>
0037 
0038 bool Plugins::Plugin::isLoaded() const
0039 {
0040     return instance;
0041 }
0042 
0043 bool Plugins::Plugin::isRemovable() const
0044 {
0045     return !pluginPath.isEmpty() && QFileInfo(pluginPath).isWritable();
0046 }
0047 
0048 bool Plugins::Plugin::operator==(const Plugin &other) const
0049 {
0050     return type == other.type && pluginId == other.pluginId;
0051 }
0052 
0053 Plugins::Plugins(QObject* parent)
0054     : QObject(parent)
0055     , m_pluginsLoaded(false)
0056     , m_speedDial(new SpeedDial(this))
0057 {
0058     loadSettings();
0059 
0060     if (!MainApplication::isTestModeEnabled()) {
0061         loadPythonSupport();
0062     }
0063 }
0064 
0065 QList<Plugins::Plugin> Plugins::availablePlugins()
0066 {
0067     loadAvailablePlugins();
0068     return m_availablePlugins;
0069 }
0070 
0071 bool Plugins::loadPlugin(Plugins::Plugin* plugin)
0072 {
0073     if (plugin->isLoaded()) {
0074         return true;
0075     }
0076 
0077     if (!initPlugin(PluginInterface::LateInitState, plugin)) {
0078         return false;
0079     }
0080 
0081     m_availablePlugins.removeOne(*plugin);
0082     m_availablePlugins.prepend(*plugin);
0083 
0084     refreshLoadedPlugins();
0085 
0086     return plugin->isLoaded();
0087 }
0088 
0089 void Plugins::unloadPlugin(Plugins::Plugin* plugin)
0090 {
0091     if (!plugin->isLoaded()) {
0092         return;
0093     }
0094 
0095     plugin->instance->unload();
0096     Q_EMIT pluginUnloaded(plugin->instance);
0097     plugin->instance = nullptr;
0098 
0099     m_availablePlugins.removeOne(*plugin);
0100     m_availablePlugins.append(*plugin);
0101 
0102     refreshLoadedPlugins();
0103 }
0104 
0105 void Plugins::removePlugin(Plugins::Plugin *plugin)
0106 {
0107     if (!plugin->isRemovable()) {
0108         return;
0109     }
0110 
0111     if (plugin->isLoaded()) {
0112         unloadPlugin(plugin);
0113     }
0114 
0115     bool result = false;
0116 
0117     QFileInfo info(plugin->pluginPath);
0118     if (info.isDir()) {
0119         result = QDir(plugin->pluginPath).removeRecursively();
0120     } else if (info.isFile()) {
0121         result = QFile::remove(plugin->pluginPath);
0122     }
0123 
0124     if (!result) {
0125         qWarning() << "Failed to remove" << plugin->pluginSpec.name;
0126         return;
0127     }
0128 
0129     m_availablePlugins.removeOne(*plugin);
0130     Q_EMIT availablePluginsChanged();
0131 }
0132 
0133 bool Plugins::addPlugin(const QString &id)
0134 {
0135     Plugin plugin = loadPlugin(id);
0136     if (plugin.type == Plugin::Invalid) {
0137         return false;
0138     }
0139     if (plugin.pluginSpec.name.isEmpty()) {
0140         qWarning() << "Invalid plugin spec of" << id << "plugin";
0141         return false;
0142     }
0143     registerAvailablePlugin(plugin);
0144     Q_EMIT availablePluginsChanged();
0145     return true;
0146 }
0147 
0148 void Plugins::loadSettings()
0149 {
0150     QStringList defaultAllowedPlugins = {
0151         QSL("internal:adblock")
0152     };
0153 
0154     // Enable KDE Frameworks Integration when running inside KDE session
0155     if (qgetenv("KDE_FULL_SESSION") == QByteArray("true")) {
0156         defaultAllowedPlugins.append(QSL("lib:KDEFrameworksIntegration.so"));
0157     }
0158 
0159     Settings settings;
0160     settings.beginGroup(QSL("Plugin-Settings"));
0161     m_allowedPlugins = settings.value(QSL("AllowedPlugins"), defaultAllowedPlugins).toStringList();
0162     settings.endGroup();
0163 }
0164 
0165 void Plugins::shutdown()
0166 {
0167     for (PluginInterface* iPlugin : std::as_const(m_loadedPlugins)) {
0168         iPlugin->unload();
0169     }
0170 }
0171 
0172 PluginSpec Plugins::createSpec(const QJsonObject &metaData)
0173 {
0174     const QString tempIcon = DataPaths::path(DataPaths::Temp) + QL1S("/icon");
0175     const QString tempMetadata = DataPaths::path(DataPaths::Temp) + QL1S("/metadata.desktop");
0176     QFile::remove(tempIcon);
0177     QFile::remove(tempMetadata);
0178     QSettings settings(tempMetadata, QSettings::IniFormat);
0179     settings.beginGroup(QSL("Desktop Entry"));
0180     for (auto it = metaData.begin(); it != metaData.end(); ++it) {
0181         const QString value = it.value().toString();
0182         if (it.key() == QL1S("Icon") && value.startsWith(QL1S("base64:"))) {
0183             QFile file(tempIcon);
0184             if (file.open(QFile::WriteOnly)) {
0185                 file.write(QByteArray::fromBase64(value.mid(7).toUtf8()));
0186                 settings.setValue(it.key(), tempIcon);
0187             }
0188         } else {
0189             settings.setValue(it.key(), it.value().toString());
0190         }
0191     }
0192     settings.sync();
0193     return createSpec(DesktopFile(tempMetadata));
0194 }
0195 
0196 PluginSpec Plugins::createSpec(const DesktopFile &metaData)
0197 {
0198     PluginSpec spec;
0199     spec.name = metaData.name();
0200     spec.description = metaData.comment();
0201     spec.version = metaData.value(QSL("X-Falkon-Version")).toString();
0202     spec.author = QSL("%1 <%2>").arg(metaData.value(QSL("X-Falkon-Author")).toString(), metaData.value(QSL("X-Falkon-Email")).toString());
0203     spec.hasSettings = metaData.value(QSL("X-Falkon-Settings")).toBool();
0204 
0205     const QString iconName = metaData.icon();
0206     if (!iconName.isEmpty()) {
0207         if (QFileInfo::exists(iconName)) {
0208             spec.icon = QIcon(iconName).pixmap(32);
0209         } else {
0210             const QString relativeFile = QFileInfo(metaData.fileName()).dir().absoluteFilePath(iconName);
0211             if (QFileInfo::exists(relativeFile)) {
0212                 spec.icon = QIcon(relativeFile).pixmap(32);
0213             } else {
0214                 spec.icon = QIcon::fromTheme(iconName).pixmap(32);
0215             }
0216         }
0217     }
0218 
0219     return spec;
0220 }
0221 
0222 void Plugins::loadPlugins()
0223 {
0224     QDir settingsDir(DataPaths::currentProfilePath() + QStringLiteral("/extensions/"));
0225     if (!settingsDir.exists()) {
0226         settingsDir.mkdir(settingsDir.absolutePath());
0227     }
0228 
0229     for (const QString &pluginId : std::as_const(m_allowedPlugins)) {
0230         Plugin plugin = loadPlugin(pluginId);
0231         if (plugin.type == Plugin::Invalid) {
0232             continue;
0233         }
0234         if (plugin.pluginSpec.name.isEmpty()) {
0235             qWarning() << "Invalid plugin spec of" << pluginId << "plugin";
0236             continue;
0237         }
0238         if (!initPlugin(PluginInterface::StartupInitState, &plugin)) {
0239             qWarning() << "Failed to init" << pluginId << "plugin";
0240             continue;
0241         }
0242         registerAvailablePlugin(plugin);
0243     }
0244 
0245     refreshLoadedPlugins();
0246 
0247     std::cout << "Falkon: " << m_loadedPlugins.count() << " extensions loaded"  << std::endl;
0248 }
0249 
0250 void Plugins::loadAvailablePlugins()
0251 {
0252     if (m_pluginsLoaded) {
0253         return;
0254     }
0255 
0256     m_pluginsLoaded = true;
0257 
0258     const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins);
0259 
0260     // InternalPlugin
0261     registerAvailablePlugin(loadInternalPlugin(QSL("adblock")));
0262 
0263     for (const QString &dir : dirs) {
0264         const auto files = QDir(dir).entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
0265         for (const QFileInfo &info : files) {
0266             Plugin plugin;
0267             const QString pluginPath = info.absoluteFilePath();
0268             if (info.isFile() && QLibrary::isLibrary(pluginPath)) {
0269                 // SharedLibraryPlugin
0270                 if (info.baseName() != QL1S("PyFalkon")) {
0271                     plugin = loadSharedLibraryPlugin(pluginPath);
0272                 }
0273             } else if (info.isDir()) {
0274                 const DesktopFile metaData(QDir(pluginPath).filePath(QSL("metadata.desktop")));
0275                 const QString type = metaData.value(QSL("X-Falkon-Type")).toString();
0276                 if (type == QL1S("Extension/Python")) {
0277                     // PythonPlugin
0278                     plugin = loadPythonPlugin(pluginPath);
0279                 } else if (type == QL1S("Extension/Qml")) {
0280                     // QmlPlugin
0281                     plugin = QmlPlugin::loadPlugin(pluginPath);
0282                 } else {
0283                     qWarning() << "Invalid type" << type << "of" << pluginPath << "plugin";
0284                 }
0285             }
0286             if (plugin.type == Plugin::Invalid) {
0287                 continue;
0288             }
0289             if (plugin.pluginSpec.name.isEmpty()) {
0290                 qWarning() << "Invalid plugin spec of" << pluginPath << "plugin";
0291                 continue;
0292             }
0293             registerAvailablePlugin(plugin);
0294         }
0295     }
0296 }
0297 
0298 void Plugins::registerAvailablePlugin(const Plugin &plugin)
0299 {
0300     if (!m_availablePlugins.contains(plugin)) {
0301         m_availablePlugins.append(plugin);
0302     }
0303 }
0304 
0305 void Plugins::refreshLoadedPlugins()
0306 {
0307     m_loadedPlugins.clear();
0308 
0309     for (const Plugin &plugin : std::as_const(m_availablePlugins)) {
0310         if (plugin.isLoaded()) {
0311             m_loadedPlugins.append(plugin.instance);
0312         }
0313     }
0314 
0315     Q_EMIT availablePluginsChanged();
0316 }
0317 
0318 void Plugins::loadPythonSupport()
0319 {
0320     const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins);
0321     for (const QString &dir : dirs) {
0322         const auto files = QDir(dir).entryInfoList({QSL("PyFalkon*")}, QDir::Files);
0323         for (const QFileInfo &info : files) {
0324             m_pythonPlugin = new QLibrary(info.absoluteFilePath(), this);
0325             m_pythonPlugin->setLoadHints(QLibrary::ExportExternalSymbolsHint);
0326             if (!m_pythonPlugin->load()) {
0327                 qWarning() << "Failed to load python support plugin" << m_pythonPlugin->errorString();
0328                 delete m_pythonPlugin;
0329                 m_pythonPlugin = nullptr;
0330             } else {
0331                 std::cout << "Falkon: Python plugin support initialized" << std::endl;
0332                 return;
0333             }
0334         }
0335     }
0336 }
0337 
0338 Plugins::Plugin Plugins::loadPlugin(const QString &id)
0339 {
0340     QString name;
0341     Plugin::Type type = Plugin::Invalid;
0342 
0343     const int colon = id.indexOf(QL1C(':'));
0344     if (colon > -1) {
0345         const auto t = QStringView{id}.left(colon);
0346         if (t == QL1S("internal")) {
0347             type = Plugin::InternalPlugin;
0348         } else if (t == QL1S("lib")) {
0349             type = Plugin::SharedLibraryPlugin;
0350         } else if (t == QL1S("python")) {
0351             type = Plugin::PythonPlugin;
0352         } else if (t == QL1S("qml")) {
0353             type = Plugin::QmlPlugin;
0354         }
0355         name = id.mid(colon + 1);
0356     } else {
0357         name = id;
0358         type = Plugin::SharedLibraryPlugin;
0359     }
0360 
0361     switch (type) {
0362     case Plugin::InternalPlugin:
0363         return loadInternalPlugin(name);
0364 
0365     case Plugin::SharedLibraryPlugin:
0366         return loadSharedLibraryPlugin(name);
0367 
0368     case Plugin::PythonPlugin:
0369         return loadPythonPlugin(name);
0370 
0371     case Plugin::QmlPlugin:
0372         return QmlPlugin::loadPlugin(name);
0373 
0374     default:
0375         return {};
0376     }
0377 }
0378 
0379 Plugins::Plugin Plugins::loadInternalPlugin(const QString &name)
0380 {
0381     if (name == QL1S("adblock")) {
0382         Plugin plugin;
0383         plugin.type = Plugin::InternalPlugin;
0384         plugin.pluginId = QSL("internal:adblock");
0385         plugin.internalInstance = new AdBlockPlugin();
0386         plugin.pluginSpec = createSpec(DesktopFile(QSL(":adblock/metadata.desktop")));
0387         return plugin;
0388     } else {
0389         return {};
0390     }
0391 }
0392 
0393 Plugins::Plugin Plugins::loadSharedLibraryPlugin(const QString &name)
0394 {
0395     QString fullPath;
0396     if (QFileInfo(name).isAbsolute()) {
0397         fullPath = name;
0398     } else {
0399         fullPath = DataPaths::locate(DataPaths::Plugins, name);
0400         if (fullPath.isEmpty()) {
0401             qWarning() << "Library plugin" << name << "not found";
0402             return {};
0403         }
0404     }
0405 
0406     Plugin plugin;
0407     plugin.type = Plugin::SharedLibraryPlugin;
0408     plugin.pluginId = QSL("lib:%1").arg(QFileInfo(fullPath).fileName());
0409     plugin.pluginPath = fullPath;
0410     plugin.pluginLoader = new QPluginLoader(fullPath);
0411     plugin.pluginSpec = createSpec(plugin.pluginLoader->metaData().value(QSL("MetaData")).toObject());
0412     return plugin;
0413 }
0414 
0415 Plugins::Plugin Plugins::loadPythonPlugin(const QString &name)
0416 {
0417     Plugin out;
0418 
0419     if (!m_pythonPlugin) {
0420         qWarning() << "Python support plugin is not loaded";
0421         return out;
0422     }
0423 
0424     auto f = (Plugin*(*)(const QString &)) m_pythonPlugin->resolve("pyfalkon_load_plugin");
0425     if (!f) {
0426         qWarning() << "Failed to resolve" << "pyfalkon_load_plugin";
0427         return out;
0428     }
0429 
0430     Plugin *p = f(name);
0431     if (p) {
0432         out = *p;
0433         delete p;
0434     }
0435 
0436     return out;
0437 }
0438 
0439 bool Plugins::initPlugin(PluginInterface::InitState state, Plugin *plugin)
0440 {
0441     if (!plugin) {
0442         return false;
0443     }
0444 
0445     switch (plugin->type) {
0446     case Plugin::InternalPlugin:
0447         initInternalPlugin(plugin);
0448         break;
0449 
0450     case Plugin::SharedLibraryPlugin:
0451         initSharedLibraryPlugin(plugin);
0452         break;
0453 
0454     case Plugin::PythonPlugin:
0455         initPythonPlugin(plugin);
0456         break;
0457 
0458     case Plugin::QmlPlugin:
0459         QmlPlugin::initPlugin(plugin);
0460         break;
0461 
0462     default:
0463         return false;
0464     }
0465 
0466     if (!plugin->instance) {
0467         return false;
0468     }
0469 
0470     // DataPaths::currentProfilePath() + QL1S("/extensions") is duplicated in qmlsettings.cpp
0471     // If you change this, please change it there too.
0472     plugin->instance->init(state, DataPaths::currentProfilePath() + QL1S("/extensions"));
0473 
0474     if (!plugin->instance->testPlugin()) {
0475         Q_EMIT pluginUnloaded(plugin->instance);
0476         plugin->instance = nullptr;
0477         return false;
0478     }
0479 
0480     return true;
0481 }
0482 
0483 void Plugins::initInternalPlugin(Plugin *plugin)
0484 {
0485     Q_ASSERT(plugin->type == Plugin::InternalPlugin);
0486 
0487     plugin->instance = plugin->internalInstance;
0488 }
0489 
0490 void Plugins::initSharedLibraryPlugin(Plugin *plugin)
0491 {
0492     Q_ASSERT(plugin->type == Plugin::SharedLibraryPlugin);
0493 
0494     plugin->instance = qobject_cast<PluginInterface*>(plugin->pluginLoader->instance());
0495 
0496     if (!plugin->instance) {
0497         qWarning() << "Loading" << plugin->pluginPath << "failed:" << plugin->pluginLoader->errorString();
0498     }
0499 }
0500 
0501 void Plugins::initPythonPlugin(Plugin *plugin)
0502 {
0503     Q_ASSERT(plugin->type == Plugin::PythonPlugin);
0504 
0505     if (!m_pythonPlugin) {
0506         qWarning() << "Python support plugin is not loaded";
0507         return;
0508     }
0509 
0510     auto f = (void(*)(Plugin *)) m_pythonPlugin->resolve("pyfalkon_init_plugin");
0511     if (!f) {
0512         qWarning() << "Failed to resolve" << "pyfalkon_init_plugin";
0513         return;
0514     }
0515 
0516     f(plugin);
0517 }