gemini scheme handler for webengine

This commit is contained in:
Martin Rotter 2024-12-19 10:41:41 +01:00
parent 65bb665f92
commit 55246c6261
8 changed files with 139 additions and 3 deletions

View File

@ -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()

View File

@ -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 {

View File

@ -12,6 +12,8 @@
#include <QSslSocket>
#include <QUrl>
#define GEMINI_MIME_TYPE "text/gemini"
//! Cryptographic user identitiy consisting
//! of a key-certificate pair and some user information.
struct CryptoIdentity {

View File

@ -129,7 +129,7 @@ QString GeminiParser::parseLink(const QRegularExpressionMatch& mtch) const {
QString link = mtch.captured(1);
QString name = mtch.captured(2);
return QSL("<p>🔗 <a href=\"%1\">%2</a></p>\n").arg(link, name.isEmpty() ? link : name);
return QSL("<p>&#128279; <a href=\"%1\">%2</a></p>\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("<div>%1</div>\n").arg(text.isEmpty() ? QString() : QSL("“%1”").arg(text));
return QSL("<div>%1</div>\n").arg(text.isEmpty() ? QString() : QSL("&#8220;%1&#8221;").arg(text));
}
QString GeminiParser::parseList(const QRegularExpressionMatch& mtch) const {

View File

@ -0,0 +1,89 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "network-web/gemini/geminischemehandler.h"
#include "definitions/definitions.h"
#include "miscellaneous/iofactory.h"
#include "network-web/gemini/geminiparser.h"
#include <QBuffer>
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<GeminiClient*>(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<GeminiClient*>(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<GeminiClient*>(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<QWebEngineUrlRequestJob*>(job);
auto* gemini_client = m_jobs.value(key);
if (gemini_client != nullptr) {
gemini_client->deleteLater();
}
if (key != nullptr) {
m_jobs.remove(key);
}
}

View File

@ -0,0 +1,28 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef GEMINISCHEMEHANDLER_H
#define GEMINISCHEMEHANDLER_H
#include "network-web/gemini/geminiclient.h"
#include <QWebEngineUrlRequestJob>
#include <QWebEngineUrlSchemeHandler>
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<QWebEngineUrlRequestJob*, GeminiClient*> m_jobs;
};
#endif // GEMINISCHEMEHANDLER_H

View File

@ -17,6 +17,7 @@
#include <QUrl>
#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

View File

@ -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;