Adds improved error handling.

This commit is contained in:
Felix (xq) Queißner 2020-06-16 23:01:16 +02:00
parent a3f3e3933c
commit 94dbe30902
24 changed files with 227 additions and 15 deletions

View File

@ -203,7 +203,39 @@ void BrowserTab::on_refresh_button_clicked()
void BrowserTab::on_networkError(ProtocolHandler::NetworkError error_code, const QString &reason)
{
this->setErrorMessage(QString("%1:\n%2").arg(error_code).arg(reason));
QString file_name;
switch(error_code)
{
case ProtocolHandler::UnknownError: file_name = "UnknownError.gemini"; break;
case ProtocolHandler::ProtocolViolation: file_name = "ProtocolViolation.gemini"; break;
case ProtocolHandler::HostNotFound: file_name = "HostNotFound.gemini"; break;
case ProtocolHandler::ConnectionRefused: file_name = "ConnectionRefused.gemini"; break;
case ProtocolHandler::ResourceNotFound: file_name = "ResourceNotFound.gemini"; break;
case ProtocolHandler::BadRequest: file_name = "BadRequest.gemini"; break;
case ProtocolHandler::ProxyRequest: file_name = "ProxyRequest.gemini"; break;
case ProtocolHandler::InternalServerError: file_name = "InternalServerError.gemini"; break;
case ProtocolHandler::InvalidClientCertificate: file_name = "InvalidClientCertificate.gemini"; break;
case ProtocolHandler::UntrustedHost: file_name = "UntrustedHost.gemini"; break;
case ProtocolHandler::MistrustedHost: file_name = "MistrustedHost.gemini"; break;
case ProtocolHandler::Unauthorized: file_name = "Unauthorized.gemini"; break;
case ProtocolHandler::TlsFailure: file_name = "TlsFailure.gemini"; break;
case ProtocolHandler::Timeout: file_name = "Timeout.gemini"; break;
}
file_name = ":/error_page/" + file_name;
QFile file_src { file_name };
if(not file_src.open(QFile::ReadOnly)) {
assert(false);
}
auto contents = QString::fromUtf8(file_src.readAll()).arg(reason).toUtf8();
this->on_requestComplete(
contents,
"text/gemini");
this->updateUI();
}
void BrowserTab::on_certificateRequired(const QString &reason)
@ -603,7 +635,7 @@ void BrowserTab::on_text_browser_customContextMenuRequested(const QPoint &pos)
mainWindow->addNewTab(false, real_url);
});
connect(menu.addAction("Copy link"), &QAction::triggered, [this, real_url]() {
connect(menu.addAction("Copy link"), &QAction::triggered, [real_url]() {
global_clipboard->setText(real_url.toString(QUrl::FullyEncoded));
});
@ -614,6 +646,14 @@ void BrowserTab::on_text_browser_customContextMenuRequested(const QPoint &pos)
this->ui->text_browser->selectAll();
});
menu.addSeparator();
QAction * copy = menu.addAction("Copy to clipboard");
copy->setShortcut(QKeySequence("Ctrl-C"));
connect(copy, &QAction::triggered, [this]() {
this->ui->text_browser->copy();
});
menu.exec(ui->text_browser->mapToGlobal(pos));
}

View File

@ -3,5 +3,19 @@
<file>about/easter-egg.gemini</file>
<file>about/help.gemini</file>
<file>about/updates.gemini</file>
<file>error_page/BadRequest.gemini</file>
<file>error_page/ConnectionRefused.gemini</file>
<file>error_page/HostNotFound.gemini</file>
<file>error_page/InternalServerError.gemini</file>
<file>error_page/InvalidClientCertificate.gemini</file>
<file>error_page/MistrustedHost.gemini</file>
<file>error_page/ProtocolViolation.gemini</file>
<file>error_page/ProxyRequest.gemini</file>
<file>error_page/ResourceNotFound.gemini</file>
<file>error_page/Timeout.gemini</file>
<file>error_page/TlsFailure.gemini</file>
<file>error_page/Unauthorized.gemini</file>
<file>error_page/UnknownError.gemini</file>
<file>error_page/UntrustedHost.gemini</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
# Bad Request
Kristall tried to access the resource and made a mistake.
> %1

View File

@ -0,0 +1,5 @@
# Connection Refused
The server refused the connection. Please check that your URL is valid and that the server actually serves the files, then try again.
> %1

View File

@ -0,0 +1,5 @@
# Host Not Found
The server you tried to reach does not exist. Please verify that your URL is valid.
> %1

View File

@ -0,0 +1,5 @@
# Internal Server Error
The server failed to handle your request.
> %1

View File

@ -0,0 +1,5 @@
# Invalid Client Certificate
Your client certificate is not accepted by the server.
> %1

View File

@ -0,0 +1,7 @@
# Mistrusted Host
The host you tried to visit does not look trustworty anymore. The certificate changed since your last visit.
If you still trust this host, please revoke trust in the settings menu, then reload the page.
> %1

View File

@ -0,0 +1,5 @@
# Protocol Violation
The server did not serve the content you requested in a well-defined manner and Kristall could not process the data sent.
> %1

View File

@ -0,0 +1,5 @@
# Proxy Request
You tried to request a resource from one host that is actually located on another host. Please verify your URL.
> %1

View File

@ -0,0 +1,5 @@
# Resource Not Found
The resource you requested is not available.
> %1

View File

@ -0,0 +1,5 @@
# Timeout
The server timed out while answering your response.
> %1

View File

@ -0,0 +1,5 @@
# TLS Failure
There was an error while negotiating the TLS encryption.
> %1

View File

@ -0,0 +1,7 @@
# Unauthorized
You are not authorized to access the requested resource.
To solve this problem, you may enable a client certificate for gemini:// resources and try again.
> %1

View File

@ -0,0 +1,5 @@
# Unknown Error
Kristall tried its best but it failed doing so. This is an error that is not handled by any special logic and bubbled up to you. That's sad. ☹
> %1

View File

@ -0,0 +1,6 @@
# Untrusted Host
The host you tried to visit is not trusted by Kristall. If you do trust this server, please add it to the list of trusted certificates!
(which is currently not possible ☹)
> %1

View File

@ -6,6 +6,12 @@ FingerClient::FingerClient() : ProtocolHandler(nullptr)
connect(&socket, &QTcpSocket::connected, this, &FingerClient::on_connected);
connect(&socket, &QTcpSocket::readyRead, this, &FingerClient::on_readRead);
connect(&socket, &QTcpSocket::disconnected, this, &FingerClient::on_finished);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
connect(&socket, &QTcpSocket::errorOccurred, this, &FingerClient::on_socketError);
#else
connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &FingerClient::on_socketError);
#endif
}
FingerClient::~FingerClient()
@ -68,3 +74,8 @@ void FingerClient::on_finished()
}
body.clear();
}
void FingerClient::on_socketError(QAbstractSocket::SocketError error_code)
{
this->emitNetworkError(error_code, socket.errorString());
}

View File

@ -27,6 +27,7 @@ private slots:
void on_connected();
void on_readRead();
void on_finished();
void on_socketError(QTcpSocket::SocketError error_code);
private:
QTcpSocket socket;

View File

@ -10,7 +10,12 @@ GeminiClient::GeminiClient() : ProtocolHandler(nullptr)
connect(&socket, &QSslSocket::readyRead, this, &GeminiClient::socketReadyRead);
connect(&socket, &QSslSocket::disconnected, this, &GeminiClient::socketDisconnected);
connect(&socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors), this, &GeminiClient::sslErrors);
connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error), this, &GeminiClient::socketError);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
connect(&socket, &QTcpSocket::errorOccurred, this, &GeminiClient::socketError);
#else
connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &GeminiClient::socketError);
#endif
QSslConfiguration ssl_config;
ssl_config.setProtocol(QSsl::TlsV1_2);
@ -318,8 +323,6 @@ void GeminiClient::socketError(QAbstractSocket::SocketError socketError)
if(socketError == QAbstractSocket::RemoteHostClosedError) {
socket.close();
} else {
// qWarning() << socketError << socket.errorString();
// TODO: Make the correct error here!
emit this->networkError(HostNotFound, socket.errorString());
this->emitNetworkError(socketError, socket.errorString());
}
}

View File

@ -6,6 +6,12 @@ GopherClient::GopherClient(QObject *parent) : ProtocolHandler(parent)
connect(&socket, &QTcpSocket::connected, this, &GopherClient::on_connected);
connect(&socket, &QTcpSocket::readyRead, this, &GopherClient::on_readRead);
connect(&socket, &QTcpSocket::disconnected, this, &GopherClient::on_finished);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
connect(&socket, &QTcpSocket::errorOccurred, this, &GopherClient::on_socketError);
#else
connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &GopherClient::on_socketError);
#endif
}
GopherClient::~GopherClient()
@ -92,3 +98,8 @@ void GopherClient::on_finished()
}
body.clear();
}
void GopherClient::on_socketError(QAbstractSocket::SocketError error_code)
{
this->emitNetworkError(error_code, socket.errorString());
}

View File

@ -23,10 +23,12 @@ public:
bool cancelRequest() override;
private slots:
private: // slots
void on_connected();
void on_readRead();
void on_finished();
void on_socketError(QAbstractSocket::SocketError errorCode);
private:
QTcpSocket socket;

View File

@ -2,7 +2,6 @@
ProtocolHandler::ProtocolHandler(QObject *parent) : QObject(parent)
{
}
bool ProtocolHandler::enableClientCertificate(const CryptoIdentity &ident)
@ -13,5 +12,34 @@ bool ProtocolHandler::enableClientCertificate(const CryptoIdentity &ident)
void ProtocolHandler::disableClientCertificate()
{
}
void ProtocolHandler::emitNetworkError(QAbstractSocket::SocketError error_code, const QString &textual_description)
{
NetworkError network_error = UnknownError;
switch (error_code)
{
case QAbstractSocket::ConnectionRefusedError:
network_error = ConnectionRefused;
break;
case QAbstractSocket::HostNotFoundError:
network_error = HostNotFound;
break;
case QAbstractSocket::SocketTimeoutError:
network_error = Timeout;
break;
case QAbstractSocket::SslHandshakeFailedError:
network_error = TlsFailure;
break;
case QAbstractSocket::SslInternalError:
network_error = TlsFailure;
break;
case QAbstractSocket::SslInvalidUserDataError:
network_error = TlsFailure;
break;
default:
qDebug() << "unhandled network error:" << error_code;
break;
}
emit this->networkError(network_error, textual_description);
}

View File

@ -1,10 +1,11 @@
#ifndef GENERICPROTOCOLCLIENT_HPP
#define GENERICPROTOCOLCLIENT_HPP
#include <QObject>
#include "cryptoidentity.hpp"
#include <QObject>
#include <QAbstractSocket>
class ProtocolHandler : public QObject
{
Q_OBJECT
@ -12,7 +13,8 @@ public:
enum NetworkError {
UnknownError, //!< There was an unhandled network error
ProtocolViolation, //!< The server responded with something unexpected and violated the protocol
HostNotFound, //!< The host
HostNotFound, //!< The host was not found by the client
ConnectionRefused, //!< The host refused connection on that port
ResourceNotFound, //!< The requested resource was not found on the server
BadRequest, //!< Our client misbehaved and did a request the server cannot understand
ProxyRequest, //!< We requested to
@ -22,6 +24,7 @@ public:
MistrustedHost, //!< We know the host and it's not the server identity we've seen before
Unauthorized, //!< The requested resource could not be accessed.
TlsFailure, //!< Unspecified TLS failure
Timeout, //!< The network connection timed out.
};
public:
explicit ProtocolHandler(QObject *parent = nullptr);
@ -54,6 +57,8 @@ signals:
//! The server wants us to use a client certificate
void certificateRequired(QString const & info);
protected:
void emitNetworkError(QAbstractSocket::SocketError error_code, QString const & textual_description);
};
#endif // GENERICPROTOCOLCLIENT_HPP

View File

@ -71,15 +71,36 @@ void WebClient::on_finished()
{
if(this->current_reply->error() != QNetworkReply::NoError)
{
NetworkError error = UnknownError;
switch(this->current_reply->error())
{
case QNetworkReply::ConnectionRefusedError: error = ConnectionRefused; break;
case QNetworkReply::RemoteHostClosedError: error = ProtocolViolation; break;
case QNetworkReply::HostNotFoundError: error = HostNotFound; break;
case QNetworkReply::TimeoutError: error = Timeout; break;
case QNetworkReply::SslHandshakeFailedError: error = TlsFailure; break;
case QNetworkReply::ContentAccessDenied: error = Unauthorized; break;
case QNetworkReply::ContentOperationNotPermittedError: error = BadRequest; break;
case QNetworkReply::ContentNotFoundError: error = ResourceNotFound; break;
case QNetworkReply::AuthenticationRequiredError: error = Unauthorized; break;
case QNetworkReply::ContentGoneError: error = ResourceNotFound; break;
case QNetworkReply::InternalServerError: error = InternalServerError; break;
case QNetworkReply::OperationNotImplementedError: error = InternalServerError; break;
case QNetworkReply::ServiceUnavailableError: error = InternalServerError; break;
default:
qDebug() << "Unhandled server error:" << this->current_reply->error();
break;
}
qDebug() << "web network error" << this->current_reply->errorString();
emit this->networkError(UnknownError, this->current_reply->errorString());
emit this->networkError(error, this->current_reply->errorString());
}
else
{
auto mime = this->current_reply->header(QNetworkRequest::ContentTypeHeader).toString();
// qDebug() << this->current_reply->url() << mime;
emit this->requestComplete(this->body, mime);
this->body.clear();
@ -90,6 +111,7 @@ void WebClient::on_finished()
void WebClient::on_sslErrors(const QList<QSslError> &errors)
{
qDebug() << "HTTP SSL Errors:";
for(auto const & err : errors)
qDebug() << err;
this->current_reply->ignoreSslErrors();