diff options
| author | Felix (xq) Queißner <git@mq32.de> | 2020-06-22 21:10:04 +0200 |
|---|---|---|
| committer | Felix (xq) Queißner <git@mq32.de> | 2020-06-22 21:10:04 +0200 |
| commit | 75ec461eeaa851cb5c53f4cfffc434e3e529ed1d (patch) | |
| tree | 3944737340718ca3675381aa06636045d397e780 /src/geminiclient.cpp | |
| parent | 8dbfb0890560fd1cd698d06fa05ac868c4db8576 (diff) | |
| download | kristall-75ec461eeaa851cb5c53f4cfffc434e3e529ed1d.tar.gz | |
Restructures the project source and cleans up a bit
Diffstat (limited to 'src/geminiclient.cpp')
| -rw-r--r-- | src/geminiclient.cpp | 375 |
1 files changed, 0 insertions, 375 deletions
diff --git a/src/geminiclient.cpp b/src/geminiclient.cpp deleted file mode 100644 index 6986250..0000000 --- a/src/geminiclient.cpp +++ /dev/null @@ -1,375 +0,0 @@ -#include "geminiclient.hpp" -#include <cassert> -#include <QDebug> -#include <QSslConfiguration> -#include "kristall.hpp" - -GeminiClient::GeminiClient() : ProtocolHandler(nullptr) -{ - connect(&socket, &QSslSocket::encrypted, this, &GeminiClient::socketEncrypted); - connect(&socket, &QSslSocket::readyRead, this, &GeminiClient::socketReadyRead); - connect(&socket, &QSslSocket::disconnected, this, &GeminiClient::socketDisconnected); -// connect(&socket, &QSslSocket::stateChanged, [](QSslSocket::SocketState state) { -// qDebug() << "Socket state changed to " << state; -// }); - connect(&socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors), this, &GeminiClient::sslErrors); - -#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 -} - -GeminiClient::~GeminiClient() -{ - is_receiving_body = false; -} - -bool GeminiClient::supportsScheme(const QString &scheme) const -{ - return (scheme == "gemini"); -} - -bool GeminiClient::startRequest(const QUrl &url, RequestOptions options) -{ - if(url.scheme() != "gemini") - return false; - - // qDebug() << "start request" << url; - - if(socket.state() != QTcpSocket::UnconnectedState) { - socket.disconnectFromHost(); - socket.close(); - if(not socket.waitForDisconnected(1500)) - return false; - } - - this->is_error_state = false; - - this->options = options; - - QSslConfiguration ssl_config = socket.sslConfiguration(); - ssl_config.setProtocol(QSsl::TlsV1_2); - if(not global_gemini_trust.enable_ca) - ssl_config.setCaCertificates(QList<QSslCertificate> { }); - else - ssl_config.setCaCertificates(QSslConfiguration::systemCaCertificates()); - socket.setSslConfiguration(ssl_config); - - socket.connectToHostEncrypted(url.host(), url.port(1965)); - - this->buffer.clear(); - this->body.clear(); - this->is_receiving_body = false; - this->suppress_socket_tls_error = true; - - if(not socket.isOpen()) - return false; - - target_url = url; - mime_type = "<invalid>"; - - return true; -} - -bool GeminiClient::isInProgress() const -{ - return (socket.state() != QTcpSocket::UnconnectedState); -} - -bool GeminiClient::cancelRequest() -{ - // qDebug() << "cancel request" << isInProgress(); - if(isInProgress()) - { - this->is_receiving_body = false; - this->socket.disconnectFromHost(); - this->buffer.clear(); - this->body.clear(); - this->socket.waitForDisconnected(500); - this->socket.close(); - bool success = not isInProgress(); - // qDebug() << "cancel success" << success; - return success; - } - else - { - return true; - } -} - -bool GeminiClient::enableClientCertificate(const CryptoIdentity &ident) -{ - this->socket.setLocalCertificate(ident.certificate); - this->socket.setPrivateKey(ident.private_key); - return true; -} - -void GeminiClient::disableClientCertificate() -{ - this->socket.setLocalCertificate(QSslCertificate{}); - this->socket.setPrivateKey(QSslKey { }); -} - -void GeminiClient::socketEncrypted() -{ - emit this->hostCertificateLoaded(this->socket.peerCertificate()); - - QString request = target_url.toString(QUrl::FormattingOptions(QUrl::FullyEncoded)) + "\r\n"; - - QByteArray request_bytes = request.toUtf8(); - - qint64 offset = 0; - while(offset < request_bytes.size()) { - auto const len = socket.write(request_bytes.constData() + offset, request_bytes.size() - offset); - if(len <= 0) - { - socket.close(); - return; - } - offset += len; - } -} - -void GeminiClient::socketReadyRead() -{ - if(this->is_error_state) // don't do any further - return; - QByteArray response = socket.readAll(); - - if(is_receiving_body) - { - body.append(response); - emit this->requestProgress(body.size()); - } - else - { - for(int i = 0; i < response.size(); i++) - { - if(response[i] == '\n') { - buffer.append(response.data(), i); - body.append(response.data() + i + 1, response.size() - i - 1); - - // "XY " <META> <CR> <LF> - if(buffer.size() <= 5) { - socket.close(); - qDebug() << buffer; - emit networkError(ProtocolViolation, "Line is too short for valid protocol"); - return; - } - if(buffer.size() >= 1200) - { - emit networkError(ProtocolViolation, "response too large!"); - socket.close(); - } - if(buffer[buffer.size() - 1] != '\r') { - socket.close(); - qDebug() << buffer; - emit networkError(ProtocolViolation, "Line does not end with <CR> <LF>"); - return; - } - if(not isdigit(buffer[0])) { - socket.close(); - qDebug() << buffer; - emit networkError(ProtocolViolation, "First character is not a digit."); - return; - } - if(not isdigit(buffer[1])) { - socket.close(); - qDebug() << buffer; - emit networkError(ProtocolViolation, "Second character is not a digit."); - return; - } - // TODO: Implement stricter version - // if(buffer[2] != ' ') { - if(not isspace(buffer[2])) { - socket.close(); - qDebug() << buffer; - emit networkError(ProtocolViolation, "Third character is not a space."); - return; - } - - QString meta = QString::fromUtf8(buffer.data() + 3, buffer.size() - 4); - - int primary_code = buffer[0] - '0'; - int secondary_code = buffer[1] - '0'; - - qDebug() << primary_code << secondary_code << meta; - - // We don't need to receive any data after that. - if(primary_code != 2) - socket.close(); - - switch(primary_code) - { - case 1: // requesting input - emit inputRequired(meta); - return; - - case 2: // success - is_receiving_body = true; - mime_type = meta; - return; - - case 3: { // redirect - QUrl new_url(meta); - if(new_url.isValid()) { - if(new_url.isRelative()) - new_url = target_url.resolved(new_url); - assert(not new_url.isRelative()); - - emit redirected(new_url, (secondary_code == 1)); - } - else { - emit networkError(ProtocolViolation, "Invalid URL for redirection!"); - } - return; - } - - case 4: { // temporary failure - NetworkError type = UnknownError; - switch(secondary_code) - { - case 1: type = InternalServerError; break; - case 2: type = InternalServerError; break; - case 3: type = InternalServerError; break; - case 4: type = UnknownError; break; - } - emit networkError(type, meta); - return; - } - - case 5: { // permanent failure - NetworkError type = UnknownError; - switch(secondary_code) - { - case 1: type = ResourceNotFound; break; - case 2: type = ResourceNotFound; break; - case 3: type = ProxyRequest; break; - case 9: type = BadRequest; break; - } - emit networkError(type, meta); - return; - } - - case 6: // client certificate required - switch(secondary_code) - { - case 0: - emit certificateRequired(meta); - return; - - case 1: - emit networkError(Unauthorized, meta); - return; - - default: - case 2: - emit networkError(InvalidClientCertificate, meta); - return; - } - return; - - default: - emit networkError(ProtocolViolation, "Unspecified status code used!"); - return; - } - - assert(false and "unreachable"); - } - } - if((buffer.size() + response.size()) >= 1200) - { - emit networkError(ProtocolViolation, "META too large!"); - socket.close(); - } - buffer.append(response); - } -} - -void GeminiClient::socketDisconnected() -{ - if(this->is_receiving_body and not this->is_error_state) { - body.append(socket.readAll()); - emit requestComplete(body, mime_type); - } -} - -void GeminiClient::sslErrors(QList<QSslError> const & errors) -{ - emit this->hostCertificateLoaded(this->socket.peerCertificate()); - - if(options & IgnoreTlsErrors) { - socket.ignoreSslErrors(errors); - return; - } - - QList<QSslError> remaining_errors = errors; - QList<QSslError> ignored_errors; - - int i = 0; - while(i < remaining_errors.size()) - { - auto const & err = remaining_errors.at(i); - - bool ignore = false; - if(SslTrust::isTrustRelated(err.error())) - { - switch(global_gemini_trust.getTrust(target_url, socket.peerCertificate())) - { - case SslTrust::Trusted: - ignore = true; - break; - case SslTrust::Untrusted: - this->is_error_state = true; - this->suppress_socket_tls_error = true; - emit this->networkError(UntrustedHost, toFingerprintString(socket.peerCertificate())); - return; - case SslTrust::Mistrusted: - this->is_error_state = true; - this->suppress_socket_tls_error = true; - emit this->networkError(MistrustedHost, toFingerprintString(socket.peerCertificate())); - return; - } - } - else if(err.error() == QSslError::UnableToVerifyFirstCertificate) - { - ignore = true; - } - - if(ignore) { - ignored_errors.append(err); - remaining_errors.removeAt(0); - } else { - i += 1; - } - } - - socket.ignoreSslErrors(ignored_errors); - - qDebug() << "ignoring" << ignored_errors.size() << "out of" << errors.size(); - - for(auto const & error : remaining_errors) { - qWarning() << int(error.error()) << error.errorString(); - } - - if(remaining_errors.size() > 0) { - emit this->networkError(TlsFailure, remaining_errors.first().errorString()); - } -} - -void GeminiClient::socketError(QAbstractSocket::SocketError socketError) -{ - // When remote host closes TLS session, the client closes the socket. - // This is more sane then erroring out here as it's a perfectly legal - // state and we know the TLS connection has ended. - if(socketError == QAbstractSocket::RemoteHostClosedError) { - socket.close(); - } else { - this->is_error_state = true; - if(not this->suppress_socket_tls_error) { - this->emitNetworkError(socketError, socket.errorString()); - } - } -} |
