Adds improved client certificate management, adds server certificate management.
This commit is contained in:
parent
5bb3f3f92e
commit
33c91102a5
|
@ -144,11 +144,11 @@ ln -s /path/to/kristall .
|
|||
### 0.3 release
|
||||
- [ ] TLS Handling
|
||||
- [ ] Allow user to ignore TLS errors
|
||||
- [ ] Enable TOFU for HTTPS/Gemini
|
||||
- [ ] Enable TOFU for HTTPS
|
||||
- [ ] Enable Client Certificate Management
|
||||
- [ ] Add option: "Transient certificates survive an application reboot and are stored on disk""
|
||||
- [ ] Add management for client certificates
|
||||
- [ ] Rename/delete certificates
|
||||
- [x] Rename/delete certificates
|
||||
- [ ] Rename/delete/merge groups
|
||||
- [ ] Import/export PEM certificates and keys
|
||||
- [ ] Ask if the client certificate should be disabled when switching host and/or protocol
|
||||
|
@ -165,13 +165,14 @@ ln -s /path/to/kristall .
|
|||
- [ ] Improve style import
|
||||
- [ ] Direct preview instead of importing it as a preset. Allow user to save preset then manually
|
||||
- [ ] Handle network errors like timeout and such
|
||||
|
||||
### Unspecced
|
||||
- [ ] Survive full torture suite
|
||||
- [ ] Correctly parse mime parameters
|
||||
- [ ] Correctly parse charset (0013, 0014)
|
||||
- [ ] Correctly parse other params (0015)
|
||||
- [ ] Correctly parse undefined params (0016)
|
||||
- [ ] Make Kristall survive gemini://egsam.pitr.ca/
|
||||
|
||||
### Unspecced
|
||||
- [ ] Recognize home directories with /~home and such and add "substyles"
|
||||
- [ ] [Add favicon support](gemini://mozz.us/files/rfc_gemini_favicon.gmi)
|
||||
- [ ] Add auto-generated "favicons"
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* Added this changelog to the software itself
|
||||
* Fixed bug: Status bar label now does elide links that are too long instead of resizing the window.
|
||||
* Fixed bug: Gopher end-of-file marker is now better detected.
|
||||
* Adds support for server certificate handling for gemini://
|
||||
*
|
||||
|
||||
## 0.2
|
||||
* Implement Ctrl+D/*Add to favourites* menu item
|
||||
|
|
|
@ -52,6 +52,7 @@ BrowserTab::BrowserTab(MainWindow * mainWindow) :
|
|||
connect(&gemini_client, &GeminiClient::transientCertificateRequested, this, &BrowserTab::on_transientCertificateRequested);
|
||||
connect(&gemini_client, &GeminiClient::authorisedCertificateRequested, this, &BrowserTab::on_authorisedCertificateRequested);
|
||||
connect(&gemini_client, &GeminiClient::certificateRejected, this, &BrowserTab::on_certificateRejected);
|
||||
connect(&gemini_client, &GeminiClient::networkError, this, &BrowserTab::on_networkError);
|
||||
|
||||
connect(&gopher_client, &GopherClient::requestComplete, this, &BrowserTab::on_requestComplete);
|
||||
connect(&gopher_client, &GopherClient::requestFailed, this, &BrowserTab::on_requestFailed);
|
||||
|
@ -268,6 +269,11 @@ void BrowserTab::on_requestFailed(const QString &reason)
|
|||
this->setErrorMessage(QString("Request failed:\n%1").arg(reason));
|
||||
}
|
||||
|
||||
void BrowserTab::on_networkError(const QString &reason)
|
||||
{
|
||||
this->setErrorMessage(QString("Network error:\n%1").arg(reason));
|
||||
}
|
||||
|
||||
void BrowserTab::on_requestComplete(const QByteArray &data, const QString &mime)
|
||||
{
|
||||
qDebug() << "Loaded" << data.length() << "bytes of type" << mime;
|
||||
|
|
|
@ -71,6 +71,8 @@ private slots:
|
|||
|
||||
void on_requestFailed(QString const & reason);
|
||||
|
||||
void on_networkError(QString const & reason);
|
||||
|
||||
void on_protocolViolation(QString const & reason);
|
||||
|
||||
void on_inputRequired(QString const & query);
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
|
||||
#include "kristall.hpp"
|
||||
|
||||
#include "newidentitiydialog.hpp"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QMessageBox>
|
||||
|
||||
CertificateManagementDialog::CertificateManagementDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
|
@ -15,7 +18,13 @@ CertificateManagementDialog::CertificateManagementDialog(QWidget *parent) :
|
|||
this->ui->certificates->setModel(&global_identities);
|
||||
this->ui->certificates->expandAll();
|
||||
|
||||
on_certificates_clicked(QModelIndex { });
|
||||
connect(
|
||||
this->ui->certificates->selectionModel(),
|
||||
&QItemSelectionModel::currentChanged,
|
||||
this,
|
||||
&CertificateManagementDialog::on_certificates_selected
|
||||
);
|
||||
on_certificates_selected(QModelIndex { }, QModelIndex { });
|
||||
}
|
||||
|
||||
CertificateManagementDialog::~CertificateManagementDialog()
|
||||
|
@ -23,11 +32,12 @@ CertificateManagementDialog::~CertificateManagementDialog()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void CertificateManagementDialog::on_certificates_clicked(const QModelIndex &index)
|
||||
void CertificateManagementDialog::on_certificates_selected(QModelIndex const& index, QModelIndex const & previous)
|
||||
{
|
||||
Q_UNUSED(previous);
|
||||
|
||||
selected_identity = global_identities.getMutableIdentity(index);
|
||||
|
||||
this->ui->delete_cert_button->setEnabled(selected_identity != nullptr);
|
||||
this->ui->export_cert_button->setEnabled(selected_identity != nullptr);
|
||||
|
||||
if(selected_identity != nullptr)
|
||||
|
@ -43,6 +53,7 @@ void CertificateManagementDialog::on_certificates_clicked(const QModelIndex &ind
|
|||
);
|
||||
this->ui->cert_notes->setPlainText(cert.user_notes);
|
||||
|
||||
this->ui->delete_cert_button->setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -52,6 +63,12 @@ void CertificateManagementDialog::on_certificates_clicked(const QModelIndex &ind
|
|||
this->ui->cert_expiration_date->setDateTime(QDateTime { });
|
||||
this->ui->cert_livetime->setText("");
|
||||
this->ui->cert_fingerprint->setPlainText("");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,3 +85,67 @@ void CertificateManagementDialog::on_cert_display_name_textChanged(const QString
|
|||
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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void CertificateManagementDialog::on_import_cert_button_clicked()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -18,12 +18,19 @@ public:
|
|||
~CertificateManagementDialog();
|
||||
|
||||
private slots:
|
||||
void on_certificates_clicked(const QModelIndex &index);
|
||||
|
||||
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();
|
||||
private:
|
||||
void on_certificates_selected(const QModelIndex &index, QModelIndex const & previous);
|
||||
private:
|
||||
Ui::CertificateManagementDialog *ui;
|
||||
|
||||
|
|
|
@ -103,6 +103,10 @@
|
|||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<normaloff>:/icons/delete-alert.svg</normaloff>:/icons/delete-alert.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <cassert>
|
||||
#include <QDebug>
|
||||
#include <QSslConfiguration>
|
||||
#include "kristall.hpp"
|
||||
|
||||
GeminiClient::GeminiClient(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
@ -11,13 +12,10 @@ GeminiClient::GeminiClient(QObject *parent) : QObject(parent)
|
|||
connect(&socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors), this, &GeminiClient::sslErrors);
|
||||
connect(&socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error), this, &GeminiClient::socketError);
|
||||
|
||||
|
||||
QSslConfiguration ssl_config;
|
||||
ssl_config.setProtocol(QSsl::TlsV1_2);
|
||||
// ssl_config.setLocalCertificate(QSslCertificate::1
|
||||
|
||||
ssl_config.setCaCertificates(QList<QSslCertificate> { });
|
||||
socket.setSslConfiguration(ssl_config);
|
||||
|
||||
}
|
||||
|
||||
GeminiClient::~GeminiClient()
|
||||
|
@ -76,6 +74,8 @@ void GeminiClient::disableClientCertificate()
|
|||
|
||||
void GeminiClient::socketEncrypted()
|
||||
{
|
||||
qDebug() << "Pub key =" << socket.peerCertificate().publicKey().toPem();
|
||||
|
||||
QString request = target_url.toString(QUrl::FormattingOptions(QUrl::FullyEncoded)) + "\r\n";
|
||||
|
||||
QByteArray request_bytes = request.toUtf8();
|
||||
|
@ -255,13 +255,59 @@ void GeminiClient::socketDisconnected()
|
|||
}
|
||||
}
|
||||
|
||||
void GeminiClient::sslErrors(const QList<QSslError> &errors)
|
||||
static bool isTrustRelated(QSslError::SslError err)
|
||||
{
|
||||
for(auto const & error : errors) {
|
||||
qWarning() << error.errorString() ;
|
||||
switch(err)
|
||||
{
|
||||
case QSslError::CertificateUntrusted: return true;
|
||||
case QSslError::SelfSignedCertificate: return true;
|
||||
case QSslError::UnableToGetLocalIssuerCertificate: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GeminiClient::sslErrors(QList<QSslError> const & errors)
|
||||
{
|
||||
QList<QSslError> remaining_errors = errors;
|
||||
QList<QSslError> ignored_errors;
|
||||
|
||||
int i = 0;
|
||||
while(i < remaining_errors.size())
|
||||
{
|
||||
auto const & err = remaining_errors.at(i);
|
||||
|
||||
bool ignore = false;
|
||||
if(isTrustRelated(err.error()))
|
||||
{
|
||||
if(global_trust.isTrusted(target_url, socket.peerCertificate()))
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
}
|
||||
else if(err.error() == QSslError::UnableToVerifyFirstCertificate)
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
|
||||
if(ignore) {
|
||||
ignored_errors.append(err);
|
||||
remaining_errors.removeAt(0);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
socket.ignoreSslErrors(errors);
|
||||
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(remaining_errors.first().errorString());
|
||||
}
|
||||
}
|
||||
|
||||
void GeminiClient::socketError(QAbstractSocket::SocketError socketError)
|
||||
|
@ -272,6 +318,7 @@ void GeminiClient::socketError(QAbstractSocket::SocketError socketError)
|
|||
if(socketError == QAbstractSocket::RemoteHostClosedError) {
|
||||
socket.close();
|
||||
} else {
|
||||
qWarning() << socketError << socket.errorString();
|
||||
// qWarning() << socketError << socket.errorString();
|
||||
emit this->networkError(socket.errorString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,8 @@ signals:
|
|||
|
||||
void certificateRejected(CertificateRejection reason, QString const & info);
|
||||
|
||||
void networkError(QString const & reason);
|
||||
|
||||
private slots:
|
||||
|
||||
void socketEncrypted();
|
||||
|
|
|
@ -38,5 +38,6 @@
|
|||
<file>icons/shield-outline.svg</file>
|
||||
<file>icons/shield-lock.svg</file>
|
||||
<file>icons/certificate.svg</file>
|
||||
<file>icons/delete-alert.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M17 4V6H3V4H6.5L7.5 3H12.5L13.5 4H17M4 19V7H16V19C16 20.1 15.1 21 14 21H6C4.9 21 4 20.1 4 19M19 16H21V18H19V16M19 9H21V14H19V9Z" /></svg>
|
After Width: | Height: | Size: 422 B |
|
@ -7,6 +7,7 @@
|
|||
IdentityCollection::IdentityCollection(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IdentityCollection::load(QSettings &settings)
|
||||
|
@ -95,28 +96,24 @@ void IdentityCollection::save(QSettings &settings) const
|
|||
settings.endArray();
|
||||
}
|
||||
|
||||
bool IdentityCollection::addGroup(const QString &group_name)
|
||||
{
|
||||
GroupNode * group;
|
||||
return internalAddGroup(group_name, group);
|
||||
}
|
||||
|
||||
bool IdentityCollection::addCertificate(const QString &group_name, const CryptoIdentity &crypto_id)
|
||||
{
|
||||
// Don't allow saving transient certificates
|
||||
if(not crypto_id.is_persistent)
|
||||
return false;
|
||||
|
||||
this->beginResetModel();
|
||||
GroupNode * group;
|
||||
internalAddGroup(group_name, group);
|
||||
|
||||
GroupNode * group = nullptr;
|
||||
for(auto const & grp : root.children)
|
||||
{
|
||||
auto * g = static_cast<GroupNode*>(grp.get());
|
||||
if(g->title == group_name) {
|
||||
group = g;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(group == nullptr) {
|
||||
group = new GroupNode();
|
||||
group->title = group_name;
|
||||
this->root.children.emplace_back(group);
|
||||
}
|
||||
QModelIndex parent_index = createIndex(group->index, 0, group);
|
||||
|
||||
beginInsertRows(parent_index, group->children.size(), group->children.size() + 1);
|
||||
|
||||
auto id = std::make_unique<IdentityNode>();
|
||||
id->identity = crypto_id;
|
||||
|
@ -124,7 +121,7 @@ bool IdentityCollection::addCertificate(const QString &group_name, const CryptoI
|
|||
|
||||
this->relayout();
|
||||
|
||||
this->endResetModel();
|
||||
this->endInsertRows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -171,6 +168,77 @@ QStringList IdentityCollection::groups() const
|
|||
return result;
|
||||
}
|
||||
|
||||
QString IdentityCollection::group(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QString { };
|
||||
|
||||
Node const *item = static_cast<Node const*>(index.internalPointer());
|
||||
|
||||
switch(item->type) {
|
||||
case Node::Root: return QString { };
|
||||
case Node::Group: return static_cast<GroupNode const *>(item)->title;
|
||||
case Node::Identity: return static_cast<IdentityNode const *>(item)->parent->as<GroupNode>().title;
|
||||
default: return QString { };
|
||||
}
|
||||
}
|
||||
|
||||
bool IdentityCollection::destroyIdentity(const QModelIndex &index)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return false;
|
||||
|
||||
Node * childItem = static_cast<Node *>(index.internalPointer());
|
||||
Node * parent = childItem->parent;
|
||||
|
||||
if (parent == &root)
|
||||
return false;
|
||||
|
||||
beginRemoveRows(this->parent(index), index.row(), index.row() + 1);
|
||||
|
||||
parent->children.erase(parent->children.begin() + childItem->index);
|
||||
|
||||
endRemoveRows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IdentityCollection::canDeleteGroup(const QString &group_name)
|
||||
{
|
||||
for(auto const & group_node : root.children)
|
||||
{
|
||||
auto & group = group_node->as<GroupNode>();
|
||||
if((group.children.size() == 0) and (group.title == group_name))
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IdentityCollection::deleteGroup(const QString &group_name)
|
||||
{
|
||||
size_t index = 0;
|
||||
for(auto it = root.children.begin(); it != root.children.end(); it++, index++)
|
||||
{
|
||||
auto & group = it->get()->as<GroupNode>();
|
||||
if(group.title == group_name) {
|
||||
if(group.children.size() > 0) {
|
||||
qDebug() << "cannot delete non-empty group" << group_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex { }, index, index + 1);
|
||||
|
||||
root.children.erase(it);
|
||||
|
||||
endRemoveRows();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QModelIndex IdentityCollection::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (not hasIndex(row, column, parent))
|
||||
|
@ -265,7 +333,7 @@ void IdentityCollection::relayout()
|
|||
group.parent = &root;
|
||||
group.index = i;
|
||||
|
||||
qDebug() << "group[" << group.index << "]" << group.as<GroupNode>().title;
|
||||
// qDebug() << "group[" << group.index << "]" << group.as<GroupNode>().title;
|
||||
|
||||
for(size_t j = 0; j < group.children.size(); j++)
|
||||
{
|
||||
|
@ -274,7 +342,33 @@ void IdentityCollection::relayout()
|
|||
id.index = j;
|
||||
assert(id.children.size() == 0);
|
||||
|
||||
qDebug() << "id[" << id.index << "]" << id.as<IdentityNode>().identity.display_name;
|
||||
// qDebug() << "id[" << id.index << "]" << id.as<IdentityNode>().identity.display_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IdentityCollection::internalAddGroup(const QString &group_name, GroupNode * & group)
|
||||
{
|
||||
for(auto const & grp : root.children)
|
||||
{
|
||||
auto * g = static_cast<GroupNode*>(grp.get());
|
||||
if(g->title == group_name) {
|
||||
group = g;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto parent = QModelIndex { };
|
||||
|
||||
beginInsertRows(parent, this->root.children.size(), this->root.children.size() + 1);
|
||||
|
||||
group = new GroupNode();
|
||||
group->title = group_name;
|
||||
this->root.children.emplace_back(group);
|
||||
|
||||
this->relayout();
|
||||
|
||||
endInsertRows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ public:
|
|||
|
||||
void save(QSettings & settings) const;
|
||||
|
||||
bool addGroup(QString const & group);
|
||||
|
||||
bool addCertificate(QString const & group, CryptoIdentity const & id);
|
||||
|
||||
CryptoIdentity getIdentity(QModelIndex const & index) const;
|
||||
|
@ -56,6 +58,14 @@ public:
|
|||
|
||||
QStringList groups() const;
|
||||
|
||||
//! Returns the group name of the index.
|
||||
QString group(QModelIndex const & index) const;
|
||||
|
||||
bool destroyIdentity(QModelIndex const & index);
|
||||
|
||||
bool canDeleteGroup(QString const & group_name);
|
||||
bool deleteGroup(QString const & group_name);
|
||||
|
||||
public:
|
||||
// Header:
|
||||
// QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
@ -73,6 +83,8 @@ public:
|
|||
private:
|
||||
void relayout();
|
||||
|
||||
bool internalAddGroup(QString const & group_name, GroupNode * & out_group);
|
||||
|
||||
private:
|
||||
RootNode root;
|
||||
};
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
#include <QClipboard>
|
||||
|
||||
#include "identitycollection.hpp"
|
||||
#include "ssltrust.hpp"
|
||||
|
||||
extern QSettings global_settings;
|
||||
extern IdentityCollection global_identities;
|
||||
extern QClipboard * global_clipboard;
|
||||
extern SslTrust global_trust;
|
||||
|
||||
#endif // KRISTALL_HPP
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
QT += core gui
|
||||
QT += core gui svg
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets network multimedia multimediawidgets
|
||||
|
||||
|
@ -30,6 +30,13 @@ win32-msvc {
|
|||
INCLUDEPATH += "C:\Program Files\OpenSSL\include"
|
||||
}
|
||||
|
||||
android: include(/home/felix/projects/android-hass/android-sdk/android_openssl/openssl.pri)
|
||||
|
||||
# android {
|
||||
# INCLUDEPATH += /home/felix/projects/android-hass/android-sdk/android_openssl/static/include
|
||||
# LIBS += -L /home/felix/projects/android-hass/android-sdk/android_openssl/static/lib/arm/
|
||||
# }
|
||||
|
||||
INCLUDEPATH += $$PWD/../lib/luis-l-gist/
|
||||
DEPENDPATH += $$PWD/../lib/luis-l-gist/
|
||||
|
||||
|
@ -58,7 +65,10 @@ SOURCES += \
|
|||
plaintextrenderer.cpp \
|
||||
protocolsetup.cpp \
|
||||
settingsdialog.cpp \
|
||||
ssltrust.cpp \
|
||||
tabbrowsinghistory.cpp \
|
||||
trustedhost.cpp \
|
||||
trustedhostcollection.cpp \
|
||||
webclient.cpp
|
||||
|
||||
HEADERS += \
|
||||
|
@ -86,7 +96,10 @@ HEADERS += \
|
|||
plaintextrenderer.hpp \
|
||||
protocolsetup.hpp \
|
||||
settingsdialog.hpp \
|
||||
ssltrust.hpp \
|
||||
tabbrowsinghistory.hpp \
|
||||
trustedhost.hpp \
|
||||
trustedhostcollection.hpp \
|
||||
webclient.hpp
|
||||
|
||||
FORMS += \
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
IdentityCollection global_identities;
|
||||
QSettings global_settings { "xqTechnologies", "Kristall" };
|
||||
QClipboard * global_clipboard;
|
||||
SslTrust global_trust;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -28,6 +29,10 @@ int main(int argc, char *argv[])
|
|||
global_identities.load(global_settings);
|
||||
global_settings.endGroup();
|
||||
|
||||
global_settings.beginGroup("Trusted Servers");
|
||||
global_trust.load(global_settings);
|
||||
global_settings.endGroup();
|
||||
|
||||
MainWindow w(&app);
|
||||
|
||||
auto urls = cli_parser.positionalArguments();
|
||||
|
|
|
@ -149,6 +149,10 @@ void MainWindow::saveSettings()
|
|||
global_identities.save(global_settings);
|
||||
global_settings.endGroup();
|
||||
|
||||
global_settings.beginGroup("Trusted Servers");
|
||||
global_trust.save(global_settings);
|
||||
global_settings.endGroup();
|
||||
|
||||
global_settings.beginGroup("Theme");
|
||||
this->current_style.save(global_settings);
|
||||
global_settings.endGroup();
|
||||
|
@ -245,6 +249,7 @@ void MainWindow::on_actionSettings_triggered()
|
|||
dialog.setStartPage(global_settings.value("start_page").toString());
|
||||
dialog.setProtocols(this->protocols);
|
||||
dialog.setUiTheme(global_settings.value("theme").toString());
|
||||
dialog.setSslTrust(global_trust);
|
||||
|
||||
if(dialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
@ -253,6 +258,7 @@ void MainWindow::on_actionSettings_triggered()
|
|||
global_settings.setValue("start_page", url.toString());
|
||||
}
|
||||
|
||||
global_trust = dialog.sslTrust();
|
||||
global_settings.setValue("theme", dialog.uiTheme());
|
||||
|
||||
this->protocols = dialog.protocols();
|
||||
|
|
|
@ -45,6 +45,11 @@ 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;
|
||||
|
@ -59,16 +64,18 @@ void NewIdentitiyDialog::updateUI()
|
|||
|
||||
void NewIdentitiyDialog::on_group_editTextChanged(const QString &arg1)
|
||||
{
|
||||
qDebug() << 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();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ public:
|
|||
CryptoIdentity createIdentity() const;
|
||||
|
||||
QString groupName() const;
|
||||
void setGroupName(QString const & name);
|
||||
|
||||
private slots:
|
||||
void on_group_editTextChanged(const QString &arg1);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <QInputDialog>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
|
||||
#include "kristall.hpp"
|
||||
|
||||
|
@ -85,6 +86,22 @@ SettingsDialog::SettingsDialog(QWidget *parent) :
|
|||
this->on_presets_currentIndexChanged(-1);
|
||||
}
|
||||
|
||||
this->ui->trust_level->clear();
|
||||
this->ui->trust_level->addItem("Trust on first encounter", QVariant::fromValue<int>(SslTrust::TrustOnFirstUse));
|
||||
this->ui->trust_level->addItem("Trust everything", QVariant::fromValue<int>(SslTrust::TrustEverything));
|
||||
this->ui->trust_level->addItem("Manually verify fingerprints", QVariant::fromValue<int>(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,
|
||||
&SettingsDialog::on_trusted_server_selection);
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
|
@ -201,6 +218,27 @@ void SettingsDialog::setUiTheme(const QString &theme)
|
|||
|
||||
}
|
||||
|
||||
SslTrust SettingsDialog::sslTrust() const
|
||||
{
|
||||
return this->current_trust;
|
||||
}
|
||||
|
||||
void SettingsDialog::setSslTrust(const SslTrust &trust)
|
||||
{
|
||||
this->current_trust = trust;
|
||||
|
||||
this->ui->trust_level->setCurrentIndex(
|
||||
this->ui->trust_level->findData(QVariant::fromValue<int>(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 SettingsDialog::reloadStylePreview()
|
||||
{
|
||||
auto const document = R"gemini(# H1 Header
|
||||
|
@ -295,6 +333,15 @@ void SettingsDialog::updateColor(QColor &input)
|
|||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::on_trusted_server_selection(const QModelIndex ¤t, const QModelIndex &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);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::on_std_change_color_clicked()
|
||||
{
|
||||
updateColor(current_style.standard_color);
|
||||
|
@ -531,3 +578,23 @@ void SettingsDialog::on_preset_export_clicked()
|
|||
this->predefined_styles.value(name).save(export_settings);
|
||||
export_settings.sync();
|
||||
}
|
||||
|
||||
void SettingsDialog::on_trust_enable_ca_clicked()
|
||||
{
|
||||
this->current_trust.enable_ca = true;
|
||||
}
|
||||
|
||||
void SettingsDialog::on_trust_disable__ca_clicked()
|
||||
{
|
||||
this->current_trust.enable_ca = false;
|
||||
}
|
||||
|
||||
void SettingsDialog::on_trust_level_currentIndexChanged(int index)
|
||||
{
|
||||
this->current_trust.trust_level = SslTrust::TrustLevel(this->ui->trust_level->itemData(index).toInt());
|
||||
}
|
||||
|
||||
void SettingsDialog::on_trust_revoke_selected_clicked()
|
||||
{
|
||||
this->current_trust.trusted_hosts.remove(this->ui->trusted_hosts->currentIndex());
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "geminirenderer.hpp"
|
||||
#include "protocolsetup.hpp"
|
||||
#include "documentstyle.hpp"
|
||||
#include "ssltrust.hpp"
|
||||
|
||||
namespace Ui {
|
||||
class SettingsDialog;
|
||||
|
@ -34,6 +35,9 @@ public:
|
|||
QString uiTheme() const;
|
||||
void setUiTheme(QString const & theme);
|
||||
|
||||
SslTrust sslTrust() const;
|
||||
void setSslTrust(SslTrust const & trust);
|
||||
|
||||
private slots:
|
||||
void on_std_change_font_clicked();
|
||||
|
||||
|
@ -89,6 +93,14 @@ private slots:
|
|||
|
||||
void on_preset_export_clicked();
|
||||
|
||||
void on_trust_enable_ca_clicked();
|
||||
|
||||
void on_trust_disable__ca_clicked();
|
||||
|
||||
void on_trust_level_currentIndexChanged(int index);
|
||||
|
||||
void on_trust_revoke_selected_clicked();
|
||||
|
||||
private:
|
||||
void reloadStylePreview();
|
||||
|
||||
|
@ -96,6 +108,8 @@ private:
|
|||
|
||||
void updateColor(QColor & input);
|
||||
|
||||
void on_trusted_server_selection(QModelIndex const & current, QModelIndex const & previous);
|
||||
|
||||
private:
|
||||
Ui::SettingsDialog *ui;
|
||||
|
||||
|
@ -103,6 +117,8 @@ private:
|
|||
std::unique_ptr<QTextDocument> preview_document;
|
||||
|
||||
QMap<QString, DocumentStyle> predefined_styles;
|
||||
|
||||
SslTrust current_trust;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_HPP
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="generic">
|
||||
<attribute name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<normaloff>:/icons/settings.svg</normaloff>:/icons/settings.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Generic</string>
|
||||
</attribute>
|
||||
|
@ -240,6 +244,10 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="style_tab">
|
||||
<attribute name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<normaloff>:/icons/palette.svg</normaloff>:/icons/palette.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Style</string>
|
||||
</attribute>
|
||||
|
@ -794,6 +802,103 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="gem_trust_page">
|
||||
<attribute name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<normaloff>:/icons/certificate.svg</normaloff>:/icons/certificate.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Gemini TLS</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>Trust Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="trust_level"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Certificate Authorities</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="trust_enable_ca">
|
||||
<property name="text">
|
||||
<string>Use local certificate authorities</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_2</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="trust_disable__ca">
|
||||
<property name="text">
|
||||
<string>Don't use local certificate authorities</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">buttonGroup_2</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Trusted Hosts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTableView" name="trusted_hosts">
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="QToolButton" name="trust_revoke_selected">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Revoke trust</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -846,9 +951,10 @@
|
|||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
<buttongroup name="gophermapBtnGroup"/>
|
||||
<buttongroup name="textHighlightsBtnGroup"/>
|
||||
<buttongroup name="gophermapBtnGroup"/>
|
||||
<buttongroup name="textRenderingBtnGroup"/>
|
||||
<buttongroup name="buttonGroup_2"/>
|
||||
<buttongroup name="buttonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
#include "ssltrust.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
void SslTrust::load(QSettings &settings)
|
||||
{
|
||||
trust_level = TrustLevel(settings.value("trust_level", int(TrustOnFirstUse)).toInt());
|
||||
enable_ca = settings.value("enable_ca", QVariant::fromValue(false)).toBool();
|
||||
|
||||
trusted_hosts.clear();
|
||||
|
||||
int size = settings.beginReadArray("trusted_hosts");
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
settings.setArrayIndex(i);
|
||||
|
||||
auto key_type = QSsl::KeyAlgorithm(settings.value("key_type").toInt());
|
||||
auto key_value = settings.value("key_bits").toByteArray();
|
||||
|
||||
TrustedHost host;
|
||||
host.host_name = settings.value("host_name").toString();
|
||||
host.trusted_at = settings.value("trusted_at").toDateTime();
|
||||
host.public_key = QSslKey(key_value, key_type, QSsl::Der, QSsl::PublicKey);
|
||||
|
||||
trusted_hosts.insert(host);
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
|
||||
void SslTrust::save(QSettings &settings) const
|
||||
{
|
||||
settings.setValue("trust_level", int(trust_level));
|
||||
settings.setValue("enable_ca", enable_ca);
|
||||
|
||||
auto all = trusted_hosts.getAll();
|
||||
settings.beginWriteArray("trusted_hosts", all.size());
|
||||
for(int i = 0; i < all.size(); i++)
|
||||
{
|
||||
settings.setArrayIndex(i);
|
||||
|
||||
settings.setValue("host_name", all.at(i).host_name);
|
||||
settings.setValue("trusted_at", all.at(i).trusted_at);
|
||||
settings.setValue("key_type", int(all.at(i).public_key.algorithm()));
|
||||
settings.setValue("key_bits", all.at(i).public_key.toDer());
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
|
||||
bool SslTrust::isTrusted(QUrl const & url, const QSslCertificate &certificate)
|
||||
{
|
||||
if(trust_level == TrustEverything)
|
||||
return true;
|
||||
|
||||
if(auto host_or_none = trusted_hosts.get(url.host()); host_or_none)
|
||||
{
|
||||
if(host_or_none->public_key == certificate.publicKey())
|
||||
return true;
|
||||
qDebug() << "certificate mismatch for" << url;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(trust_level == TrustOnFirstUse)
|
||||
{
|
||||
TrustedHost host;
|
||||
host.host_name = url.host();
|
||||
host.trusted_at = QDateTime::currentDateTime();
|
||||
host.public_key = certificate.publicKey();
|
||||
|
||||
bool ok = trusted_hosts.insert(host);
|
||||
assert(ok);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef SSLTRUST_HPP
|
||||
#define SSLTRUST_HPP
|
||||
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSettings>
|
||||
|
||||
#include "trustedhostcollection.hpp"
|
||||
|
||||
struct SslTrust
|
||||
{
|
||||
enum TrustLevel {
|
||||
TrustOnFirstUse = 0, // default
|
||||
TrustEverything = 1, // not recommended
|
||||
TrustNoOne = 2, // approve every fingerprint by hand
|
||||
};
|
||||
|
||||
SslTrust() = default;
|
||||
SslTrust(SslTrust const &) = default;
|
||||
SslTrust(SslTrust &&) = default;
|
||||
|
||||
SslTrust & operator=(SslTrust const &) = default;
|
||||
SslTrust & operator=(SslTrust &&) = default;
|
||||
|
||||
TrustLevel trust_level = TrustOnFirstUse;
|
||||
|
||||
TrustedHostCollection trusted_hosts;
|
||||
|
||||
bool enable_ca = false;
|
||||
|
||||
void load(QSettings & settings);
|
||||
void save(QSettings & settings) const;
|
||||
|
||||
bool isTrusted(QUrl const & url, QSslCertificate const & certificate);
|
||||
};
|
||||
|
||||
#endif // SSLTRUST_HPP
|
|
@ -0,0 +1,2 @@
|
|||
#include "trustedhost.hpp"
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef TRUSTEDHOST_HPP
|
||||
#define TRUSTEDHOST_HPP
|
||||
|
||||
#include <QSslKey>
|
||||
#include <QUrl>
|
||||
#include <QDateTime>
|
||||
|
||||
struct TrustedHost
|
||||
{
|
||||
QString host_name;
|
||||
QSslKey public_key;
|
||||
QDateTime trusted_at;
|
||||
};
|
||||
|
||||
#endif // TRUSTEDHOST_HPP
|
|
@ -0,0 +1,151 @@
|
|||
#include "trustedhostcollection.hpp"
|
||||
|
||||
TrustedHostCollection::TrustedHostCollection(QObject *parent)
|
||||
: QAbstractTableModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
TrustedHostCollection::TrustedHostCollection(const TrustedHostCollection & other) :
|
||||
items(other.items)
|
||||
{
|
||||
assert(other.parent() == nullptr);
|
||||
|
||||
}
|
||||
|
||||
TrustedHostCollection::TrustedHostCollection(TrustedHostCollection &&other) :
|
||||
items(std::move(other.items))
|
||||
{
|
||||
assert(other.parent() == nullptr);
|
||||
}
|
||||
|
||||
QVariant TrustedHostCollection::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Vertical)
|
||||
return QVariant { };
|
||||
if(role == Qt::DisplayRole)
|
||||
{
|
||||
switch(section)
|
||||
{
|
||||
case 0: return "Host Name";
|
||||
case 1: return "First Seen";
|
||||
case 2: return "Key Type";
|
||||
}
|
||||
}
|
||||
return QVariant { };
|
||||
}
|
||||
|
||||
int TrustedHostCollection::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return items.size();
|
||||
}
|
||||
|
||||
int TrustedHostCollection::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return 3;
|
||||
}
|
||||
|
||||
QVariant TrustedHostCollection::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant { };
|
||||
if(index.row() < 0 or index.row() >= items.size())
|
||||
return QVariant { };
|
||||
|
||||
auto const & host = items.at(index.row());
|
||||
|
||||
if(role == Qt::DisplayRole)
|
||||
{
|
||||
switch(index.column())
|
||||
{
|
||||
case 0: return host.host_name;
|
||||
case 1: return host.trusted_at.toString();
|
||||
case 2: switch(host.public_key.algorithm())
|
||||
{
|
||||
case QSsl::Rsa: return "RSA";
|
||||
case QSsl::Ec: return "EC";
|
||||
case QSsl::Dh: return "DH";
|
||||
case QSsl::Dsa: return "DSA";
|
||||
case QSsl::Opaque: return "Opaque";
|
||||
default: return "Unforseen";
|
||||
}
|
||||
}
|
||||
}
|
||||
return QVariant { };
|
||||
}
|
||||
|
||||
TrustedHostCollection &TrustedHostCollection::operator=(const TrustedHostCollection & other)
|
||||
{
|
||||
beginResetModel();
|
||||
this->items = other.items;
|
||||
endResetModel();
|
||||
return *this;
|
||||
}
|
||||
|
||||
TrustedHostCollection &TrustedHostCollection::operator=(TrustedHostCollection && other)
|
||||
{
|
||||
beginResetModel();
|
||||
this->items = std::move(other.items);
|
||||
endResetModel();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TrustedHostCollection::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
this->items.clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool TrustedHostCollection::insert(const TrustedHost &host)
|
||||
{
|
||||
for(auto const & item : items)
|
||||
{
|
||||
if(item.host_name == host.host_name)
|
||||
return false;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex { }, items.size(), items.size() + 1);
|
||||
items.append(host);
|
||||
endInsertRows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<TrustedHost> TrustedHostCollection::get(QString const & host_name) const
|
||||
{
|
||||
for(auto const & item : items)
|
||||
{
|
||||
if(item.host_name == host_name)
|
||||
return item;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TrustedHost> TrustedHostCollection::get(const QModelIndex &index) const
|
||||
{
|
||||
if(not index.isValid())
|
||||
return std::nullopt;
|
||||
if(index.row() < 0 or index.row() >= items.size())
|
||||
return std::nullopt;
|
||||
return items.at(index.row());
|
||||
}
|
||||
|
||||
void TrustedHostCollection::remove(const QModelIndex &index)
|
||||
{
|
||||
if(not index.isValid())
|
||||
return;
|
||||
if(index.row() < 0 or index.row() >= items.size())
|
||||
return;
|
||||
beginRemoveRows(QModelIndex{}, index.row(), index.row() + 1);
|
||||
items.removeAt(index.row());
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
QVector<TrustedHost> TrustedHostCollection::getAll() const
|
||||
{
|
||||
return items;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef TRUSTEDHOSTCOLLECTION_HPP
|
||||
#define TRUSTEDHOSTCOLLECTION_HPP
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#include "trustedhost.hpp"
|
||||
#include <optional>
|
||||
|
||||
class TrustedHostCollection : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrustedHostCollection(QObject *parent = nullptr);
|
||||
|
||||
TrustedHostCollection(TrustedHostCollection const &);
|
||||
TrustedHostCollection(TrustedHostCollection &&);
|
||||
|
||||
// Header:
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
// Basic functionality:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
public:
|
||||
TrustedHostCollection & operator=(TrustedHostCollection const &);
|
||||
TrustedHostCollection & operator=(TrustedHostCollection &&);
|
||||
|
||||
void clear();
|
||||
|
||||
bool insert(TrustedHost const & host);
|
||||
|
||||
std::optional<TrustedHost> get(QString const & host_name) const;
|
||||
|
||||
std::optional<TrustedHost> get(QModelIndex const & index) const;
|
||||
|
||||
void remove(QModelIndex const & index);
|
||||
|
||||
QVector<TrustedHost> getAll() const;
|
||||
|
||||
private:
|
||||
QVector<TrustedHost> items;
|
||||
};
|
||||
|
||||
#endif // TRUSTEDHOSTCOLLECTION_HPP
|
|
@ -35,6 +35,8 @@ bool WebClient::startRequest(const QUrl &url)
|
|||
|
||||
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::errorOccurred, this, &WebClient::on_networkError);
|
||||
connect(this->current_reply, &QNetworkReply::sslErrors, this, &WebClient::on_sslErrors);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -71,7 +73,7 @@ void WebClient::on_finished()
|
|||
{
|
||||
auto mime = this->current_reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||
|
||||
qDebug() << this->current_reply->url() << mime;
|
||||
// qDebug() << this->current_reply->url() << mime;
|
||||
|
||||
emit this->requestComplete(this->body, mime);
|
||||
|
||||
|
@ -80,3 +82,15 @@ void WebClient::on_finished()
|
|||
this->current_reply->deleteLater();
|
||||
this->current_reply = nullptr;
|
||||
}
|
||||
|
||||
void WebClient::on_networkError(QNetworkReply::NetworkError code)
|
||||
{
|
||||
qDebug() << code << this->current_reply->errorString();
|
||||
}
|
||||
|
||||
void WebClient::on_sslErrors(const QList<QSslError> &errors)
|
||||
{
|
||||
for(auto const & err : errors)
|
||||
qDebug() << err;
|
||||
this->current_reply->ignoreSslErrors();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class WebClient: public QObject
|
||||
{
|
||||
|
@ -26,9 +27,13 @@ signals:
|
|||
|
||||
void requestFailed(QString const & message);
|
||||
|
||||
void networkError(QString const & message);
|
||||
|
||||
private slots:
|
||||
void on_data();
|
||||
void on_finished();
|
||||
void on_networkError(QNetworkReply::NetworkError code);
|
||||
void on_sslErrors(const QList<QSslError> &errors);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager manager;
|
||||
|
|
Loading…
Reference in New Issue