aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDima Krasner <dima@dimakrasner.com>2023-11-05 19:39:06 +0200
committerFelix Queißner <felix@ib-queissner.de>2023-11-09 08:20:12 +0100
commit1a45619461fd6cb6c073695e7ae6a9e07aa445d7 (patch)
treee427461c36da6b061442227576fd62a94d25ee84
parent40448d458c4428633f337fdef707ef9e1c87ece8 (diff)
add ugly guppy:// v0.4 support
-rw-r--r--src/browsertab.cpp2
-rw-r--r--src/dialogs/settingsdialog.ui11
-rw-r--r--src/kristall.pro2
-rw-r--r--src/protocols/guppyclient.cpp179
-rw-r--r--src/protocols/guppyclient.hpp47
-rw-r--r--src/protocolsetup.hpp1
6 files changed, 242 insertions, 0 deletions
diff --git a/src/browsertab.cpp b/src/browsertab.cpp
index efb75c9..c715f5e 100644
--- a/src/browsertab.cpp
+++ b/src/browsertab.cpp
@@ -17,6 +17,7 @@
#include "protocols/geminiclient.hpp"
#include "protocols/webclient.hpp"
#include "protocols/gopherclient.hpp"
+#include "protocols/guppyclient.hpp"
#include "protocols/fingerclient.hpp"
#include "protocols/abouthandler.hpp"
#include "protocols/filehandler.hpp"
@@ -77,6 +78,7 @@ BrowserTab::BrowserTab(MainWindow *mainWindow) : QWidget(nullptr),
addProtocolHandler<GeminiClient>();
addProtocolHandler<FingerClient>();
addProtocolHandler<GopherClient>();
+ addProtocolHandler<GuppyClient>();
addProtocolHandler<WebClient>();
addProtocolHandler<AboutHandler>();
addProtocolHandler<FileHandler>();
diff --git a/src/dialogs/settingsdialog.ui b/src/dialogs/settingsdialog.ui
index 04e86d5..60b66ba 100644
--- a/src/dialogs/settingsdialog.ui
+++ b/src/dialogs/settingsdialog.ui
@@ -194,6 +194,16 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="enable_guppy">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Guppy</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="enable_finger">
<property name="enabled">
<bool>true</bool>
@@ -1643,6 +1653,7 @@
<tabstops>
<tabstop>enable_gemini</tabstop>
<tabstop>enable_gopher</tabstop>
+ <tabstop>enable_guppy</tabstop>
<tabstop>enable_finger</tabstop>
<tabstop>enable_http</tabstop>
<tabstop>enable_https</tabstop>
diff --git a/src/kristall.pro b/src/kristall.pro
index 4884897..488cc8e 100644
--- a/src/kristall.pro
+++ b/src/kristall.pro
@@ -143,6 +143,7 @@ SOURCES += \
protocols/fingerclient.cpp \
protocols/geminiclient.cpp \
protocols/gopherclient.cpp \
+ protocols/guppyclient.cpp \
protocols/webclient.cpp \
protocolsetup.cpp \
renderers/geminirenderer.cpp \
@@ -193,6 +194,7 @@ HEADERS += \
protocols/fingerclient.hpp \
protocols/geminiclient.hpp \
protocols/gopherclient.hpp \
+ protocols/guppyclient.hpp \
protocols/webclient.hpp \
protocolsetup.hpp \
renderers/geminirenderer.hpp \
diff --git a/src/protocols/guppyclient.cpp b/src/protocols/guppyclient.cpp
new file mode 100644
index 0000000..931e151
--- /dev/null
+++ b/src/protocols/guppyclient.cpp
@@ -0,0 +1,179 @@
+#include "guppyclient.hpp"
+#include "ioutil.hpp"
+#include "kristall.hpp"
+
+GuppyClient::GuppyClient(QObject *parent) : ProtocolHandler(parent)
+{
+ connect(&socket, &QUdpSocket::connected, this, &GuppyClient::on_connected);
+ connect(&socket, &QUdpSocket::readyRead, this, &GuppyClient::on_readRead);
+ connect(&timer, &QTimer::timeout, this, &GuppyClient::on_timerTick);
+
+ connect(&socket, &QAbstractSocket::hostFound, this, [this]() {
+ emit this->requestStateChange(RequestState::HostFound);
+ });
+ emit this->requestStateChange(RequestState::None);
+}
+
+GuppyClient::~GuppyClient()
+{
+
+}
+
+bool GuppyClient::supportsScheme(const QString &scheme) const
+{
+ return (scheme == "guppy");
+}
+
+bool GuppyClient::startRequest(const QUrl &url, RequestOptions options)
+{
+ Q_UNUSED(options)
+
+ if(isInProgress())
+ return false;
+
+ if(url.scheme() != "guppy")
+ return false;
+
+ emit this->requestStateChange(RequestState::Started);
+
+ this->requested_url = url;
+ this->was_cancelled = false;
+ this->prev_seq = this->first_seq = this->last_seq = 0;
+ socket.connectToHost(url.host(), url.port(6775));
+
+ return true;
+}
+
+bool GuppyClient::isInProgress() const
+{
+ return socket.isOpen();
+}
+
+bool GuppyClient::cancelRequest()
+{
+ was_cancelled = true;
+ socket.close();
+ timer.stop();
+ body.clear();
+ chunks.clear();
+ return true;
+}
+
+void GuppyClient::on_connected()
+{
+ request = (requested_url.toString(QUrl::FormattingOptions(QUrl::FullyEncoded)) + "\r\n").toUtf8();
+ if(socket.write(request.constData(), request.size()) <= 0)
+ {
+ socket.close();
+ return;
+ }
+
+ timer.start(2000);
+
+ emit this->requestStateChange(RequestState::Connected);
+}
+
+void GuppyClient::on_readRead()
+{
+ QByteArray chunk = socket.read(65535);
+
+ if(int crlf = chunk.indexOf("\r\n"); crlf > 0) {
+ QByteArray header = chunk.left(crlf);
+
+ // first response packet (success, error or redirect) header should contain a space
+ int seq = -1;
+ if(int space = header.indexOf(' '); space > 0) {
+ QByteArray meta = header.mid(space + 1);
+ seq = chunk.left(space).toInt();
+
+ if(seq < 6 || seq > 2147483647) {
+ timer.stop();
+ body.clear();
+ chunks.clear();
+ emit this->requestStateChange(RequestState::None);
+
+ if(seq == 4) emit networkError(UnknownError, meta); // error
+ else if (seq == 3) { // redirect
+ QUrl new_url(meta);
+
+ if(new_url.isRelative()) new_url = requested_url.resolved(new_url);
+ assert(not new_url.isRelative());
+
+ socket.close();
+ timer.stop();
+ body.clear();
+ chunks.clear();
+ emit this->requestStateChange(RequestState::None);
+
+ emit redirected(new_url, false);
+ } else if (seq == 1) { // input prompt
+ socket.close();
+ timer.stop();
+ body.clear();
+ chunks.clear();
+
+ emit this->requestStateChange(RequestState::None);
+
+ emit inputRequired(meta, false);
+ } else emit networkError(ProtocolViolation, QObject::tr("invalid seq"));
+
+ return;
+ }
+
+ first_seq = seq; // success
+ mime = meta;
+ } else seq = header.toInt();
+
+ if(seq < first_seq) return;
+ if(chunk.size() == crlf + 2) last_seq = seq; // eof
+ else if(seq >= first_seq) { // success or continuation
+ if(!prev_seq || seq >= prev_seq) chunks[seq] = chunk.mid(crlf + 2, chunk.size() - crlf - 2);
+
+ // postpone the timer when we receive the next packet
+ if(seq == prev_seq + 1) timer.start();
+ }
+ // acknowledge every valid packet we receive
+ QByteArray ack = QString("%1\r\n").arg(seq).toUtf8();
+ socket.write(ack.constData(), ack.size());
+ } else {
+ emitNetworkError(socket.error(), socket.errorString());
+ return;
+ }
+
+ // append all consequent chunks we have
+ int next_seq = prev_seq ? prev_seq + 1 : first_seq;
+ while(next_seq != last_seq) {
+ QByteArray next = chunks.take(next_seq);
+ if(next.isNull()) break;
+ body.append(next.constData(), next.size());
+ prev_seq = next_seq;
+ }
+
+ if(not was_cancelled) {
+ emit this->requestProgress(body.size());
+ }
+
+ // we're done when the last appended chunk is the one before the eof chunk
+ if(next_seq == last_seq) {
+ if(not was_cancelled) {
+ emit this->requestComplete(this->body, mime);
+ was_cancelled = true;
+ }
+ socket.close();
+ timer.stop();
+ body.clear();
+ chunks.clear();
+
+ emit this->requestStateChange(RequestState::None);
+ }
+}
+
+void GuppyClient::on_timerTick()
+{
+ QByteArray pkt;
+ if(prev_seq) pkt = QString("%1\r\n").arg(prev_seq).toUtf8(); // resend the last ack
+ else if(chunks.empty()) pkt = request; // resend the request
+ else return;
+
+ socket.write(pkt.constData(), pkt.size());
+}
diff --git a/src/protocols/guppyclient.hpp b/src/protocols/guppyclient.hpp
new file mode 100644
index 0000000..ebe76cb
--- /dev/null
+++ b/src/protocols/guppyclient.hpp
@@ -0,0 +1,47 @@
+#ifndef GUPPYCLIENT_HPP
+#define GUPPYCLIENT_HPP
+
+#include <QObject>
+#include <QUdpSocket>
+#include <QUrl>
+#include <QTimer>
+
+#include "protocolhandler.hpp"
+
+class GuppyClient : public ProtocolHandler
+{
+ Q_OBJECT
+public:
+ explicit GuppyClient(QObject *parent = nullptr);
+
+ ~GuppyClient() override;
+
+ bool supportsScheme(QString const & scheme) const override;
+
+ bool startRequest(QUrl const & url, RequestOptions options) override;
+
+ bool isInProgress() const override;
+
+ bool cancelRequest() override;
+
+private: // slots
+ void on_connected();
+ void on_readRead();
+ void on_finished();
+ void on_timerTick();
+ void on_socketError(QAbstractSocket::SocketError errorCode);
+
+
+private:
+ QUdpSocket socket;
+ QHash<long, QByteArray> chunks;
+ QByteArray body;
+ QUrl requested_url;
+ QByteArray request;
+ bool was_cancelled;
+ int prev_seq, first_seq, last_seq;
+ QString mime;
+ QTimer timer;
+};
+
+#endif // GUPPYCLIENT_HPP
diff --git a/src/protocolsetup.hpp b/src/protocolsetup.hpp
index db163de..3b047bc 100644
--- a/src/protocolsetup.hpp
+++ b/src/protocolsetup.hpp
@@ -7,6 +7,7 @@
MAC(http) \
MAC(https) \
MAC(gopher) \
+ MAC(guppy) \
MAC(gemini) \
MAC(finger)