From 99d239ea08d82bf2ca578dc9e07b48695e46e4ea Mon Sep 17 00:00:00 2001 From: Toni Date: Sat, 25 Apr 2026 15:05:19 +0200 Subject: [PATCH] open file without editing action --- src/crypto.h | 11 ++++--- src/mainwindow.cc | 75 ++++++++++++++++++++++++++++++++++++++++------- src/mainwindow.h | 1 + src/vault.cc | 54 +++++++++++++++++++--------------- 4 files changed, 102 insertions(+), 39 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 6bc2e89..e9007f6 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -6,23 +6,22 @@ namespace Crypto { -inline Botan::secure_vector -encrypt(const Botan::secure_vector &key, - const Botan::secure_vector &plaintext) { +inline Botan::secure_vector encrypt(const Botan::secure_vector &key, + const std::span &plaintext) { ASSERT(key.size() == 32); static Botan::AutoSeeded_RNG rng; - auto nonce = rng.random_array<24>(); // XChaCha20 is selected automatically based on the nonce size // https://github.com/randombit/botan/blob/master/src/lib/stream/chacha/chacha.cpp#L375 - auto cipher = Botan::AEAD_Mode::create_or_throw( + static auto cipher = Botan::AEAD_Mode::create_or_throw( "ChaCha20Poly1305", Botan::Cipher_Dir::Encryption); + auto nonce = rng.random_array<24>(); cipher->set_key(key); cipher->start(nonce); - Botan::secure_vector ciphertext = plaintext; + Botan::secure_vector ciphertext(plaintext.begin(), plaintext.end()); cipher->finish(ciphertext); Botan::secure_vector res; diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 6f6bac8..756f6a2 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace { @@ -16,6 +17,21 @@ std::string basename(const std::string &path) { return (pos == std::string::npos) ? path : path.substr(pos + 1); } +std::string format_bytes(u64 bytes) { + const std::array units = {"B", "KB", "MB", "GB", "TB"}; + auto size = static_cast(bytes); + i32 unit = 0; + + while (size >= 1024.0 && unit < 5) { + size /= 1024.0; + ++unit; + } + + std::ostringstream out; + out << std::fixed << std::setprecision(2) << size << " " << units.at(unit); + return out.str(); +} + } // namespace MainWindow::MainWindow(QWidget *parent) @@ -106,11 +122,15 @@ MainWindow::MainWindow(QWidget *parent) QStringList paths = QFileDialog::getOpenFileNames(this, "Choose files to add"); for (const auto &path : paths) { - std::ifstream file(path.toStdString(), std::ios::binary); - - std::string content((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); + ui->statusbar->showMessage("Adding " + path + "..."); + QCoreApplication::processEvents(); + std::ifstream file(path.toStdString(), std::ios::binary | std::ios::ate); + ASSERT(file.good()); + auto size = file.tellg(); + file.seekg(0); + std::string content(size, '\0'); + file.read(content.data(), size); m_vault->create_file(basename(path.toStdString()), content); } @@ -134,6 +154,9 @@ MainWindow::MainWindow(QWidget *parent) for (const auto &header : headers) { auto content = m_vault->read_file(header.name); if (content) { + ui->statusbar->showMessage("Extracting " + + QString::fromStdString(header.name) + "..."); + QCoreApplication::processEvents(); std::ofstream file(path.toStdString() + "/" + header.name, std::ios::binary); file.write(content->data(), static_cast(content->size())); @@ -153,7 +176,8 @@ void MainWindow::reload_fs_tree() { auto *item = new QTreeWidgetItem(ui->fsTreeWidget); item->setIcon(0, style()->standardIcon(QStyle::SP_FileIcon)); item->setText(0, QString::fromStdString(header.name)); - item->setText(1, QString::number(header.content_ciphertext_size)); + item->setText(1, QString::fromStdString( + format_bytes(header.content_ciphertext_size))); } for (int i = 0; i < ui->fsTreeWidget->columnCount(); ++i) { ui->fsTreeWidget->resizeColumnToContents(i); @@ -190,6 +214,29 @@ void MainWindow::extract_file(const std::string &filename) { } } +void MainWindow::open_file(const std::string &filename) { + auto content = m_vault->read_file(filename); + if (content) { + QTemporaryDir dir; + ASSERT(dir.isValid()); + + std::string path = dir.path().toStdString() + "/" + filename; + + std::ofstream file(path); + file.write(content->data(), static_cast(content->size())); + file.close(); + + QDesktopServices::openUrl( + QUrl::fromLocalFile(QString::fromStdString(path))); + QMessageBox::information(this, "Open", + "Click OK to delete the opened file."); + + // QTemporaryDir gets deleted when it goes out of scope + } else { + qWarning() << "File to open not found"; + } +} + void MainWindow::edit_file(const std::string &filename) { auto content = m_vault->read_file(filename); if (content) { @@ -236,6 +283,12 @@ void MainWindow::file_context_menu(const QPoint &pos) { connect(preview_action, &QAction::triggered, this, [this, item]() { preview_file(item->text(0).toStdString()); }); + QAction *open_action = + menu.addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView), + "Open in external app"); + connect(open_action, &QAction::triggered, this, + [this, item]() { open_file(item->text(0).toStdString()); }); + QAction *edit_action = menu.addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView), "Edit in external app"); @@ -283,12 +336,14 @@ void MainWindow::dropEvent(QDropEvent *event) { if (!u.isLocalFile()) { continue; } - std::ifstream file(u.toLocalFile().toStdString(), std::ios::binary); + + std::ifstream file(u.toLocalFile().toStdString(), + std::ios::binary | std::ios::ate); ASSERT(file.good()); - - std::string content((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - + auto size = file.tellg(); + file.seekg(0); + std::string content(size, '\0'); + file.read(content.data(), size); m_vault->create_file(basename(u.toLocalFile().toStdString()), content); } ui->statusbar->showMessage( diff --git a/src/mainwindow.h b/src/mainwindow.h index 04fbdac..f42fdde 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -21,6 +21,7 @@ private: void reload_fs_tree(); void preview_file(const std::string &filename); void extract_file(const std::string &filename); + void open_file(const std::string &filename); void edit_file(const std::string &filename); void file_context_menu(const QPoint &pos); }; diff --git a/src/vault.cc b/src/vault.cc index ba78504..035628b 100644 --- a/src/vault.cc +++ b/src/vault.cc @@ -3,6 +3,9 @@ #include "crypto.h" #include +#define WRITE(file, data, size) \ + ASSERT(file.write(to_char_ptr(data), static_cast(size))) + namespace { template constexpr char *to_char_ptr(T *ptr) noexcept { @@ -13,6 +16,10 @@ template constexpr const char *to_char_ptr(const T *ptr) noexcept { return reinterpret_cast(ptr); } +template constexpr std::span to_span(const T &x) { + return std::span(reinterpret_cast(x.data()), x.size()); +} + } // namespace Vault::Vault(std::string path, const std::string &password) @@ -47,16 +54,14 @@ void Vault::create(const std::string &path, const std::string &password) { auto key = Crypto::derive_key(password, salt); const std::string_view content = "LETSGO"; - Botan::secure_vector content_sv(content.begin(), content.end()); - auto check_ciphertext = Crypto::encrypt(key, content_sv); + auto check_ciphertext = Crypto::encrypt(key, to_span(content)); - std::ofstream create(path, std::ios::binary); - create.write("DULL", 4); - create.write(to_char_ptr(&VERSION), sizeof(VERSION)); - create.write(to_char_ptr(salt.data()), 16); - - create.write(to_char_ptr(check_ciphertext.data()), 46); - create.close(); + std::ofstream file(path, std::ios::binary); + WRITE(file, "DULL", 4); + WRITE(file, to_char_ptr(&VERSION), sizeof(VERSION)); + WRITE(file, to_char_ptr(salt.data()), 16); + WRITE(file, to_char_ptr(check_ciphertext.data()), 46); + file.close(); } std::vector Vault::read_file_headers() { @@ -115,21 +120,18 @@ void Vault::create_file(const std::string &filename, m_file.clear(); m_file.seekp(0, std::ios::end); - Botan::secure_vector filename_sv(filename.begin(), filename.end()); - auto filename_ciphertext = Crypto::encrypt(m_key, filename_sv); + auto filename_ciphertext = Crypto::encrypt(m_key, to_span(filename)); u64 filename_ciphertext_size = filename_ciphertext.size(); - Botan::secure_vector content_sv(content.begin(), content.end()); - auto ciphertext = Crypto::encrypt(m_key, content_sv); + auto ciphertext = Crypto::encrypt(m_key, to_span(content)); u64 ciphertext_size = ciphertext.size(); - ASSERT(m_file.write("\0", 1)); // deleted flag - ASSERT(m_file.write(to_char_ptr(&filename_ciphertext_size), sizeof(u64))); - ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()), - static_cast(filename_ciphertext_size))); - ASSERT(m_file.write(to_char_ptr(&ciphertext_size), sizeof(u64))); - ASSERT(m_file.write(to_char_ptr(ciphertext.data()), - static_cast(ciphertext_size))); + bool deleted = false; + WRITE(m_file, &deleted, sizeof(bool)); // deleted flag + WRITE(m_file, &filename_ciphertext_size, sizeof(u64)); + WRITE(m_file, filename_ciphertext.data(), filename_ciphertext_size); + WRITE(m_file, &ciphertext_size, sizeof(u64)); + WRITE(m_file, ciphertext.data(), ciphertext_size); m_file.flush(); } @@ -148,12 +150,18 @@ void Vault::delete_file(const std::string &filename) { if (!header->deleted && header->name == filename) { // zero the ciphertext and mark as deleted m_file.seekp(m_file.tellg()); - for (size_t i = 0; i < header->content_ciphertext_size; i++) { - m_file.write(to_char_ptr("\0"), 1); + std::array zeros = {0}; + u64 remaining = header->content_ciphertext_size; + while (remaining > 0) { + u64 chunk = std::min(remaining, sizeof(zeros)); + WRITE(m_file, zeros.data(), chunk); + remaining -= chunk; } m_file.seekp(current_pos, std::ios::beg); - m_file.write(to_char_ptr("\1"), 1); + bool deleted = true; + WRITE(m_file, &deleted, sizeof(bool)); + m_file.flush(); break; }