diff options
| author | Dima Krasner <dima@dimakrasner.com> | 2023-11-05 19:39:06 +0200 |
|---|---|---|
| committer | Felix Queißner <felix@ib-queissner.de> | 2023-11-09 08:20:12 +0100 |
| commit | 1a45619461fd6cb6c073695e7ae6a9e07aa445d7 (patch) | |
| tree | e427461c36da6b061442227576fd62a94d25ee84 | |
| parent | 40448d458c4428633f337fdef707ef9e1c87ece8 (diff) | |
add ugly guppy:// v0.4 support
| -rw-r--r-- | src/browsertab.cpp | 2 | ||||
| -rw-r--r-- | src/dialogs/settingsdialog.ui | 11 | ||||
| -rw-r--r-- | src/kristall.pro | 2 | ||||
| -rw-r--r-- | src/protocols/guppyclient.cpp | 179 | ||||
| -rw-r--r-- | src/protocols/guppyclient.hpp | 47 | ||||
| -rw-r--r-- | src/protocolsetup.hpp | 1 |
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) |
