diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index b00200153..09318ca92 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -443,6 +443,8 @@ if(NO_LITE) network-web/webengine/urlinterceptor.h network-web/webengine/webenginepage.cpp network-web/webengine/webenginepage.h + network-web/gemini/geminischemehandler.cpp + network-web/gemini/geminischemehandler.h ) endif() diff --git a/src/librssguard/network-web/downloader.cpp b/src/librssguard/network-web/downloader.cpp index 10a8b9291..663a2ef06 100644 --- a/src/librssguard/network-web/downloader.cpp +++ b/src/librssguard/network-web/downloader.cpp @@ -49,7 +49,7 @@ void Downloader::geminiFinished(const QByteArray& data, const QString& mime) { m_lastOutputError = QNetworkReply::NetworkError::NoError; m_lastOutputMultipartData = {}; - if (mime.startsWith(QSL("text/gemini"))) { + if (mime.startsWith(QSL(GEMINI_MIME_TYPE))) { m_lastOutputData = GeminiParser().geminiToHtml(data).toUtf8(); } else { diff --git a/src/librssguard/network-web/gemini/geminiclient.h b/src/librssguard/network-web/gemini/geminiclient.h index 4aa1dddfb..46ebbfbbe 100644 --- a/src/librssguard/network-web/gemini/geminiclient.h +++ b/src/librssguard/network-web/gemini/geminiclient.h @@ -12,6 +12,8 @@ #include #include +#define GEMINI_MIME_TYPE "text/gemini" + //! Cryptographic user identitiy consisting //! of a key-certificate pair and some user information. struct CryptoIdentity { diff --git a/src/librssguard/network-web/gemini/geminiparser.cpp b/src/librssguard/network-web/gemini/geminiparser.cpp index df8649e04..1cd663922 100644 --- a/src/librssguard/network-web/gemini/geminiparser.cpp +++ b/src/librssguard/network-web/gemini/geminiparser.cpp @@ -129,7 +129,7 @@ QString GeminiParser::parseLink(const QRegularExpressionMatch& mtch) const { QString link = mtch.captured(1); QString name = mtch.captured(2); - return QSL("

🔗 %2

\n").arg(link, name.isEmpty() ? link : name); + return QSL("

🔗 %2

\n").arg(link, name.isEmpty() ? link : name); } QString GeminiParser::parseHeading(const QRegularExpressionMatch& mtch, QString* clean_header) const { @@ -147,7 +147,7 @@ QString GeminiParser::parseHeading(const QRegularExpressionMatch& mtch, QString* QString GeminiParser::parseQuote(const QRegularExpressionMatch& mtch) const { QString text = mtch.captured(1); - return QSL("
%1
\n").arg(text.isEmpty() ? QString() : QSL("“%1”").arg(text)); + return QSL("
%1
\n").arg(text.isEmpty() ? QString() : QSL("“%1”").arg(text)); } QString GeminiParser::parseList(const QRegularExpressionMatch& mtch) const { diff --git a/src/librssguard/network-web/gemini/geminischemehandler.cpp b/src/librssguard/network-web/gemini/geminischemehandler.cpp new file mode 100644 index 000000000..84e90fd8b --- /dev/null +++ b/src/librssguard/network-web/gemini/geminischemehandler.cpp @@ -0,0 +1,89 @@ +// For license of this file, see /LICENSE.md. + +#include "network-web/gemini/geminischemehandler.h" + +#include "definitions/definitions.h" +#include "miscellaneous/iofactory.h" +#include "network-web/gemini/geminiparser.h" + +#include + +GeminiSchemeHandler::GeminiSchemeHandler(QObject* parent) : QWebEngineUrlSchemeHandler(parent) {} + +void GeminiSchemeHandler::requestStarted(QWebEngineUrlRequestJob* request) { + GeminiClient* gemini_client = new GeminiClient(this); + + m_jobs.insert(request, gemini_client); + + connect(gemini_client, &GeminiClient::redirected, this, &GeminiSchemeHandler::onRedirect); + connect(gemini_client, &GeminiClient::requestComplete, this, &GeminiSchemeHandler::onCompleted); + connect(gemini_client, &GeminiClient::networkError, this, &GeminiSchemeHandler::onNetworkError); + + connect(request, &QWebEngineUrlRequestJob::destroyed, this, &GeminiSchemeHandler::onJobDeleted); + + gemini_client->startRequest(request->requestUrl(), GeminiClient::RequestOptions::IgnoreTlsErrors); +} + +void GeminiSchemeHandler::onRedirect(const QUrl& uri, bool is_permanent) { + GeminiClient* gemini_client = qobject_cast(sender()); + auto* job = m_jobs.key(gemini_client); + + if (job != nullptr) { + job->redirect(uri); + m_jobs.remove(job); + gemini_client->deleteLater(); + } +} + +void GeminiSchemeHandler::onCompleted(const QByteArray& data, const QString& mime) { + GeminiClient* gemini_client = qobject_cast(sender()); + auto* job = m_jobs.key(gemini_client); + + if (job != nullptr) { + QBuffer* buf = new QBuffer(); + QString target_mime; + buf->open(QBuffer::ReadWrite); + + if (mime.startsWith(QSL(GEMINI_MIME_TYPE))) { + // IOFactory::writeFile("a", data); + + buf->write(GeminiParser().geminiToHtml(data).toUtf8()); + target_mime = QSL("text/html"); + } + else { + buf->write(data); + target_mime = mime; + } + + buf->seek(0); + + connect(job, &QWebEngineUrlRequestJob::destroyed, buf, &QBuffer::deleteLater); + job->reply(target_mime.toLocal8Bit(), buf); + m_jobs.remove(job); + gemini_client->deleteLater(); + } +} + +void GeminiSchemeHandler::onNetworkError(GeminiClient::NetworkError error, const QString& reason) { + GeminiClient* gemini_client = qobject_cast(sender()); + auto* job = m_jobs.key(gemini_client); + + if (job != nullptr) { + job->fail(QWebEngineUrlRequestJob::Error::RequestFailed); + m_jobs.remove(job); + gemini_client->deleteLater(); + } +} + +void GeminiSchemeHandler::onJobDeleted(QObject* job) { + auto* key = qobject_cast(job); + auto* gemini_client = m_jobs.value(key); + + if (gemini_client != nullptr) { + gemini_client->deleteLater(); + } + + if (key != nullptr) { + m_jobs.remove(key); + } +} diff --git a/src/librssguard/network-web/gemini/geminischemehandler.h b/src/librssguard/network-web/gemini/geminischemehandler.h new file mode 100644 index 000000000..3ffa9aabd --- /dev/null +++ b/src/librssguard/network-web/gemini/geminischemehandler.h @@ -0,0 +1,28 @@ +// For license of this file, see /LICENSE.md. + +#ifndef GEMINISCHEMEHANDLER_H +#define GEMINISCHEMEHANDLER_H + +#include "network-web/gemini/geminiclient.h" + +#include +#include + +class GeminiSchemeHandler : public QWebEngineUrlSchemeHandler { + public: + explicit GeminiSchemeHandler(QObject* parent = nullptr); + + virtual void requestStarted(QWebEngineUrlRequestJob* request); + + private slots: + void onRedirect(const QUrl& uri, bool is_permanent); + void onCompleted(const QByteArray& data, const QString& mime); + void onNetworkError(GeminiClient::NetworkError error, const QString& reason); + + void onJobDeleted(QObject* job); + + private: + QHash m_jobs; +}; + +#endif // GEMINISCHEMEHANDLER_H diff --git a/src/librssguard/network-web/webfactory.cpp b/src/librssguard/network-web/webfactory.cpp index 14f8637a0..be6e3c53b 100644 --- a/src/librssguard/network-web/webfactory.cpp +++ b/src/librssguard/network-web/webfactory.cpp @@ -17,6 +17,7 @@ #include #if defined(NO_LITE) +#include "network-web/gemini/geminischemehandler.h" #include "network-web/webengine/networkurlinterceptor.h" #if QT_VERSION_MAJOR == 6 @@ -40,6 +41,16 @@ WebFactory::WebFactory(QObject* parent) : QObject(parent), m_apiServer(nullptr), } #if defined(NO_LITE) + + QWebEngineUrlScheme gemini_scheme("gemini"); + + gemini_scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); + // gemini_scheme.setFlags(QWebEngineUrlScheme::Flag::SecureScheme); + + QWebEngineUrlScheme::registerScheme(gemini_scheme); + + m_geminiHandler = new GeminiSchemeHandler(this); + if (qApp->settings()->value(GROUP(Browser), SETTING(Browser::DisableCache)).toBool()) { qWarningNN << LOGSEC_NETWORK << "Using off-the-record WebEngine profile."; @@ -49,6 +60,8 @@ WebFactory::WebFactory(QObject* parent) : QObject(parent), m_apiServer(nullptr), m_engineProfile = new QWebEngineProfile(QSL(APP_LOW_NAME), this); } + m_engineProfile->installUrlSchemeHandler("gemini", m_geminiHandler); + m_engineSettings = nullptr; m_urlInterceptor = new NetworkUrlInterceptor(this); #endif diff --git a/src/librssguard/network-web/webfactory.h b/src/librssguard/network-web/webfactory.h index b1db2e1ef..fe10ccc80 100644 --- a/src/librssguard/network-web/webfactory.h +++ b/src/librssguard/network-web/webfactory.h @@ -13,6 +13,7 @@ class QWebEngineProfile; class QWebEngineSettings; class QAction; class NetworkUrlInterceptor; +class GeminiSchemeHandler; #endif class QMenu; @@ -92,6 +93,7 @@ class RSSGUARD_DLLSPEC WebFactory : public QObject { QWebEngineProfile* m_engineProfile; NetworkUrlInterceptor* m_urlInterceptor; QAction* m_engineSettings; + GeminiSchemeHandler* m_geminiHandler; #endif ApiServer* m_apiServer;