From 75ec461eeaa851cb5c53f4cfffc434e3e529ed1d Mon Sep 17 00:00:00 2001 From: "Felix (xq) Queißner" Date: Mon, 22 Jun 2020 21:10:04 +0200 Subject: Restructures the project source and cleans up a bit --- src/abouthandler.cpp | 61 -- src/abouthandler.hpp | 23 - src/browsertab.cpp | 22 +- src/browsertab.hpp | 2 +- src/browsertab.ui | 16 +- src/certificateiodialog.cpp | 126 ---- src/certificateiodialog.hpp | 50 -- src/certificateiodialog.ui | 127 ---- src/certificatemanagementdialog.cpp | 308 --------- src/certificatemanagementdialog.hpp | 45 -- src/certificatemanagementdialog.ui | 294 --------- src/certificateselectiondialog.cpp | 130 ---- src/certificateselectiondialog.hpp | 54 -- src/certificateselectiondialog.ui | 172 ----- src/dialogs/certificateiodialog.cpp | 126 ++++ src/dialogs/certificateiodialog.hpp | 50 ++ src/dialogs/certificateiodialog.ui | 127 ++++ src/dialogs/certificatemanagementdialog.cpp | 308 +++++++++ src/dialogs/certificatemanagementdialog.hpp | 45 ++ src/dialogs/certificatemanagementdialog.ui | 294 +++++++++ src/dialogs/certificateselectiondialog.cpp | 130 ++++ src/dialogs/certificateselectiondialog.hpp | 54 ++ src/dialogs/certificateselectiondialog.ui | 172 +++++ src/dialogs/newidentitiydialog.cpp | 81 +++ src/dialogs/newidentitiydialog.hpp | 41 ++ src/dialogs/newidentitiydialog.ui | 118 ++++ src/dialogs/settingsdialog.cpp | 613 ++++++++++++++++++ src/dialogs/settingsdialog.hpp | 143 +++++ src/dialogs/settingsdialog.ui | 940 ++++++++++++++++++++++++++++ src/elidelabel.cpp | 59 -- src/elidelabel.hpp | 28 - src/filehandler.cpp | 45 -- src/filehandler.hpp | 23 - src/fingerclient.cpp | 83 --- src/fingerclient.hpp | 39 -- src/geminiclient.cpp | 375 ----------- src/geminiclient.hpp | 55 -- src/geminirenderer.cpp | 343 ---------- src/geminirenderer.hpp | 41 -- src/gopherclient.cpp | 109 ---- src/gopherclient.hpp | 42 -- src/gophermaprenderer.cpp | 192 ------ src/gophermaprenderer.hpp | 26 - src/kristall.pro | 90 +-- src/mainwindow.cpp | 4 +- src/mainwindow.hpp | 4 +- src/mediaplayer.cpp | 86 --- src/mediaplayer.hpp | 42 -- src/mediaplayer.ui | 85 --- src/newidentitiydialog.cpp | 81 --- src/newidentitiydialog.hpp | 41 -- src/newidentitiydialog.ui | 118 ---- src/plaintextrenderer.cpp | 19 - src/plaintextrenderer.hpp | 25 - src/protocols/abouthandler.cpp | 61 ++ src/protocols/abouthandler.hpp | 23 + src/protocols/filehandler.cpp | 45 ++ src/protocols/filehandler.hpp | 23 + src/protocols/fingerclient.cpp | 83 +++ src/protocols/fingerclient.hpp | 39 ++ src/protocols/geminiclient.cpp | 375 +++++++++++ src/protocols/geminiclient.hpp | 55 ++ src/protocols/gopherclient.cpp | 109 ++++ src/protocols/gopherclient.hpp | 42 ++ src/protocols/webclient.cpp | 229 +++++++ src/protocols/webclient.hpp | 48 ++ src/renderers/geminirenderer.cpp | 343 ++++++++++ src/renderers/geminirenderer.hpp | 41 ++ src/renderers/gophermaprenderer.cpp | 192 ++++++ src/renderers/gophermaprenderer.hpp | 26 + src/renderers/plaintextrenderer.cpp | 19 + src/renderers/plaintextrenderer.hpp | 25 + src/searchbar.cpp | 26 - src/searchbar.hpp | 19 - src/settingsdialog.cpp | 613 ------------------ src/settingsdialog.hpp | 143 ----- src/settingsdialog.ui | 915 --------------------------- src/ssltrusteditor.cpp | 82 --- src/ssltrusteditor.hpp | 44 -- src/ssltrusteditor.ui | 101 --- src/webclient.cpp | 229 ------- src/webclient.hpp | 48 -- src/widgets/elidelabel.cpp | 59 ++ src/widgets/elidelabel.hpp | 28 + src/widgets/mediaplayer.cpp | 86 +++ src/widgets/mediaplayer.hpp | 42 ++ src/widgets/mediaplayer.ui | 85 +++ src/widgets/searchbar.cpp | 26 + src/widgets/searchbar.hpp | 19 + src/widgets/ssltrusteditor.cpp | 82 +++ src/widgets/ssltrusteditor.hpp | 44 ++ src/widgets/ssltrusteditor.ui | 101 +++ 92 files changed, 5663 insertions(+), 5634 deletions(-) delete mode 100644 src/abouthandler.cpp delete mode 100644 src/abouthandler.hpp delete mode 100644 src/certificateiodialog.cpp delete mode 100644 src/certificateiodialog.hpp delete mode 100644 src/certificateiodialog.ui delete mode 100644 src/certificatemanagementdialog.cpp delete mode 100644 src/certificatemanagementdialog.hpp delete mode 100644 src/certificatemanagementdialog.ui delete mode 100644 src/certificateselectiondialog.cpp delete mode 100644 src/certificateselectiondialog.hpp delete mode 100644 src/certificateselectiondialog.ui create mode 100644 src/dialogs/certificateiodialog.cpp create mode 100644 src/dialogs/certificateiodialog.hpp create mode 100644 src/dialogs/certificateiodialog.ui create mode 100644 src/dialogs/certificatemanagementdialog.cpp create mode 100644 src/dialogs/certificatemanagementdialog.hpp create mode 100644 src/dialogs/certificatemanagementdialog.ui create mode 100644 src/dialogs/certificateselectiondialog.cpp create mode 100644 src/dialogs/certificateselectiondialog.hpp create mode 100644 src/dialogs/certificateselectiondialog.ui create mode 100644 src/dialogs/newidentitiydialog.cpp create mode 100644 src/dialogs/newidentitiydialog.hpp create mode 100644 src/dialogs/newidentitiydialog.ui create mode 100644 src/dialogs/settingsdialog.cpp create mode 100644 src/dialogs/settingsdialog.hpp create mode 100644 src/dialogs/settingsdialog.ui delete mode 100644 src/elidelabel.cpp delete mode 100644 src/elidelabel.hpp delete mode 100644 src/filehandler.cpp delete mode 100644 src/filehandler.hpp delete mode 100644 src/fingerclient.cpp delete mode 100644 src/fingerclient.hpp delete mode 100644 src/geminiclient.cpp delete mode 100644 src/geminiclient.hpp delete mode 100644 src/geminirenderer.cpp delete mode 100644 src/geminirenderer.hpp delete mode 100644 src/gopherclient.cpp delete mode 100644 src/gopherclient.hpp delete mode 100644 src/gophermaprenderer.cpp delete mode 100644 src/gophermaprenderer.hpp delete mode 100644 src/mediaplayer.cpp delete mode 100644 src/mediaplayer.hpp delete mode 100644 src/mediaplayer.ui delete mode 100644 src/newidentitiydialog.cpp delete mode 100644 src/newidentitiydialog.hpp delete mode 100644 src/newidentitiydialog.ui delete mode 100644 src/plaintextrenderer.cpp delete mode 100644 src/plaintextrenderer.hpp create mode 100644 src/protocols/abouthandler.cpp create mode 100644 src/protocols/abouthandler.hpp create mode 100644 src/protocols/filehandler.cpp create mode 100644 src/protocols/filehandler.hpp create mode 100644 src/protocols/fingerclient.cpp create mode 100644 src/protocols/fingerclient.hpp create mode 100644 src/protocols/geminiclient.cpp create mode 100644 src/protocols/geminiclient.hpp create mode 100644 src/protocols/gopherclient.cpp create mode 100644 src/protocols/gopherclient.hpp create mode 100644 src/protocols/webclient.cpp create mode 100644 src/protocols/webclient.hpp create mode 100644 src/renderers/geminirenderer.cpp create mode 100644 src/renderers/geminirenderer.hpp create mode 100644 src/renderers/gophermaprenderer.cpp create mode 100644 src/renderers/gophermaprenderer.hpp create mode 100644 src/renderers/plaintextrenderer.cpp create mode 100644 src/renderers/plaintextrenderer.hpp delete mode 100644 src/searchbar.cpp delete mode 100644 src/searchbar.hpp delete mode 100644 src/settingsdialog.cpp delete mode 100644 src/settingsdialog.hpp delete mode 100644 src/settingsdialog.ui delete mode 100644 src/ssltrusteditor.cpp delete mode 100644 src/ssltrusteditor.hpp delete mode 100644 src/ssltrusteditor.ui delete mode 100644 src/webclient.cpp delete mode 100644 src/webclient.hpp create mode 100644 src/widgets/elidelabel.cpp create mode 100644 src/widgets/elidelabel.hpp create mode 100644 src/widgets/mediaplayer.cpp create mode 100644 src/widgets/mediaplayer.hpp create mode 100644 src/widgets/mediaplayer.ui create mode 100644 src/widgets/searchbar.cpp create mode 100644 src/widgets/searchbar.hpp create mode 100644 src/widgets/ssltrusteditor.cpp create mode 100644 src/widgets/ssltrusteditor.hpp create mode 100644 src/widgets/ssltrusteditor.ui (limited to 'src') diff --git a/src/abouthandler.cpp b/src/abouthandler.cpp deleted file mode 100644 index 01eefff..0000000 --- a/src/abouthandler.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "abouthandler.hpp" -#include "kristall.hpp" - -#include -#include - -AboutHandler::AboutHandler() -{ - -} - -bool AboutHandler::supportsScheme(const QString &scheme) const -{ - return (scheme == "about"); -} - -bool AboutHandler::startRequest(const QUrl &url, ProtocolHandler::RequestOptions options) -{ - Q_UNUSED(options) - if (url.path() == "blank") - { - emit this->requestComplete("", "text/gemini"); - } - else if (url.path() == "favourites") - { - QByteArray document; - - document.append("# Favourites\n"); - document.append("\n"); - - for (auto const &fav : global_favourites.getAll()) - { - document.append("=> " + fav.toString().toUtf8() + "\n"); - } - - this->requestComplete(document, "text/gemini"); - } - else - { - QFile file(QString(":/about/%1.gemini").arg(url.path())); - if (file.open(QFile::ReadOnly)) - { - emit this->requestComplete(file.readAll(), "text/gemini"); - } - else - { - emit this->networkError(ResourceNotFound, "The requested resource does not exist."); - } - } - return true; -} - -bool AboutHandler::isInProgress() const -{ - return false; -} - -bool AboutHandler::cancelRequest() -{ - return true; -} diff --git a/src/abouthandler.hpp b/src/abouthandler.hpp deleted file mode 100644 index 86b9180..0000000 --- a/src/abouthandler.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef ABOUTHANDLER_HPP -#define ABOUTHANDLER_HPP - -#include - -#include "protocolhandler.hpp" - -class AboutHandler : public ProtocolHandler -{ - Q_OBJECT -public: - AboutHandler(); - - bool supportsScheme(QString const & scheme) const override; - - bool startRequest(QUrl const & url, ProtocolHandler::RequestOptions options) override; - - bool isInProgress() const override; - - bool cancelRequest() override; -}; - -#endif // ABOUTHANDLER_HPP diff --git a/src/browsertab.cpp b/src/browsertab.cpp index 4a149de..f73ac4b 100644 --- a/src/browsertab.cpp +++ b/src/browsertab.cpp @@ -1,22 +1,22 @@ #include "browsertab.hpp" #include "ui_browsertab.h" #include "mainwindow.hpp" -#include "settingsdialog.hpp" -#include "gophermaprenderer.hpp" -#include "geminirenderer.hpp" -#include "plaintextrenderer.hpp" +#include "renderers/gophermaprenderer.hpp" +#include "renderers/geminirenderer.hpp" +#include "renderers/plaintextrenderer.hpp" #include "mimeparser.hpp" -#include "certificateselectiondialog.hpp" +#include "dialogs/settingsdialog.hpp" +#include "dialogs/certificateselectiondialog.hpp" -#include "geminiclient.hpp" -#include "webclient.hpp" -#include "gopherclient.hpp" -#include "fingerclient.hpp" -#include "abouthandler.hpp" -#include "filehandler.hpp" +#include "protocols/geminiclient.hpp" +#include "protocols/webclient.hpp" +#include "protocols/gopherclient.hpp" +#include "protocols/fingerclient.hpp" +#include "protocols/abouthandler.hpp" +#include "protocols/filehandler.hpp" #include "ioutil.hpp" #include "kristall.hpp" diff --git a/src/browsertab.hpp b/src/browsertab.hpp index d4f5817..12cbc77 100644 --- a/src/browsertab.hpp +++ b/src/browsertab.hpp @@ -12,7 +12,7 @@ #include "documentoutlinemodel.hpp" #include "tabbrowsinghistory.hpp" -#include "geminirenderer.hpp" +#include "renderers/geminirenderer.hpp" #include "cryptoidentity.hpp" diff --git a/src/browsertab.ui b/src/browsertab.ui index 63bd9ec..f96db06 100644 --- a/src/browsertab.ui +++ b/src/browsertab.ui @@ -43,7 +43,8 @@ Back - + + .. @@ -59,7 +60,8 @@ Forward - + + .. @@ -75,7 +77,8 @@ Stop - + + .. @@ -91,7 +94,8 @@ Reload - + + .. @@ -201,7 +205,7 @@ p, li { white-space: pre-wrap; } MediaPlayer QWidget -
mediaplayer.hpp
+
widgets/mediaplayer.hpp
1
@@ -212,7 +216,7 @@ p, li { white-space: pre-wrap; } SearchBar QLineEdit -
searchbar.hpp
+
widgets/searchbar.hpp
diff --git a/src/certificateiodialog.cpp b/src/certificateiodialog.cpp deleted file mode 100644 index 8c9fcd5..0000000 --- a/src/certificateiodialog.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "certificateiodialog.hpp" -#include "ui_certificateiodialog.h" - -#include -#include -#include - -CertificateIoDialog::CertificateIoDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::CertificateIoDialog) -{ - ui->setupUi(this); - - this->ui->key_type->clear(); - this->ui->key_type->addItem("RSA", QVariant::fromValue(QSsl::Rsa)); - this->ui->key_type->addItem("ECDSA", QVariant::fromValue(QSsl::Ec)); - - this->updateUI(); -} - -CertificateIoDialog::~CertificateIoDialog() -{ - delete ui; -} - -void CertificateIoDialog::setIoMode(CertificateIoDialog::IoMode mode) -{ - this->current_mode = mode; - if(mode == Export) { - this->setWindowTitle(tr("Export Certificate")); - } else { - this->setWindowTitle(tr("Import Certificate")); - } - this->ui->key_type->setEnabled(mode == Import); - this->updateUI(); -} - -QSsl::KeyAlgorithm CertificateIoDialog::keyAlgorithm() const -{ - return QSsl::KeyAlgorithm(this->ui->key_type->currentData().toInt()); -} - -void CertificateIoDialog::setKeyAlgorithm(QSsl::KeyAlgorithm alg) -{ - this->ui->key_type->setCurrentIndex(-1); - for(int i = 0; i< this->ui->key_type->count(); i++) { - if(this->ui->key_type->itemData(i).toInt() == int(alg)) { - this->ui->key_type->setCurrentIndex(i); - break; - } - } -} - -QString CertificateIoDialog::keyFileName() const -{ - return this->ui->key_file_name->text(); -} - -QString CertificateIoDialog::certificateFileName() const -{ - return this->ui->certificate_file_name->text(); -} - -void CertificateIoDialog::on_select_certificate_file_button_clicked() -{ - QFileDialog dialog { this }; - - dialog.setNameFilter("Certificate File(*.pem *.der)"); - dialog.setAcceptMode((this->current_mode == Export) ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); - dialog.selectFile(this->ui->certificate_file_name->text()); - - if(dialog.exec() != QDialog::Accepted) - return; - - this->ui->certificate_file_name->setText(dialog.selectedFiles().first()); - - this->updateUI(); -} - -void CertificateIoDialog::on_select_key_file_button_clicked() -{ - QFileDialog dialog { this }; - - dialog.setNameFilter("Certificate File(*.pem *.der)"); - dialog.setAcceptMode((this->current_mode == Export) ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); - dialog.selectFile(this->ui->key_file_name->text()); - - if(dialog.exec() != QDialog::Accepted) - return; - - this->ui->key_file_name->setText(dialog.selectedFiles().first()); - - this->updateUI(); -} - -void CertificateIoDialog::on_certificate_file_name_textChanged(const QString &arg1) -{ - Q_UNUSED(arg1) - this->updateUI(); -} - -void CertificateIoDialog::on_key_file_name_textChanged(const QString &arg1) -{ - Q_UNUSED(arg1) - this->updateUI(); -} - -void CertificateIoDialog::updateUI() -{ - QString cert_file_name = certificateFileName(); - QString key_file_name = keyFileName(); - - bool ok = true; - - ok &= (cert_file_name.endsWith(".pem") or cert_file_name.endsWith(".der")); - ok &= (key_file_name.endsWith(".pem") or key_file_name.endsWith(".der")); - - ok &= (this->ui->key_type->currentIndex() >= 0); - - if(current_mode == Import) { - ok &= QFile(cert_file_name).exists(); - ok &= QFile(key_file_name).exists(); - } - - this->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok); -} diff --git a/src/certificateiodialog.hpp b/src/certificateiodialog.hpp deleted file mode 100644 index 590b282..0000000 --- a/src/certificateiodialog.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef CERTIFICATEIODIALOG_HPP -#define CERTIFICATEIODIALOG_HPP - -#include -#include - -namespace Ui { -class CertificateIoDialog; -} - -class CertificateIoDialog : public QDialog -{ - Q_OBJECT -public: - enum IoMode { - Import, - Export - }; -public: - explicit CertificateIoDialog(QWidget *parent = nullptr); - ~CertificateIoDialog(); - - IoMode mode() const { return this->current_mode; } - void setIoMode(IoMode mode); - - QSsl::KeyAlgorithm keyAlgorithm() const; - void setKeyAlgorithm(QSsl::KeyAlgorithm alg); - - QString keyFileName() const; - QString certificateFileName() const; - -private slots: - void on_select_certificate_file_button_clicked(); - - void on_select_key_file_button_clicked(); - - void on_certificate_file_name_textChanged(const QString &arg1); - - void on_key_file_name_textChanged(const QString &arg1); - -private: - void updateUI(); - -private: - Ui::CertificateIoDialog *ui; - - IoMode current_mode; -}; - -#endif // CERTIFICATEIODIALOG_HPP diff --git a/src/certificateiodialog.ui b/src/certificateiodialog.ui deleted file mode 100644 index 074df28..0000000 --- a/src/certificateiodialog.ui +++ /dev/null @@ -1,127 +0,0 @@ - - - CertificateIoDialog - - - - 0 - 0 - 411 - 160 - - - - Dialog - - - - - - - - Key Type - - - - - - - false - - - false - - - - - - - Key File - - - - - - - - - - - - ... - - - - - - - - - Certificate File - - - - - - - - - - - - ... - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - CertificateIoDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - CertificateIoDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/certificatemanagementdialog.cpp b/src/certificatemanagementdialog.cpp deleted file mode 100644 index 5141b30..0000000 --- a/src/certificatemanagementdialog.cpp +++ /dev/null @@ -1,308 +0,0 @@ -#include "certificatemanagementdialog.hpp" -#include "ui_certificatemanagementdialog.h" - -#include "kristall.hpp" - -#include "newidentitiydialog.hpp" -#include "certificateiodialog.hpp" -#include "ioutil.hpp" - -#include -#include - -CertificateManagementDialog::CertificateManagementDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::CertificateManagementDialog), - selected_identity { nullptr } -{ - ui->setupUi(this); - - this->ui->certificates->setModel(&global_identities); - this->ui->certificates->expandAll(); - - connect( - this->ui->certificates->selectionModel(), - &QItemSelectionModel::currentChanged, - this, - &CertificateManagementDialog::on_certificates_selected - ); - on_certificates_selected(QModelIndex { }, QModelIndex { }); -} - -CertificateManagementDialog::~CertificateManagementDialog() -{ - delete ui; -} - -void CertificateManagementDialog::on_certificates_selected(QModelIndex const& index, QModelIndex const & previous) -{ - Q_UNUSED(previous); - - selected_identity = global_identities.getMutableIdentity(index); - - this->ui->export_cert_button->setEnabled(selected_identity != nullptr); - - if(selected_identity != nullptr) - { - auto & cert = *selected_identity; - this->ui->groupBox->setEnabled(true); - this->ui->cert_display_name->setText(cert.display_name); - this->ui->cert_common_name->setText(cert.certificate.subjectInfo(QSslCertificate::CommonName).join(", ")); - this->ui->cert_expiration_date->setDateTime(cert.certificate.expiryDate()); - this->ui->cert_livetime->setText(QString("%1 days").arg(QDateTime::currentDateTime().daysTo(cert.certificate.expiryDate()))); - this->ui->cert_fingerprint->setPlainText(toFingerprintString(cert.certificate)); - this->ui->cert_notes->setPlainText(cert.user_notes); - - this->ui->cert_host_filter->setText(cert.host_filter); - this->ui->cert_auto_enable->setEnabled(not cert.host_filter.isEmpty()); - this->ui->cert_auto_enable->setChecked(cert.auto_enable); - - this->ui->delete_cert_button->setEnabled(true); - } - else - { - this->ui->groupBox->setEnabled(false); - this->ui->cert_display_name->setText(""); - this->ui->cert_common_name->setText(""); - this->ui->cert_expiration_date->setDateTime(QDateTime { }); - this->ui->cert_livetime->setText(""); - this->ui->cert_fingerprint->setPlainText(""); - this->ui->cert_host_filter->setText(""); - this->ui->cert_auto_enable->setChecked(false); - - if(auto group_name = global_identities.group(index); not group_name.isEmpty()) { - this->ui->delete_cert_button->setEnabled(global_identities.canDeleteGroup(group_name)); - } else { - this->ui->delete_cert_button->setEnabled(false); - } - } -} - -void CertificateManagementDialog::on_cert_notes_textChanged() -{ - if(this->selected_identity != nullptr) { - this->selected_identity->user_notes = this->ui->cert_notes->toPlainText(); - } -} - -void CertificateManagementDialog::on_cert_display_name_textChanged(const QString &arg1) -{ - Q_UNUSED(arg1) - if(this->selected_identity != nullptr) { - this->selected_identity->display_name = this->ui->cert_display_name->text(); - } -} - -void CertificateManagementDialog::on_delete_cert_button_clicked() -{ - auto index = this->ui->certificates->currentIndex(); - - if(global_identities.getMutableIdentity(index) != nullptr) - { - auto answer = QMessageBox::question( - this, - "Kristall", - "Do you really want to delete this certificate?\r\n\r\nYou will not be able to restore the identity after this!", - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No - ); - if(answer != QMessageBox::Yes) - return; - if(not global_identities.destroyIdentity(index)) { - QMessageBox::warning(this, "Kristall", "Could not destroy identity!"); - } - } - else if(auto group_name = global_identities.group(index); not group_name.isEmpty()) { - - auto answer = QMessageBox::question( - this, - "Kristall", - QString("Do you want to delete the group '%1'").arg(group_name) - ); - if(answer != QMessageBox::Yes) - return; - - if(not global_identities.deleteGroup(group_name)) { - QMessageBox::warning(this, "Kristall", "Could not delete group!"); - } - } -} - -void CertificateManagementDialog::on_export_cert_button_clicked() -{ - if(this->selected_identity == nullptr) - return; - CertificateIoDialog dialog { this }; - - dialog.setKeyAlgorithm(this->selected_identity->private_key.algorithm()); - dialog.setIoMode(CertificateIoDialog::Export); - - if(dialog.exec() != QDialog::Accepted) - return; - - { - QFile cert_file { dialog.certificateFileName() }; - if(not cert_file.open(QFile::WriteOnly)) { - QMessageBox::warning( - this, - "Kristall", - tr("The file %1 could not be found!").arg(dialog.certificateFileName()) - ); - return; - } - - QByteArray cert_blob; - if(dialog.certificateFileName().endsWith(".der")) { - cert_blob = this->selected_identity->certificate.toDer(); - } else { - cert_blob = this->selected_identity->certificate.toPem(); - } - - if(not IoUtil::writeAll(cert_file, cert_blob)) { - QMessageBox::warning( - this, - "Kristall", - tr("The file %1 could not be created found!").arg(dialog.certificateFileName()) - ); - return; - } - } - - { - QFile key_file { dialog.keyFileName() }; - if(not key_file.open(QFile::WriteOnly)) { - QMessageBox::warning( - this, - "Kristall", - tr("The file %1 could not be found!").arg(dialog.keyFileName()) - ); - return; - } - - QByteArray key_blob; - if(dialog.keyFileName().endsWith(".der")) { - key_blob = this->selected_identity->private_key.toDer(); - } else { - key_blob = this->selected_identity->private_key.toPem(); - } - - if(not IoUtil::writeAll(key_file, key_blob)) { - QMessageBox::warning( - this, - "Kristall", - tr("The file %1 could not be created found!").arg(dialog.keyFileName()) - ); - return; - } - } -} - -void CertificateManagementDialog::on_import_cert_button_clicked() -{ - CertificateIoDialog dialog { this }; - - dialog.setIoMode(CertificateIoDialog::Import); - - if(dialog.exec() != QDialog::Accepted) - return; - - QFile cert_file { dialog.certificateFileName() }; - if(not cert_file.open(QFile::ReadOnly)) { - QMessageBox::warning( - this, - "Kristall", - tr("The file %1 could not be found!").arg(dialog.certificateFileName()) - ); - return; - } - - QFile key_file { dialog.keyFileName() }; - if(not key_file.open(QFile::ReadOnly)) { - QMessageBox::warning( - this, - "Kristall", - tr("The file %1 could not be found!").arg(dialog.keyFileName()) - ); - return; - } - - CryptoIdentity ident; - ident.private_key = QSslKey { - &key_file, - dialog.keyAlgorithm(), - dialog.keyFileName().endsWith(".der") ? QSsl::Der : QSsl::Pem, - QSsl::PrivateKey - }; - ident.certificate = QSslCertificate { - &cert_file, - dialog.keyFileName().endsWith(".der") ? QSsl::Der : QSsl::Pem, - }; - ident.user_notes = tr("Imported from:\r\nkey: %1\r\n:cert: %2").arg(dialog.keyFileName()).arg(dialog.certificateFileName()); - ident.display_name = "Imported Certificate"; - ident.auto_enable = false; - ident.host_filter = ""; - ident.is_persistent = true; - - if(ident.private_key.isNull()) { - QMessageBox::warning( - this, - "Kristall", - tr("The key file %1 could not be loaded. Please verify your key file.").arg(dialog.keyFileName()) - ); - return; - } - - if(ident.certificate.isNull()) { - QMessageBox::warning( - this, - "Kristall", - tr("The certificate file %1 could not be loaded. Please verify your certificate.").arg(dialog.keyFileName()) - ); - return; - } - - if(not global_identities.addCertificate(tr("Imported Certificates"), ident)) { - QMessageBox::warning( - this, - "Kristall", - tr("Failed to import the certificate.") - ); - } -} - -void CertificateManagementDialog::on_create_cert_button_clicked() -{ - NewIdentitiyDialog dialog { this }; - - dialog.setGroupName(global_identities.group(this->ui->certificates->currentIndex())); - - if(dialog.exec() != QDialog::Accepted) - return; - - auto id = dialog.createIdentity(); - if(not id.isValid()) - return; - id.is_persistent = true; - - global_identities.addCertificate( - dialog.groupName(), - id); -} - -void CertificateManagementDialog::on_cert_host_filter_textChanged(const QString &host_filter) -{ - if(this->selected_identity != nullptr) { - this->ui->cert_auto_enable->setEnabled(not host_filter.isEmpty()); - this->selected_identity->host_filter = host_filter; - } else { - this->ui->cert_auto_enable->setEnabled(false); - } - -} - -void CertificateManagementDialog::on_cert_auto_enable_clicked(bool checked) -{ - if(this->selected_identity != nullptr) { - this->selected_identity->auto_enable = checked; - } -} diff --git a/src/certificatemanagementdialog.hpp b/src/certificatemanagementdialog.hpp deleted file mode 100644 index 0d7178a..0000000 --- a/src/certificatemanagementdialog.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CERTIFICATEMANAGEMENTDIALOG_HPP -#define CERTIFICATEMANAGEMENTDIALOG_HPP - -#include - -#include "cryptoidentity.hpp" - -namespace Ui { -class CertificateManagementDialog; -} - -class CertificateManagementDialog : public QDialog -{ - Q_OBJECT - -public: - explicit CertificateManagementDialog(QWidget *parent = nullptr); - ~CertificateManagementDialog(); - -private slots: - void on_cert_notes_textChanged(); - - void on_cert_display_name_textChanged(const QString &arg1); - - void on_delete_cert_button_clicked(); - - void on_export_cert_button_clicked(); - - void on_import_cert_button_clicked(); - - void on_create_cert_button_clicked(); - - void on_cert_host_filter_textChanged(const QString &arg1); - - void on_cert_auto_enable_clicked(bool checked); - -private: - void on_certificates_selected(const QModelIndex &index, QModelIndex const & previous); -private: - Ui::CertificateManagementDialog *ui; - - CryptoIdentity * selected_identity; -}; - -#endif // CERTIFICATEMANAGEMENTDIALOG_HPP diff --git a/src/certificatemanagementdialog.ui b/src/certificatemanagementdialog.ui deleted file mode 100644 index 82dd788..0000000 --- a/src/certificatemanagementdialog.ui +++ /dev/null @@ -1,294 +0,0 @@ - - - CertificateManagementDialog - - - - 0 - 0 - 731 - 480 - - - - Certificate Manager - - - - - - - - - - - 0 - - - - - true - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - false - - - - - - - - - Craete new certificate - - - Create... - - - - - - - - - - Import certificate - - - Import... - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Export certificate - - - Export... - - - - - - - - - - false - - - Delete certificate - - - Delete - - - - - - - - - - - - - - true - - - Certificate - - - - - - - - Display Name - - - - - - - - - - Common Name - - - - - - - true - - - - - - - Expiration Date - - - - - - - true - - - true - - - - - - - Expires in - - - - - - - true - - - ??? days - - - - - - - Fingerprint - - - - - - - Notes - - - - - - - true - - - - - - - - - - Host Filter - - - - - - - gemini://* - - - - - - - If this is checked, Kristall will automatically enable this certificate when visiting a URL matching the host filter - - - Auto-Enable Certificate - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - CertificateManagementDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - CertificateManagementDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/certificateselectiondialog.cpp b/src/certificateselectiondialog.cpp deleted file mode 100644 index f4dab38..0000000 --- a/src/certificateselectiondialog.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "certificateselectiondialog.hpp" -#include "ui_certificateselectiondialog.h" - -#include "certificatehelper.hpp" -#include "kristall.hpp" -#include "newidentitiydialog.hpp" - -#include -#include -#include - -CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::CertificateSelectionDialog) -{ - ui->setupUi(this); - this->ui->server_request->setVisible(false); - - this->ui->certificates->setModel(&global_identities); - this->ui->certificates->expandAll(); - - connect(this->ui->certificates->selectionModel(), &QItemSelectionModel::currentChanged, this, &CertificateSelectionDialog::on_currentChanged); -} - -CertificateSelectionDialog::~CertificateSelectionDialog() -{ - delete ui; -} - -void CertificateSelectionDialog::setServerQuery(const QString &query) -{ - this->ui->server_request->setText(query); - this->ui->server_request->setVisible(not query.isEmpty()); -} - -CryptoIdentity CertificateSelectionDialog::identity() const -{ - return cryto_identity; -} - -void CertificateSelectionDialog::on_use_temp_cert_30m_clicked() -{ - acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(1800 * 12)); -} - -void CertificateSelectionDialog::on_use_temp_cert_1h_clicked() -{ - acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(3600)); -} - -void CertificateSelectionDialog::on_use_temp_cert_12h_clicked() -{ - acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(3600 * 12)); -} - -void CertificateSelectionDialog::on_use_temp_cert_24h_clicked() -{ - acceptTemporaryWithTimeout(QDateTime::currentDateTime().addDays(1)); -} - -void CertificateSelectionDialog::on_use_temp_cert_48h_clicked() -{ - acceptTemporaryWithTimeout(QDateTime::currentDateTime().addDays(2)); -} - -void CertificateSelectionDialog::acceptTemporaryWithTimeout(QDateTime timeout) -{ - std::default_random_engine rng; - rng.seed(QDateTime::currentDateTime().toMSecsSinceEpoch()); - - std::uniform_int_distribution distr(0, 255); - - char items[8]; - for(auto & c : items) { - c = distr(rng); - } - - this->cryto_identity = CertificateHelper::createNewIdentity( - QByteArray(items, sizeof items).toBase64(QByteArray::OmitTrailingEquals), - timeout); - - this->accept(); -} - -void CertificateSelectionDialog::on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - Q_UNUSED(current) - Q_UNUSED(previous) - auto id = global_identities.getIdentity(current); - - this->ui->use_selected_cert->setEnabled(id.isValid()); -} - -void CertificateSelectionDialog::on_create_new_cert_clicked() -{ - NewIdentitiyDialog dialog { this }; - - if(dialog.exec() != QDialog::Accepted) - return; - - auto id = dialog.createIdentity(); - if(not id.isValid()) - return; - id.is_persistent = true; - - global_identities.addCertificate( - dialog.groupName(), - id); -} - -void CertificateSelectionDialog::on_use_selected_cert_clicked() -{ - auto sel = this->ui->certificates->selectionModel()->currentIndex(); - this->cryto_identity = global_identities.getIdentity(sel); - if(this->cryto_identity.isValid()) { - this->accept(); - } else { - qDebug() << "Tried to use an invalid identity when the button should not be enabled. This is a bug!"; - } -} - -void CertificateSelectionDialog::on_certificates_doubleClicked(const QModelIndex &index) -{ - this->cryto_identity = global_identities.getIdentity(index); - if(this->cryto_identity.isValid()) { - this->accept(); - } else { - qDebug() << "Tried to use an invalid identity when the button should not be enabled. This is a bug!"; - } -} diff --git a/src/certificateselectiondialog.hpp b/src/certificateselectiondialog.hpp deleted file mode 100644 index e628f5c..0000000 --- a/src/certificateselectiondialog.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CERTIFICATESELECTIONDIALOG_HPP -#define CERTIFICATESELECTIONDIALOG_HPP - -#include - -#include "cryptoidentity.hpp" - -namespace Ui { -class CertificateSelectionDialog; -} - -class CertificateSelectionDialog : public QDialog -{ - Q_OBJECT - -public: - explicit CertificateSelectionDialog(QWidget *parent = nullptr); - ~CertificateSelectionDialog(); - - void setServerQuery(QString const & query); - - CryptoIdentity identity() const; - -private slots: - void on_use_temp_cert_30m_clicked(); - - void on_use_temp_cert_1h_clicked(); - - void on_use_temp_cert_12h_clicked(); - - void on_use_temp_cert_24h_clicked(); - - void on_use_temp_cert_48h_clicked(); - - void on_create_new_cert_clicked(); - - void on_use_selected_cert_clicked(); - - void on_certificates_doubleClicked(const QModelIndex &index); - -private: - //! Creates an anonymous identity with a randomly chosen name that - //! will time out on `timeout`, then accepts the dialog. - void acceptTemporaryWithTimeout(QDateTime timeout); - - - void on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous); -private: - Ui::CertificateSelectionDialog *ui; - - CryptoIdentity cryto_identity; -}; - -#endif // CERTIFICATESELECTIONDIALOG_HPP diff --git a/src/certificateselectiondialog.ui b/src/certificateselectiondialog.ui deleted file mode 100644 index d6128f9..0000000 --- a/src/certificateselectiondialog.ui +++ /dev/null @@ -1,172 +0,0 @@ - - - CertificateSelectionDialog - - - - 0 - 0 - 474 - 355 - - - - Select client certificate - - - - :/icons/certificate.svg:/icons/certificate.svg - - - - - - Select existing certificate: - - - - - - - - 75 - true - - - - TextLabel - - - true - - - - - - - true - - - - - - - - - Create new identity - - - - :/icons/plus.svg:/icons/plus.svg - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Use - - - - - - - - - Qt::Horizontal - - - - - - - Create transient session certificate: - - - - - - - - - 30 Minutes - - - - - - - 1 Hour - - - - - - - 12 Hours - - - - - - - 24 Hours - - - - - - - 48 Hours - - - - - - - - - QDialogButtonBox::Cancel - - - - - - - - - - - buttonBox - rejected() - CertificateSelectionDialog - reject() - - - 236 - 333 - - - 236 - 177 - - - - - diff --git a/src/dialogs/certificateiodialog.cpp b/src/dialogs/certificateiodialog.cpp new file mode 100644 index 0000000..8c9fcd5 --- /dev/null +++ b/src/dialogs/certificateiodialog.cpp @@ -0,0 +1,126 @@ +#include "certificateiodialog.hpp" +#include "ui_certificateiodialog.h" + +#include +#include +#include + +CertificateIoDialog::CertificateIoDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateIoDialog) +{ + ui->setupUi(this); + + this->ui->key_type->clear(); + this->ui->key_type->addItem("RSA", QVariant::fromValue(QSsl::Rsa)); + this->ui->key_type->addItem("ECDSA", QVariant::fromValue(QSsl::Ec)); + + this->updateUI(); +} + +CertificateIoDialog::~CertificateIoDialog() +{ + delete ui; +} + +void CertificateIoDialog::setIoMode(CertificateIoDialog::IoMode mode) +{ + this->current_mode = mode; + if(mode == Export) { + this->setWindowTitle(tr("Export Certificate")); + } else { + this->setWindowTitle(tr("Import Certificate")); + } + this->ui->key_type->setEnabled(mode == Import); + this->updateUI(); +} + +QSsl::KeyAlgorithm CertificateIoDialog::keyAlgorithm() const +{ + return QSsl::KeyAlgorithm(this->ui->key_type->currentData().toInt()); +} + +void CertificateIoDialog::setKeyAlgorithm(QSsl::KeyAlgorithm alg) +{ + this->ui->key_type->setCurrentIndex(-1); + for(int i = 0; i< this->ui->key_type->count(); i++) { + if(this->ui->key_type->itemData(i).toInt() == int(alg)) { + this->ui->key_type->setCurrentIndex(i); + break; + } + } +} + +QString CertificateIoDialog::keyFileName() const +{ + return this->ui->key_file_name->text(); +} + +QString CertificateIoDialog::certificateFileName() const +{ + return this->ui->certificate_file_name->text(); +} + +void CertificateIoDialog::on_select_certificate_file_button_clicked() +{ + QFileDialog dialog { this }; + + dialog.setNameFilter("Certificate File(*.pem *.der)"); + dialog.setAcceptMode((this->current_mode == Export) ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); + dialog.selectFile(this->ui->certificate_file_name->text()); + + if(dialog.exec() != QDialog::Accepted) + return; + + this->ui->certificate_file_name->setText(dialog.selectedFiles().first()); + + this->updateUI(); +} + +void CertificateIoDialog::on_select_key_file_button_clicked() +{ + QFileDialog dialog { this }; + + dialog.setNameFilter("Certificate File(*.pem *.der)"); + dialog.setAcceptMode((this->current_mode == Export) ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); + dialog.selectFile(this->ui->key_file_name->text()); + + if(dialog.exec() != QDialog::Accepted) + return; + + this->ui->key_file_name->setText(dialog.selectedFiles().first()); + + this->updateUI(); +} + +void CertificateIoDialog::on_certificate_file_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1) + this->updateUI(); +} + +void CertificateIoDialog::on_key_file_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1) + this->updateUI(); +} + +void CertificateIoDialog::updateUI() +{ + QString cert_file_name = certificateFileName(); + QString key_file_name = keyFileName(); + + bool ok = true; + + ok &= (cert_file_name.endsWith(".pem") or cert_file_name.endsWith(".der")); + ok &= (key_file_name.endsWith(".pem") or key_file_name.endsWith(".der")); + + ok &= (this->ui->key_type->currentIndex() >= 0); + + if(current_mode == Import) { + ok &= QFile(cert_file_name).exists(); + ok &= QFile(key_file_name).exists(); + } + + this->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok); +} diff --git a/src/dialogs/certificateiodialog.hpp b/src/dialogs/certificateiodialog.hpp new file mode 100644 index 0000000..590b282 --- /dev/null +++ b/src/dialogs/certificateiodialog.hpp @@ -0,0 +1,50 @@ +#ifndef CERTIFICATEIODIALOG_HPP +#define CERTIFICATEIODIALOG_HPP + +#include +#include + +namespace Ui { +class CertificateIoDialog; +} + +class CertificateIoDialog : public QDialog +{ + Q_OBJECT +public: + enum IoMode { + Import, + Export + }; +public: + explicit CertificateIoDialog(QWidget *parent = nullptr); + ~CertificateIoDialog(); + + IoMode mode() const { return this->current_mode; } + void setIoMode(IoMode mode); + + QSsl::KeyAlgorithm keyAlgorithm() const; + void setKeyAlgorithm(QSsl::KeyAlgorithm alg); + + QString keyFileName() const; + QString certificateFileName() const; + +private slots: + void on_select_certificate_file_button_clicked(); + + void on_select_key_file_button_clicked(); + + void on_certificate_file_name_textChanged(const QString &arg1); + + void on_key_file_name_textChanged(const QString &arg1); + +private: + void updateUI(); + +private: + Ui::CertificateIoDialog *ui; + + IoMode current_mode; +}; + +#endif // CERTIFICATEIODIALOG_HPP diff --git a/src/dialogs/certificateiodialog.ui b/src/dialogs/certificateiodialog.ui new file mode 100644 index 0000000..074df28 --- /dev/null +++ b/src/dialogs/certificateiodialog.ui @@ -0,0 +1,127 @@ + + + CertificateIoDialog + + + + 0 + 0 + 411 + 160 + + + + Dialog + + + + + + + + Key Type + + + + + + + false + + + false + + + + + + + Key File + + + + + + + + + + + + ... + + + + + + + + + Certificate File + + + + + + + + + + + + ... + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CertificateIoDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CertificateIoDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialogs/certificatemanagementdialog.cpp b/src/dialogs/certificatemanagementdialog.cpp new file mode 100644 index 0000000..5141b30 --- /dev/null +++ b/src/dialogs/certificatemanagementdialog.cpp @@ -0,0 +1,308 @@ +#include "certificatemanagementdialog.hpp" +#include "ui_certificatemanagementdialog.h" + +#include "kristall.hpp" + +#include "newidentitiydialog.hpp" +#include "certificateiodialog.hpp" +#include "ioutil.hpp" + +#include +#include + +CertificateManagementDialog::CertificateManagementDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateManagementDialog), + selected_identity { nullptr } +{ + ui->setupUi(this); + + this->ui->certificates->setModel(&global_identities); + this->ui->certificates->expandAll(); + + connect( + this->ui->certificates->selectionModel(), + &QItemSelectionModel::currentChanged, + this, + &CertificateManagementDialog::on_certificates_selected + ); + on_certificates_selected(QModelIndex { }, QModelIndex { }); +} + +CertificateManagementDialog::~CertificateManagementDialog() +{ + delete ui; +} + +void CertificateManagementDialog::on_certificates_selected(QModelIndex const& index, QModelIndex const & previous) +{ + Q_UNUSED(previous); + + selected_identity = global_identities.getMutableIdentity(index); + + this->ui->export_cert_button->setEnabled(selected_identity != nullptr); + + if(selected_identity != nullptr) + { + auto & cert = *selected_identity; + this->ui->groupBox->setEnabled(true); + this->ui->cert_display_name->setText(cert.display_name); + this->ui->cert_common_name->setText(cert.certificate.subjectInfo(QSslCertificate::CommonName).join(", ")); + this->ui->cert_expiration_date->setDateTime(cert.certificate.expiryDate()); + this->ui->cert_livetime->setText(QString("%1 days").arg(QDateTime::currentDateTime().daysTo(cert.certificate.expiryDate()))); + this->ui->cert_fingerprint->setPlainText(toFingerprintString(cert.certificate)); + this->ui->cert_notes->setPlainText(cert.user_notes); + + this->ui->cert_host_filter->setText(cert.host_filter); + this->ui->cert_auto_enable->setEnabled(not cert.host_filter.isEmpty()); + this->ui->cert_auto_enable->setChecked(cert.auto_enable); + + this->ui->delete_cert_button->setEnabled(true); + } + else + { + this->ui->groupBox->setEnabled(false); + this->ui->cert_display_name->setText(""); + this->ui->cert_common_name->setText(""); + this->ui->cert_expiration_date->setDateTime(QDateTime { }); + this->ui->cert_livetime->setText(""); + this->ui->cert_fingerprint->setPlainText(""); + this->ui->cert_host_filter->setText(""); + this->ui->cert_auto_enable->setChecked(false); + + if(auto group_name = global_identities.group(index); not group_name.isEmpty()) { + this->ui->delete_cert_button->setEnabled(global_identities.canDeleteGroup(group_name)); + } else { + this->ui->delete_cert_button->setEnabled(false); + } + } +} + +void CertificateManagementDialog::on_cert_notes_textChanged() +{ + if(this->selected_identity != nullptr) { + this->selected_identity->user_notes = this->ui->cert_notes->toPlainText(); + } +} + +void CertificateManagementDialog::on_cert_display_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1) + if(this->selected_identity != nullptr) { + this->selected_identity->display_name = this->ui->cert_display_name->text(); + } +} + +void CertificateManagementDialog::on_delete_cert_button_clicked() +{ + auto index = this->ui->certificates->currentIndex(); + + if(global_identities.getMutableIdentity(index) != nullptr) + { + auto answer = QMessageBox::question( + this, + "Kristall", + "Do you really want to delete this certificate?\r\n\r\nYou will not be able to restore the identity after this!", + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No + ); + if(answer != QMessageBox::Yes) + return; + if(not global_identities.destroyIdentity(index)) { + QMessageBox::warning(this, "Kristall", "Could not destroy identity!"); + } + } + else if(auto group_name = global_identities.group(index); not group_name.isEmpty()) { + + auto answer = QMessageBox::question( + this, + "Kristall", + QString("Do you want to delete the group '%1'").arg(group_name) + ); + if(answer != QMessageBox::Yes) + return; + + if(not global_identities.deleteGroup(group_name)) { + QMessageBox::warning(this, "Kristall", "Could not delete group!"); + } + } +} + +void CertificateManagementDialog::on_export_cert_button_clicked() +{ + if(this->selected_identity == nullptr) + return; + CertificateIoDialog dialog { this }; + + dialog.setKeyAlgorithm(this->selected_identity->private_key.algorithm()); + dialog.setIoMode(CertificateIoDialog::Export); + + if(dialog.exec() != QDialog::Accepted) + return; + + { + QFile cert_file { dialog.certificateFileName() }; + if(not cert_file.open(QFile::WriteOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.certificateFileName()) + ); + return; + } + + QByteArray cert_blob; + if(dialog.certificateFileName().endsWith(".der")) { + cert_blob = this->selected_identity->certificate.toDer(); + } else { + cert_blob = this->selected_identity->certificate.toPem(); + } + + if(not IoUtil::writeAll(cert_file, cert_blob)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be created found!").arg(dialog.certificateFileName()) + ); + return; + } + } + + { + QFile key_file { dialog.keyFileName() }; + if(not key_file.open(QFile::WriteOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.keyFileName()) + ); + return; + } + + QByteArray key_blob; + if(dialog.keyFileName().endsWith(".der")) { + key_blob = this->selected_identity->private_key.toDer(); + } else { + key_blob = this->selected_identity->private_key.toPem(); + } + + if(not IoUtil::writeAll(key_file, key_blob)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be created found!").arg(dialog.keyFileName()) + ); + return; + } + } +} + +void CertificateManagementDialog::on_import_cert_button_clicked() +{ + CertificateIoDialog dialog { this }; + + dialog.setIoMode(CertificateIoDialog::Import); + + if(dialog.exec() != QDialog::Accepted) + return; + + QFile cert_file { dialog.certificateFileName() }; + if(not cert_file.open(QFile::ReadOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.certificateFileName()) + ); + return; + } + + QFile key_file { dialog.keyFileName() }; + if(not key_file.open(QFile::ReadOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.keyFileName()) + ); + return; + } + + CryptoIdentity ident; + ident.private_key = QSslKey { + &key_file, + dialog.keyAlgorithm(), + dialog.keyFileName().endsWith(".der") ? QSsl::Der : QSsl::Pem, + QSsl::PrivateKey + }; + ident.certificate = QSslCertificate { + &cert_file, + dialog.keyFileName().endsWith(".der") ? QSsl::Der : QSsl::Pem, + }; + ident.user_notes = tr("Imported from:\r\nkey: %1\r\n:cert: %2").arg(dialog.keyFileName()).arg(dialog.certificateFileName()); + ident.display_name = "Imported Certificate"; + ident.auto_enable = false; + ident.host_filter = ""; + ident.is_persistent = true; + + if(ident.private_key.isNull()) { + QMessageBox::warning( + this, + "Kristall", + tr("The key file %1 could not be loaded. Please verify your key file.").arg(dialog.keyFileName()) + ); + return; + } + + if(ident.certificate.isNull()) { + QMessageBox::warning( + this, + "Kristall", + tr("The certificate file %1 could not be loaded. Please verify your certificate.").arg(dialog.keyFileName()) + ); + return; + } + + if(not global_identities.addCertificate(tr("Imported Certificates"), ident)) { + QMessageBox::warning( + this, + "Kristall", + tr("Failed to import the certificate.") + ); + } +} + +void CertificateManagementDialog::on_create_cert_button_clicked() +{ + NewIdentitiyDialog dialog { this }; + + dialog.setGroupName(global_identities.group(this->ui->certificates->currentIndex())); + + if(dialog.exec() != QDialog::Accepted) + return; + + auto id = dialog.createIdentity(); + if(not id.isValid()) + return; + id.is_persistent = true; + + global_identities.addCertificate( + dialog.groupName(), + id); +} + +void CertificateManagementDialog::on_cert_host_filter_textChanged(const QString &host_filter) +{ + if(this->selected_identity != nullptr) { + this->ui->cert_auto_enable->setEnabled(not host_filter.isEmpty()); + this->selected_identity->host_filter = host_filter; + } else { + this->ui->cert_auto_enable->setEnabled(false); + } + +} + +void CertificateManagementDialog::on_cert_auto_enable_clicked(bool checked) +{ + if(this->selected_identity != nullptr) { + this->selected_identity->auto_enable = checked; + } +} diff --git a/src/dialogs/certificatemanagementdialog.hpp b/src/dialogs/certificatemanagementdialog.hpp new file mode 100644 index 0000000..0d7178a --- /dev/null +++ b/src/dialogs/certificatemanagementdialog.hpp @@ -0,0 +1,45 @@ +#ifndef CERTIFICATEMANAGEMENTDIALOG_HPP +#define CERTIFICATEMANAGEMENTDIALOG_HPP + +#include + +#include "cryptoidentity.hpp" + +namespace Ui { +class CertificateManagementDialog; +} + +class CertificateManagementDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CertificateManagementDialog(QWidget *parent = nullptr); + ~CertificateManagementDialog(); + +private slots: + void on_cert_notes_textChanged(); + + void on_cert_display_name_textChanged(const QString &arg1); + + void on_delete_cert_button_clicked(); + + void on_export_cert_button_clicked(); + + void on_import_cert_button_clicked(); + + void on_create_cert_button_clicked(); + + void on_cert_host_filter_textChanged(const QString &arg1); + + void on_cert_auto_enable_clicked(bool checked); + +private: + void on_certificates_selected(const QModelIndex &index, QModelIndex const & previous); +private: + Ui::CertificateManagementDialog *ui; + + CryptoIdentity * selected_identity; +}; + +#endif // CERTIFICATEMANAGEMENTDIALOG_HPP diff --git a/src/dialogs/certificatemanagementdialog.ui b/src/dialogs/certificatemanagementdialog.ui new file mode 100644 index 0000000..82dd788 --- /dev/null +++ b/src/dialogs/certificatemanagementdialog.ui @@ -0,0 +1,294 @@ + + + CertificateManagementDialog + + + + 0 + 0 + 731 + 480 + + + + Certificate Manager + + + + + + + + + + + 0 + + + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + false + + + + + + + + + Craete new certificate + + + Create... + + + + + + + + + + Import certificate + + + Import... + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Export certificate + + + Export... + + + + + + + + + + false + + + Delete certificate + + + Delete + + + + + + + + + + + + + + true + + + Certificate + + + + + + + + Display Name + + + + + + + + + + Common Name + + + + + + + true + + + + + + + Expiration Date + + + + + + + true + + + true + + + + + + + Expires in + + + + + + + true + + + ??? days + + + + + + + Fingerprint + + + + + + + Notes + + + + + + + true + + + + + + + + + + Host Filter + + + + + + + gemini://* + + + + + + + If this is checked, Kristall will automatically enable this certificate when visiting a URL matching the host filter + + + Auto-Enable Certificate + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CertificateManagementDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CertificateManagementDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialogs/certificateselectiondialog.cpp b/src/dialogs/certificateselectiondialog.cpp new file mode 100644 index 0000000..f4dab38 --- /dev/null +++ b/src/dialogs/certificateselectiondialog.cpp @@ -0,0 +1,130 @@ +#include "certificateselectiondialog.hpp" +#include "ui_certificateselectiondialog.h" + +#include "certificatehelper.hpp" +#include "kristall.hpp" +#include "newidentitiydialog.hpp" + +#include +#include +#include + +CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateSelectionDialog) +{ + ui->setupUi(this); + this->ui->server_request->setVisible(false); + + this->ui->certificates->setModel(&global_identities); + this->ui->certificates->expandAll(); + + connect(this->ui->certificates->selectionModel(), &QItemSelectionModel::currentChanged, this, &CertificateSelectionDialog::on_currentChanged); +} + +CertificateSelectionDialog::~CertificateSelectionDialog() +{ + delete ui; +} + +void CertificateSelectionDialog::setServerQuery(const QString &query) +{ + this->ui->server_request->setText(query); + this->ui->server_request->setVisible(not query.isEmpty()); +} + +CryptoIdentity CertificateSelectionDialog::identity() const +{ + return cryto_identity; +} + +void CertificateSelectionDialog::on_use_temp_cert_30m_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(1800 * 12)); +} + +void CertificateSelectionDialog::on_use_temp_cert_1h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(3600)); +} + +void CertificateSelectionDialog::on_use_temp_cert_12h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(3600 * 12)); +} + +void CertificateSelectionDialog::on_use_temp_cert_24h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addDays(1)); +} + +void CertificateSelectionDialog::on_use_temp_cert_48h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addDays(2)); +} + +void CertificateSelectionDialog::acceptTemporaryWithTimeout(QDateTime timeout) +{ + std::default_random_engine rng; + rng.seed(QDateTime::currentDateTime().toMSecsSinceEpoch()); + + std::uniform_int_distribution distr(0, 255); + + char items[8]; + for(auto & c : items) { + c = distr(rng); + } + + this->cryto_identity = CertificateHelper::createNewIdentity( + QByteArray(items, sizeof items).toBase64(QByteArray::OmitTrailingEquals), + timeout); + + this->accept(); +} + +void CertificateSelectionDialog::on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + auto id = global_identities.getIdentity(current); + + this->ui->use_selected_cert->setEnabled(id.isValid()); +} + +void CertificateSelectionDialog::on_create_new_cert_clicked() +{ + NewIdentitiyDialog dialog { this }; + + if(dialog.exec() != QDialog::Accepted) + return; + + auto id = dialog.createIdentity(); + if(not id.isValid()) + return; + id.is_persistent = true; + + global_identities.addCertificate( + dialog.groupName(), + id); +} + +void CertificateSelectionDialog::on_use_selected_cert_clicked() +{ + auto sel = this->ui->certificates->selectionModel()->currentIndex(); + this->cryto_identity = global_identities.getIdentity(sel); + if(this->cryto_identity.isValid()) { + this->accept(); + } else { + qDebug() << "Tried to use an invalid identity when the button should not be enabled. This is a bug!"; + } +} + +void CertificateSelectionDialog::on_certificates_doubleClicked(const QModelIndex &index) +{ + this->cryto_identity = global_identities.getIdentity(index); + if(this->cryto_identity.isValid()) { + this->accept(); + } else { + qDebug() << "Tried to use an invalid identity when the button should not be enabled. This is a bug!"; + } +} diff --git a/src/dialogs/certificateselectiondialog.hpp b/src/dialogs/certificateselectiondialog.hpp new file mode 100644 index 0000000..e628f5c --- /dev/null +++ b/src/dialogs/certificateselectiondialog.hpp @@ -0,0 +1,54 @@ +#ifndef CERTIFICATESELECTIONDIALOG_HPP +#define CERTIFICATESELECTIONDIALOG_HPP + +#include + +#include "cryptoidentity.hpp" + +namespace Ui { +class CertificateSelectionDialog; +} + +class CertificateSelectionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CertificateSelectionDialog(QWidget *parent = nullptr); + ~CertificateSelectionDialog(); + + void setServerQuery(QString const & query); + + CryptoIdentity identity() const; + +private slots: + void on_use_temp_cert_30m_clicked(); + + void on_use_temp_cert_1h_clicked(); + + void on_use_temp_cert_12h_clicked(); + + void on_use_temp_cert_24h_clicked(); + + void on_use_temp_cert_48h_clicked(); + + void on_create_new_cert_clicked(); + + void on_use_selected_cert_clicked(); + + void on_certificates_doubleClicked(const QModelIndex &index); + +private: + //! Creates an anonymous identity with a randomly chosen name that + //! will time out on `timeout`, then accepts the dialog. + void acceptTemporaryWithTimeout(QDateTime timeout); + + + void on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous); +private: + Ui::CertificateSelectionDialog *ui; + + CryptoIdentity cryto_identity; +}; + +#endif // CERTIFICATESELECTIONDIALOG_HPP diff --git a/src/dialogs/certificateselectiondialog.ui b/src/dialogs/certificateselectiondialog.ui new file mode 100644 index 0000000..d6128f9 --- /dev/null +++ b/src/dialogs/certificateselectiondialog.ui @@ -0,0 +1,172 @@ + + + CertificateSelectionDialog + + + + 0 + 0 + 474 + 355 + + + + Select client certificate + + + + :/icons/certificate.svg:/icons/certificate.svg + + + + + + Select existing certificate: + + + + + + + + 75 + true + + + + TextLabel + + + true + + + + + + + true + + + + + + + + + Create new identity + + + + :/icons/plus.svg:/icons/plus.svg + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Use + + + + + + + + + Qt::Horizontal + + + + + + + Create transient session certificate: + + + + + + + + + 30 Minutes + + + + + + + 1 Hour + + + + + + + 12 Hours + + + + + + + 24 Hours + + + + + + + 48 Hours + + + + + + + + + QDialogButtonBox::Cancel + + + + + + + + + + + buttonBox + rejected() + CertificateSelectionDialog + reject() + + + 236 + 333 + + + 236 + 177 + + + + + diff --git a/src/dialogs/newidentitiydialog.cpp b/src/dialogs/newidentitiydialog.cpp new file mode 100644 index 0000000..af2a97e --- /dev/null +++ b/src/dialogs/newidentitiydialog.cpp @@ -0,0 +1,81 @@ +#include "newidentitiydialog.hpp" +#include "ui_newidentitiydialog.h" + +#include "certificatehelper.hpp" +#include "kristall.hpp" + +#include +#include + +NewIdentitiyDialog::NewIdentitiyDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewIdentitiyDialog) +{ + ui->setupUi(this); + + ui->display_name->setText("Unnamed"); + ui->common_name->setText("Unnamed"); + ui->expiration_date->setDate(QDate::currentDate().addYears(1)); + ui->expiration_date->setTime(QTime(12, 00)); + + ui->group->clear(); + for(auto group_name : global_identities.groups()) + { + ui->group->addItem(group_name); + } +} + +NewIdentitiyDialog::~NewIdentitiyDialog() +{ + delete ui; +} + +CryptoIdentity NewIdentitiyDialog::createIdentity() const +{ + auto id = CertificateHelper::createNewIdentity( + this->ui->common_name->text(), + this->ui->expiration_date->dateTime() + ); + id.display_name = this->ui->display_name->text(); + return id; +} + +QString NewIdentitiyDialog::groupName() const +{ + return this->ui->group->currentText(); +} + +void NewIdentitiyDialog::setGroupName(const QString &name) +{ + this->ui->group->setCurrentText(name); +} + +void NewIdentitiyDialog::updateUI() +{ + bool is_ok = true; + + is_ok &= (not this->ui->group->currentText().isEmpty()); + is_ok &= (not this->ui->common_name->text().isEmpty()); + is_ok &= (not this->ui->display_name->text().isEmpty()); + is_ok &= (this->ui->expiration_date->dateTime() > QDateTime::currentDateTime()); + + this->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(is_ok); +} + +void NewIdentitiyDialog::on_group_editTextChanged(const QString &arg1) +{ + Q_UNUSED(arg1); + this->updateUI(); +} + +void NewIdentitiyDialog::on_display_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1); + this->updateUI(); +} + +void NewIdentitiyDialog::on_common_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1); + this->updateUI(); +} diff --git a/src/dialogs/newidentitiydialog.hpp b/src/dialogs/newidentitiydialog.hpp new file mode 100644 index 0000000..83d740b --- /dev/null +++ b/src/dialogs/newidentitiydialog.hpp @@ -0,0 +1,41 @@ +#ifndef NEWIDENTITIYDIALOG_HPP +#define NEWIDENTITIYDIALOG_HPP + +#include + +#include "cryptoidentity.hpp" + +namespace Ui { +class NewIdentitiyDialog; +} + +class NewIdentitiyDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewIdentitiyDialog(QWidget *parent = nullptr); + ~NewIdentitiyDialog(); + + //! Creates a new identity from the currently set + //! user settings. + CryptoIdentity createIdentity() const; + + QString groupName() const; + void setGroupName(QString const & name); + +private slots: + void on_group_editTextChanged(const QString &arg1); + + void on_display_name_textChanged(const QString &arg1); + + void on_common_name_textChanged(const QString &arg1); + +private: + void updateUI(); + +private: + Ui::NewIdentitiyDialog *ui; +}; + +#endif // NEWIDENTITIYDIALOG_HPP diff --git a/src/dialogs/newidentitiydialog.ui b/src/dialogs/newidentitiydialog.ui new file mode 100644 index 0000000..556ac93 --- /dev/null +++ b/src/dialogs/newidentitiydialog.ui @@ -0,0 +1,118 @@ + + + NewIdentitiyDialog + + + + 0 + 0 + 328 + 191 + + + + Create new certificate + + + + :/icons/certificate.svg:/icons/certificate.svg + + + + + + + + Display Name + + + + + + + Expiration Date + + + + + + + + + + + + + + + + Common Name + + + + + + + Group + + + + + + + true + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + NewIdentitiyDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NewIdentitiyDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialogs/settingsdialog.cpp b/src/dialogs/settingsdialog.cpp new file mode 100644 index 0000000..ebd1664 --- /dev/null +++ b/src/dialogs/settingsdialog.cpp @@ -0,0 +1,613 @@ +#include "settingsdialog.hpp" +#include "ui_settingsdialog.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "kristall.hpp" + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SettingsDialog), + current_style() +{ + ui->setupUi(this); + + static_assert(DocumentStyle::Fixed == 0); + static_assert(DocumentStyle::AutoDarkTheme == 1); + static_assert(DocumentStyle::AutoLightTheme == 2); + + this->ui->auto_theme->clear(); + this->ui->auto_theme->addItem(tr("Disabled"), QVariant::fromValue(DocumentStyle::Fixed)); + this->ui->auto_theme->addItem(tr("Dark Theme"), QVariant::fromValue(DocumentStyle::AutoDarkTheme)); + this->ui->auto_theme->addItem(tr("Light Theme"), QVariant::fromValue(DocumentStyle::AutoLightTheme)); + + this->ui->ui_theme->clear(); + this->ui->ui_theme->addItem(tr("OS Default"), QVariant::fromValue(int(Theme::os_default))); + this->ui->ui_theme->addItem(tr("Light"), QVariant::fromValue(int(Theme::light))); + this->ui->ui_theme->addItem(tr("Dark"), QVariant::fromValue(int(Theme::dark))); + + setGeminiStyle(DocumentStyle { }); + + int items = global_settings.beginReadArray("Themes"); + + this->predefined_styles.clear(); + for(int i = 0; i < items; i++) + { + global_settings.setArrayIndex(i); + + QString name = global_settings.value("name").toString(); + + DocumentStyle style; + style.load(global_settings); + + this->predefined_styles.insert(name, style); + } + + global_settings.endArray(); + + this->ui->presets->clear(); + for(auto const & style_name : this->predefined_styles.keys()) + { + this->ui->presets->addItem(style_name); + } + + if(items > 0) { + on_presets_currentIndexChanged(0); + } else { + this->on_presets_currentIndexChanged(-1); + } + + this->ui->redirection_mode->clear(); + this->ui->redirection_mode->addItem("Ask for cross-scheme or cross-host redirection", int(GenericSettings::WarnOnHostChange | GenericSettings::WarnOnSchemeChange)); + this->ui->redirection_mode->addItem("Ask for cross-scheme redirection", int(GenericSettings::WarnOnSchemeChange)); + this->ui->redirection_mode->addItem("Ask for cross-host redirection", int(GenericSettings::WarnOnHostChange)); + this->ui->redirection_mode->addItem("Ask for all redirection", int(GenericSettings::WarnAlways)); + this->ui->redirection_mode->addItem("Silently redirect everything", int(GenericSettings::WarnNever)); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +static QString formatFont(QFont const & font) +{ + QString style; + if(font.italic() and font.bold()) + style = "bold, italic"; + else if(font.italic()) + style = "italic"; + else if(font.bold()) + style = "bold"; + else + style = "regular"; + + return QString("%1 (%2pt, %3)") + .arg(font.family()) + .arg(font.pointSizeF()) + .arg(style); +} + +void SettingsDialog::setGeminiStyle(DocumentStyle const &style) +{ + static const QString COLOR_STYLE("border: 1px solid black; padding: 4px; background-color : %1; color : %2;"); + + this->current_style = style; + + this->ui->auto_theme->setCurrentIndex(this->current_style.theme); + + this->ui->page_margin->setValue(this->current_style.margin); + + auto setFontAndColor = [this](QLabel * label, QFont font, QColor color) + { + label->setText(formatFont(font)); + label->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(color.name())); + }; + + ui->bg_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg("#FF00FF")); + + ui->quote_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.blockquote_color.name()) + .arg("#FF00FF")); + + ui->link_local_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(this->current_style.internal_link_color.name())); + + ui->link_foreign_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(this->current_style.external_link_color.name())); + + ui->link_cross_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(this->current_style.cross_scheme_link_color.name())); + + setFontAndColor(this->ui->std_preview, this->current_style.standard_font, this->current_style.standard_color); + setFontAndColor(this->ui->pre_preview, this->current_style.preformatted_font, this->current_style.preformatted_color); + setFontAndColor(this->ui->h1_preview, this->current_style.h1_font, this->current_style.h1_color); + setFontAndColor(this->ui->h2_preview, this->current_style.h2_font, this->current_style.h2_color); + setFontAndColor(this->ui->h3_preview, this->current_style.h3_font, this->current_style.h3_color); + + this->reloadStylePreview(); +} + +ProtocolSetup SettingsDialog::protocols() const +{ + ProtocolSetup protocols; +#define M(X) \ + protocols.X = this->ui->enable_##X->isChecked(); + PROTOCOLS(M) +#undef M + return protocols; +} + +void SettingsDialog::setProtocols(ProtocolSetup const & protocols) +{ +#define M(X) \ + this->ui->enable_##X->setChecked(protocols.X); + PROTOCOLS(M) + #undef M +} + +SslTrust SettingsDialog::geminiSslTrust() const +{ + return this->ui->gemini_trust_editor->trust(); +} + +void SettingsDialog::setGeminiSslTrust(const SslTrust &trust) +{ + return this->ui->gemini_trust_editor->setTrust(trust); +} + +SslTrust SettingsDialog::httpsSslTrust() const +{ + return this->ui->https_trust_editor->trust(); +} + +void SettingsDialog::setHttpsSslTrust(const SslTrust &trust) +{ + this->ui->https_trust_editor->setTrust(trust); +} + +void SettingsDialog::setOptions(const GenericSettings &options) +{ + this->current_options = options; + + this->ui->ui_theme->setCurrentIndex(0); + for(int i = 0; i < this->ui->ui_theme->count(); i++) { + if(this->ui->ui_theme->itemData(i).toInt() == int(options.theme)) { + this->ui->ui_theme->setCurrentIndex(i); + break; + } + } + + this->ui->start_page->setText(this->current_options.start_page); + + if(this->current_options.gophermap_display == GenericSettings::PlainText) { + this->ui->gophermap_text->setChecked(true); + } else { + this->ui->gophermap_icon->setChecked(true); + } + + if(this->current_options.text_display == GenericSettings::PlainText) { + this->ui->fancypants_off->setChecked(true); + } else { + this->ui->fancypants_on->setChecked(true); + } + + if(this->current_options.enable_text_decoration) { + this->ui->texthl_on->setChecked(true); + } else { + this->ui->texthl_off->setChecked(true); + } + + if(this->current_options.use_os_scheme_handler) { + this->ui->scheme_os_default->setChecked(true); + } else { + this->ui->scheme_error->setChecked(true); + } + + this->ui->max_redirects->setValue(this->current_options.max_redirections); + + this->ui->redirection_mode->setCurrentIndex(0); + for(int i = 0; i < this->ui->redirection_mode->count(); i++) + { + if(this->ui->redirection_mode->itemData(i).toInt() == this->current_options.redirection_policy) { + this->ui->redirection_mode->setCurrentIndex(i); + break; + } + } + + this->ui->network_timeout->setValue(this->current_options.network_timeout); +} + +GenericSettings SettingsDialog::options() const +{ + return this->current_options; +} + +void SettingsDialog::reloadStylePreview() +{ + QFile document_src { ":/about/style-preview.gemini" }; + bool ok = document_src.open(QFile::ReadOnly); + assert(ok and "failed to find style-preview.gemini!"); + + auto const document = document_src.readAll(); + + QString host = this->ui->preview_url->text(); + if(host.length() == 0) + host = "preview"; + + QUrl url { QUrl(QString("about://%1/foobar").arg(host)) }; + + DocumentOutlineModel outline; + auto doc = GeminiRenderer::render( + document, + url, + current_style.derive(url), + outline + ); + + ui->style_preview->setStyleSheet(QString("QTextBrowser { background-color: %1; }") + .arg(doc->background_color.name())); + ui->style_preview->setDocument(doc.get()); + preview_document = std::move(doc); +} + +void SettingsDialog::updateFont(QFont & input) +{ + QFontDialog dialog { this }; + + dialog.setCurrentFont(input); + + if(dialog.exec() == QDialog::Accepted) { + input = dialog.currentFont(); + setGeminiStyle(current_style); + } +} + +void SettingsDialog::on_std_change_font_clicked() +{ + updateFont(current_style.standard_font); +} + +void SettingsDialog::on_pre_change_font_clicked() +{ + updateFont(current_style.preformatted_font); +} + +void SettingsDialog::on_h1_change_font_clicked() +{ + updateFont(current_style.h1_font); +} + +void SettingsDialog::on_h2_change_font_clicked() +{ + updateFont(current_style.h2_font); +} + +void SettingsDialog::on_h3_change_font_clicked() +{ + updateFont(current_style.h3_font); +} + +void SettingsDialog::updateColor(QColor &input) +{ + QColorDialog dialog { this }; + + dialog.setCurrentColor(input); + + if(dialog.exec() == QDialog::Accepted) { + input = dialog.currentColor(); + setGeminiStyle(current_style); + } +} + +void SettingsDialog::on_std_change_color_clicked() +{ + updateColor(current_style.standard_color); +} + +void SettingsDialog::on_pre_change_color_clicked() +{ + updateColor(current_style.preformatted_color); +} + +void SettingsDialog::on_h1_change_color_clicked() +{ + updateColor(current_style.h1_color); +} + +void SettingsDialog::on_h2_change_color_clicked() +{ + updateColor(current_style.h2_color); +} + +void SettingsDialog::on_h3_change_color_clicked() +{ + updateColor(current_style.h3_color); +} + +void SettingsDialog::on_bg_change_color_clicked() +{ + updateColor(current_style.background_color); +} + +void SettingsDialog::on_link_local_change_color_clicked() +{ + updateColor(current_style.internal_link_color); +} + +void SettingsDialog::on_link_foreign_change_color_clicked() +{ + updateColor(current_style.external_link_color); +} + +void SettingsDialog::on_link_cross_change_color_clicked() +{ + updateColor(current_style.cross_scheme_link_color); +} +void SettingsDialog::on_quote_change_color_clicked() +{ + updateColor(current_style.blockquote_color); +} + +void SettingsDialog::on_link_local_prefix_textChanged(const QString &text) +{ + current_style.internal_link_prefix = text; + reloadStylePreview(); +} + +void SettingsDialog::on_link_foreign_prefix_textChanged(const QString &text) +{ + current_style.external_link_prefix = text; + reloadStylePreview(); +} + +void SettingsDialog::on_auto_theme_currentIndexChanged(int index) +{ + if(index >= 0) { + current_style.theme = DocumentStyle::Theme(index); + reloadStylePreview(); + } +} + +void SettingsDialog::on_preview_url_textChanged(const QString &) +{ + this->reloadStylePreview(); +} + +void SettingsDialog::on_page_margin_valueChanged(double value) +{ + this->current_style.margin = value; + this->reloadStylePreview(); +} + +void SettingsDialog::on_presets_currentIndexChanged(int index) +{ + this->ui->preset_load->setEnabled(index >= 0); + this->ui->preset_save->setEnabled(index >= 0); + this->ui->preset_export->setEnabled(index >= 0); +} + +void SettingsDialog::on_preset_new_clicked() +{ + QInputDialog dlg { this }; + dlg.setInputMode(QInputDialog::TextInput); + dlg.setOkButtonText("Save"); + dlg.setCancelButtonText("Cancel"); + dlg.setLabelText("Enter the name of your new preset:"); + + if(dlg.exec() != QInputDialog::Accepted) + return; + QString name = dlg.textValue(); + + bool override = false; + if(this->predefined_styles.contains(name)) + { + auto response = QMessageBox::question(this, "Kristall", QString("A style with the name '%1' already exists! Replace?").arg(name)); + if(response != QMessageBox::Yes) + return; + override = true; + } + + this->predefined_styles.insert(name, this->current_style); + + if(not override) + { + this->ui->presets->addItem(name); + } +} + +void SettingsDialog::on_preset_save_clicked() +{ + QString name = this->ui->presets->currentText(); + if(name.isEmpty()) + return; + + auto response = QMessageBox::question(this, "Kristall", QString("Do you want to override the style '%1'?").arg(name)); + if(response != QMessageBox::Yes) + return; + + this->predefined_styles.insert(name, this->current_style); +} + + +void SettingsDialog::on_preset_load_clicked() +{ + QString name = this->ui->presets->currentText(); + if(name.isEmpty()) + return; + + auto response = QMessageBox::question(this, "Kristall", QString("Do you want to load the style '%1'?\r\nThis will discard all currently set up values!").arg(name)); + if(response != QMessageBox::Yes) + return; + + this->setGeminiStyle(this->predefined_styles.value(name)); +} + + +void SettingsDialog::on_SettingsDialog_accepted() +{ + global_settings.beginWriteArray("Themes", this->predefined_styles.size()); + + int index = 0; + for(auto const & style_name : this->predefined_styles.keys()) + { + global_settings.setArrayIndex(index); + + global_settings.setValue("name", style_name); + this->predefined_styles.value(style_name).save(global_settings); + + index += 1; + } + global_settings.endArray(); +} + +void SettingsDialog::on_preset_import_clicked() +{ + QFileDialog dialog { this }; + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.selectNameFilter("Kristall Theme (*.kthm)"); + + if(dialog.exec() !=QFileDialog::Accepted) + return; + + QString fileName = dialog.selectedFiles().at(0); + + QSettings import_settings { fileName, QSettings::IniFormat }; + + QString name; + + name = import_settings.value("name").toString(); + + while(name.isEmpty()) + { + QInputDialog dlg { this }; + dlg.setInputMode(QInputDialog::TextInput); + dlg.setOkButtonText("Save"); + dlg.setCancelButtonText("Cancel"); + dlg.setLabelText("Imported preset has no name.\r\nPlease enter a name for the preset:"); + if(dlg.exec() != QDialog::Accepted) + return; + name = dlg.textValue(); + } + + bool override = false; + if(this->predefined_styles.contains(name)) + { + auto response = QMessageBox::question(this, "Kristall", QString("Do you want to override the style '%1'?").arg(name)); + if(response != QMessageBox::Yes) + return; + override = true; + } + + DocumentStyle style; + style.load(import_settings); + + this->predefined_styles.insert(name, style); + + if(not override) + { + this->ui->presets->addItem(name); + } +} + +void SettingsDialog::on_preset_export_clicked() +{ + QString name = this->ui->presets->currentText(); + if(name.isEmpty()) + return; + + QFileDialog dialog { this }; + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.selectNameFilter("Kristall Theme (*.kthm)"); + dialog.selectFile(QString("%1.kthm").arg(name)); + + if(dialog.exec() !=QFileDialog::Accepted) + return; + + QString fileName = dialog.selectedFiles().at(0); + + QSettings export_settings { fileName, QSettings::IniFormat }; + export_settings.setValue("name", name); + this->predefined_styles.value(name).save(export_settings); + export_settings.sync(); +} + +void SettingsDialog::on_start_page_textChanged(const QString &start_page) +{ + this->current_options.start_page = start_page; +} + +void SettingsDialog::on_ui_theme_currentIndexChanged(int index) +{ + this->current_options.theme = Theme(this->ui->ui_theme->itemData(index).toInt()); +} + +void SettingsDialog::on_fancypants_on_clicked() +{ + this->current_options.text_display = GenericSettings::FormattedText; +} + +void SettingsDialog::on_fancypants_off_clicked() +{ + this->current_options.text_display = GenericSettings::PlainText; +} + +void SettingsDialog::on_texthl_on_clicked() +{ + this->current_options.enable_text_decoration = true; +} + +void SettingsDialog::on_texthl_off_clicked() +{ + this->current_options.enable_text_decoration = false; +} + +void SettingsDialog::on_gophermap_icon_clicked() +{ + this->current_options.gophermap_display = GenericSettings::FormattedText; +} + +void SettingsDialog::on_gophermap_text_clicked() +{ + this->current_options.gophermap_display = GenericSettings::PlainText; +} + +void SettingsDialog::on_scheme_os_default_clicked() +{ + this->current_options.use_os_scheme_handler = true; +} + +void SettingsDialog::on_scheme_error_clicked() +{ + this->current_options.use_os_scheme_handler = false; +} + +void SettingsDialog::on_redirection_mode_currentIndexChanged(int index) +{ + this->current_options.redirection_policy = GenericSettings::RedirectionWarning(this->ui->redirection_mode->itemData(index).toInt()); +} + +void SettingsDialog::on_max_redirects_valueChanged(int max_redirections) +{ + this->current_options.max_redirections = max_redirections; +} + +void SettingsDialog::on_network_timeout_valueChanged(int timeout) +{ + this->current_options.network_timeout = timeout; +} diff --git a/src/dialogs/settingsdialog.hpp b/src/dialogs/settingsdialog.hpp new file mode 100644 index 0000000..045b861 --- /dev/null +++ b/src/dialogs/settingsdialog.hpp @@ -0,0 +1,143 @@ +#ifndef SETTINGSDIALOG_HPP +#define SETTINGSDIALOG_HPP + +#include + +#include "renderers/geminirenderer.hpp" +#include "protocolsetup.hpp" +#include "documentstyle.hpp" +#include "ssltrust.hpp" +#include "kristall.hpp" + +namespace Ui { +class SettingsDialog; +} + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(QWidget *parent = nullptr); + ~SettingsDialog(); + + void setGeminiStyle(DocumentStyle const & style); + + DocumentStyle geminiStyle() const { + return current_style; + } + + ProtocolSetup protocols() const; + void setProtocols(ProtocolSetup const & proto); + + SslTrust geminiSslTrust() const; + void setGeminiSslTrust(SslTrust const & trust); + + SslTrust httpsSslTrust() const; + void setHttpsSslTrust(SslTrust const & trust); + + GenericSettings options() const; + void setOptions(GenericSettings const & options); + +private slots: + void on_std_change_font_clicked(); + + void on_pre_change_font_clicked(); + + void on_h1_change_font_clicked(); + + void on_h2_change_font_clicked(); + + void on_h3_change_font_clicked(); + + void on_std_change_color_clicked(); + + void on_pre_change_color_clicked(); + + void on_h1_change_color_clicked(); + + void on_h2_change_color_clicked(); + + void on_h3_change_color_clicked(); + + void on_bg_change_color_clicked(); + + void on_link_local_change_color_clicked(); + + void on_link_foreign_change_color_clicked(); + + void on_link_cross_change_color_clicked(); + + void on_link_local_prefix_textChanged(const QString &arg1); + + void on_link_foreign_prefix_textChanged(const QString &arg1); + + void on_auto_theme_currentIndexChanged(int index); + + void on_preview_url_textChanged(const QString &arg1); + + void on_page_margin_valueChanged(double arg1); + + void on_presets_currentIndexChanged(int index); + + void on_preset_new_clicked(); + + void on_SettingsDialog_accepted(); + + void on_quote_change_color_clicked(); + + void on_preset_save_clicked(); + + void on_preset_load_clicked(); + + void on_preset_import_clicked(); + + void on_preset_export_clicked(); + + void on_start_page_textChanged(const QString &arg1); + + void on_ui_theme_currentIndexChanged(int index); + + void on_fancypants_on_clicked(); + + void on_fancypants_off_clicked(); + + void on_texthl_on_clicked(); + + void on_texthl_off_clicked(); + + void on_gophermap_icon_clicked(); + + void on_gophermap_text_clicked(); + + void on_scheme_os_default_clicked(); + + void on_scheme_error_clicked(); + + void on_redirection_mode_currentIndexChanged(int index); + + void on_max_redirects_valueChanged(int arg1); + + void on_network_timeout_valueChanged(int arg1); + +private: + void reloadStylePreview(); + + void updateFont(QFont & input); + + void updateColor(QColor & input); + +private: + Ui::SettingsDialog *ui; + + DocumentStyle current_style; + std::unique_ptr preview_document; + + QMap predefined_styles; + + SslTrust current_trust; + + GenericSettings current_options; +}; + +#endif // SETTINGSDIALOG_HPP diff --git a/src/dialogs/settingsdialog.ui b/src/dialogs/settingsdialog.ui new file mode 100644 index 0000000..9acbca6 --- /dev/null +++ b/src/dialogs/settingsdialog.ui @@ -0,0 +1,940 @@ + + + SettingsDialog + + + + 0 + 0 + 850 + 650 + + + + Settings + + + + .. + + + + + + 2 + + + + + .. + + + Generic + + + + + + UI Theme + + + + + + + + + + Start Page: + + + + + + + about://blank + + + + + + + Enabled Protocols + + + + + + + + + Gemini + + + + + + + true + + + Gopher + + + + + + + true + + + Finger + + + + + + + HTTP + + + + + + + HTTPS + + + + + + + + + Text Rendering + + + + + + + + + Fancy + + + true + + + textRenderingBtnGroup + + + + + + + Always plain text + + + textRenderingBtnGroup + + + + + + + + + Enable text highlights + + + + + + + + + On (Experimental) + + + textHighlightsBtnGroup + + + + + + + Off + + + true + + + textHighlightsBtnGroup + + + + + + + + + Gopher Map + + + + + + + + + Use icons + + + true + + + gophermapBtnGroup + + + + + + + Use text only + + + gophermapBtnGroup + + + + + + + + + Unknown Scheme + + + + + + + + + Use OS default handler + + + buttonGroup + + + + + + + Display error message + + + buttonGroup + + + + + + + + + Max. Number of Redirections + + + + + + + 5 + + + + + + + Redirection Handling + + + + + + + + + + Network Timeout + + + + + + + ms + + + 100 + + + 90000 + + + + + + + + + .. + + + Style + + + + + + 5 + + + + + + + + + + Qt::PlainText + + + + + + + + + + + .. + + + + + + + + + Background Color + + + + + + + Standard Font + + + + + + + + + QFrame::NoFrame + + + This text will be displayed for normal text. + + + + + + + + + + + .. + + + + + + + + + + + .. + + + + + + + + + Preformatted Font + + + + + + + + + QFrame::NoFrame + + + This text will be displayed for preformatted text. + + + + + + + + + + + .. + + + + + + + + + + + .. + + + + + + + + + H1 Font + + + + + + + + + QFrame::NoFrame + + + This text will be displayed for a level 1 heading. + + + + + + + + + + + .. + + + + + + + + + + + .. + + + + + + + + + H2 Font + + + + + + + + + QFrame::NoFrame + + + This text will be displayed for a level 2 heading. + + + + + + + + + + + .. + + + + + + + + + + + .. + + + + + + + + + H3 Font + + + + + + + + + border: 1px solid black; + + + QFrame::NoFrame + + + This text will be displayed for a level 3 heading. + + + Qt::PlainText + + + + + + + + + + + .. + + + + + + + + + + + .. + + + + + + + + + Local Link Color + + + + + + + Foreign Link Color + + + + + + + Cross-Scheme-Color + + + + + + + Local Link Prefix + + + + + + + Extern Link Prefix + + + + + + + + + + + + + + + + + + + + + + + This is a local reference + + + Qt::PlainText + + + + + + + + + + + .. + + + + + + + + + + + This is a foreign reference + + + Qt::PlainText + + + + + + + + + + + .. + + + + + + + + + + + This reference is cross-scheme + + + Qt::PlainText + + + + + + + + + + + .. + + + + + + + + + Auto-Theme Generation + + + + + + + + + + Page Margin + + + + + + + px + + + 0 + + + 350.000000000000000 + + + + + + + + + + + + Save as new preset + + + ... + + + + .. + + + + + + + Override current preset + + + ... + + + + .. + + + + + + + Load preset + + + ... + + + + .. + + + + + + + Imports preset… + + + -1 + + + ... + + + + .. + + + + + + + Export preset… + + + ... + + + + .. + + + + + + + + + Presets + + + + + + + Block Quote Background + + + + + + + + + + + + + + + + ... + + + + .. + + + + + + + + + + + + + false + + + + + + + host.name + + + + + + + + + + + .. + + + Gemini TLS + + + + + + + + + + + .. + + + HTTPS TLS + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SslTrustEditor + QWidget +
widgets/ssltrusteditor.hpp
+ 1 +
+
+ + + + buttonBox + rejected() + SettingsDialog + reject() + + + 325 + 470 + + + 286 + 274 + + + + + buttonBox + accepted() + SettingsDialog + accept() + + + 257 + 470 + + + 157 + 274 + + + + + + + + + + +
diff --git a/src/elidelabel.cpp b/src/elidelabel.cpp deleted file mode 100644 index dc0a6b2..0000000 --- a/src/elidelabel.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "elidelabel.hpp" - -#include - -ElideLabel::ElideLabel(QWidget* parent) - : QLabel(parent) -{ - m_elideMode = Qt::ElideNone; -} - -ElideLabel::ElideLabel(const QString &text, QWidget* parent) : QLabel(text, parent) -{ - m_elideMode = Qt::ElideNone; -} - -ElideLabel::~ElideLabel() -{ -} - -void ElideLabel::setElideMode(Qt::TextElideMode mode) -{ - m_elideMode = mode; -} - -Qt::TextElideMode ElideLabel::elideMode() const -{ - return m_elideMode; -} - -void ElideLabel::paintEvent(QPaintEvent * event) -{ - if (m_elideMode == Qt::ElideNone) - { - QLabel::paintEvent(event); - } - else - { - QFrame::paintEvent(event); - QPainter painter(this); - QRect r = contentsRect(); - painter.drawText(r, alignment(), fontMetrics().elidedText(text(), m_elideMode, r.width())); - } -} - -QSize ElideLabel::minimumSizeHint() const -{ - if (m_elideMode != Qt::ElideNone) - { - const QFontMetrics& fm = fontMetrics(); -#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) - QSize size(fm.horizontalAdvance("..."), fm.height()); -#else - QSize size(fm.width("..."), fm.height()); -#endif - return size; - } - - return QLabel::minimumSizeHint(); -} diff --git a/src/elidelabel.hpp b/src/elidelabel.hpp deleted file mode 100644 index 9eae69d..0000000 --- a/src/elidelabel.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef ELIDELABEL_HPP -#define ELIDELABEL_HPP - -#include - -class ElideLabel : public QLabel -{ - Q_OBJECT - Q_PROPERTY(Qt::TextElideMode elideMode READ elideMode WRITE setElideMode) - -public: - ElideLabel(QWidget* parent = 0); - ElideLabel(const QString &text, QWidget* parent = 0); - ~ElideLabel(); - - void setElideMode(Qt::TextElideMode mode); - Qt::TextElideMode elideMode() const; - - QSize minimumSizeHint() const; - -protected: - virtual void paintEvent(QPaintEvent * event); - -private: - Qt::TextElideMode m_elideMode; -}; - -#endif // ELIDELABEL_HPP diff --git a/src/filehandler.cpp b/src/filehandler.cpp deleted file mode 100644 index d26cd57..0000000 --- a/src/filehandler.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "filehandler.hpp" - -#include -#include -#include - -FileHandler::FileHandler() -{ - -} - -bool FileHandler::supportsScheme(const QString &scheme) const -{ - return (scheme == "file"); -} - -bool FileHandler::startRequest(const QUrl &url, RequestOptions options) -{ - Q_UNUSED(options) - - QFile file { url.path() }; - - if (file.open(QFile::ReadOnly)) - { - QMimeDatabase db; - auto mime = db.mimeTypeForUrl(url).name(); - auto data = file.readAll(); - emit this->requestComplete(data, mime); - } - else - { - emit this->networkError(ResourceNotFound, "The requested file does not exist!"); - } - return true; -} - -bool FileHandler::isInProgress() const -{ - return false; -} - -bool FileHandler::cancelRequest() -{ - return true; -} diff --git a/src/filehandler.hpp b/src/filehandler.hpp deleted file mode 100644 index 55ac248..0000000 --- a/src/filehandler.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef FILEHANDLER_HPP -#define FILEHANDLER_HPP - -#include - -#include "protocolhandler.hpp" - -class FileHandler : public ProtocolHandler -{ - Q_OBJECT -public: - FileHandler(); - - bool supportsScheme(QString const & scheme) const override; - - bool startRequest(QUrl const & url, RequestOptions options) override; - - bool isInProgress() const override; - - bool cancelRequest() override; -}; - -#endif // FILEHANDLER_HPP diff --git a/src/fingerclient.cpp b/src/fingerclient.cpp deleted file mode 100644 index 1c9a6a3..0000000 --- a/src/fingerclient.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "fingerclient.hpp" -#include "ioutil.hpp" - -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::of(&QTcpSocket::error), this, &FingerClient::on_socketError); -#endif -} - -FingerClient::~FingerClient() -{ - -} - -bool FingerClient::supportsScheme(const QString &scheme) const -{ - return (scheme == "finger"); -} - -bool FingerClient::startRequest(const QUrl &url, RequestOptions options) -{ - Q_UNUSED(options) - - if(isInProgress()) - return false; - - if(url.scheme() != "finger") - return false; - - this->requested_user = url.userName(); - this->was_cancelled = false; - socket.connectToHost(url.host(), url.port(79)); - - return true; -} - -bool FingerClient::isInProgress() const -{ - return socket.isOpen(); -} - -bool FingerClient::cancelRequest() -{ - was_cancelled = true; - socket.close(); - body.clear(); - return true; -} - -void FingerClient::on_connected() -{ - auto blob = (requested_user + "\r\n").toUtf8(); - - IoUtil::writeAll(socket, blob); -} - -void FingerClient::on_readRead() -{ - body.append(socket.readAll()); - emit this->requestProgress(body.size()); -} - -void FingerClient::on_finished() -{ - if(not was_cancelled) - { - emit this->requestComplete(this->body, "text/finger"); - was_cancelled = true; - } - body.clear(); -} - -void FingerClient::on_socketError(QAbstractSocket::SocketError error_code) -{ - this->emitNetworkError(error_code, socket.errorString()); -} diff --git a/src/fingerclient.hpp b/src/fingerclient.hpp deleted file mode 100644 index 63d04fd..0000000 --- a/src/fingerclient.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef FINGERCLIENT_HPP -#define FINGERCLIENT_HPP - -#include -#include -#include - -#include "protocolhandler.hpp" - -class FingerClient : public ProtocolHandler -{ - Q_OBJECT -public: - explicit FingerClient(); - - ~FingerClient() 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_socketError(QTcpSocket::SocketError error_code); - -private: - QTcpSocket socket; - QByteArray body; - bool was_cancelled; - QString requested_user; -}; - -#endif // FINGERCLIENT_HPP 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 -#include -#include -#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 &>::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::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 { }); - 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 = ""; - - 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 " - 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 "); - 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 const & errors) -{ - emit this->hostCertificateLoaded(this->socket.peerCertificate()); - - if(options & IgnoreTlsErrors) { - socket.ignoreSslErrors(errors); - return; - } - - QList remaining_errors = errors; - QList 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()); - } - } -} diff --git a/src/geminiclient.hpp b/src/geminiclient.hpp deleted file mode 100644 index 85c4f76..0000000 --- a/src/geminiclient.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef GEMINICLIENT_HPP -#define GEMINICLIENT_HPP - -#include -#include -#include -#include - -#include "protocolhandler.hpp" - -class GeminiClient : public ProtocolHandler -{ -private: - Q_OBJECT -public: - explicit GeminiClient(); - - ~GeminiClient() override; - - bool supportsScheme(QString const & scheme) const override; - - bool startRequest(QUrl const & url, RequestOptions options) override; - - bool isInProgress() const override; - - bool cancelRequest() override; - - bool enableClientCertificate(CryptoIdentity const & ident) override; - void disableClientCertificate() override; - -private slots: - void socketEncrypted(); - - void socketReadyRead(); - - void socketDisconnected(); - - void sslErrors(const QList &errors); - - void socketError(QAbstractSocket::SocketError socketError); - -private: - bool is_receiving_body; - bool suppress_socket_tls_error; - bool is_error_state; - - QUrl target_url; - QSslSocket socket; - QByteArray buffer; - QByteArray body; - QString mime_type; - RequestOptions options; -}; - -#endif // GEMINICLIENT_HPP diff --git a/src/geminirenderer.cpp b/src/geminirenderer.cpp deleted file mode 100644 index ec00869..0000000 --- a/src/geminirenderer.cpp +++ /dev/null @@ -1,343 +0,0 @@ -#include "geminirenderer.hpp" - -#include -#include -#include -#include -#include - -#include "kristall.hpp" - -static QByteArray trim_whitespace(QByteArray items) -{ - int start = 0; - while (start < items.size() and isspace(items.at(start))) - { - start += 1; - } - int end = items.size() - 1; - while (end > 0 and isspace(items.at(end))) - { - end -= 1; - } - return items.mid(start, end - start + 1); -} - -std::unique_ptr GeminiRenderer::render( - const QByteArray &input, - QUrl const &root_url, - DocumentStyle const & themed_style, - DocumentOutlineModel &outline) -{ - QTextCharFormat preformatted; - preformatted.setFont(themed_style.preformatted_font); - preformatted.setForeground(themed_style.preformatted_color); - - QTextCharFormat standard; - standard.setFont(themed_style.standard_font); - standard.setForeground(themed_style.standard_color); - - QTextCharFormat standard_link; - standard_link.setFont(themed_style.standard_font); - standard_link.setForeground(QBrush(themed_style.internal_link_color)); - - QTextCharFormat external_link; - external_link.setFont(themed_style.standard_font); - external_link.setForeground(QBrush(themed_style.external_link_color)); - - QTextCharFormat cross_protocol_link; - cross_protocol_link.setFont(themed_style.standard_font); - cross_protocol_link.setForeground(QBrush(themed_style.cross_scheme_link_color)); - - QTextCharFormat standard_h1; - standard_h1.setFont(themed_style.h1_font); - standard_h1.setForeground(QBrush(themed_style.h1_color)); - - QTextCharFormat standard_h2; - standard_h2.setFont(themed_style.h2_font); - standard_h2.setForeground(QBrush(themed_style.h2_color)); - - QTextCharFormat standard_h3; - standard_h3.setFont(themed_style.h3_font); - standard_h3.setForeground(QBrush(themed_style.h3_color)); - - std::unique_ptr result = std::make_unique(); - result->setDocumentMargin(themed_style.margin); - result->background_color = themed_style.background_color; - result->setIndentWidth(20); - - bool emit_fancy_text = global_options.enable_text_decoration; - - QTextCursor cursor{result.get()}; - - QTextBlockFormat standard_format = cursor.blockFormat(); - - QTextBlockFormat preformatted_format = standard_format; - preformatted_format.setNonBreakableLines(true); - - QTextBlockFormat block_quote_format = standard_format; - block_quote_format.setIndent(1); - block_quote_format.setBackground(themed_style.blockquote_color); - - - bool verbatim = false; - QTextList *current_list = nullptr; - bool blockquote = false; - - outline.beginBuild(); - - int anchor_id = 0; - - auto unique_anchor_name = [&]() -> QString { - return QString("auto-title-%1").arg(++anchor_id); - }; - - QList lines = input.split('\n'); - for (auto const &line : lines) - { - if (verbatim) - { - if (line.startsWith("```")) - { - cursor.setBlockFormat(standard_format); - verbatim = false; - } - else - { - cursor.setBlockFormat(preformatted_format); - cursor.setCharFormat(preformatted); - cursor.insertText(line + "\n"); - } - } - else - { - if (line.startsWith("* ")) - { - if (current_list == nullptr) - { - cursor.deletePreviousChar(); - current_list = cursor.insertList(QTextListFormat::ListDisc); - } - else - { - cursor.insertBlock(); - } - - QString item = trim_whitespace(line.mid(1)); - - cursor.insertText(item, standard); - continue; - } - else - { - if (current_list != nullptr) - { - cursor.insertBlock(); - cursor.setBlockFormat(standard_format); - } - current_list = nullptr; - } - - if(line.startsWith(">")) - { - if(not blockquote ) { - // cursor.insertBlock(); - } - blockquote = true; - - cursor.setBlockFormat(block_quote_format); - cursor.insertText(trim_whitespace(line.mid(1)) + "\n", standard); - - continue; - } - else - { - if(blockquote) { - cursor.setBlockFormat(standard_format); - } - blockquote = false; - } - - if (line.startsWith("###")) - { - auto heading = trim_whitespace(line.mid(3)); - - auto id = unique_anchor_name(); - auto fmt = standard_h3; - fmt.setAnchor(true); - fmt.setAnchorNames(QStringList { id }); - - cursor.insertText(heading + "\n", fmt); - outline.appendH3(heading, id); - } - else if (line.startsWith("##")) - { - auto heading = trim_whitespace(line.mid(2)); - - auto id = unique_anchor_name(); - auto fmt = standard_h2; - fmt.setAnchor(true); - fmt.setAnchorNames(QStringList { id }); - - cursor.insertText(heading + "\n", fmt); - outline.appendH2(heading, id); - } - else if (line.startsWith("#")) - { - auto heading = trim_whitespace(line.mid(1)); - - auto id = unique_anchor_name(); - auto fmt = standard_h1; - fmt.setAnchor(true); - fmt.setAnchorNames(QStringList { id }); - - cursor.insertText(heading + "\n", fmt); - outline.appendH1(heading, id); - } - else if (line.startsWith("=>")) - { - auto const part = line.mid(2).trimmed(); - - QByteArray link, title; - - int index = -1; - for (int i = 0; i < part.size(); i++) - { - if (isspace(part[i])) - { - index = i; - break; - } - } - - if (index > 0) - { - link = trim_whitespace(part.mid(0, index)); - title = trim_whitespace(part.mid(index + 1)); - } - else - { - link = trim_whitespace(part); - title = trim_whitespace(part); - } - - auto local_url = QUrl(link); - - auto absolute_url = root_url.resolved(QUrl(link)); - - // qDebug() << link << title; - - auto fmt = standard_link; - - QString prefix; - if (absolute_url.host() == root_url.host()) - { - prefix = themed_style.internal_link_prefix; - fmt = standard_link; - } - else - { - prefix = themed_style.external_link_prefix; - fmt = external_link; - } - - QString suffix = ""; - if (absolute_url.scheme() != root_url.scheme()) - { - if(absolute_url.scheme() != "kristall+ctrl") { - suffix = " [" + absolute_url.scheme().toUpper() + "]"; - fmt = cross_protocol_link; - } - } - - fmt.setAnchor(true); - fmt.setAnchorHref(absolute_url.toString()); - cursor.insertText(prefix + title + suffix + "\n", fmt); - } - else if (line.startsWith("```")) - { - verbatim = true; - } - else - { - if(emit_fancy_text) - { - // TODO: Fix UTF-8 encoding here… Don't emit single characters but always spans! - - bool rendering_bold = false; - bool rendering_underlined = false; - - QTextCharFormat fmt = standard; - - QByteArray buffer; - - auto flush = [&]() { - if(buffer.size() > 0) { - cursor.insertText(QString::fromUtf8(buffer), fmt); - buffer.resize(0); - } - }; - - for(int i = 0; i < line.length(); i += 1) - { - char c = line.at(i); - if(c == ' ') { - flush(); - fmt = standard; - buffer.append(' '); - rendering_bold = false; - rendering_underlined = false; - } - else if(c == '*') { - if(rendering_bold) { - buffer.append('*'); - } - flush(); - rendering_bold = not rendering_bold; - auto f = fmt.font(); - f.setBold(rendering_bold); - fmt.setFont(f); - if(rendering_bold) { - buffer.append('*'); - } - } - else if(c == '_') { - if(rendering_underlined) { - buffer.append(' '); - } - flush(); - rendering_underlined = not rendering_underlined; - auto f = fmt.font(); - fmt.setUnderlineStyle(rendering_underlined ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline); - if(rendering_underlined) { - buffer.append(' '); - } - } - else { - buffer.append(c); - } - } - - flush(); - - cursor.insertText("\n", standard); - } - else { - cursor.insertText(line + "\n", standard); - } - } - } - } - - outline.endBuild(); - return result; -} - -GeminiDocument::GeminiDocument(QObject *parent) : QTextDocument(parent), - background_color(0x00, 0x00, 0x00) -{ -} - -GeminiDocument::~GeminiDocument() -{ -} diff --git a/src/geminirenderer.hpp b/src/geminirenderer.hpp deleted file mode 100644 index 7173d50..0000000 --- a/src/geminirenderer.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef GEMINIRENDERER_HPP -#define GEMINIRENDERER_HPP - -#include -#include -#include -#include - -#include "documentoutlinemodel.hpp" - -#include "documentstyle.hpp" - -class GeminiDocument : - public QTextDocument -{ - Q_OBJECT -public: - explicit GeminiDocument(QObject * parent = nullptr); - ~GeminiDocument() override; - - QColor background_color; -}; - -struct GeminiRenderer -{ - GeminiRenderer() = delete; - - //! Renders the given byte sequence into a GeminiDocument. - //! @param input The utf8 encoded input string - //! @param root_url The url that is used to resolve relative links - //! @param style The style which is used to render the document - //! @param outline The extracted outline from the document - static std::unique_ptr render( - QByteArray const & input, - QUrl const & root_url, - DocumentStyle const & style, - DocumentOutlineModel & outline - ); -}; - -#endif // GEMINIRENDERER_HPP diff --git a/src/gopherclient.cpp b/src/gopherclient.cpp deleted file mode 100644 index 63c35ca..0000000 --- a/src/gopherclient.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "gopherclient.hpp" -#include "ioutil.hpp" - -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::of(&QTcpSocket::error), this, &GopherClient::on_socketError); -#endif -} - -GopherClient::~GopherClient() -{ - -} - -bool GopherClient::supportsScheme(const QString &scheme) const -{ - return (scheme == "gopher"); -} - -bool GopherClient::startRequest(const QUrl &url, RequestOptions options) -{ - Q_UNUSED(options) - - if(isInProgress()) - return false; - - if(url.scheme() != "gopher") - return false; - - // Second char on the URL path denotes the Gopher type - // See https://tools.ietf.org/html/rfc4266 - QString type = url.path().mid(1, 1); - - mime = "application/octet-stream"; - if(type == "") mime = "text/gophermap"; - else if(type == "0") mime = "text/plain"; - else if(type == "1") mime = "text/gophermap"; - else if(type == "g") mime = "image/gif"; - else if(type == "I") mime = "image/unknown"; - else if(type == "h") mime = "text/html"; - else if(type == "s") mime = "audio/unknown"; - - is_processing_binary = (type == "5") or (type == "9") or (type == "I") or (type == "g"); - - this->requested_url = url; - this->was_cancelled = false; - socket.connectToHost(url.host(), url.port(70)); - - return true; -} - -bool GopherClient::isInProgress() const -{ - return socket.isOpen(); -} - -bool GopherClient::cancelRequest() -{ - was_cancelled = true; - socket.close(); - body.clear(); - return true; -} - -void GopherClient::on_connected() -{ - auto blob = (requested_url.path().mid(2) + "\r\n").toUtf8(); - - IoUtil::writeAll(socket, blob); -} - -void GopherClient::on_readRead() -{ - body.append(socket.readAll()); - - if(not is_processing_binary) { - // Strip the "lone dot" from gopher data - if(int index = body.indexOf("\r\n.\r\n"); index >= 0) { - body.resize(index + 2); - socket.close(); - } - } - - if(not was_cancelled) { - emit this->requestProgress(body.size()); - } -} - -void GopherClient::on_finished() -{ - if(not was_cancelled) - { - emit this->requestComplete(this->body, mime); - was_cancelled = true; - } - body.clear(); -} - -void GopherClient::on_socketError(QAbstractSocket::SocketError error_code) -{ - this->emitNetworkError(error_code, socket.errorString()); -} diff --git a/src/gopherclient.hpp b/src/gopherclient.hpp deleted file mode 100644 index 888ebd8..0000000 --- a/src/gopherclient.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef GOPHERCLIENT_HPP -#define GOPHERCLIENT_HPP - -#include -#include -#include - -#include "protocolhandler.hpp" - -class GopherClient : public ProtocolHandler -{ - Q_OBJECT -public: - explicit GopherClient(QObject *parent = nullptr); - - ~GopherClient() 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_socketError(QAbstractSocket::SocketError errorCode); - - -private: - QTcpSocket socket; - QByteArray body; - QUrl requested_url; - bool was_cancelled; - QString mime; - bool is_processing_binary; -}; - -#endif // GOPHERCLIENT_HPP diff --git a/src/gophermaprenderer.cpp b/src/gophermaprenderer.cpp deleted file mode 100644 index 6779a9a..0000000 --- a/src/gophermaprenderer.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#include "gophermaprenderer.hpp" -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "kristall.hpp" - - -std::unique_ptr GophermapRenderer::render(const QByteArray &input, const QUrl &root_url, const DocumentStyle &themed_style) -{ - QTextCharFormat standard; - standard.setFont(themed_style.preformatted_font); - standard.setForeground(themed_style.preformatted_color); - - QTextCharFormat standard_link; - standard_link.setFont(themed_style.preformatted_font); - standard_link.setForeground(QBrush(themed_style.internal_link_color)); - - QTextCharFormat external_link; - external_link.setFont(themed_style.standard_font); - external_link.setForeground(QBrush(themed_style.external_link_color)); - - bool emit_text_only = (global_options.gophermap_display == GenericSettings::PlainText); - - std::unique_ptr result = std::make_unique(); - result->setDocumentMargin(themed_style.margin); - - if(not emit_text_only) - { - result->addResource(QTextDocument::ImageResource, QUrl("gopher/binary"), QVariant::fromValue(QImage(":/icons/gopher/binary.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/directory"), QVariant::fromValue(QImage(":/icons/gopher/directory.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/dns"), QVariant::fromValue(QImage(":/icons/gopher/dns.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/error"), QVariant::fromValue(QImage(":/icons/gopher/error.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/gif"), QVariant::fromValue(QImage(":/icons/gopher/gif.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/html"), QVariant::fromValue(QImage(":/icons/gopher/html.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/image"), QVariant::fromValue(QImage(":/icons/gopher/image.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/mirror"), QVariant::fromValue(QImage(":/icons/gopher/mirror.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/search"), QVariant::fromValue(QImage(":/icons/gopher/search.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/sound"), QVariant::fromValue(QImage(":/icons/gopher/sound.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/telnet"), QVariant::fromValue(QImage(":/icons/gopher/telnet.svg"))); - result->addResource(QTextDocument::ImageResource, QUrl("gopher/text"), QVariant::fromValue(QImage(":/icons/gopher/text.svg"))); - } - - QTextCursor cursor{result.get()}; - - QTextBlockFormat non_list_format = cursor.blockFormat(); - - char last_type = '1'; - - QList lines = input.split('\n'); - for (auto const &line : lines) - { - if (line.length() < 2) // skip lines without - continue; - - if (line[line.size() - 1] != '\r') - continue; - - auto items = line.mid(1, line.length() - 2).split('\t'); - if (items.size() < 2) // invalid - continue; - - QString icon; - QString scheme = "gopher"; - - auto type = line.at(0); - switch (type) - { - case '0': // Text File - icon = "text"; - break; - case '1': // Gopher submenu or link to another gopher server - icon = "directory"; - break; - case '2': // CCSO Nameserver - icon = "dns"; - break; - case '3': // Error code returned by a Gopher server to indicate failure - icon = "error"; - break; - case '4': // BinHex-encoded file (primarily for Macintosh computers) - icon = "binary"; - break; - case '5': // DOS file - icon = "binary"; - break; - case '6': // uuencoded file - icon = "binary"; - break; - case '7': // Gopher full-text search - icon = "search"; - break; - case '8': // Telnet - icon = "telnet"; - scheme = "telnet"; - break; - case '9': // Binary file - icon = "binary"; - break; - case '+': // Mirror or alternate server (for load balancing or in case of primary server downtime) - icon = "mirror"; - break; - case 'g': // GIF file - icon = "gif"; - break; - case 'I': // Image file - icon = "image"; - break; - case 'T': // Telnet 3270 - icon = "telnet"; - scheme = "telnet"; - break; - //Non-Canonical Types - case 'h': // HTML file - icon = "html"; - break; - case 'i': // Informational message - icon = "informational"; - break; - case 's': // Sound file - icon = "sound"; - break; - default: // unknown - continue; - } - if(type == '+') { - type = last_type; - } else { - last_type = type; - } - - QString title = items.at(0); - - if (type == 'i') - { - cursor.insertText(title + "\n", standard); - } - else - { - QString dst_url; - switch (items.size()) - { - case 0: - assert(false); - case 1: - assert(false); - case 2: - dst_url = root_url.resolved(QUrl(items.at(1))).toString(); - break; - case 3: - dst_url = scheme + "://" + items.at(2) + "/" + QString(type) + items.at(1); - break; - default: - dst_url = scheme + "://" + items.at(2) + ":" + items.at(3) + "/" + QString(type) + items.at(1); - break; - } - - if (not QUrl(dst_url).isValid()) - { - // invlaid URL generated - qDebug() << line << dst_url; - } - - if(emit_text_only) - { - cursor.insertText("[" + icon + "] ", standard); - } - else - { - QTextImageFormat icon_fmt; - icon_fmt.setName(QString("gopher/%1").arg(icon)); - icon_fmt.setVerticalAlignment(QTextImageFormat::AlignTop); - - cursor.insertImage(icon_fmt); - cursor.insertText(" "); - } - - QTextCharFormat fmt = standard_link; - fmt.setAnchor(true); - fmt.setAnchorHref(dst_url); - cursor.insertText(title + "\n", fmt); - } - } - - return result; -} diff --git a/src/gophermaprenderer.hpp b/src/gophermaprenderer.hpp deleted file mode 100644 index 3835cec..0000000 --- a/src/gophermaprenderer.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef GOPHERMAPRENDERER_HPP -#define GOPHERMAPRENDERER_HPP - -#include "documentstyle.hpp" - -#include -#include - -struct GophermapRenderer -{ - GophermapRenderer() = delete; - - - //! Renders the given byte sequence into a GeminiDocument. - //! @param input The utf8 encoded input string - //! @param root_url The url that is used to resolve relative links - //! @param style The style which is used to render the document - //! @param outline The extracted outline from the document - static std::unique_ptr render( - QByteArray const & input, - QUrl const & root_url, - DocumentStyle const & style - ); -}; - -#endif // GOPHERMAPRENDERER_HPP diff --git a/src/kristall.pro b/src/kristall.pro index 73b3778..c5a29f5 100644 --- a/src/kristall.pro +++ b/src/kristall.pro @@ -52,90 +52,90 @@ DEPENDPATH += $$PWD/../lib/luis-l-gist/ SOURCES += \ ../lib/luis-l-gist/interactiveview.cpp \ - abouthandler.cpp \ browsertab.cpp \ certificatehelper.cpp \ - certificateiodialog.cpp \ - certificatemanagementdialog.cpp \ - certificateselectiondialog.cpp \ cryptoidentity.cpp \ + dialogs/certificateiodialog.cpp \ + dialogs/certificatemanagementdialog.cpp \ + dialogs/certificateselectiondialog.cpp \ + dialogs/newidentitiydialog.cpp \ + dialogs/settingsdialog.cpp \ documentoutlinemodel.cpp \ documentstyle.cpp \ - elidelabel.cpp \ favouritecollection.cpp \ - filehandler.cpp \ - fingerclient.cpp \ - geminiclient.cpp \ - geminirenderer.cpp \ - gopherclient.cpp \ - gophermaprenderer.cpp \ identitycollection.cpp \ ioutil.cpp \ main.cpp \ mainwindow.cpp \ - mediaplayer.cpp \ + widgets/mediaplayer.cpp \ mimeparser.cpp \ - newidentitiydialog.cpp \ - plaintextrenderer.cpp \ protocolhandler.cpp \ + protocols/abouthandler.cpp \ + protocols/filehandler.cpp \ + protocols/fingerclient.cpp \ + protocols/geminiclient.cpp \ + protocols/gopherclient.cpp \ + protocols/webclient.cpp \ protocolsetup.cpp \ - searchbar.cpp \ - settingsdialog.cpp \ + renderers/geminirenderer.cpp \ + renderers/gophermaprenderer.cpp \ + renderers/plaintextrenderer.cpp \ ssltrust.cpp \ - ssltrusteditor.cpp \ tabbrowsinghistory.cpp \ trustedhost.cpp \ trustedhostcollection.cpp \ - webclient.cpp + widgets/elidelabel.cpp \ + widgets/searchbar.cpp \ + widgets/ssltrusteditor.cpp HEADERS += \ ../lib/luis-l-gist/interactiveview.hpp \ - abouthandler.hpp \ browsertab.hpp \ certificatehelper.hpp \ - certificateiodialog.hpp \ - certificatemanagementdialog.hpp \ - certificateselectiondialog.hpp \ cryptoidentity.hpp \ + dialogs/certificateiodialog.hpp \ + dialogs/certificatemanagementdialog.hpp \ + dialogs/certificateselectiondialog.hpp \ + dialogs/newidentitiydialog.hpp \ + dialogs/settingsdialog.hpp \ documentoutlinemodel.hpp \ documentstyle.hpp \ - elidelabel.hpp \ favouritecollection.hpp \ - filehandler.hpp \ - fingerclient.hpp \ - geminiclient.hpp \ - geminirenderer.hpp \ - gopherclient.hpp \ - gophermaprenderer.hpp \ identitycollection.hpp \ ioutil.hpp \ kristall.hpp \ mainwindow.hpp \ - mediaplayer.hpp \ + widgets/mediaplayer.hpp \ mimeparser.hpp \ - newidentitiydialog.hpp \ - plaintextrenderer.hpp \ protocolhandler.hpp \ + protocols/abouthandler.hpp \ + protocols/filehandler.hpp \ + protocols/fingerclient.hpp \ + protocols/geminiclient.hpp \ + protocols/gopherclient.hpp \ + protocols/webclient.hpp \ protocolsetup.hpp \ - searchbar.hpp \ - settingsdialog.hpp \ + renderers/geminirenderer.hpp \ + renderers/gophermaprenderer.hpp \ + renderers/plaintextrenderer.hpp \ ssltrust.hpp \ - ssltrusteditor.hpp \ tabbrowsinghistory.hpp \ trustedhost.hpp \ trustedhostcollection.hpp \ - webclient.hpp + widgets/elidelabel.hpp \ + widgets/searchbar.hpp \ + widgets/ssltrusteditor.hpp FORMS += \ - browsertab.ui \ - certificateiodialog.ui \ - certificatemanagementdialog.ui \ - certificateselectiondialog.ui \ - mainwindow.ui \ - mediaplayer.ui \ - newidentitiydialog.ui \ - settingsdialog.ui \ - ssltrusteditor.ui + browsertab.ui \ + dialogs/certificateiodialog.ui \ + dialogs/certificatemanagementdialog.ui \ + dialogs/certificateselectiondialog.ui \ + dialogs/newidentitiydialog.ui \ + dialogs/settingsdialog.ui \ + mainwindow.ui \ + widgets/mediaplayer.ui \ + widgets/ssltrusteditor.ui TRANSLATIONS += \ kristall_en_US.ts diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9711cfa..625fc3a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,7 +1,7 @@ #include "mainwindow.hpp" #include "ui_mainwindow.h" #include "browsertab.hpp" -#include "settingsdialog.hpp" +#include "dialogs/settingsdialog.hpp" #include #include #include @@ -13,7 +13,7 @@ #include "ioutil.hpp" #include "kristall.hpp" -#include "certificatemanagementdialog.hpp" +#include "dialogs/certificatemanagementdialog.hpp" MainWindow::MainWindow(QApplication * app, QWidget *parent) : QMainWindow(parent), diff --git a/src/mainwindow.hpp b/src/mainwindow.hpp index dfa7ebc..85b9d59 100644 --- a/src/mainwindow.hpp +++ b/src/mainwindow.hpp @@ -7,10 +7,10 @@ #include "favouritecollection.hpp" -#include "geminirenderer.hpp" +#include "renderers/geminirenderer.hpp" #include "protocolsetup.hpp" -#include "elidelabel.hpp" +#include "widgets/elidelabel.hpp" #include "browsertab.hpp" diff --git a/src/mediaplayer.cpp b/src/mediaplayer.cpp deleted file mode 100644 index e510d21..0000000 --- a/src/mediaplayer.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "mediaplayer.hpp" -#include "ui_mediaplayer.h" - -#include -#include -#include -#include - -MediaPlayer::MediaPlayer(QWidget *parent) : - QWidget(parent), - ui(new Ui::MediaPlayer), - media_stream(), - player() -{ - ui->setupUi(this); - - this->player.setVideoOutput(this->ui->video_out); - - connect(&this->player, &QMediaPlayer::durationChanged, this->ui->media_progress, &QSlider::setMaximum); - connect(&this->player, &QMediaPlayer::positionChanged, this->ui->media_progress, &QSlider::setValue); - - connect(&this->player, &QMediaPlayer::audioAvailableChanged, this->ui->mute_button, &QToolButton::setEnabled); - // connect(&this->player, &QMediaPlayer::videoAvailableChanged, this->ui->video_out, &QVideoWidget::setVisible); - - connect(&this->player, &QMediaPlayer::stateChanged, this, &MediaPlayer::on_media_playbackChanged); - connect(&this->player, &QMediaPlayer::mediaStatusChanged, [](QMediaPlayer::MediaStatus status) { - qDebug() << "media status changed" << status; - }); - - connect(&this->player, &QMediaPlayer::positionChanged, this, &MediaPlayer::on_media_positionChanged); - - connect(this->ui->media_progress, &QSlider::valueChanged, &this->player, &QMediaPlayer::setPosition); -} - -MediaPlayer::~MediaPlayer() -{ - delete ui; -} - -void MediaPlayer::setMedia(QByteArray const & data, QUrl const & ref_url, QString const & mime) -{ - this->player.stop(); - - this->mime = mime; - - this->media_stream.close(); - this->media_stream.setData(data); // = QBuffer { &this->backing_buffer }; - this->media_stream.open(QIODevice::ReadOnly); - - QMediaContent content { ref_url }; - - this->player.setMedia(content, &this->media_stream); -} - -void MediaPlayer::stopPlaying() -{ - this->player.stop(); -} - -void MediaPlayer::on_playpause_button_clicked() -{ - if(this->player.state() != QMediaPlayer::PlayingState) { - this->player.play(); - } else { - this->player.pause(); - } -} - -void MediaPlayer::on_mute_button_clicked(bool checked) -{ - this->player.setMuted(checked); -} - -void MediaPlayer::on_media_positionChanged(qint64 pos) -{ - auto time = QTime::fromMSecsSinceStartOfDay(pos); - - this->ui->media_position->setText(time.toString()); -} - -void MediaPlayer::on_media_playbackChanged(QMediaPlayer::State status) -{ - this->ui->playpause_button->setIcon( - (status == QMediaPlayer::PlayingState) ? QIcon::fromTheme("pause") : QIcon::fromTheme("play") - ); -} diff --git a/src/mediaplayer.hpp b/src/mediaplayer.hpp deleted file mode 100644 index c39a800..0000000 --- a/src/mediaplayer.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef MEDIAPLAYER_HPP -#define MEDIAPLAYER_HPP - -#include -#include -#include -#include - -namespace Ui { -class MediaPlayer; -} - -class MediaPlayer : public QWidget -{ - Q_OBJECT - -public: - explicit MediaPlayer(QWidget *parent = nullptr); - ~MediaPlayer(); - - void setMedia(QByteArray const & data, QUrl const & ref_url, QString const & mime); - - void stopPlaying(); - -private slots: - void on_playpause_button_clicked(); - - void on_mute_button_clicked(bool checked); - -private: // slots - void on_media_positionChanged(qint64 pos); - - void on_media_playbackChanged(QMediaPlayer::State); - -private: - Ui::MediaPlayer *ui; - QBuffer media_stream; - QString mime; - QMediaPlayer player; -}; - -#endif // MEDIAPLAYER_HPP diff --git a/src/mediaplayer.ui b/src/mediaplayer.ui deleted file mode 100644 index 39fb570..0000000 --- a/src/mediaplayer.ui +++ /dev/null @@ -1,85 +0,0 @@ - - - MediaPlayer - - - - 0 - 0 - 640 - 480 - - - - Form - - - - - - - - - - - Click to play/pause - - - ... - - - - - - - - - - Qt::Horizontal - - - - - - - 00:00:00 - - - Qt::PlainText - - - - - - - Mute/unmute audio - - - ... - - - - - - true - - - false - - - - - - - - - - QVideoWidget - QWidget -
QVideoWidget
- 1 -
-
- - -
diff --git a/src/newidentitiydialog.cpp b/src/newidentitiydialog.cpp deleted file mode 100644 index af2a97e..0000000 --- a/src/newidentitiydialog.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "newidentitiydialog.hpp" -#include "ui_newidentitiydialog.h" - -#include "certificatehelper.hpp" -#include "kristall.hpp" - -#include -#include - -NewIdentitiyDialog::NewIdentitiyDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::NewIdentitiyDialog) -{ - ui->setupUi(this); - - ui->display_name->setText("Unnamed"); - ui->common_name->setText("Unnamed"); - ui->expiration_date->setDate(QDate::currentDate().addYears(1)); - ui->expiration_date->setTime(QTime(12, 00)); - - ui->group->clear(); - for(auto group_name : global_identities.groups()) - { - ui->group->addItem(group_name); - } -} - -NewIdentitiyDialog::~NewIdentitiyDialog() -{ - delete ui; -} - -CryptoIdentity NewIdentitiyDialog::createIdentity() const -{ - auto id = CertificateHelper::createNewIdentity( - this->ui->common_name->text(), - this->ui->expiration_date->dateTime() - ); - id.display_name = this->ui->display_name->text(); - return id; -} - -QString NewIdentitiyDialog::groupName() const -{ - return this->ui->group->currentText(); -} - -void NewIdentitiyDialog::setGroupName(const QString &name) -{ - this->ui->group->setCurrentText(name); -} - -void NewIdentitiyDialog::updateUI() -{ - bool is_ok = true; - - is_ok &= (not this->ui->group->currentText().isEmpty()); - is_ok &= (not this->ui->common_name->text().isEmpty()); - is_ok &= (not this->ui->display_name->text().isEmpty()); - is_ok &= (this->ui->expiration_date->dateTime() > QDateTime::currentDateTime()); - - this->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(is_ok); -} - -void NewIdentitiyDialog::on_group_editTextChanged(const QString &arg1) -{ - Q_UNUSED(arg1); - this->updateUI(); -} - -void NewIdentitiyDialog::on_display_name_textChanged(const QString &arg1) -{ - Q_UNUSED(arg1); - this->updateUI(); -} - -void NewIdentitiyDialog::on_common_name_textChanged(const QString &arg1) -{ - Q_UNUSED(arg1); - this->updateUI(); -} diff --git a/src/newidentitiydialog.hpp b/src/newidentitiydialog.hpp deleted file mode 100644 index 83d740b..0000000 --- a/src/newidentitiydialog.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef NEWIDENTITIYDIALOG_HPP -#define NEWIDENTITIYDIALOG_HPP - -#include - -#include "cryptoidentity.hpp" - -namespace Ui { -class NewIdentitiyDialog; -} - -class NewIdentitiyDialog : public QDialog -{ - Q_OBJECT - -public: - explicit NewIdentitiyDialog(QWidget *parent = nullptr); - ~NewIdentitiyDialog(); - - //! Creates a new identity from the currently set - //! user settings. - CryptoIdentity createIdentity() const; - - QString groupName() const; - void setGroupName(QString const & name); - -private slots: - void on_group_editTextChanged(const QString &arg1); - - void on_display_name_textChanged(const QString &arg1); - - void on_common_name_textChanged(const QString &arg1); - -private: - void updateUI(); - -private: - Ui::NewIdentitiyDialog *ui; -}; - -#endif // NEWIDENTITIYDIALOG_HPP diff --git a/src/newidentitiydialog.ui b/src/newidentitiydialog.ui deleted file mode 100644 index 556ac93..0000000 --- a/src/newidentitiydialog.ui +++ /dev/null @@ -1,118 +0,0 @@ - - - NewIdentitiyDialog - - - - 0 - 0 - 328 - 191 - - - - Create new certificate - - - - :/icons/certificate.svg:/icons/certificate.svg - - - - - - - - Display Name - - - - - - - Expiration Date - - - - - - - - - - - - - - - - Common Name - - - - - - - Group - - - - - - - true - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - - buttonBox - accepted() - NewIdentitiyDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - NewIdentitiyDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/plaintextrenderer.cpp b/src/plaintextrenderer.cpp deleted file mode 100644 index 37801a4..0000000 --- a/src/plaintextrenderer.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "plaintextrenderer.hpp" - -#include -#include - -std::unique_ptr PlainTextRenderer::render(const QByteArray &input, const DocumentStyle &style) -{ - QTextCharFormat standard; - standard.setFont(style.preformatted_font); - standard.setForeground(style.preformatted_color); - - std::unique_ptr result = std::make_unique(); - result->setDocumentMargin(style.margin); - - QTextCursor cursor { result.get() }; - cursor.insertText(QString::fromUtf8(input), standard); - - return result; -} diff --git a/src/plaintextrenderer.hpp b/src/plaintextrenderer.hpp deleted file mode 100644 index cfa2ccb..0000000 --- a/src/plaintextrenderer.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef PLAINTEXTRENDERER_HPP -#define PLAINTEXTRENDERER_HPP - -#include "documentstyle.hpp" - -#include -#include - -struct PlainTextRenderer -{ - PlainTextRenderer() = delete; - - - //! Renders the given byte sequence into a GeminiDocument. - //! @param input The utf8 encoded input string - //! @param root_url The url that is used to resolve relative links - //! @param style The style which is used to render the document - //! @param outline The extracted outline from the document - static std::unique_ptr render( - QByteArray const & input, - DocumentStyle const & style - ); -}; - -#endif // PLAINTEXTRENDERER_HPP diff --git a/src/protocols/abouthandler.cpp b/src/protocols/abouthandler.cpp new file mode 100644 index 0000000..01eefff --- /dev/null +++ b/src/protocols/abouthandler.cpp @@ -0,0 +1,61 @@ +#include "abouthandler.hpp" +#include "kristall.hpp" + +#include +#include + +AboutHandler::AboutHandler() +{ + +} + +bool AboutHandler::supportsScheme(const QString &scheme) const +{ + return (scheme == "about"); +} + +bool AboutHandler::startRequest(const QUrl &url, ProtocolHandler::RequestOptions options) +{ + Q_UNUSED(options) + if (url.path() == "blank") + { + emit this->requestComplete("", "text/gemini"); + } + else if (url.path() == "favourites") + { + QByteArray document; + + document.append("# Favourites\n"); + document.append("\n"); + + for (auto const &fav : global_favourites.getAll()) + { + document.append("=> " + fav.toString().toUtf8() + "\n"); + } + + this->requestComplete(document, "text/gemini"); + } + else + { + QFile file(QString(":/about/%1.gemini").arg(url.path())); + if (file.open(QFile::ReadOnly)) + { + emit this->requestComplete(file.readAll(), "text/gemini"); + } + else + { + emit this->networkError(ResourceNotFound, "The requested resource does not exist."); + } + } + return true; +} + +bool AboutHandler::isInProgress() const +{ + return false; +} + +bool AboutHandler::cancelRequest() +{ + return true; +} diff --git a/src/protocols/abouthandler.hpp b/src/protocols/abouthandler.hpp new file mode 100644 index 0000000..86b9180 --- /dev/null +++ b/src/protocols/abouthandler.hpp @@ -0,0 +1,23 @@ +#ifndef ABOUTHANDLER_HPP +#define ABOUTHANDLER_HPP + +#include + +#include "protocolhandler.hpp" + +class AboutHandler : public ProtocolHandler +{ + Q_OBJECT +public: + AboutHandler(); + + bool supportsScheme(QString const & scheme) const override; + + bool startRequest(QUrl const & url, ProtocolHandler::RequestOptions options) override; + + bool isInProgress() const override; + + bool cancelRequest() override; +}; + +#endif // ABOUTHANDLER_HPP diff --git a/src/protocols/filehandler.cpp b/src/protocols/filehandler.cpp new file mode 100644 index 0000000..d26cd57 --- /dev/null +++ b/src/protocols/filehandler.cpp @@ -0,0 +1,45 @@ +#include "filehandler.hpp" + +#include +#include +#include + +FileHandler::FileHandler() +{ + +} + +bool FileHandler::supportsScheme(const QString &scheme) const +{ + return (scheme == "file"); +} + +bool FileHandler::startRequest(const QUrl &url, RequestOptions options) +{ + Q_UNUSED(options) + + QFile file { url.path() }; + + if (file.open(QFile::ReadOnly)) + { + QMimeDatabase db; + auto mime = db.mimeTypeForUrl(url).name(); + auto data = file.readAll(); + emit this->requestComplete(data, mime); + } + else + { + emit this->networkError(ResourceNotFound, "The requested file does not exist!"); + } + return true; +} + +bool FileHandler::isInProgress() const +{ + return false; +} + +bool FileHandler::cancelRequest() +{ + return true; +} diff --git a/src/protocols/filehandler.hpp b/src/protocols/filehandler.hpp new file mode 100644 index 0000000..55ac248 --- /dev/null +++ b/src/protocols/filehandler.hpp @@ -0,0 +1,23 @@ +#ifndef FILEHANDLER_HPP +#define FILEHANDLER_HPP + +#include + +#include "protocolhandler.hpp" + +class FileHandler : public ProtocolHandler +{ + Q_OBJECT +public: + FileHandler(); + + bool supportsScheme(QString const & scheme) const override; + + bool startRequest(QUrl const & url, RequestOptions options) override; + + bool isInProgress() const override; + + bool cancelRequest() override; +}; + +#endif // FILEHANDLER_HPP diff --git a/src/protocols/fingerclient.cpp b/src/protocols/fingerclient.cpp new file mode 100644 index 0000000..1c9a6a3 --- /dev/null +++ b/src/protocols/fingerclient.cpp @@ -0,0 +1,83 @@ +#include "fingerclient.hpp" +#include "ioutil.hpp" + +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::of(&QTcpSocket::error), this, &FingerClient::on_socketError); +#endif +} + +FingerClient::~FingerClient() +{ + +} + +bool FingerClient::supportsScheme(const QString &scheme) const +{ + return (scheme == "finger"); +} + +bool FingerClient::startRequest(const QUrl &url, RequestOptions options) +{ + Q_UNUSED(options) + + if(isInProgress()) + return false; + + if(url.scheme() != "finger") + return false; + + this->requested_user = url.userName(); + this->was_cancelled = false; + socket.connectToHost(url.host(), url.port(79)); + + return true; +} + +bool FingerClient::isInProgress() const +{ + return socket.isOpen(); +} + +bool FingerClient::cancelRequest() +{ + was_cancelled = true; + socket.close(); + body.clear(); + return true; +} + +void FingerClient::on_connected() +{ + auto blob = (requested_user + "\r\n").toUtf8(); + + IoUtil::writeAll(socket, blob); +} + +void FingerClient::on_readRead() +{ + body.append(socket.readAll()); + emit this->requestProgress(body.size()); +} + +void FingerClient::on_finished() +{ + if(not was_cancelled) + { + emit this->requestComplete(this->body, "text/finger"); + was_cancelled = true; + } + body.clear(); +} + +void FingerClient::on_socketError(QAbstractSocket::SocketError error_code) +{ + this->emitNetworkError(error_code, socket.errorString()); +} diff --git a/src/protocols/fingerclient.hpp b/src/protocols/fingerclient.hpp new file mode 100644 index 0000000..63d04fd --- /dev/null +++ b/src/protocols/fingerclient.hpp @@ -0,0 +1,39 @@ +#ifndef FINGERCLIENT_HPP +#define FINGERCLIENT_HPP + +#include +#include +#include + +#include "protocolhandler.hpp" + +class FingerClient : public ProtocolHandler +{ + Q_OBJECT +public: + explicit FingerClient(); + + ~FingerClient() 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_socketError(QTcpSocket::SocketError error_code); + +private: + QTcpSocket socket; + QByteArray body; + bool was_cancelled; + QString requested_user; +}; + +#endif // FINGERCLIENT_HPP diff --git a/src/protocols/geminiclient.cpp b/src/protocols/geminiclient.cpp new file mode 100644 index 0000000..6986250 --- /dev/null +++ b/src/protocols/geminiclient.cpp @@ -0,0 +1,375 @@ +#include "geminiclient.hpp" +#include +#include +#include +#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 &>::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::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 { }); + 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 = ""; + + 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 " + 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 "); + 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 const & errors) +{ + emit this->hostCertificateLoaded(this->socket.peerCertificate()); + + if(options & IgnoreTlsErrors) { + socket.ignoreSslErrors(errors); + return; + } + + QList remaining_errors = errors; + QList 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()); + } + } +} diff --git a/src/protocols/geminiclient.hpp b/src/protocols/geminiclient.hpp new file mode 100644 index 0000000..85c4f76 --- /dev/null +++ b/src/protocols/geminiclient.hpp @@ -0,0 +1,55 @@ +#ifndef GEMINICLIENT_HPP +#define GEMINICLIENT_HPP + +#include +#include +#include +#include + +#include "protocolhandler.hpp" + +class GeminiClient : public ProtocolHandler +{ +private: + Q_OBJECT +public: + explicit GeminiClient(); + + ~GeminiClient() override; + + bool supportsScheme(QString const & scheme) const override; + + bool startRequest(QUrl const & url, RequestOptions options) override; + + bool isInProgress() const override; + + bool cancelRequest() override; + + bool enableClientCertificate(CryptoIdentity const & ident) override; + void disableClientCertificate() override; + +private slots: + void socketEncrypted(); + + void socketReadyRead(); + + void socketDisconnected(); + + void sslErrors(const QList &errors); + + void socketError(QAbstractSocket::SocketError socketError); + +private: + bool is_receiving_body; + bool suppress_socket_tls_error; + bool is_error_state; + + QUrl target_url; + QSslSocket socket; + QByteArray buffer; + QByteArray body; + QString mime_type; + RequestOptions options; +}; + +#endif // GEMINICLIENT_HPP diff --git a/src/protocols/gopherclient.cpp b/src/protocols/gopherclient.cpp new file mode 100644 index 0000000..63c35ca --- /dev/null +++ b/src/protocols/gopherclient.cpp @@ -0,0 +1,109 @@ +#include "gopherclient.hpp" +#include "ioutil.hpp" + +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::of(&QTcpSocket::error), this, &GopherClient::on_socketError); +#endif +} + +GopherClient::~GopherClient() +{ + +} + +bool GopherClient::supportsScheme(const QString &scheme) const +{ + return (scheme == "gopher"); +} + +bool GopherClient::startRequest(const QUrl &url, RequestOptions options) +{ + Q_UNUSED(options) + + if(isInProgress()) + return false; + + if(url.scheme() != "gopher") + return false; + + // Second char on the URL path denotes the Gopher type + // See https://tools.ietf.org/html/rfc4266 + QString type = url.path().mid(1, 1); + + mime = "application/octet-stream"; + if(type == "") mime = "text/gophermap"; + else if(type == "0") mime = "text/plain"; + else if(type == "1") mime = "text/gophermap"; + else if(type == "g") mime = "image/gif"; + else if(type == "I") mime = "image/unknown"; + else if(type == "h") mime = "text/html"; + else if(type == "s") mime = "audio/unknown"; + + is_processing_binary = (type == "5") or (type == "9") or (type == "I") or (type == "g"); + + this->requested_url = url; + this->was_cancelled = false; + socket.connectToHost(url.host(), url.port(70)); + + return true; +} + +bool GopherClient::isInProgress() const +{ + return socket.isOpen(); +} + +bool GopherClient::cancelRequest() +{ + was_cancelled = true; + socket.close(); + body.clear(); + return true; +} + +void GopherClient::on_connected() +{ + auto blob = (requested_url.path().mid(2) + "\r\n").toUtf8(); + + IoUtil::writeAll(socket, blob); +} + +void GopherClient::on_readRead() +{ + body.append(socket.readAll()); + + if(not is_processing_binary) { + // Strip the "lone dot" from gopher data + if(int index = body.indexOf("\r\n.\r\n"); index >= 0) { + body.resize(index + 2); + socket.close(); + } + } + + if(not was_cancelled) { + emit this->requestProgress(body.size()); + } +} + +void GopherClient::on_finished() +{ + if(not was_cancelled) + { + emit this->requestComplete(this->body, mime); + was_cancelled = true; + } + body.clear(); +} + +void GopherClient::on_socketError(QAbstractSocket::SocketError error_code) +{ + this->emitNetworkError(error_code, socket.errorString()); +} diff --git a/src/protocols/gopherclient.hpp b/src/protocols/gopherclient.hpp new file mode 100644 index 0000000..888ebd8 --- /dev/null +++ b/src/protocols/gopherclient.hpp @@ -0,0 +1,42 @@ +#ifndef GOPHERCLIENT_HPP +#define GOPHERCLIENT_HPP + +#include +#include +#include + +#include "protocolhandler.hpp" + +class GopherClient : public ProtocolHandler +{ + Q_OBJECT +public: + explicit GopherClient(QObject *parent = nullptr); + + ~GopherClient() 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_socketError(QAbstractSocket::SocketError errorCode); + + +private: + QTcpSocket socket; + QByteArray body; + QUrl requested_url; + bool was_cancelled; + QString mime; + bool is_processing_binary; +}; + +#endif // GOPHERCLIENT_HPP diff --git a/src/protocols/webclient.cpp b/src/protocols/webclient.cpp new file mode 100644 index 0000000..ed87694 --- /dev/null +++ b/src/protocols/webclient.cpp @@ -0,0 +1,229 @@ +#include "webclient.hpp" +#include "kristall.hpp" + +#include +#include + +WebClient::WebClient() : + ProtocolHandler(nullptr), + current_reply(nullptr) +{ + manager.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); +} + +WebClient::~WebClient() +{ + +} + +bool WebClient::supportsScheme(const QString &scheme) const +{ + return (scheme == "https") or (scheme == "http"); +} + +bool WebClient::startRequest(const QUrl &url, RequestOptions options) +{ + if(url.scheme() != "http" and url.scheme() != "https") + return false; + + if(this->current_reply != nullptr) + return true; + + this->options = options; + this->body.clear(); + + QNetworkRequest request(url); + + auto ssl_config = request.sslConfiguration(); + // ssl_config.setProtocol(QSsl::TlsV1_2); + if(global_https_trust.enable_ca) + ssl_config.setCaCertificates(QSslConfiguration::systemCaCertificates()); + else + ssl_config.setCaCertificates(QList { }); + + if(this->current_identity.isValid()) { + ssl_config.setLocalCertificate(this->current_identity.certificate); + ssl_config.setPrivateKey(this->current_identity.private_key); + } + + // request.setMaximumRedirectsAllowed(5); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, false); + request.setSslConfiguration(ssl_config); + + this->manager.clearAccessCache(); + this->manager.clearConnectionCache(); + this->current_reply = manager.get(request); + if(this->current_reply == nullptr) + return false; + + this->suppress_socket_tls_error = true; + + connect(this->current_reply, &QNetworkReply::readyRead, this, &WebClient::on_data); + connect(this->current_reply, &QNetworkReply::finished, this, &WebClient::on_finished); + connect(this->current_reply, &QNetworkReply::sslErrors, this, &WebClient::on_sslErrors); + connect(this->current_reply, &QNetworkReply::redirected, this, &WebClient::on_redirected); + + return true; +} + +bool WebClient::isInProgress() const +{ + return (this->current_reply != nullptr); +} + +bool WebClient::cancelRequest() +{ + if(this->current_reply != nullptr) + { + this->current_reply->abort(); + this->current_reply = nullptr; + } + this->body.clear(); + return true; +} + +bool WebClient::enableClientCertificate(const CryptoIdentity &ident) +{ + current_identity = ident; + return true; +} + +void WebClient::disableClientCertificate() +{ + current_identity = CryptoIdentity(); +} + +void WebClient::on_data() +{ + this->body.append(this->current_reply->readAll()); + emit this->requestProgress(this->body.size()); +} + +void WebClient::on_finished() +{ + emit this->hostCertificateLoaded(this->current_reply->sslConfiguration().peerCertificate()); + + auto * const reply = this->current_reply; + this->current_reply = nullptr; + + reply->deleteLater(); + + if(reply->error() != QNetworkReply::NoError) + { + NetworkError error = UnknownError; + switch(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:" << reply->error(); + break; + } + + qDebug() << "web network error" << reply->errorString(); + qDebug() << this->body; + + if(not this->suppress_socket_tls_error) { + emit this->networkError(error, reply->errorString()); + } + } + else + { + int statusCode =reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if(statusCode >= 200 and statusCode < 300) { + auto mime = reply->header(QNetworkRequest::ContentTypeHeader).toString(); + emit this->requestComplete(this->body, mime); + } + else if(statusCode >= 300 and statusCode < 400) { + auto url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + + emit this->redirected(url, (statusCode == 301) or (statusCode == 308)); + } + else { + emit networkError(UnknownError, QString("Unhandled HTTP status code %1").arg(statusCode)); + } + + this->body.clear(); + } +} + +void WebClient::on_sslErrors(const QList &errors) +{ + emit this->hostCertificateLoaded(this->current_reply->sslConfiguration().peerCertificate()); + + if(options & IgnoreTlsErrors) { + this->current_reply->ignoreSslErrors(errors); + return; + } + + QList remaining_errors = errors; + QList 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())) + { + auto cert = this->current_reply->sslConfiguration().peerCertificate(); + switch(global_https_trust.getTrust(this->current_reply->url(), cert)) + { + case SslTrust::Trusted: + ignore = true; + break; + case SslTrust::Untrusted: + this->suppress_socket_tls_error = true; + emit this->networkError(UntrustedHost, toFingerprintString(cert)); + return; + case SslTrust::Mistrusted: + this->suppress_socket_tls_error = true; + emit this->networkError(MistrustedHost, toFingerprintString(cert)); + return; + } + } + else if(err.error() == QSslError::UnableToVerifyFirstCertificate) + { + ignore = true; + } + + if(ignore) { + ignored_errors.append(err); + remaining_errors.removeAt(0); + } else { + i += 1; + } + } + + current_reply->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 WebClient::on_redirected(const QUrl &url) +{ + qDebug() << "redirected to" << url; +} diff --git a/src/protocols/webclient.hpp b/src/protocols/webclient.hpp new file mode 100644 index 0000000..58ae029 --- /dev/null +++ b/src/protocols/webclient.hpp @@ -0,0 +1,48 @@ +#ifndef WEBCLIENT_HPP +#define WEBCLIENT_HPP + +#include +#include +#include + +#include "protocolhandler.hpp" + +class WebClient: public ProtocolHandler +{ +private: + Q_OBJECT +public: + explicit WebClient(); + + ~WebClient() override; + + bool supportsScheme(QString const & scheme) const override; + + bool startRequest(QUrl const & url, RequestOptions options) override; + + bool isInProgress() const override; + + bool cancelRequest() override; + + bool enableClientCertificate(CryptoIdentity const & ident) override; + void disableClientCertificate() override; + +private slots: + void on_data(); + void on_finished(); + void on_sslErrors(const QList &errors); + void on_redirected(const QUrl &url); + +private: + QNetworkAccessManager manager; + QNetworkReply * current_reply; + + QByteArray body; + RequestOptions options; + + CryptoIdentity current_identity; + + bool suppress_socket_tls_error; +}; + +#endif // WEBCLIENT_HPP diff --git a/src/renderers/geminirenderer.cpp b/src/renderers/geminirenderer.cpp new file mode 100644 index 0000000..ec00869 --- /dev/null +++ b/src/renderers/geminirenderer.cpp @@ -0,0 +1,343 @@ +#include "geminirenderer.hpp" + +#include +#include +#include +#include +#include + +#include "kristall.hpp" + +static QByteArray trim_whitespace(QByteArray items) +{ + int start = 0; + while (start < items.size() and isspace(items.at(start))) + { + start += 1; + } + int end = items.size() - 1; + while (end > 0 and isspace(items.at(end))) + { + end -= 1; + } + return items.mid(start, end - start + 1); +} + +std::unique_ptr GeminiRenderer::render( + const QByteArray &input, + QUrl const &root_url, + DocumentStyle const & themed_style, + DocumentOutlineModel &outline) +{ + QTextCharFormat preformatted; + preformatted.setFont(themed_style.preformatted_font); + preformatted.setForeground(themed_style.preformatted_color); + + QTextCharFormat standard; + standard.setFont(themed_style.standard_font); + standard.setForeground(themed_style.standard_color); + + QTextCharFormat standard_link; + standard_link.setFont(themed_style.standard_font); + standard_link.setForeground(QBrush(themed_style.internal_link_color)); + + QTextCharFormat external_link; + external_link.setFont(themed_style.standard_font); + external_link.setForeground(QBrush(themed_style.external_link_color)); + + QTextCharFormat cross_protocol_link; + cross_protocol_link.setFont(themed_style.standard_font); + cross_protocol_link.setForeground(QBrush(themed_style.cross_scheme_link_color)); + + QTextCharFormat standard_h1; + standard_h1.setFont(themed_style.h1_font); + standard_h1.setForeground(QBrush(themed_style.h1_color)); + + QTextCharFormat standard_h2; + standard_h2.setFont(themed_style.h2_font); + standard_h2.setForeground(QBrush(themed_style.h2_color)); + + QTextCharFormat standard_h3; + standard_h3.setFont(themed_style.h3_font); + standard_h3.setForeground(QBrush(themed_style.h3_color)); + + std::unique_ptr result = std::make_unique(); + result->setDocumentMargin(themed_style.margin); + result->background_color = themed_style.background_color; + result->setIndentWidth(20); + + bool emit_fancy_text = global_options.enable_text_decoration; + + QTextCursor cursor{result.get()}; + + QTextBlockFormat standard_format = cursor.blockFormat(); + + QTextBlockFormat preformatted_format = standard_format; + preformatted_format.setNonBreakableLines(true); + + QTextBlockFormat block_quote_format = standard_format; + block_quote_format.setIndent(1); + block_quote_format.setBackground(themed_style.blockquote_color); + + + bool verbatim = false; + QTextList *current_list = nullptr; + bool blockquote = false; + + outline.beginBuild(); + + int anchor_id = 0; + + auto unique_anchor_name = [&]() -> QString { + return QString("auto-title-%1").arg(++anchor_id); + }; + + QList lines = input.split('\n'); + for (auto const &line : lines) + { + if (verbatim) + { + if (line.startsWith("```")) + { + cursor.setBlockFormat(standard_format); + verbatim = false; + } + else + { + cursor.setBlockFormat(preformatted_format); + cursor.setCharFormat(preformatted); + cursor.insertText(line + "\n"); + } + } + else + { + if (line.startsWith("* ")) + { + if (current_list == nullptr) + { + cursor.deletePreviousChar(); + current_list = cursor.insertList(QTextListFormat::ListDisc); + } + else + { + cursor.insertBlock(); + } + + QString item = trim_whitespace(line.mid(1)); + + cursor.insertText(item, standard); + continue; + } + else + { + if (current_list != nullptr) + { + cursor.insertBlock(); + cursor.setBlockFormat(standard_format); + } + current_list = nullptr; + } + + if(line.startsWith(">")) + { + if(not blockquote ) { + // cursor.insertBlock(); + } + blockquote = true; + + cursor.setBlockFormat(block_quote_format); + cursor.insertText(trim_whitespace(line.mid(1)) + "\n", standard); + + continue; + } + else + { + if(blockquote) { + cursor.setBlockFormat(standard_format); + } + blockquote = false; + } + + if (line.startsWith("###")) + { + auto heading = trim_whitespace(line.mid(3)); + + auto id = unique_anchor_name(); + auto fmt = standard_h3; + fmt.setAnchor(true); + fmt.setAnchorNames(QStringList { id }); + + cursor.insertText(heading + "\n", fmt); + outline.appendH3(heading, id); + } + else if (line.startsWith("##")) + { + auto heading = trim_whitespace(line.mid(2)); + + auto id = unique_anchor_name(); + auto fmt = standard_h2; + fmt.setAnchor(true); + fmt.setAnchorNames(QStringList { id }); + + cursor.insertText(heading + "\n", fmt); + outline.appendH2(heading, id); + } + else if (line.startsWith("#")) + { + auto heading = trim_whitespace(line.mid(1)); + + auto id = unique_anchor_name(); + auto fmt = standard_h1; + fmt.setAnchor(true); + fmt.setAnchorNames(QStringList { id }); + + cursor.insertText(heading + "\n", fmt); + outline.appendH1(heading, id); + } + else if (line.startsWith("=>")) + { + auto const part = line.mid(2).trimmed(); + + QByteArray link, title; + + int index = -1; + for (int i = 0; i < part.size(); i++) + { + if (isspace(part[i])) + { + index = i; + break; + } + } + + if (index > 0) + { + link = trim_whitespace(part.mid(0, index)); + title = trim_whitespace(part.mid(index + 1)); + } + else + { + link = trim_whitespace(part); + title = trim_whitespace(part); + } + + auto local_url = QUrl(link); + + auto absolute_url = root_url.resolved(QUrl(link)); + + // qDebug() << link << title; + + auto fmt = standard_link; + + QString prefix; + if (absolute_url.host() == root_url.host()) + { + prefix = themed_style.internal_link_prefix; + fmt = standard_link; + } + else + { + prefix = themed_style.external_link_prefix; + fmt = external_link; + } + + QString suffix = ""; + if (absolute_url.scheme() != root_url.scheme()) + { + if(absolute_url.scheme() != "kristall+ctrl") { + suffix = " [" + absolute_url.scheme().toUpper() + "]"; + fmt = cross_protocol_link; + } + } + + fmt.setAnchor(true); + fmt.setAnchorHref(absolute_url.toString()); + cursor.insertText(prefix + title + suffix + "\n", fmt); + } + else if (line.startsWith("```")) + { + verbatim = true; + } + else + { + if(emit_fancy_text) + { + // TODO: Fix UTF-8 encoding here… Don't emit single characters but always spans! + + bool rendering_bold = false; + bool rendering_underlined = false; + + QTextCharFormat fmt = standard; + + QByteArray buffer; + + auto flush = [&]() { + if(buffer.size() > 0) { + cursor.insertText(QString::fromUtf8(buffer), fmt); + buffer.resize(0); + } + }; + + for(int i = 0; i < line.length(); i += 1) + { + char c = line.at(i); + if(c == ' ') { + flush(); + fmt = standard; + buffer.append(' '); + rendering_bold = false; + rendering_underlined = false; + } + else if(c == '*') { + if(rendering_bold) { + buffer.append('*'); + } + flush(); + rendering_bold = not rendering_bold; + auto f = fmt.font(); + f.setBold(rendering_bold); + fmt.setFont(f); + if(rendering_bold) { + buffer.append('*'); + } + } + else if(c == '_') { + if(rendering_underlined) { + buffer.append(' '); + } + flush(); + rendering_underlined = not rendering_underlined; + auto f = fmt.font(); + fmt.setUnderlineStyle(rendering_underlined ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline); + if(rendering_underlined) { + buffer.append(' '); + } + } + else { + buffer.append(c); + } + } + + flush(); + + cursor.insertText("\n", standard); + } + else { + cursor.insertText(line + "\n", standard); + } + } + } + } + + outline.endBuild(); + return result; +} + +GeminiDocument::GeminiDocument(QObject *parent) : QTextDocument(parent), + background_color(0x00, 0x00, 0x00) +{ +} + +GeminiDocument::~GeminiDocument() +{ +} diff --git a/src/renderers/geminirenderer.hpp b/src/renderers/geminirenderer.hpp new file mode 100644 index 0000000..7173d50 --- /dev/null +++ b/src/renderers/geminirenderer.hpp @@ -0,0 +1,41 @@ +#ifndef GEMINIRENDERER_HPP +#define GEMINIRENDERER_HPP + +#include +#include +#include +#include + +#include "documentoutlinemodel.hpp" + +#include "documentstyle.hpp" + +class GeminiDocument : + public QTextDocument +{ + Q_OBJECT +public: + explicit GeminiDocument(QObject * parent = nullptr); + ~GeminiDocument() override; + + QColor background_color; +}; + +struct GeminiRenderer +{ + GeminiRenderer() = delete; + + //! Renders the given byte sequence into a GeminiDocument. + //! @param input The utf8 encoded input string + //! @param root_url The url that is used to resolve relative links + //! @param style The style which is used to render the document + //! @param outline The extracted outline from the document + static std::unique_ptr render( + QByteArray const & input, + QUrl const & root_url, + DocumentStyle const & style, + DocumentOutlineModel & outline + ); +}; + +#endif // GEMINIRENDERER_HPP diff --git a/src/renderers/gophermaprenderer.cpp b/src/renderers/gophermaprenderer.cpp new file mode 100644 index 0000000..6779a9a --- /dev/null +++ b/src/renderers/gophermaprenderer.cpp @@ -0,0 +1,192 @@ +#include "gophermaprenderer.hpp" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kristall.hpp" + + +std::unique_ptr GophermapRenderer::render(const QByteArray &input, const QUrl &root_url, const DocumentStyle &themed_style) +{ + QTextCharFormat standard; + standard.setFont(themed_style.preformatted_font); + standard.setForeground(themed_style.preformatted_color); + + QTextCharFormat standard_link; + standard_link.setFont(themed_style.preformatted_font); + standard_link.setForeground(QBrush(themed_style.internal_link_color)); + + QTextCharFormat external_link; + external_link.setFont(themed_style.standard_font); + external_link.setForeground(QBrush(themed_style.external_link_color)); + + bool emit_text_only = (global_options.gophermap_display == GenericSettings::PlainText); + + std::unique_ptr result = std::make_unique(); + result->setDocumentMargin(themed_style.margin); + + if(not emit_text_only) + { + result->addResource(QTextDocument::ImageResource, QUrl("gopher/binary"), QVariant::fromValue(QImage(":/icons/gopher/binary.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/directory"), QVariant::fromValue(QImage(":/icons/gopher/directory.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/dns"), QVariant::fromValue(QImage(":/icons/gopher/dns.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/error"), QVariant::fromValue(QImage(":/icons/gopher/error.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/gif"), QVariant::fromValue(QImage(":/icons/gopher/gif.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/html"), QVariant::fromValue(QImage(":/icons/gopher/html.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/image"), QVariant::fromValue(QImage(":/icons/gopher/image.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/mirror"), QVariant::fromValue(QImage(":/icons/gopher/mirror.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/search"), QVariant::fromValue(QImage(":/icons/gopher/search.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/sound"), QVariant::fromValue(QImage(":/icons/gopher/sound.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/telnet"), QVariant::fromValue(QImage(":/icons/gopher/telnet.svg"))); + result->addResource(QTextDocument::ImageResource, QUrl("gopher/text"), QVariant::fromValue(QImage(":/icons/gopher/text.svg"))); + } + + QTextCursor cursor{result.get()}; + + QTextBlockFormat non_list_format = cursor.blockFormat(); + + char last_type = '1'; + + QList lines = input.split('\n'); + for (auto const &line : lines) + { + if (line.length() < 2) // skip lines without + continue; + + if (line[line.size() - 1] != '\r') + continue; + + auto items = line.mid(1, line.length() - 2).split('\t'); + if (items.size() < 2) // invalid + continue; + + QString icon; + QString scheme = "gopher"; + + auto type = line.at(0); + switch (type) + { + case '0': // Text File + icon = "text"; + break; + case '1': // Gopher submenu or link to another gopher server + icon = "directory"; + break; + case '2': // CCSO Nameserver + icon = "dns"; + break; + case '3': // Error code returned by a Gopher server to indicate failure + icon = "error"; + break; + case '4': // BinHex-encoded file (primarily for Macintosh computers) + icon = "binary"; + break; + case '5': // DOS file + icon = "binary"; + break; + case '6': // uuencoded file + icon = "binary"; + break; + case '7': // Gopher full-text search + icon = "search"; + break; + case '8': // Telnet + icon = "telnet"; + scheme = "telnet"; + break; + case '9': // Binary file + icon = "binary"; + break; + case '+': // Mirror or alternate server (for load balancing or in case of primary server downtime) + icon = "mirror"; + break; + case 'g': // GIF file + icon = "gif"; + break; + case 'I': // Image file + icon = "image"; + break; + case 'T': // Telnet 3270 + icon = "telnet"; + scheme = "telnet"; + break; + //Non-Canonical Types + case 'h': // HTML file + icon = "html"; + break; + case 'i': // Informational message + icon = "informational"; + break; + case 's': // Sound file + icon = "sound"; + break; + default: // unknown + continue; + } + if(type == '+') { + type = last_type; + } else { + last_type = type; + } + + QString title = items.at(0); + + if (type == 'i') + { + cursor.insertText(title + "\n", standard); + } + else + { + QString dst_url; + switch (items.size()) + { + case 0: + assert(false); + case 1: + assert(false); + case 2: + dst_url = root_url.resolved(QUrl(items.at(1))).toString(); + break; + case 3: + dst_url = scheme + "://" + items.at(2) + "/" + QString(type) + items.at(1); + break; + default: + dst_url = scheme + "://" + items.at(2) + ":" + items.at(3) + "/" + QString(type) + items.at(1); + break; + } + + if (not QUrl(dst_url).isValid()) + { + // invlaid URL generated + qDebug() << line << dst_url; + } + + if(emit_text_only) + { + cursor.insertText("[" + icon + "] ", standard); + } + else + { + QTextImageFormat icon_fmt; + icon_fmt.setName(QString("gopher/%1").arg(icon)); + icon_fmt.setVerticalAlignment(QTextImageFormat::AlignTop); + + cursor.insertImage(icon_fmt); + cursor.insertText(" "); + } + + QTextCharFormat fmt = standard_link; + fmt.setAnchor(true); + fmt.setAnchorHref(dst_url); + cursor.insertText(title + "\n", fmt); + } + } + + return result; +} diff --git a/src/renderers/gophermaprenderer.hpp b/src/renderers/gophermaprenderer.hpp new file mode 100644 index 0000000..3835cec --- /dev/null +++ b/src/renderers/gophermaprenderer.hpp @@ -0,0 +1,26 @@ +#ifndef GOPHERMAPRENDERER_HPP +#define GOPHERMAPRENDERER_HPP + +#include "documentstyle.hpp" + +#include +#include + +struct GophermapRenderer +{ + GophermapRenderer() = delete; + + + //! Renders the given byte sequence into a GeminiDocument. + //! @param input The utf8 encoded input string + //! @param root_url The url that is used to resolve relative links + //! @param style The style which is used to render the document + //! @param outline The extracted outline from the document + static std::unique_ptr render( + QByteArray const & input, + QUrl const & root_url, + DocumentStyle const & style + ); +}; + +#endif // GOPHERMAPRENDERER_HPP diff --git a/src/renderers/plaintextrenderer.cpp b/src/renderers/plaintextrenderer.cpp new file mode 100644 index 0000000..37801a4 --- /dev/null +++ b/src/renderers/plaintextrenderer.cpp @@ -0,0 +1,19 @@ +#include "plaintextrenderer.hpp" + +#include +#include + +std::unique_ptr PlainTextRenderer::render(const QByteArray &input, const DocumentStyle &style) +{ + QTextCharFormat standard; + standard.setFont(style.preformatted_font); + standard.setForeground(style.preformatted_color); + + std::unique_ptr result = std::make_unique(); + result->setDocumentMargin(style.margin); + + QTextCursor cursor { result.get() }; + cursor.insertText(QString::fromUtf8(input), standard); + + return result; +} diff --git a/src/renderers/plaintextrenderer.hpp b/src/renderers/plaintextrenderer.hpp new file mode 100644 index 0000000..cfa2ccb --- /dev/null +++ b/src/renderers/plaintextrenderer.hpp @@ -0,0 +1,25 @@ +#ifndef PLAINTEXTRENDERER_HPP +#define PLAINTEXTRENDERER_HPP + +#include "documentstyle.hpp" + +#include +#include + +struct PlainTextRenderer +{ + PlainTextRenderer() = delete; + + + //! Renders the given byte sequence into a GeminiDocument. + //! @param input The utf8 encoded input string + //! @param root_url The url that is used to resolve relative links + //! @param style The style which is used to render the document + //! @param outline The extracted outline from the document + static std::unique_ptr render( + QByteArray const & input, + DocumentStyle const & style + ); +}; + +#endif // PLAINTEXTRENDERER_HPP diff --git a/src/searchbar.cpp b/src/searchbar.cpp deleted file mode 100644 index 4d8ea3a..0000000 --- a/src/searchbar.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "searchbar.hpp" - -#include - -SearchBar::SearchBar(QWidget *parent) : QLineEdit(parent) -{ - -} - -void SearchBar::keyPressEvent(QKeyEvent *event) -{ - if(event->key() == Qt::Key_Escape) { - emit this->escapePressed(); - } else { - QLineEdit::keyPressEvent(event); - } -} - -void SearchBar::keyReleaseEvent(QKeyEvent *event) -{ - if(event->key() == Qt::Key_Escape) { - // Eat the event - } else { - QLineEdit::keyReleaseEvent(event); - } -} diff --git a/src/searchbar.hpp b/src/searchbar.hpp deleted file mode 100644 index e03331f..0000000 --- a/src/searchbar.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef SEARCHBAR_HPP -#define SEARCHBAR_HPP - -#include - -class SearchBar : public QLineEdit -{ - Q_OBJECT -public: - explicit SearchBar(QWidget *parent = nullptr); - -signals: - void escapePressed(); -public: - void keyPressEvent(QKeyEvent *event) override; - void keyReleaseEvent(QKeyEvent *event) override; -}; - -#endif // SEARCHBAR_HPP diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp deleted file mode 100644 index ebd1664..0000000 --- a/src/settingsdialog.cpp +++ /dev/null @@ -1,613 +0,0 @@ -#include "settingsdialog.hpp" -#include "ui_settingsdialog.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "kristall.hpp" - -SettingsDialog::SettingsDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::SettingsDialog), - current_style() -{ - ui->setupUi(this); - - static_assert(DocumentStyle::Fixed == 0); - static_assert(DocumentStyle::AutoDarkTheme == 1); - static_assert(DocumentStyle::AutoLightTheme == 2); - - this->ui->auto_theme->clear(); - this->ui->auto_theme->addItem(tr("Disabled"), QVariant::fromValue(DocumentStyle::Fixed)); - this->ui->auto_theme->addItem(tr("Dark Theme"), QVariant::fromValue(DocumentStyle::AutoDarkTheme)); - this->ui->auto_theme->addItem(tr("Light Theme"), QVariant::fromValue(DocumentStyle::AutoLightTheme)); - - this->ui->ui_theme->clear(); - this->ui->ui_theme->addItem(tr("OS Default"), QVariant::fromValue(int(Theme::os_default))); - this->ui->ui_theme->addItem(tr("Light"), QVariant::fromValue(int(Theme::light))); - this->ui->ui_theme->addItem(tr("Dark"), QVariant::fromValue(int(Theme::dark))); - - setGeminiStyle(DocumentStyle { }); - - int items = global_settings.beginReadArray("Themes"); - - this->predefined_styles.clear(); - for(int i = 0; i < items; i++) - { - global_settings.setArrayIndex(i); - - QString name = global_settings.value("name").toString(); - - DocumentStyle style; - style.load(global_settings); - - this->predefined_styles.insert(name, style); - } - - global_settings.endArray(); - - this->ui->presets->clear(); - for(auto const & style_name : this->predefined_styles.keys()) - { - this->ui->presets->addItem(style_name); - } - - if(items > 0) { - on_presets_currentIndexChanged(0); - } else { - this->on_presets_currentIndexChanged(-1); - } - - this->ui->redirection_mode->clear(); - this->ui->redirection_mode->addItem("Ask for cross-scheme or cross-host redirection", int(GenericSettings::WarnOnHostChange | GenericSettings::WarnOnSchemeChange)); - this->ui->redirection_mode->addItem("Ask for cross-scheme redirection", int(GenericSettings::WarnOnSchemeChange)); - this->ui->redirection_mode->addItem("Ask for cross-host redirection", int(GenericSettings::WarnOnHostChange)); - this->ui->redirection_mode->addItem("Ask for all redirection", int(GenericSettings::WarnAlways)); - this->ui->redirection_mode->addItem("Silently redirect everything", int(GenericSettings::WarnNever)); -} - -SettingsDialog::~SettingsDialog() -{ - delete ui; -} - -static QString formatFont(QFont const & font) -{ - QString style; - if(font.italic() and font.bold()) - style = "bold, italic"; - else if(font.italic()) - style = "italic"; - else if(font.bold()) - style = "bold"; - else - style = "regular"; - - return QString("%1 (%2pt, %3)") - .arg(font.family()) - .arg(font.pointSizeF()) - .arg(style); -} - -void SettingsDialog::setGeminiStyle(DocumentStyle const &style) -{ - static const QString COLOR_STYLE("border: 1px solid black; padding: 4px; background-color : %1; color : %2;"); - - this->current_style = style; - - this->ui->auto_theme->setCurrentIndex(this->current_style.theme); - - this->ui->page_margin->setValue(this->current_style.margin); - - auto setFontAndColor = [this](QLabel * label, QFont font, QColor color) - { - label->setText(formatFont(font)); - label->setStyleSheet(COLOR_STYLE - .arg(this->current_style.background_color.name()) - .arg(color.name())); - }; - - ui->bg_preview->setStyleSheet(COLOR_STYLE - .arg(this->current_style.background_color.name()) - .arg("#FF00FF")); - - ui->quote_preview->setStyleSheet(COLOR_STYLE - .arg(this->current_style.blockquote_color.name()) - .arg("#FF00FF")); - - ui->link_local_preview->setStyleSheet(COLOR_STYLE - .arg(this->current_style.background_color.name()) - .arg(this->current_style.internal_link_color.name())); - - ui->link_foreign_preview->setStyleSheet(COLOR_STYLE - .arg(this->current_style.background_color.name()) - .arg(this->current_style.external_link_color.name())); - - ui->link_cross_preview->setStyleSheet(COLOR_STYLE - .arg(this->current_style.background_color.name()) - .arg(this->current_style.cross_scheme_link_color.name())); - - setFontAndColor(this->ui->std_preview, this->current_style.standard_font, this->current_style.standard_color); - setFontAndColor(this->ui->pre_preview, this->current_style.preformatted_font, this->current_style.preformatted_color); - setFontAndColor(this->ui->h1_preview, this->current_style.h1_font, this->current_style.h1_color); - setFontAndColor(this->ui->h2_preview, this->current_style.h2_font, this->current_style.h2_color); - setFontAndColor(this->ui->h3_preview, this->current_style.h3_font, this->current_style.h3_color); - - this->reloadStylePreview(); -} - -ProtocolSetup SettingsDialog::protocols() const -{ - ProtocolSetup protocols; -#define M(X) \ - protocols.X = this->ui->enable_##X->isChecked(); - PROTOCOLS(M) -#undef M - return protocols; -} - -void SettingsDialog::setProtocols(ProtocolSetup const & protocols) -{ -#define M(X) \ - this->ui->enable_##X->setChecked(protocols.X); - PROTOCOLS(M) - #undef M -} - -SslTrust SettingsDialog::geminiSslTrust() const -{ - return this->ui->gemini_trust_editor->trust(); -} - -void SettingsDialog::setGeminiSslTrust(const SslTrust &trust) -{ - return this->ui->gemini_trust_editor->setTrust(trust); -} - -SslTrust SettingsDialog::httpsSslTrust() const -{ - return this->ui->https_trust_editor->trust(); -} - -void SettingsDialog::setHttpsSslTrust(const SslTrust &trust) -{ - this->ui->https_trust_editor->setTrust(trust); -} - -void SettingsDialog::setOptions(const GenericSettings &options) -{ - this->current_options = options; - - this->ui->ui_theme->setCurrentIndex(0); - for(int i = 0; i < this->ui->ui_theme->count(); i++) { - if(this->ui->ui_theme->itemData(i).toInt() == int(options.theme)) { - this->ui->ui_theme->setCurrentIndex(i); - break; - } - } - - this->ui->start_page->setText(this->current_options.start_page); - - if(this->current_options.gophermap_display == GenericSettings::PlainText) { - this->ui->gophermap_text->setChecked(true); - } else { - this->ui->gophermap_icon->setChecked(true); - } - - if(this->current_options.text_display == GenericSettings::PlainText) { - this->ui->fancypants_off->setChecked(true); - } else { - this->ui->fancypants_on->setChecked(true); - } - - if(this->current_options.enable_text_decoration) { - this->ui->texthl_on->setChecked(true); - } else { - this->ui->texthl_off->setChecked(true); - } - - if(this->current_options.use_os_scheme_handler) { - this->ui->scheme_os_default->setChecked(true); - } else { - this->ui->scheme_error->setChecked(true); - } - - this->ui->max_redirects->setValue(this->current_options.max_redirections); - - this->ui->redirection_mode->setCurrentIndex(0); - for(int i = 0; i < this->ui->redirection_mode->count(); i++) - { - if(this->ui->redirection_mode->itemData(i).toInt() == this->current_options.redirection_policy) { - this->ui->redirection_mode->setCurrentIndex(i); - break; - } - } - - this->ui->network_timeout->setValue(this->current_options.network_timeout); -} - -GenericSettings SettingsDialog::options() const -{ - return this->current_options; -} - -void SettingsDialog::reloadStylePreview() -{ - QFile document_src { ":/about/style-preview.gemini" }; - bool ok = document_src.open(QFile::ReadOnly); - assert(ok and "failed to find style-preview.gemini!"); - - auto const document = document_src.readAll(); - - QString host = this->ui->preview_url->text(); - if(host.length() == 0) - host = "preview"; - - QUrl url { QUrl(QString("about://%1/foobar").arg(host)) }; - - DocumentOutlineModel outline; - auto doc = GeminiRenderer::render( - document, - url, - current_style.derive(url), - outline - ); - - ui->style_preview->setStyleSheet(QString("QTextBrowser { background-color: %1; }") - .arg(doc->background_color.name())); - ui->style_preview->setDocument(doc.get()); - preview_document = std::move(doc); -} - -void SettingsDialog::updateFont(QFont & input) -{ - QFontDialog dialog { this }; - - dialog.setCurrentFont(input); - - if(dialog.exec() == QDialog::Accepted) { - input = dialog.currentFont(); - setGeminiStyle(current_style); - } -} - -void SettingsDialog::on_std_change_font_clicked() -{ - updateFont(current_style.standard_font); -} - -void SettingsDialog::on_pre_change_font_clicked() -{ - updateFont(current_style.preformatted_font); -} - -void SettingsDialog::on_h1_change_font_clicked() -{ - updateFont(current_style.h1_font); -} - -void SettingsDialog::on_h2_change_font_clicked() -{ - updateFont(current_style.h2_font); -} - -void SettingsDialog::on_h3_change_font_clicked() -{ - updateFont(current_style.h3_font); -} - -void SettingsDialog::updateColor(QColor &input) -{ - QColorDialog dialog { this }; - - dialog.setCurrentColor(input); - - if(dialog.exec() == QDialog::Accepted) { - input = dialog.currentColor(); - setGeminiStyle(current_style); - } -} - -void SettingsDialog::on_std_change_color_clicked() -{ - updateColor(current_style.standard_color); -} - -void SettingsDialog::on_pre_change_color_clicked() -{ - updateColor(current_style.preformatted_color); -} - -void SettingsDialog::on_h1_change_color_clicked() -{ - updateColor(current_style.h1_color); -} - -void SettingsDialog::on_h2_change_color_clicked() -{ - updateColor(current_style.h2_color); -} - -void SettingsDialog::on_h3_change_color_clicked() -{ - updateColor(current_style.h3_color); -} - -void SettingsDialog::on_bg_change_color_clicked() -{ - updateColor(current_style.background_color); -} - -void SettingsDialog::on_link_local_change_color_clicked() -{ - updateColor(current_style.internal_link_color); -} - -void SettingsDialog::on_link_foreign_change_color_clicked() -{ - updateColor(current_style.external_link_color); -} - -void SettingsDialog::on_link_cross_change_color_clicked() -{ - updateColor(current_style.cross_scheme_link_color); -} -void SettingsDialog::on_quote_change_color_clicked() -{ - updateColor(current_style.blockquote_color); -} - -void SettingsDialog::on_link_local_prefix_textChanged(const QString &text) -{ - current_style.internal_link_prefix = text; - reloadStylePreview(); -} - -void SettingsDialog::on_link_foreign_prefix_textChanged(const QString &text) -{ - current_style.external_link_prefix = text; - reloadStylePreview(); -} - -void SettingsDialog::on_auto_theme_currentIndexChanged(int index) -{ - if(index >= 0) { - current_style.theme = DocumentStyle::Theme(index); - reloadStylePreview(); - } -} - -void SettingsDialog::on_preview_url_textChanged(const QString &) -{ - this->reloadStylePreview(); -} - -void SettingsDialog::on_page_margin_valueChanged(double value) -{ - this->current_style.margin = value; - this->reloadStylePreview(); -} - -void SettingsDialog::on_presets_currentIndexChanged(int index) -{ - this->ui->preset_load->setEnabled(index >= 0); - this->ui->preset_save->setEnabled(index >= 0); - this->ui->preset_export->setEnabled(index >= 0); -} - -void SettingsDialog::on_preset_new_clicked() -{ - QInputDialog dlg { this }; - dlg.setInputMode(QInputDialog::TextInput); - dlg.setOkButtonText("Save"); - dlg.setCancelButtonText("Cancel"); - dlg.setLabelText("Enter the name of your new preset:"); - - if(dlg.exec() != QInputDialog::Accepted) - return; - QString name = dlg.textValue(); - - bool override = false; - if(this->predefined_styles.contains(name)) - { - auto response = QMessageBox::question(this, "Kristall", QString("A style with the name '%1' already exists! Replace?").arg(name)); - if(response != QMessageBox::Yes) - return; - override = true; - } - - this->predefined_styles.insert(name, this->current_style); - - if(not override) - { - this->ui->presets->addItem(name); - } -} - -void SettingsDialog::on_preset_save_clicked() -{ - QString name = this->ui->presets->currentText(); - if(name.isEmpty()) - return; - - auto response = QMessageBox::question(this, "Kristall", QString("Do you want to override the style '%1'?").arg(name)); - if(response != QMessageBox::Yes) - return; - - this->predefined_styles.insert(name, this->current_style); -} - - -void SettingsDialog::on_preset_load_clicked() -{ - QString name = this->ui->presets->currentText(); - if(name.isEmpty()) - return; - - auto response = QMessageBox::question(this, "Kristall", QString("Do you want to load the style '%1'?\r\nThis will discard all currently set up values!").arg(name)); - if(response != QMessageBox::Yes) - return; - - this->setGeminiStyle(this->predefined_styles.value(name)); -} - - -void SettingsDialog::on_SettingsDialog_accepted() -{ - global_settings.beginWriteArray("Themes", this->predefined_styles.size()); - - int index = 0; - for(auto const & style_name : this->predefined_styles.keys()) - { - global_settings.setArrayIndex(index); - - global_settings.setValue("name", style_name); - this->predefined_styles.value(style_name).save(global_settings); - - index += 1; - } - global_settings.endArray(); -} - -void SettingsDialog::on_preset_import_clicked() -{ - QFileDialog dialog { this }; - dialog.setAcceptMode(QFileDialog::AcceptOpen); - dialog.selectNameFilter("Kristall Theme (*.kthm)"); - - if(dialog.exec() !=QFileDialog::Accepted) - return; - - QString fileName = dialog.selectedFiles().at(0); - - QSettings import_settings { fileName, QSettings::IniFormat }; - - QString name; - - name = import_settings.value("name").toString(); - - while(name.isEmpty()) - { - QInputDialog dlg { this }; - dlg.setInputMode(QInputDialog::TextInput); - dlg.setOkButtonText("Save"); - dlg.setCancelButtonText("Cancel"); - dlg.setLabelText("Imported preset has no name.\r\nPlease enter a name for the preset:"); - if(dlg.exec() != QDialog::Accepted) - return; - name = dlg.textValue(); - } - - bool override = false; - if(this->predefined_styles.contains(name)) - { - auto response = QMessageBox::question(this, "Kristall", QString("Do you want to override the style '%1'?").arg(name)); - if(response != QMessageBox::Yes) - return; - override = true; - } - - DocumentStyle style; - style.load(import_settings); - - this->predefined_styles.insert(name, style); - - if(not override) - { - this->ui->presets->addItem(name); - } -} - -void SettingsDialog::on_preset_export_clicked() -{ - QString name = this->ui->presets->currentText(); - if(name.isEmpty()) - return; - - QFileDialog dialog { this }; - dialog.setAcceptMode(QFileDialog::AcceptSave); - dialog.selectNameFilter("Kristall Theme (*.kthm)"); - dialog.selectFile(QString("%1.kthm").arg(name)); - - if(dialog.exec() !=QFileDialog::Accepted) - return; - - QString fileName = dialog.selectedFiles().at(0); - - QSettings export_settings { fileName, QSettings::IniFormat }; - export_settings.setValue("name", name); - this->predefined_styles.value(name).save(export_settings); - export_settings.sync(); -} - -void SettingsDialog::on_start_page_textChanged(const QString &start_page) -{ - this->current_options.start_page = start_page; -} - -void SettingsDialog::on_ui_theme_currentIndexChanged(int index) -{ - this->current_options.theme = Theme(this->ui->ui_theme->itemData(index).toInt()); -} - -void SettingsDialog::on_fancypants_on_clicked() -{ - this->current_options.text_display = GenericSettings::FormattedText; -} - -void SettingsDialog::on_fancypants_off_clicked() -{ - this->current_options.text_display = GenericSettings::PlainText; -} - -void SettingsDialog::on_texthl_on_clicked() -{ - this->current_options.enable_text_decoration = true; -} - -void SettingsDialog::on_texthl_off_clicked() -{ - this->current_options.enable_text_decoration = false; -} - -void SettingsDialog::on_gophermap_icon_clicked() -{ - this->current_options.gophermap_display = GenericSettings::FormattedText; -} - -void SettingsDialog::on_gophermap_text_clicked() -{ - this->current_options.gophermap_display = GenericSettings::PlainText; -} - -void SettingsDialog::on_scheme_os_default_clicked() -{ - this->current_options.use_os_scheme_handler = true; -} - -void SettingsDialog::on_scheme_error_clicked() -{ - this->current_options.use_os_scheme_handler = false; -} - -void SettingsDialog::on_redirection_mode_currentIndexChanged(int index) -{ - this->current_options.redirection_policy = GenericSettings::RedirectionWarning(this->ui->redirection_mode->itemData(index).toInt()); -} - -void SettingsDialog::on_max_redirects_valueChanged(int max_redirections) -{ - this->current_options.max_redirections = max_redirections; -} - -void SettingsDialog::on_network_timeout_valueChanged(int timeout) -{ - this->current_options.network_timeout = timeout; -} diff --git a/src/settingsdialog.hpp b/src/settingsdialog.hpp deleted file mode 100644 index 0256f95..0000000 --- a/src/settingsdialog.hpp +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef SETTINGSDIALOG_HPP -#define SETTINGSDIALOG_HPP - -#include - -#include "geminirenderer.hpp" -#include "protocolsetup.hpp" -#include "documentstyle.hpp" -#include "ssltrust.hpp" -#include "kristall.hpp" - -namespace Ui { -class SettingsDialog; -} - -class SettingsDialog : public QDialog -{ - Q_OBJECT - -public: - explicit SettingsDialog(QWidget *parent = nullptr); - ~SettingsDialog(); - - void setGeminiStyle(DocumentStyle const & style); - - DocumentStyle geminiStyle() const { - return current_style; - } - - ProtocolSetup protocols() const; - void setProtocols(ProtocolSetup const & proto); - - SslTrust geminiSslTrust() const; - void setGeminiSslTrust(SslTrust const & trust); - - SslTrust httpsSslTrust() const; - void setHttpsSslTrust(SslTrust const & trust); - - GenericSettings options() const; - void setOptions(GenericSettings const & options); - -private slots: - void on_std_change_font_clicked(); - - void on_pre_change_font_clicked(); - - void on_h1_change_font_clicked(); - - void on_h2_change_font_clicked(); - - void on_h3_change_font_clicked(); - - void on_std_change_color_clicked(); - - void on_pre_change_color_clicked(); - - void on_h1_change_color_clicked(); - - void on_h2_change_color_clicked(); - - void on_h3_change_color_clicked(); - - void on_bg_change_color_clicked(); - - void on_link_local_change_color_clicked(); - - void on_link_foreign_change_color_clicked(); - - void on_link_cross_change_color_clicked(); - - void on_link_local_prefix_textChanged(const QString &arg1); - - void on_link_foreign_prefix_textChanged(const QString &arg1); - - void on_auto_theme_currentIndexChanged(int index); - - void on_preview_url_textChanged(const QString &arg1); - - void on_page_margin_valueChanged(double arg1); - - void on_presets_currentIndexChanged(int index); - - void on_preset_new_clicked(); - - void on_SettingsDialog_accepted(); - - void on_quote_change_color_clicked(); - - void on_preset_save_clicked(); - - void on_preset_load_clicked(); - - void on_preset_import_clicked(); - - void on_preset_export_clicked(); - - void on_start_page_textChanged(const QString &arg1); - - void on_ui_theme_currentIndexChanged(int index); - - void on_fancypants_on_clicked(); - - void on_fancypants_off_clicked(); - - void on_texthl_on_clicked(); - - void on_texthl_off_clicked(); - - void on_gophermap_icon_clicked(); - - void on_gophermap_text_clicked(); - - void on_scheme_os_default_clicked(); - - void on_scheme_error_clicked(); - - void on_redirection_mode_currentIndexChanged(int index); - - void on_max_redirects_valueChanged(int arg1); - - void on_network_timeout_valueChanged(int arg1); - -private: - void reloadStylePreview(); - - void updateFont(QFont & input); - - void updateColor(QColor & input); - -private: - Ui::SettingsDialog *ui; - - DocumentStyle current_style; - std::unique_ptr preview_document; - - QMap predefined_styles; - - SslTrust current_trust; - - GenericSettings current_options; -}; - -#endif // SETTINGSDIALOG_HPP diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui deleted file mode 100644 index 401e36b..0000000 --- a/src/settingsdialog.ui +++ /dev/null @@ -1,915 +0,0 @@ - - - SettingsDialog - - - - 0 - 0 - 850 - 650 - - - - Settings - - - - - - - - - 0 - - - - - - - Generic - - - - - - UI Theme - - - - - - - - - - Start Page: - - - - - - - about://blank - - - - - - - Enabled Protocols - - - - - - - - - Gemini - - - - - - - true - - - Gopher - - - - - - - true - - - Finger - - - - - - - HTTP - - - - - - - HTTPS - - - - - - - - - Text Rendering - - - - - - - - - Fancy - - - true - - - textRenderingBtnGroup - - - - - - - Always plain text - - - textRenderingBtnGroup - - - - - - - - - Enable text highlights - - - - - - - - - On (Experimental) - - - textHighlightsBtnGroup - - - - - - - Off - - - true - - - textHighlightsBtnGroup - - - - - - - - - Gopher Map - - - - - - - - - Use icons - - - true - - - gophermapBtnGroup - - - - - - - Use text only - - - gophermapBtnGroup - - - - - - - - - Unknown Scheme - - - - - - - - - Use OS default handler - - - buttonGroup - - - - - - - Display error message - - - buttonGroup - - - - - - - - - Max. Number of Redirections - - - - - - - 5 - - - - - - - Redirection Handling - - - - - - - - - - Network Timeout - - - - - - - ms - - - 100 - - - 90000 - - - - - - - - - - - Style - - - - - - 5 - - - - - - - - - - Qt::PlainText - - - - - - - - - - - - - - - - - - - Background Color - - - - - - - Standard Font - - - - - - - - - QFrame::NoFrame - - - This text will be displayed for normal text. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Preformatted Font - - - - - - - - - QFrame::NoFrame - - - This text will be displayed for preformatted text. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - H1 Font - - - - - - - - - QFrame::NoFrame - - - This text will be displayed for a level 1 heading. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - H2 Font - - - - - - - - - QFrame::NoFrame - - - This text will be displayed for a level 2 heading. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - H3 Font - - - - - - - - - border: 1px solid black; - - - QFrame::NoFrame - - - This text will be displayed for a level 3 heading. - - - Qt::PlainText - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Local Link Color - - - - - - - Foreign Link Color - - - - - - - Cross-Scheme-Color - - - - - - - Local Link Prefix - - - - - - - Extern Link Prefix - - - - - - - - - - - - - - - - - - - - - - - This is a local reference - - - Qt::PlainText - - - - - - - - - - - - - - - - - - - - - This is a foreign reference - - - Qt::PlainText - - - - - - - - - - - - - - - - - - - - - This reference is cross-scheme - - - Qt::PlainText - - - - - - - - - - - - - - - - - - - Auto-Theme Generation - - - - - - - - - - Page Margin - - - - - - - px - - - 0 - - - 350.000000000000000 - - - - - - - - - - - - Save as new preset - - - ... - - - - - - - - - - Override current preset - - - ... - - - - - - - - - - Load preset - - - ... - - - - - - - - - - Imports preset… - - - -1 - - - ... - - - - - - - - - - Export preset… - - - ... - - - - - - - - - - - - Presets - - - - - - - Block Quote Background - - - - - - - - - - - - - - - - ... - - - - - - - - - - - - - - - - false - - - - - - - host.name - - - - - - - - - - - - - Gemini TLS - - - - - - - - - - - - - HTTPS TLS - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - SslTrustEditor - QWidget -
ssltrusteditor.hpp
- 1 -
-
- - - - buttonBox - rejected() - SettingsDialog - reject() - - - 325 - 470 - - - 286 - 274 - - - - - buttonBox - accepted() - SettingsDialog - accept() - - - 257 - 470 - - - 157 - 274 - - - - - - - - - - -
diff --git a/src/ssltrusteditor.cpp b/src/ssltrusteditor.cpp deleted file mode 100644 index af10a72..0000000 --- a/src/ssltrusteditor.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "ssltrusteditor.hpp" -#include "ui_ssltrusteditor.h" - -SslTrustEditor::SslTrustEditor(QWidget *parent) : - QWidget(parent), - ui(new Ui::SslTrustEditor) -{ - ui->setupUi(this); - - this->ui->trust_level->clear(); - this->ui->trust_level->addItem("Trust on first encounter", QVariant::fromValue(SslTrust::TrustOnFirstUse)); - this->ui->trust_level->addItem("Trust everything", QVariant::fromValue(SslTrust::TrustEverything)); - this->ui->trust_level->addItem("Manually verify fingerprints", QVariant::fromValue(SslTrust::TrustNoOne)); - - this->ui->trusted_hosts->setModel(&this->current_trust.trusted_hosts); - - this->ui->trusted_hosts->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - this->ui->trusted_hosts->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - this->ui->trusted_hosts->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - - connect( - this->ui->trusted_hosts->selectionModel(), - &QItemSelectionModel::currentChanged, - this, - &SslTrustEditor::on_trusted_server_selection); -} - -SslTrustEditor::~SslTrustEditor() -{ - delete ui; -} - -SslTrust SslTrustEditor::trust() const -{ - return this->current_trust; -} - -void SslTrustEditor::setTrust(const SslTrust &trust) -{ - this->current_trust = trust; - - this->ui->trust_level->setCurrentIndex( - this->ui->trust_level->findData(QVariant::fromValue(trust.trust_level)) - ); - - if(trust.enable_ca) - this->ui->trust_enable_ca->setChecked(true); - else - this->ui->trust_disable__ca->setChecked(true); - - this->ui->trusted_hosts->resizeColumnsToContents(); -} - -void SslTrustEditor::on_trust_revoke_selected_clicked() -{ - this->current_trust.trusted_hosts.remove(this->ui->trusted_hosts->currentIndex()); -} - -void SslTrustEditor::on_trust_enable_ca_clicked() -{ - this->current_trust.enable_ca = true; -} - -void SslTrustEditor::on_trust_disable__ca_clicked() -{ - this->current_trust.enable_ca = false; -} - -void SslTrustEditor::on_trust_level_currentIndexChanged(int index) -{ - this->current_trust.trust_level = SslTrust::TrustLevel(this->ui->trust_level->itemData(index).toInt()); -} - -void SslTrustEditor::on_trusted_server_selection(const QModelIndex ¤t, const QModelIndex &previous) -{ - Q_UNUSED(previous); - if(auto host = this->current_trust.trusted_hosts.get(current); host) { - this->ui->trust_revoke_selected->setEnabled(true); - } else { - this->ui->trust_revoke_selected->setEnabled(false); - } -} diff --git a/src/ssltrusteditor.hpp b/src/ssltrusteditor.hpp deleted file mode 100644 index 841ba64..0000000 --- a/src/ssltrusteditor.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef SSLTRUSTEDITOR_HPP -#define SSLTRUSTEDITOR_HPP - -#include - -#include "ssltrust.hpp" - -namespace Ui { -class SslTrustEditor; -} - -class SslTrustEditor : public QWidget -{ - Q_OBJECT - -public: - explicit SslTrustEditor(QWidget *parent = nullptr); - ~SslTrustEditor(); - - - SslTrust trust() const; - void setTrust(SslTrust const & trust); - -private slots: - void on_trust_revoke_selected_clicked(); - - void on_trust_enable_ca_clicked(); - - void on_trust_disable__ca_clicked(); - - void on_trust_level_currentIndexChanged(int index); - -private: - - - void on_trusted_server_selection(QModelIndex const & current, QModelIndex const & previous); - -private: - Ui::SslTrustEditor *ui; - - SslTrust current_trust; -}; - -#endif // SSLTRUSTEDITOR_HPP diff --git a/src/ssltrusteditor.ui b/src/ssltrusteditor.ui deleted file mode 100644 index 068c065..0000000 --- a/src/ssltrusteditor.ui +++ /dev/null @@ -1,101 +0,0 @@ - - - SslTrustEditor - - - - 0 - 0 - 640 - 480 - - - - Form - - - - - - Trust Level - - - - - - - - - - Certificate Authorities - - - - - - - - - Use local certificate authorities - - - - - - - Don't use local certificate authorities - - - - - - - - - Trusted Hosts - - - - - - - - - true - - - - - - - - - false - - - Revoke trust - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - diff --git a/src/webclient.cpp b/src/webclient.cpp deleted file mode 100644 index ed87694..0000000 --- a/src/webclient.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include "webclient.hpp" -#include "kristall.hpp" - -#include -#include - -WebClient::WebClient() : - ProtocolHandler(nullptr), - current_reply(nullptr) -{ - manager.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); -} - -WebClient::~WebClient() -{ - -} - -bool WebClient::supportsScheme(const QString &scheme) const -{ - return (scheme == "https") or (scheme == "http"); -} - -bool WebClient::startRequest(const QUrl &url, RequestOptions options) -{ - if(url.scheme() != "http" and url.scheme() != "https") - return false; - - if(this->current_reply != nullptr) - return true; - - this->options = options; - this->body.clear(); - - QNetworkRequest request(url); - - auto ssl_config = request.sslConfiguration(); - // ssl_config.setProtocol(QSsl::TlsV1_2); - if(global_https_trust.enable_ca) - ssl_config.setCaCertificates(QSslConfiguration::systemCaCertificates()); - else - ssl_config.setCaCertificates(QList { }); - - if(this->current_identity.isValid()) { - ssl_config.setLocalCertificate(this->current_identity.certificate); - ssl_config.setPrivateKey(this->current_identity.private_key); - } - - // request.setMaximumRedirectsAllowed(5); - request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, false); - request.setSslConfiguration(ssl_config); - - this->manager.clearAccessCache(); - this->manager.clearConnectionCache(); - this->current_reply = manager.get(request); - if(this->current_reply == nullptr) - return false; - - this->suppress_socket_tls_error = true; - - connect(this->current_reply, &QNetworkReply::readyRead, this, &WebClient::on_data); - connect(this->current_reply, &QNetworkReply::finished, this, &WebClient::on_finished); - connect(this->current_reply, &QNetworkReply::sslErrors, this, &WebClient::on_sslErrors); - connect(this->current_reply, &QNetworkReply::redirected, this, &WebClient::on_redirected); - - return true; -} - -bool WebClient::isInProgress() const -{ - return (this->current_reply != nullptr); -} - -bool WebClient::cancelRequest() -{ - if(this->current_reply != nullptr) - { - this->current_reply->abort(); - this->current_reply = nullptr; - } - this->body.clear(); - return true; -} - -bool WebClient::enableClientCertificate(const CryptoIdentity &ident) -{ - current_identity = ident; - return true; -} - -void WebClient::disableClientCertificate() -{ - current_identity = CryptoIdentity(); -} - -void WebClient::on_data() -{ - this->body.append(this->current_reply->readAll()); - emit this->requestProgress(this->body.size()); -} - -void WebClient::on_finished() -{ - emit this->hostCertificateLoaded(this->current_reply->sslConfiguration().peerCertificate()); - - auto * const reply = this->current_reply; - this->current_reply = nullptr; - - reply->deleteLater(); - - if(reply->error() != QNetworkReply::NoError) - { - NetworkError error = UnknownError; - switch(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:" << reply->error(); - break; - } - - qDebug() << "web network error" << reply->errorString(); - qDebug() << this->body; - - if(not this->suppress_socket_tls_error) { - emit this->networkError(error, reply->errorString()); - } - } - else - { - int statusCode =reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if(statusCode >= 200 and statusCode < 300) { - auto mime = reply->header(QNetworkRequest::ContentTypeHeader).toString(); - emit this->requestComplete(this->body, mime); - } - else if(statusCode >= 300 and statusCode < 400) { - auto url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - - emit this->redirected(url, (statusCode == 301) or (statusCode == 308)); - } - else { - emit networkError(UnknownError, QString("Unhandled HTTP status code %1").arg(statusCode)); - } - - this->body.clear(); - } -} - -void WebClient::on_sslErrors(const QList &errors) -{ - emit this->hostCertificateLoaded(this->current_reply->sslConfiguration().peerCertificate()); - - if(options & IgnoreTlsErrors) { - this->current_reply->ignoreSslErrors(errors); - return; - } - - QList remaining_errors = errors; - QList 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())) - { - auto cert = this->current_reply->sslConfiguration().peerCertificate(); - switch(global_https_trust.getTrust(this->current_reply->url(), cert)) - { - case SslTrust::Trusted: - ignore = true; - break; - case SslTrust::Untrusted: - this->suppress_socket_tls_error = true; - emit this->networkError(UntrustedHost, toFingerprintString(cert)); - return; - case SslTrust::Mistrusted: - this->suppress_socket_tls_error = true; - emit this->networkError(MistrustedHost, toFingerprintString(cert)); - return; - } - } - else if(err.error() == QSslError::UnableToVerifyFirstCertificate) - { - ignore = true; - } - - if(ignore) { - ignored_errors.append(err); - remaining_errors.removeAt(0); - } else { - i += 1; - } - } - - current_reply->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 WebClient::on_redirected(const QUrl &url) -{ - qDebug() << "redirected to" << url; -} diff --git a/src/webclient.hpp b/src/webclient.hpp deleted file mode 100644 index 58ae029..0000000 --- a/src/webclient.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef WEBCLIENT_HPP -#define WEBCLIENT_HPP - -#include -#include -#include - -#include "protocolhandler.hpp" - -class WebClient: public ProtocolHandler -{ -private: - Q_OBJECT -public: - explicit WebClient(); - - ~WebClient() override; - - bool supportsScheme(QString const & scheme) const override; - - bool startRequest(QUrl const & url, RequestOptions options) override; - - bool isInProgress() const override; - - bool cancelRequest() override; - - bool enableClientCertificate(CryptoIdentity const & ident) override; - void disableClientCertificate() override; - -private slots: - void on_data(); - void on_finished(); - void on_sslErrors(const QList &errors); - void on_redirected(const QUrl &url); - -private: - QNetworkAccessManager manager; - QNetworkReply * current_reply; - - QByteArray body; - RequestOptions options; - - CryptoIdentity current_identity; - - bool suppress_socket_tls_error; -}; - -#endif // WEBCLIENT_HPP diff --git a/src/widgets/elidelabel.cpp b/src/widgets/elidelabel.cpp new file mode 100644 index 0000000..dc0a6b2 --- /dev/null +++ b/src/widgets/elidelabel.cpp @@ -0,0 +1,59 @@ +#include "elidelabel.hpp" + +#include + +ElideLabel::ElideLabel(QWidget* parent) + : QLabel(parent) +{ + m_elideMode = Qt::ElideNone; +} + +ElideLabel::ElideLabel(const QString &text, QWidget* parent) : QLabel(text, parent) +{ + m_elideMode = Qt::ElideNone; +} + +ElideLabel::~ElideLabel() +{ +} + +void ElideLabel::setElideMode(Qt::TextElideMode mode) +{ + m_elideMode = mode; +} + +Qt::TextElideMode ElideLabel::elideMode() const +{ + return m_elideMode; +} + +void ElideLabel::paintEvent(QPaintEvent * event) +{ + if (m_elideMode == Qt::ElideNone) + { + QLabel::paintEvent(event); + } + else + { + QFrame::paintEvent(event); + QPainter painter(this); + QRect r = contentsRect(); + painter.drawText(r, alignment(), fontMetrics().elidedText(text(), m_elideMode, r.width())); + } +} + +QSize ElideLabel::minimumSizeHint() const +{ + if (m_elideMode != Qt::ElideNone) + { + const QFontMetrics& fm = fontMetrics(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + QSize size(fm.horizontalAdvance("..."), fm.height()); +#else + QSize size(fm.width("..."), fm.height()); +#endif + return size; + } + + return QLabel::minimumSizeHint(); +} diff --git a/src/widgets/elidelabel.hpp b/src/widgets/elidelabel.hpp new file mode 100644 index 0000000..9eae69d --- /dev/null +++ b/src/widgets/elidelabel.hpp @@ -0,0 +1,28 @@ +#ifndef ELIDELABEL_HPP +#define ELIDELABEL_HPP + +#include + +class ElideLabel : public QLabel +{ + Q_OBJECT + Q_PROPERTY(Qt::TextElideMode elideMode READ elideMode WRITE setElideMode) + +public: + ElideLabel(QWidget* parent = 0); + ElideLabel(const QString &text, QWidget* parent = 0); + ~ElideLabel(); + + void setElideMode(Qt::TextElideMode mode); + Qt::TextElideMode elideMode() const; + + QSize minimumSizeHint() const; + +protected: + virtual void paintEvent(QPaintEvent * event); + +private: + Qt::TextElideMode m_elideMode; +}; + +#endif // ELIDELABEL_HPP diff --git a/src/widgets/mediaplayer.cpp b/src/widgets/mediaplayer.cpp new file mode 100644 index 0000000..e510d21 --- /dev/null +++ b/src/widgets/mediaplayer.cpp @@ -0,0 +1,86 @@ +#include "mediaplayer.hpp" +#include "ui_mediaplayer.h" + +#include +#include +#include +#include + +MediaPlayer::MediaPlayer(QWidget *parent) : + QWidget(parent), + ui(new Ui::MediaPlayer), + media_stream(), + player() +{ + ui->setupUi(this); + + this->player.setVideoOutput(this->ui->video_out); + + connect(&this->player, &QMediaPlayer::durationChanged, this->ui->media_progress, &QSlider::setMaximum); + connect(&this->player, &QMediaPlayer::positionChanged, this->ui->media_progress, &QSlider::setValue); + + connect(&this->player, &QMediaPlayer::audioAvailableChanged, this->ui->mute_button, &QToolButton::setEnabled); + // connect(&this->player, &QMediaPlayer::videoAvailableChanged, this->ui->video_out, &QVideoWidget::setVisible); + + connect(&this->player, &QMediaPlayer::stateChanged, this, &MediaPlayer::on_media_playbackChanged); + connect(&this->player, &QMediaPlayer::mediaStatusChanged, [](QMediaPlayer::MediaStatus status) { + qDebug() << "media status changed" << status; + }); + + connect(&this->player, &QMediaPlayer::positionChanged, this, &MediaPlayer::on_media_positionChanged); + + connect(this->ui->media_progress, &QSlider::valueChanged, &this->player, &QMediaPlayer::setPosition); +} + +MediaPlayer::~MediaPlayer() +{ + delete ui; +} + +void MediaPlayer::setMedia(QByteArray const & data, QUrl const & ref_url, QString const & mime) +{ + this->player.stop(); + + this->mime = mime; + + this->media_stream.close(); + this->media_stream.setData(data); // = QBuffer { &this->backing_buffer }; + this->media_stream.open(QIODevice::ReadOnly); + + QMediaContent content { ref_url }; + + this->player.setMedia(content, &this->media_stream); +} + +void MediaPlayer::stopPlaying() +{ + this->player.stop(); +} + +void MediaPlayer::on_playpause_button_clicked() +{ + if(this->player.state() != QMediaPlayer::PlayingState) { + this->player.play(); + } else { + this->player.pause(); + } +} + +void MediaPlayer::on_mute_button_clicked(bool checked) +{ + this->player.setMuted(checked); +} + +void MediaPlayer::on_media_positionChanged(qint64 pos) +{ + auto time = QTime::fromMSecsSinceStartOfDay(pos); + + this->ui->media_position->setText(time.toString()); +} + +void MediaPlayer::on_media_playbackChanged(QMediaPlayer::State status) +{ + this->ui->playpause_button->setIcon( + (status == QMediaPlayer::PlayingState) ? QIcon::fromTheme("pause") : QIcon::fromTheme("play") + ); +} diff --git a/src/widgets/mediaplayer.hpp b/src/widgets/mediaplayer.hpp new file mode 100644 index 0000000..c39a800 --- /dev/null +++ b/src/widgets/mediaplayer.hpp @@ -0,0 +1,42 @@ +#ifndef MEDIAPLAYER_HPP +#define MEDIAPLAYER_HPP + +#include +#include +#include +#include + +namespace Ui { +class MediaPlayer; +} + +class MediaPlayer : public QWidget +{ + Q_OBJECT + +public: + explicit MediaPlayer(QWidget *parent = nullptr); + ~MediaPlayer(); + + void setMedia(QByteArray const & data, QUrl const & ref_url, QString const & mime); + + void stopPlaying(); + +private slots: + void on_playpause_button_clicked(); + + void on_mute_button_clicked(bool checked); + +private: // slots + void on_media_positionChanged(qint64 pos); + + void on_media_playbackChanged(QMediaPlayer::State); + +private: + Ui::MediaPlayer *ui; + QBuffer media_stream; + QString mime; + QMediaPlayer player; +}; + +#endif // MEDIAPLAYER_HPP diff --git a/src/widgets/mediaplayer.ui b/src/widgets/mediaplayer.ui new file mode 100644 index 0000000..39fb570 --- /dev/null +++ b/src/widgets/mediaplayer.ui @@ -0,0 +1,85 @@ + + + MediaPlayer + + + + 0 + 0 + 640 + 480 + + + + Form + + + + + + + + + + + Click to play/pause + + + ... + + + + + + + + + + Qt::Horizontal + + + + + + + 00:00:00 + + + Qt::PlainText + + + + + + + Mute/unmute audio + + + ... + + + + + + true + + + false + + + + + + + + + + QVideoWidget + QWidget +
QVideoWidget
+ 1 +
+
+ + +
diff --git a/src/widgets/searchbar.cpp b/src/widgets/searchbar.cpp new file mode 100644 index 0000000..4d8ea3a --- /dev/null +++ b/src/widgets/searchbar.cpp @@ -0,0 +1,26 @@ +#include "searchbar.hpp" + +#include + +SearchBar::SearchBar(QWidget *parent) : QLineEdit(parent) +{ + +} + +void SearchBar::keyPressEvent(QKeyEvent *event) +{ + if(event->key() == Qt::Key_Escape) { + emit this->escapePressed(); + } else { + QLineEdit::keyPressEvent(event); + } +} + +void SearchBar::keyReleaseEvent(QKeyEvent *event) +{ + if(event->key() == Qt::Key_Escape) { + // Eat the event + } else { + QLineEdit::keyReleaseEvent(event); + } +} diff --git a/src/widgets/searchbar.hpp b/src/widgets/searchbar.hpp new file mode 100644 index 0000000..e03331f --- /dev/null +++ b/src/widgets/searchbar.hpp @@ -0,0 +1,19 @@ +#ifndef SEARCHBAR_HPP +#define SEARCHBAR_HPP + +#include + +class SearchBar : public QLineEdit +{ + Q_OBJECT +public: + explicit SearchBar(QWidget *parent = nullptr); + +signals: + void escapePressed(); +public: + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; +}; + +#endif // SEARCHBAR_HPP diff --git a/src/widgets/ssltrusteditor.cpp b/src/widgets/ssltrusteditor.cpp new file mode 100644 index 0000000..af10a72 --- /dev/null +++ b/src/widgets/ssltrusteditor.cpp @@ -0,0 +1,82 @@ +#include "ssltrusteditor.hpp" +#include "ui_ssltrusteditor.h" + +SslTrustEditor::SslTrustEditor(QWidget *parent) : + QWidget(parent), + ui(new Ui::SslTrustEditor) +{ + ui->setupUi(this); + + this->ui->trust_level->clear(); + this->ui->trust_level->addItem("Trust on first encounter", QVariant::fromValue(SslTrust::TrustOnFirstUse)); + this->ui->trust_level->addItem("Trust everything", QVariant::fromValue(SslTrust::TrustEverything)); + this->ui->trust_level->addItem("Manually verify fingerprints", QVariant::fromValue(SslTrust::TrustNoOne)); + + this->ui->trusted_hosts->setModel(&this->current_trust.trusted_hosts); + + this->ui->trusted_hosts->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + this->ui->trusted_hosts->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + this->ui->trusted_hosts->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + + connect( + this->ui->trusted_hosts->selectionModel(), + &QItemSelectionModel::currentChanged, + this, + &SslTrustEditor::on_trusted_server_selection); +} + +SslTrustEditor::~SslTrustEditor() +{ + delete ui; +} + +SslTrust SslTrustEditor::trust() const +{ + return this->current_trust; +} + +void SslTrustEditor::setTrust(const SslTrust &trust) +{ + this->current_trust = trust; + + this->ui->trust_level->setCurrentIndex( + this->ui->trust_level->findData(QVariant::fromValue(trust.trust_level)) + ); + + if(trust.enable_ca) + this->ui->trust_enable_ca->setChecked(true); + else + this->ui->trust_disable__ca->setChecked(true); + + this->ui->trusted_hosts->resizeColumnsToContents(); +} + +void SslTrustEditor::on_trust_revoke_selected_clicked() +{ + this->current_trust.trusted_hosts.remove(this->ui->trusted_hosts->currentIndex()); +} + +void SslTrustEditor::on_trust_enable_ca_clicked() +{ + this->current_trust.enable_ca = true; +} + +void SslTrustEditor::on_trust_disable__ca_clicked() +{ + this->current_trust.enable_ca = false; +} + +void SslTrustEditor::on_trust_level_currentIndexChanged(int index) +{ + this->current_trust.trust_level = SslTrust::TrustLevel(this->ui->trust_level->itemData(index).toInt()); +} + +void SslTrustEditor::on_trusted_server_selection(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(previous); + if(auto host = this->current_trust.trusted_hosts.get(current); host) { + this->ui->trust_revoke_selected->setEnabled(true); + } else { + this->ui->trust_revoke_selected->setEnabled(false); + } +} diff --git a/src/widgets/ssltrusteditor.hpp b/src/widgets/ssltrusteditor.hpp new file mode 100644 index 0000000..841ba64 --- /dev/null +++ b/src/widgets/ssltrusteditor.hpp @@ -0,0 +1,44 @@ +#ifndef SSLTRUSTEDITOR_HPP +#define SSLTRUSTEDITOR_HPP + +#include + +#include "ssltrust.hpp" + +namespace Ui { +class SslTrustEditor; +} + +class SslTrustEditor : public QWidget +{ + Q_OBJECT + +public: + explicit SslTrustEditor(QWidget *parent = nullptr); + ~SslTrustEditor(); + + + SslTrust trust() const; + void setTrust(SslTrust const & trust); + +private slots: + void on_trust_revoke_selected_clicked(); + + void on_trust_enable_ca_clicked(); + + void on_trust_disable__ca_clicked(); + + void on_trust_level_currentIndexChanged(int index); + +private: + + + void on_trusted_server_selection(QModelIndex const & current, QModelIndex const & previous); + +private: + Ui::SslTrustEditor *ui; + + SslTrust current_trust; +}; + +#endif // SSLTRUSTEDITOR_HPP diff --git a/src/widgets/ssltrusteditor.ui b/src/widgets/ssltrusteditor.ui new file mode 100644 index 0000000..068c065 --- /dev/null +++ b/src/widgets/ssltrusteditor.ui @@ -0,0 +1,101 @@ + + + SslTrustEditor + + + + 0 + 0 + 640 + 480 + + + + Form + + + + + + Trust Level + + + + + + + + + + Certificate Authorities + + + + + + + + + Use local certificate authorities + + + + + + + Don't use local certificate authorities + + + + + + + + + Trusted Hosts + + + + + + + + + true + + + + + + + + + false + + + Revoke trust + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + -- cgit v1.2.3