File indexing completed on 2024-05-19 05:01:22

0001 /*
0002     This file is part of the KDE project.
0003 
0004     SPDX-FileCopyrightText: 2021 Stefano Crocco <stefano.crocco@alice.it>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "webenginepartcontrols.h"
0010 #include "schemehandlers/errorschemehandler.h"
0011 #include "webengineurlrequestinterceptor.h"
0012 #include "schemehandlers/kiohandler.h"
0013 #include "about/konq_aboutpage.h"
0014 #include "spellcheckermanager.h"
0015 #include "webenginepartdownloadmanager.h"
0016 #include "certificateerrordialogmanager.h"
0017 #include "webenginewallet.h"
0018 #include "webenginepart.h"
0019 #include "webenginepage.h"
0020 #include "navigationrecorder.h"
0021 #include <webenginepart_debug.h>
0022 #include "profile.h"
0023 #include "interfaces/browser.h"
0024 #include "schemehandlers/execschemehandler.h"
0025 
0026 #include <KProtocolInfo>
0027 #include <KSharedConfig>
0028 #include <KConfigGroup>
0029 #include <KLocalizedString>
0030 
0031 #include <QWebEngineProfile>
0032 #include <QWebEngineUrlScheme>
0033 #include <QWebEngineSettings>
0034 #include <QWebEngineScriptCollection>
0035 #include <QApplication>
0036 #include <QLocale>
0037 #include <QSettings>
0038 #include <QJsonDocument>
0039 #include <QMessageBox>
0040 
0041 using namespace KonqInterfaces;
0042 using namespace WebEngine;
0043 
0044 WebEnginePartControls::WebEnginePartControls(): QObject(),
0045     m_profile(nullptr), m_cookieJar(nullptr), m_spellCheckerManager(nullptr), m_downloadManager(nullptr),
0046     m_certificateErrorDialogManager(new KonqWebEnginePart::CertificateErrorDialogManager(this)),
0047     m_navigationRecorder(new NavigationRecorder(this))
0048 {
0049     QVector<QByteArray> localSchemes = {"error", "konq", "tar", "bookmarks"};
0050     const QStringList protocols = KProtocolInfo::protocols();
0051     for(const QString &prot : protocols){
0052         if (KProtocolInfo::defaultMimetype(prot) == "text/html") {
0053             localSchemes.append(QByteArray(prot.toLatin1()));
0054         }
0055     }
0056     for (const QByteArray &name : qAsConst(localSchemes)){
0057         QWebEngineUrlScheme scheme(name);
0058         scheme.setFlags(QWebEngineUrlScheme::LocalScheme|QWebEngineUrlScheme::LocalAccessAllowed);
0059         scheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
0060         QWebEngineUrlScheme::registerScheme(scheme);
0061     }
0062 
0063     QWebEngineUrlScheme execScheme(QByteArrayLiteral("exec"));
0064     execScheme.setFlags(QWebEngineUrlScheme::LocalScheme);
0065     execScheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
0066     QWebEngineUrlScheme::registerScheme(execScheme);
0067 
0068     Browser *browser = Browser::browser(qApp);
0069     if (browser) {
0070         connect(browser, &Browser::configurationChanged, this, &WebEnginePartControls::reparseConfiguration);
0071         connect(browser, &Browser::userAgentChanged, this, &WebEnginePartControls::setHttpUserAgent);
0072     }
0073 }
0074 
0075 WebEnginePartControls::~WebEnginePartControls()
0076 {
0077 }
0078 
0079 WebEnginePartControls* WebEnginePartControls::self()
0080 {
0081     static WebEnginePartControls s_self;
0082     return &s_self;
0083 }
0084 
0085 bool WebEnginePartControls::isReady() const
0086 {
0087     return m_profile;
0088 }
0089 
0090 void WebEnginePartControls::registerScripts()
0091 {
0092     if (!m_profile) {
0093         qCDebug(WEBENGINEPART_LOG) << "Attempting to register scripts before setting the profile";
0094         return;
0095     }
0096 
0097     QFile jsonFile(QStringLiteral(":/scripts.json"));
0098     jsonFile.open(QIODevice::ReadOnly);
0099     QJsonObject obj = QJsonDocument::fromJson(jsonFile.readAll()).object();
0100     Q_ASSERT(!obj.isEmpty());
0101     jsonFile.close();
0102     for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) {
0103         QJsonObject scriptData = it.value().toObject();
0104         Q_ASSERT(!scriptData.isEmpty());
0105         QWebEngineScript script = scriptFromJson(it.key(), scriptData);
0106         if (!script.name().isEmpty()) {
0107             m_profile->scripts()->insert(script);
0108         }
0109     }
0110 }
0111 
0112 QWebEngineScript WebEnginePartControls::scriptFromJson(const QString& name, const QJsonObject& obj)
0113 {
0114     QWebEngineScript script;
0115     QString file = obj.value(QLatin1String("file")).toString();
0116     if (file.isEmpty()) {
0117         return script;
0118     }
0119     QFile sourceFile(file);
0120     sourceFile.open(QIODevice::ReadOnly);
0121     Q_ASSERT(sourceFile.isOpen());
0122     script.setSourceCode(QString(sourceFile.readAll()));
0123     QJsonValue val = obj.value(QLatin1String("injectionPoint"));
0124     if (!val.isNull()) {
0125         int injectionPoint = val.toInt(-1);
0126         //NOTE:keep in sync with the values of QWebEngineScript::InjectionPoint
0127         Q_ASSERT(injectionPoint >=QWebEngineScript::Deferred && injectionPoint <= QWebEngineScript::DocumentCreation);
0128         script.setInjectionPoint(static_cast<QWebEngineScript::InjectionPoint>(injectionPoint));
0129     }
0130     val = obj.value(QLatin1String("worldId"));
0131     if (!val.isNull()) {
0132         int world= val.toInt(-1);
0133         //NOTE: keep in sync with the values of QWebEngineScript::ScriptWorldId
0134         Q_ASSERT(world >= QWebEngineScript::MainWorld);
0135         script.setWorldId(world);
0136     }
0137     val = obj.value(QStringLiteral("runsOnSubFrames"));
0138     if (!val.isBool()) {
0139         script.setRunsOnSubFrames(val.toBool());
0140     }
0141     script.setName(name);
0142     return script;
0143 }
0144 
0145 void WebEnginePartControls::setup(KonqWebEnginePart::Profile* profile)
0146 {
0147     if (!profile || isReady()) {
0148         return;
0149     }
0150     m_profile = profile;
0151 
0152     registerScripts();
0153 
0154     m_profile->installUrlSchemeHandler("error", new ErrorSchemeHandler(m_profile));
0155     m_profile->installUrlSchemeHandler("konq", new KonqUrlSchemeHandler(m_profile));
0156     m_profile->installUrlSchemeHandler("help", new KIOHandler(m_profile));
0157     m_profile->installUrlSchemeHandler("tar", new KIOHandler(m_profile));
0158     m_profile->installUrlSchemeHandler("bookmarks", new KIOHandler(m_profile));
0159     m_profile->installUrlSchemeHandler("exec", new ExecSchemeHandler(m_profile));
0160 
0161     m_profile->setUrlRequestInterceptor(new WebEngineUrlRequestInterceptor(this));
0162 
0163     m_cookieJar = new WebEnginePartCookieJar(profile, this);
0164     Browser *browser = Browser::browser(qApp);
0165     if (browser) {
0166         m_profile->setHttpUserAgent(browser->userAgent());
0167 #ifdef MANAGE_COOKIES_INTERNALLY
0168         browser->setCookieJar(m_cookieJar);
0169 #endif
0170     }
0171     m_spellCheckerManager = new SpellCheckerManager(profile, this);
0172     m_downloadManager= new WebEnginePartDownloadManager(profile, this);
0173     m_profile->settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true);
0174     QString langHeader = determineHttpAcceptLanguageHeader();
0175     if (!langHeader.isEmpty()) {
0176         m_profile->setHttpAcceptLanguage(langHeader);
0177     }
0178 
0179     reparseConfiguration();
0180 }
0181 
0182 WebEnginePartDownloadManager* WebEnginePartControls::downloadManager() const
0183 {
0184     return m_downloadManager;
0185 }
0186 
0187 SpellCheckerManager* WebEnginePartControls::spellCheckerManager() const
0188 {
0189     return m_spellCheckerManager;
0190 }
0191 
0192 bool WebEnginePartControls::handleCertificateError(const QWebEngineCertificateError& ce, WebEnginePage* page)
0193 {
0194     return m_certificateErrorDialogManager->handleCertificateError(ce, page);
0195 }
0196 
0197 QString WebEnginePartControls::determineHttpAcceptLanguageHeader() const
0198 {
0199     //According to comments in KSwitchLanguageDialog, the settings are stored in an INI format using QSettings,
0200     //rather than using KConfig
0201     QSettings appLangSettings(QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "klanguageoverridesrc"), QSettings::IniFormat);
0202     appLangSettings.beginGroup(QStringLiteral("Language"));
0203     QString lang(appLangSettings.value(QApplication::instance()->applicationName()).toByteArray());
0204     if (lang.isEmpty()) {
0205         KSharedConfig::Ptr cfg;
0206         cfg = KSharedConfig::openConfig(QStringLiteral("plasma-localerc"));
0207         lang = cfg->group(QStringLiteral("Translations")).readEntry(QStringLiteral("LANGUAGE"));
0208         if (lang.isEmpty()) {
0209             lang = QLocale::system().name();
0210         }
0211         if (lang.isEmpty()) {
0212             return QString();
0213         }
0214     }
0215     QStringList languages = lang.split(':');
0216     QString header = languages.at(0);
0217     //In Qt6, QList::length() returns a qsizetype, which means you can't pass it to std::min.
0218     //Casting it to an int shouldn't be a problem, since the number of languages should be
0219     //small
0220     int max = std::min(static_cast<int>(languages.length()), 10);
0221     //The counter starts from 1 because the first entry has already been inserted above
0222     for (int i = 1; i < max; ++i) {
0223         header.append(QString(", %1;q=0.%2").arg(languages.at(i)).arg(10-i));
0224     }
0225     return header;
0226 }
0227 
0228 NavigationRecorder * WebEnginePartControls::navigationRecorder() const
0229 {
0230     return m_navigationRecorder;
0231 }
0232 
0233 void WebEnginePartControls::reparseConfiguration()
0234 {
0235     if (!m_profile) {
0236         return;
0237     }
0238     KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
0239     KConfigGroup grp = cfg->group(QStringLiteral("Cache"));
0240     if (grp.readEntry(QStringLiteral("CacheEnabled"), true)) {
0241         QWebEngineProfile::HttpCacheType type = grp.readEntry(QStringLiteral("MemoryCache"), false) ? QWebEngineProfile::MemoryHttpCache : QWebEngineProfile::DiskHttpCache;
0242         m_profile->setHttpCacheType(type);
0243         m_profile->setHttpCacheMaximumSize(grp.readEntry(QStringLiteral("MaximumCacheSize"), 0));
0244         //NOTE: According to the documentation, setCachePath resets the cache path to its default value if the argument is a null QString
0245         //it doesn't specify what it does if the string is empty but not null. Experimenting, it seems the behavior is the same
0246         m_profile->setCachePath(grp.readEntry(QStringLiteral("CustomCacheDir"), QString()));
0247 
0248     } else {
0249         m_profile->setHttpCacheType(QWebEngineProfile::NoCache);
0250     }
0251 
0252     updateUserStyleSheetScript();
0253 }
0254 
0255 void WebEnginePartControls::updateUserStyleSheetScript()
0256 {
0257 #if QT_VERSION_MAJOR < 6
0258     QList<QWebEngineScript> oldScripts = m_profile->scripts()->findScripts(s_userStyleSheetScriptName);
0259 #else
0260     QList<QWebEngineScript> oldScripts = m_profile->scripts()->find(s_userStyleSheetScriptName);
0261 #endif
0262     bool hadUserStyleSheet = !oldScripts.isEmpty();
0263     //Remove old style sheets. Note that oldScripts should either be empty or contain only one element
0264     //In theory, we could reuse the previous script, if it turns out to be the same as the new one, but
0265     //I think this way is faster (besides being easier)
0266     for (const QWebEngineScript &s : oldScripts) {
0267         m_profile->scripts()->remove(s);
0268     }
0269     QUrl userStyleSheetUrl(WebEngineSettings::self()->userStyleSheet());
0270     bool userStyleSheetEnabled = !userStyleSheetUrl.isEmpty();
0271     //If the user stylesheet is disable and it was already disabled, there's nothing to do; if there was a custom stylesheet in use,
0272     //we'll have to remove it, so we can't return
0273     if (!userStyleSheetEnabled && !hadUserStyleSheet) {
0274         return;
0275     }
0276 
0277     QString css;
0278     if (userStyleSheetEnabled) {
0279         //Read the contents of the custom style sheet
0280         QFile cssFile(userStyleSheetUrl.path());
0281         cssFile.open(QFile::ReadOnly);
0282         if (cssFile.isOpen()) {
0283             css = cssFile.readAll();
0284             cssFile.close();
0285         }
0286         else {
0287             auto msg = i18n("Couldn't open the file <tt>%1</tt> containing the user style sheet. The default style sheet will be used", userStyleSheetUrl.path());
0288             QMessageBox::warning(qApp->activeWindow(), QString(), msg);
0289             //Only return if no custom stylesheet was in use
0290             if (!hadUserStyleSheet) {
0291                 return;
0292             }
0293             userStyleSheetEnabled = false;
0294         }
0295     }
0296 
0297     //Create the js code
0298     QFile applyUserCssFile(":/applyuserstylesheet.js");
0299     applyUserCssFile.open(QFile::ReadOnly);
0300     Q_ASSERT(applyUserCssFile.isOpen());
0301     QString code{QString(applyUserCssFile.readAll()).arg(s_userStyleSheetScriptName).arg(css.simplified())};
0302     applyUserCssFile.close();
0303 
0304     //Tell pages to update their stylesheet. If `css` is empty, the script will remove the <style> element from the pages;
0305     //if `css` is not empty, it will update or create the appropriate <style> element
0306     emit updateStyleSheet(code);
0307     if (!userStyleSheetEnabled) {
0308         return;
0309     }
0310 
0311     //Create a script to inject in new pages
0312     QWebEngineScript applyUserCss;
0313     applyUserCss.setName(s_userStyleSheetScriptName);
0314     applyUserCss.setInjectionPoint(QWebEngineScript::DocumentReady);
0315     applyUserCss.setWorldId(QWebEngineScript::ApplicationWorld);
0316     applyUserCss.setSourceCode(code);
0317     m_profile->scripts()->insert(applyUserCss);
0318 }
0319 
0320 
0321 QString WebEnginePartControls::httpUserAgent() const
0322 {
0323     return m_profile ? m_profile->httpUserAgent() : QString();
0324 }
0325 
0326 void WebEnginePartControls::setHttpUserAgent(const QString& uaString)
0327 {
0328     if (!m_profile || m_profile->httpUserAgent() == uaString) {
0329         return;
0330     }
0331     m_profile->setHttpUserAgent(uaString);
0332     emit userAgentChanged(uaString);
0333 }
0334 
0335 QString WebEnginePartControls::defaultHttpUserAgent() const
0336 {
0337     return m_defaultUserAgent;
0338 }