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 }