Skip to content
API Documentation
BastionGuard™
ScanPage.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#include "Resource.hpp"
22#include "ScanPage.hpp"
23#include "Backend.hpp"
24#include "Quarantine.hpp"
25#include "Utils.hpp"
27#include "AurScan.hpp"
28#include <atomic>
29#include <chrono>
30#include <iostream>
31#include <fstream>
32#include <filesystem>
33#include <unordered_set>
34#include <openssl/sha.h>
35#include <nlohmann/json.hpp>
36#include <sys/inotify.h>
37#include <unistd.h>
38
39namespace fs = std::filesystem;
40using json = nlohmann::json;
41
42
43extern std::string globalMalwareBazaarApiKey;
44
45
46static std::unordered_set<std::string> recentlyScanned;
47
48
49// ---------------------------------------------------------------------------
50// Build/compile detection: salta la scansione automatica quando l'utente sta
51// compilando, altrimenti l'inotify scatta su centinaia di file in pochi secondi
52// e il sistema rallenta troppo.
53// ---------------------------------------------------------------------------
54
55// Burst counter: se arrivano troppi eventi in una finestra breve, si presume
56// una compilazione in corso e si entra in modalità "throttle".
57static std::atomic<int> g_recentEventCount{0};
58static std::atomic<int64_t> g_burstWindowStartMs{0};
59static std::atomic<int64_t> g_throttleUntilMs{0};
60
61// Soglie (regolabili)
62static constexpr int kBurstWindowMs = 2000; // finestra di osservazione
63static constexpr int kBurstThreshold = 20; // > N eventi nella finestra = burst
64static constexpr int64_t kThrottleHoldMs = 5000; // dopo un burst, sospende per N ms
65
66static int64_t nowMs() {
67 return std::chrono::duration_cast<std::chrono::milliseconds>(
68 std::chrono::steady_clock::now().time_since_epoch()).count();
69}
70
71static bool ends_with(const std::string& s, const std::string& suf) {
72 return s.size() >= suf.size() &&
73 s.compare(s.size() - suf.size(), suf.size(), suf) == 0;
74}
75
76// Estensioni tipiche di artefatti di compilazione: oggetti, librerie statiche/
77// condivise temporanee, bytecode, ecc. Sui file con queste estensioni saltiamo
78// la scansione automatica perché vengono prodotti a raffica dai compilatori.
79static bool isBuildArtifactExt(const std::string& path) {
80 static const char* exts[] = {
81 ".o", ".obj", ".a", ".lo", ".la", ".pyc", ".pyo",
82 ".class", ".d", ".gch", ".pch", ".mod", ".swp", ".swo",
83 ".rlib", ".rmeta", ".tmp", ".bc", ".ll"
84 };
85 for (auto* e : exts) {
86 if (ends_with(path, e)) return true;
87 }
88 return false;
89}
90
91// Directory tipiche dove i sistemi di build scrivono (CMake, Cargo, Node,
92// Maven/Gradle, autotools, Meson, ecc.). Se il path le contiene, skip.
93static bool isInBuildDir(const std::string& path) {
94 static const char* needles[] = {
95 "/build/", "/cmake-build-", "/CMakeFiles/", "/.ninja_",
96 "/target/", // Cargo / Rust, Maven
97 "/node_modules/", // npm
98 "/dist/", "/out/", "/bin/", "/obj/",
99 "/.gradle/", "/.m2/", "/.cargo/",
100 "/__pycache__/", "/.pytest_cache/",
101 "/.next/", "/.nuxt/", "/.svelte-kit/",
102 "/.tox/", "/.venv/", "/venv/",
103 "/Debug/", "/Release/"
104 };
105 for (auto* n : needles) {
106 if (path.find(n) != std::string::npos) return true;
107 }
108 return false;
109}
110
111static std::string normalizePath(std::string p) {
112 while (!p.empty() && p.back() == '/')
113 p.pop_back();
114 return p;
115}
116
117static std::string trim(const std::string& s) {
118 size_t start = s.find_first_not_of(" \n\r\t");
119 if (start == std::string::npos) return "";
120 size_t end = s.find_last_not_of(" \n\r\t");
121 return s.substr(start, end - start + 1);
122}
123
124
126{
127 if (alive_) alive_->store(false, std::memory_order_release);
128}
129
130
131ScanPage::ScanPage(Gtk::Window& parent)
132: Gtk::Box(Gtk::Orientation::VERTICAL, 5),
133parentWindow(parent)
134{
135 set_margin(8);
136 set_spacing(5);
137
138
139 buttonBox.set_spacing(8);
141 buttonBox.append(btnChooseFile);
142 buttonBox.append(btnScan);
143 append(buttonBox);
144
145 btnChooseFolder.signal_clicked().connect(sigc::mem_fun(*this, &ScanPage::onChooseFolder));
146 btnChooseFile.signal_clicked().connect(sigc::mem_fun(*this, &ScanPage::onChooseFile));
147 btnScan.signal_clicked().connect(sigc::mem_fun(*this, &ScanPage::onStartScan));
148
149 // ===== UI scansione automatica =====
150 autoScanLabel.set_text(_("Scansione automatica"));
151 autoScanBox.set_spacing(8);
154 append(autoScanBox);
155
156 autoScanSwitch.set_active(true);
157 autoScanEnabled = true;
158
159 autoScanSwitch.property_active().signal_changed().connect([this]() {
160 autoScanEnabled = autoScanSwitch.get_active();
161 enqueueLog(autoScanEnabled ? _("⚙️ Scansione automatica attiva")
162 : _("⛔ Scansione automatica disattivata"));
163 if (autoScanEnabled) {
166 } else {
168 }
169 saveConfig();
170 });
171
172 textBuffer = Gtk::TextBuffer::create();
173 textView.set_buffer(textBuffer);
174 textView.set_editable(false);
175 textView.set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
176
177 auto scrollLog = Gtk::make_managed<Gtk::ScrolledWindow>();
178 scrollLog->set_child(textView);
179 scrollLog->set_min_content_height(180);
180 scrollLog->set_expand(true);
181 append(*scrollLog);
182
183
184 auto scrollFiles = Gtk::make_managed<Gtk::ScrolledWindow>();
185 scrollFiles->set_child(fileListBox);
186 scrollFiles->set_min_content_height(150);
187 scrollFiles->set_expand(true);
188 append(*scrollFiles);
189
190
191 apiKeyBox.set_spacing(8);
192 auto lbl = Gtk::make_managed<Gtk::Label>(_("API Key VirusTotal:"));
193 lbl->set_valign(Gtk::Align::CENTER);
194 apiKeyEntry.set_hexpand(true);
195 apiKeyEntry.set_visibility(false);
196 apiKeyBox.append(*lbl);
197 apiKeyBox.append(apiKeyEntry);
198
199 apiKeyBox.set_spacing(8);
200
201
202 lbl->set_valign(Gtk::Align::CENTER);
203
204
205 apiKeyEntry.set_hexpand(true);
206 apiKeyEntry.set_visibility(false);
207 apiKeyEntry.set_valign(Gtk::Align::CENTER);
208
209
210 btnShowApiKey.set_icon_name("view-reveal-symbolic");
211 btnShowApiKey.add_css_class("flat");
212 btnShowApiKey.set_valign(Gtk::Align::CENTER);
213
214
215 btnApiKeySave.set_valign(Gtk::Align::CENTER);
216
217
218 btnPrivacy.set_icon_name("dialog-information-symbolic");
219 btnPrivacy.set_tooltip_text(_("Privacy e dati cloud"));
220 btnPrivacy.add_css_class("flat");
221 btnPrivacy.set_valign(Gtk::Align::CENTER);
222
223 btnShowApiKey.signal_clicked().connect(
224 sigc::mem_fun(*this, &ScanPage::onToggleApiKeyVisibility)
225 );
226
227
228 apiKeyActionsBox.set_valign(Gtk::Align::CENTER);
232
233
234 apiKeySizeGroup = Gtk::SizeGroup::create(Gtk::SizeGroup::Mode::VERTICAL);
235 apiKeySizeGroup->add_widget(apiKeyEntry);
236 apiKeySizeGroup->add_widget(btnShowApiKey);
237 apiKeySizeGroup->add_widget(btnApiKeySave);
238 apiKeySizeGroup->add_widget(btnPrivacy);
239
240
241 apiKeyBox.append(*lbl);
242 apiKeyBox.append(apiKeyEntry);
244
245 append(apiKeyBox);
246
247
248
249 btnPrivacy.set_label(_("Privacy / Dati cloud"));
250 btnPrivacy.set_icon_name("dialog-information");
251 btnPrivacy.set_halign(Gtk::Align::START);
252 btnPrivacy.add_css_class("flat");
253
254 btnPrivacy.signal_clicked().connect(
255 sigc::mem_fun(*this, &ScanPage::showCloudPrivacyDisclaimer)
256 );
257
258
259
260 btnApiKeySave.signal_clicked().connect(sigc::mem_fun(*this, &ScanPage::onApiKeySaveClicked));
261
262 loadConfig();
265
266
267 inotifyFd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
268 if (inotifyFd < 0) {
269 enqueueLog(_("❌ Errore: inotify non inizializzato"));
270 } else {
271 enqueueLog(_("📡 Inotify inizializzato"));
272
273 inotifySource = Glib::IOSource::create(inotifyFd, Glib::IOCondition::IO_IN);
274
275 inotifySource->connect(sigc::mem_fun(*this, &ScanPage::onInotifyEvent));
276 inotifySource->attach(Glib::MainContext::get_default());
277 }
278
279
283}
284
285
286void ScanPage::enqueueLog(const std::string& msg) {
287 std::lock_guard<std::mutex> lock(logMutex);
288 pendingLog.push_back(msg);
289 if (!logFlushScheduled) {
290 logFlushScheduled = true;
291 Glib::signal_timeout().connect_once(
292 [this]() { flushLog(); }, 50
293 );
294 }
295}
296
298 std::vector<std::string> batch;
299 {
300 std::lock_guard<std::mutex> lock(logMutex);
301 batch.swap(pendingLog);
302 logFlushScheduled = false;
303 }
304 Glib::ustring s;
305 for (auto& m : batch) s += m + "\n";
306 textBuffer->insert(textBuffer->end(), s);
307}
308
309
310std::string getConfigPath() {
311 return Glib::get_home_dir() + "/.local/share/BastionGuard/config.json";
312}
313
315 try {
316 fs::create_directories(Glib::get_home_dir() + "/.local/share/BastionGuard");
317 json cfg;
318 cfg["auto_scan_enabled"] = autoScanEnabled;
319 std::ofstream ofs(getConfigPath());
320 ofs << cfg.dump(4);
321 } catch (...) {}
322}
323
325 try {
326 if (!fs::exists(getConfigPath())) return;
327 json cfg;
328 std::ifstream ifs(getConfigPath());
329 ifs >> cfg;
330 autoScanEnabled = cfg.value("auto_scan_enabled", true);
332 } catch (...) {}
333}
334
335void ScanPage::addInotifyRecursive(const std::string& root)
336{
337 namespace fs = std::filesystem;
338
339 if (!fs::exists(root)) return;
340
341 std::string norm = normalizePath(root);
342
343 // Evita duplicati
344 if (alreadyWatched.count(norm)) return;
345
346 // ANTI-BOOTLOOP: se la cartella è una directory di build (CMake, Cargo,
347 // node_modules, .venv, target/, …), NON aggiungere watch. Durante una
348 // compilazione vengono creati migliaia di file in pochi secondi: senza
349 // questo filtro inotify scatena un cascade di eventi che a sua volta
350 // satura il main loop (handleInotifyFileEvent -> clamdscan async ->
351 // ulteriori scritture) → bootloop di ScanPage e degrado del sistema.
352 if (isInBuildDir(norm + "/")) {
353 return;
354 }
355
356 alreadyWatched.insert(norm);
357
358 int wd = inotify_add_watch(
359 inotifyFd,
360 norm.c_str(),
361 IN_CREATE | IN_MOVED_TO | IN_CLOSE_WRITE | IN_ATTRIB
362 );
363
364 if (wd < 0) {
365 enqueueLog(_("⚠️ Impossibile monitorare: ") + norm);
366 return;
367 }
368
369 watchMap[wd] = norm;
370 enqueueLog(_("📁 Watch attivo: ") + norm);
371
372
373 try {
374 for (auto& entry : fs::directory_iterator(norm)) {
375 if (entry.is_directory()) {
376 std::string sub = entry.path().string();
377 // ignora cartelle nascoste
378 std::string name = entry.path().filename().string();
379 if (!name.empty() && name[0] == '.')
380 continue;
381
382 // ANTI-BOOTLOOP: non scendere in directory di build note
383 // (build/, target/, node_modules/, CMakeFiles/, ecc.).
384 // Il check con "/" finale forza isInBuildDir a matchare il
385 // segmento completo.
386 if (isInBuildDir(sub + "/")) {
387 continue;
388 }
389
391 }
392 }
393 } catch (...) {
394
395 }
396}
397
398
399bool ScanPage::onInotifyEvent(Glib::IOCondition cond)
400{
401 if (!static_cast<bool>(cond & Glib::IOCondition::IO_IN))
402 return true;
403
404 char buffer[4096]
405 __attribute__((aligned(__alignof__(struct inotify_event))));
406
407 ssize_t len = read(inotifyFd, buffer, sizeof(buffer));
408 if (len <= 0)
409 return true;
410
411 const char* ptr = buffer;
412
413 while (ptr < buffer + len) {
414 const inotify_event* ev =
415 reinterpret_cast<const inotify_event*>(ptr);
416
417 std::string parentDir =
418 watchMap.count(ev->wd) ? watchMap[ev->wd] : "";
419
420 std::string name = ev->len ? std::string(ev->name) : "";
421 std::string fullPath = parentDir + "/" + name;
422
423 handleInotifyFileEvent(fullPath, ev->mask);
424
425 ptr += sizeof(struct inotify_event) + ev->len;
426 }
427
428 return true;
429}
430
432{
433 auto dialog = Gtk::FileDialog::create();
434 dialog->set_title(_("Seleziona cartella"));
435 dialog->set_modal(true);
436
437 dialog->select_folder(
439 sigc::bind(
440 sigc::mem_fun(*this, &ScanPage::onChooseFolderFinish),
441 dialog
442 )
443 );
444}
445
446void ScanPage::onChooseFolderFinish(const Glib::RefPtr<Gio::AsyncResult>& result,
447 Glib::RefPtr<Gtk::FileDialog> dialog)
448{
449 try {
450 auto file = dialog->select_folder_finish(result);
451 if (!file)
452 return;
453
454 selectedPath = file->get_path();
455 enqueueLog(_("📁 Cartella selezionata: ") + selectedPath);
456 }
457 catch (const Glib::Error&) {
458 }
459}
460
461
462
464{
465 auto dialog = Gtk::FileDialog::create();
466 dialog->set_title(_("Seleziona file"));
467 dialog->set_modal(true);
468
469 dialog->open(
471 sigc::bind(
472 sigc::mem_fun(*this, &ScanPage::onChooseFileFinish),
473 dialog
474 )
475 );
476}
477
478void ScanPage::onChooseFileFinish(const Glib::RefPtr<Gio::AsyncResult>& result,
479 Glib::RefPtr<Gtk::FileDialog> dialog)
480{
481 try {
482 auto file = dialog->open_finish(result);
483 if (!file)
484 return;
485
486 selectedPath = file->get_path();
487 enqueueLog(_("📄 File selezionato: ") + selectedPath);
488 }
489 catch (const Glib::Error&) {
490 }
491}
492
493
495{
496 std::string path = Glib::get_home_dir() + "/.config/BastionGuard/cloud.conf";
497
498 try {
499 if (!fs::exists(path)) {
501 return;
502 }
503
504 std::ifstream f(path);
505 std::string line;
506
507 while (std::getline(f, line)) {
508 line = trim(line);
509 if (line.empty() || line[0] == '#') continue;
510
511 size_t eq = line.find('=');
512 if (eq == std::string::npos) continue;
513
514 std::string key = trim(line.substr(0, eq));
515 std::string value = trim(line.substr(eq + 1));
516
517 if (key == "malware_bazaar_api_key") {
518 malwareBazaarApiKey = value;
519 }
520 }
521
523 enqueueLog(_("🔑 API Key VirusTotal caricata da cloud.conf"));
525 }
526 catch (...) {
527 enqueueLog(_("⚠️ Errore caricamento cloud.conf"));
528 }
529}
530
531
533{
534 std::string dir = Glib::get_home_dir() + "/.config/BastionGuard";
535 std::string path = dir + "/cloud.conf";
536
537 try {
538 fs::create_directories(dir);
539
540 std::ofstream f(path);
541 f << "malware_bazaar_api_key=" << malwareBazaarApiKey << "\n";
542
543 enqueueLog(_("💾 API Key salvata in cloud.conf"));
545 }
546 catch (...) {
547 enqueueLog(_("❌ Errore salvataggio cloud.conf"));
548 }
549}
550
556
557
558void ScanPage::handleInotifyFileEvent(const std::string& path, uint32_t mask)
559{
560 if (!autoScanEnabled) return;
561 if (path.empty()) return;
562
563 // Filtro directory rumorose / sensibili. Usiamo segmenti con slash su
564 // entrambi i lati per evitare falsi positivi come "/builder" o
565 // "/.localdomain". Il filtro per le build dir vere è isInBuildDir()
566 // poco sotto, qui filtriamo solo cache/git/local privati.
567 if (path.find("/.cache/") != std::string::npos ||
568 path.find("/.local/") != std::string::npos ||
569 path.find("/.git/") != std::string::npos)
570 return;
571
572
573 if (!(mask & (IN_CLOSE_WRITE | IN_MOVED_TO)))
574 return;
575
576
577 if (ends_with(path, ".part") || ends_with(path, ".crdownload"))
578 return;
579
580 // ---- Anti-compilazione: salta artefatti di build e directory di build.
581 // Sui progetti che si compilano si producono molti file in pochi secondi,
582 // se partisse una scansione per ognuno il sistema rallenterebbe troppo
583 // (bootloop di ScanPage). Questo check va fatto PRIMA del burst counter,
584 // così i file di build non lo gonfiano (altrimenti rischiamo di entrare
585 // in throttle anche quando l'utente sta solo scaricando legittimamente).
586 if (isBuildArtifactExt(path) || isInBuildDir(path))
587 return;
588
589 // ---- Anti-compilazione (parte 2): rilevamento "burst di scrittura".
590 // Se in una finestra breve arrivano molti eventi vicini, presumiamo che
591 // sia in corso una compilazione: entriamo in throttle e per qualche
592 // secondo non scansioniamo nulla automaticamente.
593 {
594 const int64_t now = nowMs();
595
596 // Se siamo già in throttle, salta direttamente.
597 if (now < g_throttleUntilMs.load(std::memory_order_relaxed)) {
598 return;
599 }
600
601 // Aggiorna/azzera la finestra di osservazione.
602 int64_t winStart = g_burstWindowStartMs.load(std::memory_order_relaxed);
603 if (winStart == 0 || now - winStart > kBurstWindowMs) {
604 g_burstWindowStartMs.store(now, std::memory_order_relaxed);
605 g_recentEventCount.store(1, std::memory_order_relaxed);
606 } else {
607 int n = g_recentEventCount.fetch_add(1, std::memory_order_relaxed) + 1;
608 if (n > kBurstThreshold) {
609 // Burst rilevato → throttle: niente scansione automatica.
611 std::memory_order_relaxed);
612 g_burstWindowStartMs.store(0, std::memory_order_relaxed);
613 g_recentEventCount.store(0, std::memory_order_relaxed);
614 enqueueLog(_("⏸️ Compilazione/burst rilevato: scansione automatica sospesa temporaneamente"));
615 return;
616 }
617 }
618 }
619
620 if (recentlyScanned.count(path)) return;
621 recentlyScanned.insert(path);
622 Glib::signal_timeout().connect_once([path](){
623 recentlyScanned.erase(path);
624 }, 1500);
625
626
627 Glib::signal_timeout().connect_once(
628 [this, path]() {
629 if (!fs::exists(path)) return;
631 },
632 100
633 );
634}
635
636
637
639{
640 alreadyWatched.clear();
641 watchMap.clear();
642
643 if (inotifyFd >= 0) {
644 close(inotifyFd);
645 inotifyFd = -1;
646 }
647
648 enqueueLog(_("🛑 Tutti i monitor inotify sono stati disattivati"));
649}
650
652{
653 if (scanInProgress) {
654 enqueueLog(_("⏳ È già in corso un'altra scansione..."));
655 return;
656 }
657
658 if (selectedPath.empty()) {
659 textBuffer->set_text(_("⚠️ Nessun file o cartella selezionato.\n"));
660 return;
661 }
662
663 scanInProgress = true;
665
666 textBuffer->set_text(_("🔍 Avvio scansione con clamdscan...\n\n"));
667
668 infectedFilesBuffer.clear();
669 fileListBox.remove_all();
670
671 try {
672 std::vector<std::string> argv = {
673 "clamdscan",
674 "--fdpass",
675 "--no-summary",
676 "--infected",
678 };
679
680 subprocess = Gio::Subprocess::create(
681 argv,
682 Gio::Subprocess::Flags::STDOUT_PIPE |
683 Gio::Subprocess::Flags::STDERR_PIPE
684 );
685
686 stdoutStream = subprocess->get_stdout_pipe();
687 dataStream = Gio::DataInputStream::create(stdoutStream);
688
689 dataStream->read_line_async(
690 sigc::mem_fun(*this, &ScanPage::readOutput),
691 Glib::RefPtr<Gio::Cancellable>(),
692 Glib::PRIORITY_DEFAULT
693 );
694 }
695 catch (const Glib::Error& ex) {
696 textBuffer->insert(textBuffer->end(),
697 _("❌ Errore avvio clamdscan: ") + Glib::ustring(ex.what()) + "\n"
698 );
699 scanInProgress = false;
700 }
701}
702
703
704void ScanPage::readOutput(const Glib::RefPtr<Gio::AsyncResult>& result)
705{
706 try {
707 std::string line;
708 bool ok = dataStream->read_line_finish(result, line);
709
710 if (!ok) {
711 scanInProgress = false;
712 textBuffer->insert(textBuffer->end(), _("✅ Scansione completata.\n"));
713 return;
714 }
715
716 auto alive = alive_;
717 Glib::signal_idle().connect_once([this, alive, line]() {
718 BastionGuard::if_alive(alive, [this, &line]() {
719 textBuffer->insert(textBuffer->end(), line + "\n");
720
721 // Esempio: "Scanning /path/file"
722 if (line.rfind("Scanning ", 0) == 0) {
723 std::string filepath = line.substr(9);
724 addFileToList(filepath);
725 }
726
727 // Esempio: "/file: Virus FOUND"
728 if (line.find("FOUND") != std::string::npos) {
729
730 size_t pos = line.find(": ");
731 size_t end = line.find(" FOUND");
732
733 if (pos != std::string::npos && end != std::string::npos) {
734 std::string infected = line.substr(0, pos);
735 std::string virus = line.substr(pos + 2, end - (pos + 2));
736
737 infectedFilesBuffer.push_back(infected);
738 handleInfectedFileWithName(infected, virus);
739 }
740 }
741 });
742 });
743
744 // Continua lettura
745 dataStream->read_line_async(
746 sigc::mem_fun(*this, &ScanPage::readOutput),
747 Glib::RefPtr<Gio::Cancellable>(),
748 Glib::PRIORITY_DEFAULT
749 );
750 }
751 catch (const Glib::Error& ex) {
752 textBuffer->insert(textBuffer->end(),
753 _("❌ Errore lettura output: ") + Glib::ustring(ex.what()) + "\n"
754 );
755 scanInProgress = false;
756 }
757}
758
759
760void ScanPage::scanFileAutomatically(const std::string& filepath)
761{
762 if (!autoScanEnabled) return;
763 if (!fs::exists(filepath)) return;
764
765 if (!fs::is_regular_file(filepath)) {
766 enqueueLog(_("📁 Ignorato (non è un file regolare): ") + filepath);
767 return;
768 }
769
770 try {
771 std::uintmax_t size = fs::file_size(filepath);
772 if (size < 64) {
773 enqueueLog(_("⏳ File troppo piccolo, potrebbe essere ancora in scrittura: ") + filepath);
774 return;
775 }
776 }
777 catch (...) {
778 enqueueLog(_("❌ Impossibile determinare dimensione file: ") + filepath);
779 return;
780 }
781
782 try {
783 auto size = fs::file_size(filepath);
784 auto timep = fs::last_write_time(filepath).time_since_epoch().count();
785
786 std::string key = filepath + "_" + std::to_string(size) + "_" + std::to_string(timep);
787
788 if (recentlyScanned.count(key)) return;
789 recentlyScanned.insert(key);
790
791 Glib::signal_timeout().connect_once([key]() {
792 recentlyScanned.erase(key);
793 }, 5000);
794
795 enqueueLog(_("🔍 Scansione automatica per: ") + filepath);
796
797 std::vector<std::string> argv = {
798 "clamdscan",
799 "--fdpass",
800 "--no-summary",
801 "--infected",
802 filepath
803 };
804
805 auto proc = Gio::Subprocess::create(
806 argv,
807 Gio::Subprocess::Flags::STDOUT_PIPE
808 );
809
810 auto out = proc->get_stdout_pipe();
811 auto ds = Gio::DataInputStream::create(out);
812
813 auto reader = std::make_shared<std::function<void()>>();
814 *reader = [this, ds, filepath, reader]() {
815 ds->read_line_async(
816 [this, ds, filepath, reader](const Glib::RefPtr<Gio::AsyncResult>& res) {
817 try {
818 std::string line;
819 bool ok = ds->read_line_finish(res, line);
820
821 // EOF: ClamAV non ha trovato nulla
822 if (!ok) {
823 auto alive = alive_;
824 Glib::signal_idle().connect_once([this, alive, filepath]() {
825 BastionGuard::if_alive(alive, [this, &filepath]() {
826 enqueueLog(_("⬜ ClamAV pulito, avvio controllo cloud..."));
827 cloudCheckMalwareBazaar(filepath);
828 });
829 });
830 return;
831 }
832
833 // Trovato malware
834 if (line.find("FOUND") != std::string::npos) {
835 size_t pos = line.find(": ");
836 size_t end = line.find(" FOUND");
837
838 std::string virus = "Sconosciuto";
839 if (pos != std::string::npos && end != std::string::npos)
840 virus = line.substr(pos + 2, end - (pos + 2));
841
842 auto alive = alive_;
843 Glib::signal_idle().connect_once([this, alive, filepath, virus]() {
844 BastionGuard::if_alive(alive, [this, &filepath, &virus]() {
845 handleInfectedFileWithName(filepath, virus);
846 });
847 });
848 return;
849 }
850
851 // Continua a leggere fino a EOF
852 (*reader)();
853 }
854 catch (const Glib::Error& ex) {
855 enqueueLog(std::string(_("❌ Errore scansione automatica: ")) + ex.what());
856 }
857 catch (const std::exception& ex) {
858 enqueueLog(std::string(_("❌ Eccezione scansione automatica: ")) + ex.what());
859 }
860 catch (...) {
861 enqueueLog(_("❌ Errore scansione automatica"));
862 }
863 },
864 Glib::RefPtr<Gio::Cancellable>(),
865 Glib::PRIORITY_DEFAULT
866 );
867 };
868
869 (*reader)();
870 }
871 catch (const std::exception& ex) {
872 enqueueLog(std::string(_("❌ Errore accesso file: ")) + ex.what());
873 }
874}
875
876
877
878void ScanPage::addFileToList(const std::string& filepath)
879{
880 auto row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
881
882 auto icon = Gtk::make_managed<Gtk::Image>();
883 icon->set_from_icon_name("text-x-generic");
884 icon->set_pixel_size(20);
885 row->append(*icon);
886
887 auto lbl = Gtk::make_managed<Gtk::Label>(filepath);
888 lbl->set_hexpand(true);
889 lbl->set_ellipsize(Pango::EllipsizeMode::END);
890 row->append(*lbl);
891
892 fileListBox.append(*row);
893}
894
895std::vector<std::string> ScanPage::detectBrowserDownloadDirs()
896{
897 std::vector<std::string> dirs;
898 std::string home = Glib::get_home_dir();
899
900
901 std::string def = home + "/Downloads";
902 if (fs::exists(def)) dirs.push_back(def);
903
904
905 try {
906 std::string base = home + "/.mozilla/firefox";
907 if (fs::exists(base)) {
908 for (auto& entry : fs::directory_iterator(base)) {
909 if (!entry.is_directory()) continue;
910
911 fs::path prefs = entry.path() / "prefs.js";
912 if (!fs::exists(prefs)) continue;
913
914 std::ifstream f(prefs);
915 std::string line;
916
917 while (std::getline(f, line)) {
918 if (line.find("browser.download.dir") != std::string::npos) {
919 size_t s = line.find('"', line.find(',') + 1);
920 size_t e = line.find('"', s + 1);
921 if (s != std::string::npos && e != std::string::npos) {
922 std::string d = line.substr(s + 1, e - s - 1);
923 if (fs::exists(d)) dirs.push_back(d);
924 }
925 }
926 }
927 }
928 }
929 } catch (...) {}
930
931
932 std::vector<std::string> cfgs = {
933 home + "/.config/google-chrome/Default/Preferences",
934 home + "/.config/chromium/Default/Preferences",
935 home + "/.config/microsoft-edge/Default/Preferences"
936 };
937
938 for (auto& pref : cfgs) {
939 try {
940 if (!fs::exists(pref)) continue;
941
942 std::ifstream f(pref);
943 std::string content(
944 (std::istreambuf_iterator<char>(f)),
945 std::istreambuf_iterator<char>()
946 );
947
948 std::string key = "\"default_directory\"";
949 size_t pos = content.find(key);
950 if (pos == std::string::npos) continue;
951
952 size_t s = content.find('"', pos + key.size());
953 size_t e = content.find('"', s + 1);
954
955 if (s != std::string::npos && e != std::string::npos) {
956 std::string d = content.substr(s + 1, e - s - 1);
957 if (fs::exists(d)) dirs.push_back(d);
958 }
959 } catch (...) {}
960 }
961
962
963 std::sort(dirs.begin(), dirs.end());
964 dirs.erase(std::unique(dirs.begin(), dirs.end()), dirs.end());
965
966 return dirs;
967}
968
969
970bool ScanPage::isOutsideKnownDownloadDirs(const std::string& path)
971{
972 for (const auto& d : knownDownloadDirs) {
973 if (path.rfind(d, 0) == 0) return false;
974 }
975 return true;
976}
977
978
980{
982
983 for (const auto& d : knownDownloadDirs) {
984 if (!fs::exists(d)) continue;
986 }
987
988 enqueueLog(_("📡 Monitoraggio Download attivo (inotify)"));
989}
990
991
992std::vector<std::string> ScanPage::getXdgUserDirs()
993{
994 std::vector<std::string> out;
995
996 std::string p = Glib::get_home_dir() + "/.config/user-dirs.dirs";
997 if (!fs::exists(p)) return out;
998
999 std::ifstream f(p);
1000 std::string line;
1001
1002 while (std::getline(f, line)) {
1003 if (line.find("XDG_") != 0 || line.find("_DIR=") == std::string::npos)
1004 continue;
1005
1006 size_t q1 = line.find('"');
1007 size_t q2 = line.find('"', q1 + 1);
1008
1009 if (q1 == std::string::npos || q2 == std::string::npos)
1010 continue;
1011
1012 std::string value = line.substr(q1 + 1, q2 - q1 - 1);
1013 if (value.find("$HOME") == 0)
1014 value.replace(0, 5, Glib::get_home_dir());
1015
1016 if (fs::exists(value))
1017 out.push_back(value);
1018 }
1019
1020 return out;
1021}
1022
1024{
1025 // ANTI-BOOTLOOP: NON monitorare $HOME né /tmp ricorsivamente.
1026 // Un singolo `make -j` in qualsiasi cartella del home (incluso il
1027 // checkout di BastionGuard stesso) creerebbe migliaia di file
1028 // sotto build/CMakeFiles/, e l'inotify watcher genererebbe una
1029 // cascata di eventi → scansioni clamdscan in serie → main loop
1030 // bloccato → "bootloop" visivo della ScanPage.
1031 //
1032 // Limitiamo i watch alle XDG user dirs (Downloads, Desktop,
1033 // Documents, …) e a ~/.var/app, che è l'unico path Flatpak
1034 // dove i browser scrivono i download.
1035 std::vector<std::string> dirs;
1036
1037 auto xdg = getXdgUserDirs();
1038 dirs.insert(dirs.end(), xdg.begin(), xdg.end());
1039
1040 std::string varApp = Glib::get_home_dir() + "/.var/app";
1041 if (fs::exists(varApp)) dirs.push_back(varApp);
1042
1043 std::sort(dirs.begin(), dirs.end());
1044 dirs.erase(std::unique(dirs.begin(), dirs.end()), dirs.end());
1045
1046 for (auto& d : dirs) {
1047 if (!fs::exists(d)) continue;
1048 // addInotifyRecursive a sua volta filtra le build dirs
1049 // (per esempio se l'utente ha ~/Documents/progetti/foo/build/).
1051 }
1052
1053 enqueueLog(_("🌍 Monitor globale inotify attivo (XDG dirs)"));
1054}
1055
1056
1057void ScanPage::logMessage(const std::string& msg)
1058{
1059 textBuffer->insert(textBuffer->end(), msg + "\n");
1060}
1061
1062
1063void ScanPage::onAurPkgbuildDetected(const std::string& path)
1064{
1065 // Caso speciale: INFECTED:PATH
1066 if (path.rfind("INFECTED:", 0) == 0) {
1067 std::string real = path.substr(11);
1068 enqueueLog(_("⚠️ PKGBUILD sospetto: ") + real);
1069 handleInfectedFileWithName(real, "AUR.StaticHeuristic");
1070 return;
1071 }
1072
1073 enqueueLog(_("📦 PKGBUILD rilevato: ") + path);
1074
1075
1077
1078
1079 scanAurAuto(path);
1080}
1081
1082void ScanPage::scanAurAuto(const std::string& path)
1083{
1084 if (!fs::exists(path)) {
1085 enqueueLog(_("❌ PKGBUILD non trovato: ") + path);
1086 return;
1087 }
1088
1089 enqueueLog(_("🦠 Scansione AUR: ") + path);
1090
1091 std::vector<std::string> argv = {
1092 "clamdscan",
1093 "--fdpass",
1094 "--no-summary",
1095 "--infected",
1096 path
1097 };
1098
1099 try {
1100 auto proc = Gio::Subprocess::create(
1101 argv,
1102 Gio::Subprocess::Flags::STDOUT_PIPE
1103 );
1104
1105 auto out = proc->get_stdout_pipe();
1106 auto ds = Gio::DataInputStream::create(out);
1107
1108 ds->read_line_async(
1109 [this, ds, path](const Glib::RefPtr<Gio::AsyncResult>& res) {
1110 try {
1111 std::string line;
1112 bool ok = ds->read_line_finish(res, line);
1113
1114 if (ok && line.find("FOUND") != std::string::npos) {
1115
1116 size_t pos = line.find(": ");
1117 size_t end = line.find(" FOUND");
1118
1119 std::string virus = "Sconosciuto";
1120 if (pos != std::string::npos && end != std::string::npos)
1121 virus = line.substr(pos + 2, end - (pos + 2));
1122
1123 auto alive = alive_;
1124 Glib::signal_idle().connect_once([this, alive, path, virus]() {
1125 BastionGuard::if_alive(alive, [this, &path, &virus]() {
1126 handleInfectedFileWithName(path, virus);
1127 });
1128 });
1129
1130 return;
1131 }
1132
1133 auto alive = alive_;
1134 Glib::signal_idle().connect_once([this, alive, path]() {
1135 BastionGuard::if_alive(alive, [this, &path]() {
1136 enqueueLog(_("✅ PKGBUILD pulito: ") + path);
1137 });
1138 });
1139 }
1140 catch (...) {
1141 enqueueLog(_("❌ Errore lettura output AUR"));
1142 }
1143 },
1144 Glib::RefPtr<Gio::Cancellable>(),
1145 Glib::PRIORITY_DEFAULT
1146 );
1147 }
1148 catch (...) {
1149 enqueueLog(_("❌ Errore avvio clamdscan per PKGBUILD"));
1150 }
1151}
1152
1153
1154void ScanPage::cloudCheckMalwareBazaarWorker(const std::string& filepath)
1155{
1156 if (!fs::exists(filepath))
1157 return;
1158
1159 std::string sha = MalwareBazaar::sha256_file(filepath);
1160 if (sha.empty())
1161 return;
1162
1164
1165 if (!alive_->load()) return;
1166
1167 // Rientro nel main loop GTK
1168 auto alive = alive_;
1169 Glib::signal_idle().connect_once(
1170 [this, alive, filepath, r]() {
1171 BastionGuard::if_alive(alive, [this, &filepath, &r]() {
1172 if (!r.found) {
1173 enqueueLog(_("🔏 Nessun match cloud → File sicuro."));
1174 return;
1175 }
1176
1177 enqueueLog("🚨 Malware identificato:");
1178 enqueueLog("Nome: " + r.malwareName);
1179 enqueueLog("Tipo: " + r.fileType);
1180 enqueueLog("Tags: " + r.tags);
1181
1182 try {
1183 auto conn = Gio::DBus::Connection::get_sync(
1184 Gio::DBus::BusType::SESSION
1185 );
1186 auto proxy = Gio::DBus::Proxy::create_sync(
1187 conn,
1188 "org.BastionGuard.Ransomware.Alert",
1189 "/org/BastionGuard/ransomware/alert",
1190 "org.BastionGuard.Ransomware.Alert"
1191 );
1192
1193 auto params = Glib::VariantContainerBase::create_tuple({
1194 Glib::Variant<Glib::ustring>::create(filepath),
1195 Glib::Variant<Glib::ustring>::create(
1196 "VirusTotal: " + r.malwareName)
1197 });
1198
1199 proxy->call_sync("ShowAlert", params);
1200 }
1201 catch (...) {
1203 &parentWindow,
1204 (Glib::ustring("Malware cloud:\n") + filepath +
1205 "\nNome: " + r.malwareName).raw(),
1206 Gtk::MessageType::WARNING
1207 );
1208 }
1209 });
1210 }
1211 );
1212}
1213
1214static std::atomic<int> activeCloudChecks{0};
1215
1216void ScanPage::cloudCheckMalwareBazaar(const std::string& filepath)
1217{
1218 if (activeCloudChecks.load() >= 4) {
1219 enqueueLog(_("⏳ Troppi controlli cloud in corso, salto temporaneamente."));
1220 return;
1221 }
1222
1223 enqueueLog(_("🌐 Avvio controllo cloud..."));
1225
1226 auto alive = alive_;
1227 std::thread([this, alive, filepath]() {
1228 try {
1230 }
1231 catch (const std::exception& ex) {
1232 if (!alive->load()) { activeCloudChecks--; return; }
1233 Glib::signal_idle().connect_once([this, alive, msg = std::string(ex.what())]() {
1234 BastionGuard::if_alive(alive, [this, &msg]() {
1235 enqueueLog(std::string(_("❌ Errore controllo cloud: ")) + msg);
1236 });
1237 });
1238 }
1239 catch (...) {
1240 if (!alive->load()) { activeCloudChecks--; return; }
1241 Glib::signal_idle().connect_once([this, alive]() {
1242 BastionGuard::if_alive(alive, [this]() {
1243 enqueueLog(_("❌ Errore controllo cloud sconosciuto."));
1244 });
1245 });
1246 }
1247
1249 }).detach();
1250}
1251
1252
1253void ScanPage::handleInfectedFile(const std::string& filepath)
1254{
1255 handleInfectedFileWithName(filepath, "ClamAV.Detected");
1256}
1257
1258
1259void ScanPage::handleInfectedFiles(const std::vector<std::string>& files)
1260{
1261 if (files.empty()) return;
1262
1263 try {
1264 auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SESSION);
1265 auto proxy = Gio::DBus::Proxy::create_sync(
1266 conn,
1267 "org.BastionGuard.Ransomware.Alert",
1268 "/org/BastionGuard/ransomware/alert",
1269 "org.BastionGuard.Ransomware.Alert"
1270 );
1271
1272 for (const auto& f : files) {
1273 auto params = Glib::VariantContainerBase::create_tuple({
1274 Glib::Variant<Glib::ustring>::create(f),
1275 Glib::Variant<Glib::ustring>::create("ClamAV.Detected")
1276 });
1277 proxy->call_sync("ShowAlert", params);
1278
1279 enqueueLog(_("⚠️ Alert inviato per file infetto: ") + f);
1280 }
1281 }
1282 catch (...) {
1284 &parentWindow,
1285 _("File infetto rilevato:\n") + files.front(),
1286 Gtk::MessageType::WARNING
1287 );
1288 }
1289}
1290
1291
1293 const std::string& filepath,
1294 const std::string& virusName)
1295{
1296 try {
1297 auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SESSION);
1298 auto proxy = Gio::DBus::Proxy::create_sync(
1299 conn,
1300 "org.BastionGuard.Ransomware.Alert",
1301 "/org/BastionGuard/ransomware/alert",
1302 "org.BastionGuard.Ransomware.Alert"
1303 );
1304
1305 auto params = Glib::VariantContainerBase::create_tuple({
1306 Glib::Variant<Glib::ustring>::create(filepath),
1307 Glib::Variant<Glib::ustring>::create(virusName)
1308 });
1309
1310 proxy->call_sync("ShowAlert", params);
1311
1312 enqueueLog(_("⚠️ File infetto: ") + filepath + " (" + virusName + ")");
1313 }
1314 catch (...) {
1316 &parentWindow,
1317 _("File infetto:\n") + filepath + "\nVirus: " + virusName,
1318 Gtk::MessageType::WARNING
1319 );
1320 }
1321}
1322
1323
1324void ScanPage::showAlertWindow(const std::string& file, const std::string& family)
1325{
1327 &parentWindow,
1328 _("Minaccia rilevata:\n") + file + _("\nTipo: ") + family,
1329 Gtk::MessageType::WARNING
1330 );
1331}
1332
1333
1334bool ScanPage::safeExists(const std::string& p) noexcept
1335{
1336 try { return fs::exists(p); }
1337 catch (...) { return false; }
1338}
1339
1340std::uintmax_t safeSize(const std::string& p) noexcept
1341{
1342 try { return fs::file_size(p); }
1343 catch (...) { return 0; }
1344}
1345
1347{
1348 auto *dialog = new Gtk::Window();
1349 dialog->set_title("");
1350 dialog->set_default_size(560, 300);
1351 dialog->set_resizable(false);
1352 dialog->set_modal(false);
1353 dialog->add_css_class("app-dialog");
1354 dialog->add_css_class("main-window");
1355 dialog->add_css_class("app-window");
1356
1357 /* CSS globale */
1358 try {
1359 auto css = Gtk::CssProvider::create();
1360 css->load_from_path(resource("BastionGuard.css"));
1361 auto display = Gdk::Display::get_default();
1362 Gtk::StyleContext::add_provider_for_display(
1363 display, css,
1364 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1365 } catch (...) {}
1366
1367 /* Header custom */
1368 auto *header = Gtk::make_managed<Gtk::HeaderBar>();
1369 header->set_show_title_buttons(false);
1370 header->set_decoration_layout("");
1371 header->add_css_class("custom-headerbar");
1372
1373 auto *close_box = Gtk::make_managed<Gtk::Box>(
1374 Gtk::Orientation::HORIZONTAL);
1375 close_box->set_margin_end(6);
1376
1377 try {
1378 auto icon = Gdk::Pixbuf::create_from_file(
1379 resource("icon-close.png"), 28, 28, true);
1380 auto *btn = Gtk::make_managed<Gtk::Button>();
1381 btn->set_child(
1382 *Gtk::make_managed<Gtk::Image>(icon));
1383 btn->add_css_class("header-button");
1384 btn->signal_clicked().connect(
1385 [dialog]() { dialog->close(); delete dialog; });
1386 close_box->append(*btn);
1387 } catch (...) {}
1388
1389 header->pack_end(*close_box);
1390
1391 auto *wrap =
1392 Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
1393 wrap->append(*header);
1394 dialog->set_titlebar(*wrap);
1395
1396 /* Contenuto */
1397 auto *vbox = Gtk::make_managed<Gtk::Box>(
1398 Gtk::Orientation::VERTICAL, 14);
1399 vbox->set_margin(24);
1400
1401 auto title = Gtk::make_managed<Gtk::Label>(
1402 _("🔐 Privacy – Controllo Cloud"));
1403 title->add_css_class("title");
1404 title->set_halign(Gtk::Align::CENTER);
1405
1406 auto msg = Gtk::make_managed<Gtk::Label>(_(
1407 "BastionGuard NON carica alcun file online.\n\n"
1408 "• Nessun contenuto locale viene trasmesso\n"
1409 "• Viene calcolato solo l’hash SHA256 del file\n"
1410 "• L’hash è confrontato con database pubblici\n"
1411 "• Nessun percorso o dato personale viene inviato\n"
1412 "• La API Key resta salvata esclusivamente in locale\n\n"
1413 "Il controllo cloud è una verifica di reputazione, "
1414 "non un upload del file.")
1415 );
1416 msg->set_wrap(true);
1417 msg->set_justify(Gtk::Justification::CENTER);
1418 msg->set_halign(Gtk::Align::CENTER);
1419
1420 auto closeBtn =
1421 Gtk::make_managed<Gtk::Button>("Ho capito");
1422 closeBtn->add_css_class("btn-success");
1423 closeBtn->set_halign(Gtk::Align::CENTER);
1424 closeBtn->signal_clicked().connect(
1425 [dialog]() { dialog->close(); delete dialog; });
1426
1427 vbox->append(*title);
1428 vbox->append(*msg);
1429 vbox->append(*closeBtn);
1430
1431 dialog->set_child(*vbox);
1432 dialog->present();
1433}
1434
1436{
1438
1439 apiKeyEntry.set_visibility(apiKeyVisible);
1440
1441 btnShowApiKey.set_icon_name(
1443 ? "view-conceal-symbolic"
1444 : "view-reveal-symbolic"
1445 );
1446}
static std::string trim(const std::string &s)
Definition Backend.cpp:112
nlohmann::json json
Definition Backend.cpp:2908
#define _(s)
void loadCloudApiKeys()
std::string globalMalwareBazaarApiKey
static std::atomic< int64_t > g_burstWindowStartMs
Definition ScanPage.cpp:58
static int64_t nowMs()
Definition ScanPage.cpp:66
static constexpr int64_t kThrottleHoldMs
Definition ScanPage.cpp:64
std::uintmax_t safeSize(const std::string &p) noexcept
static bool isBuildArtifactExt(const std::string &path)
Definition ScanPage.cpp:79
static constexpr int kBurstThreshold
Definition ScanPage.cpp:63
std::string getConfigPath()
Definition ScanPage.cpp:310
static std::atomic< int > activeCloudChecks
static std::string trim(const std::string &s)
Definition ScanPage.cpp:117
static bool ends_with(const std::string &s, const std::string &suf)
Definition ScanPage.cpp:71
static std::atomic< int > g_recentEventCount
Definition ScanPage.cpp:57
static std::string normalizePath(std::string p)
Definition ScanPage.cpp:111
static std::atomic< int64_t > g_throttleUntilMs
Definition ScanPage.cpp:59
static std::unordered_set< std::string > recentlyScanned
Definition ScanPage.cpp:46
static constexpr int kBurstWindowMs
Definition ScanPage.cpp:62
static bool isInBuildDir(const std::string &path)
Definition ScanPage.cpp:93
static MBResult lookupHash(const std::string &sha256)
static std::string sha256_file(const std::string &filepath)
Glib::RefPtr< Gio::InputStream > stdoutStream
Definition ScanPage.hpp:64
BastionGuard::AliveFlag alive_
Definition ScanPage.hpp:166
bool onInotifyEvent(Glib::IOCondition cond)
Definition ScanPage.cpp:399
Glib::RefPtr< Glib::IOSource > inotifySource
Definition ScanPage.hpp:117
void scanAurAuto(const std::string &path)
void enableAutoScanDownloads()
Definition ScanPage.cpp:979
Gtk::Entry apiKeyEntry
Definition ScanPage.hpp:69
void logMessage(const std::string &msg)
std::vector< std::string > infectedFilesBuffer
Definition ScanPage.hpp:96
Gtk::TextView textView
Definition ScanPage.hpp:56
std::string selectedPath
Definition ScanPage.hpp:95
std::vector< std::string > detectBrowserDownloadDirs()
Definition ScanPage.cpp:895
void showAlertWindow(const std::string &file, const std::string &family)
Gtk::ListBox fileListBox
Definition ScanPage.hpp:59
bool autoScanEnabled
Definition ScanPage.hpp:54
std::vector< std::string > pendingLog
Definition ScanPage.hpp:80
void onStartScan()
Definition ScanPage.cpp:651
std::mutex logMutex
Definition ScanPage.hpp:79
Gtk::Button btnScan
Definition ScanPage.hpp:49
void handleInfectedFile(const std::string &filepath)
Glib::RefPtr< Gtk::SizeGroup > apiKeySizeGroup
Definition ScanPage.hpp:61
bool safeExists(const std::string &p) noexcept
void showCloudPrivacyDisclaimer()
Gtk::Switch autoScanSwitch
Definition ScanPage.hpp:53
void onChooseFolderFinish(const Glib::RefPtr< Gio::AsyncResult > &result, Glib::RefPtr< Gtk::FileDialog > dialog)
Definition ScanPage.cpp:446
void cloudCheckMalwareBazaarWorker(const std::string &filepath)
void onToggleApiKeyVisibility()
void addInotifyRecursive(const std::string &path)
Definition ScanPage.cpp:335
std::unordered_map< int, std::string > watchMap
Definition ScanPage.hpp:120
void saveCloudConfig()
Definition ScanPage.cpp:532
std::vector< std::string > getXdgUserDirs()
Definition ScanPage.cpp:992
void handleInfectedFileWithName(const std::string &filepath, const std::string &virusName)
bool logFlushScheduled
Definition ScanPage.hpp:81
void readOutput(const Glib::RefPtr< Gio::AsyncResult > &result)
Definition ScanPage.cpp:704
Gtk::Box buttonBox
Definition ScanPage.hpp:46
Gtk::Label autoScanLabel
Definition ScanPage.hpp:52
Glib::RefPtr< Gtk::TextBuffer > textBuffer
Definition ScanPage.hpp:57
void handleInfectedFiles(const std::vector< std::string > &files)
Gtk::Button btnChooseFolder
Definition ScanPage.hpp:47
bool scanInProgress
Definition ScanPage.hpp:97
void enqueueLog(const std::string &msg)
Definition ScanPage.cpp:286
void flushLog()
Definition ScanPage.cpp:297
Gtk::Button btnShowApiKey
Definition ScanPage.hpp:70
bool apiKeyVisible
Definition ScanPage.hpp:74
void saveConfig()
Definition ScanPage.cpp:314
void enableGlobalDownloadMonitor()
std::unordered_set< std::string > alreadyWatched
Definition ScanPage.hpp:123
std::string malwareBazaarApiKey
Definition ScanPage.hpp:75
void onAurPkgbuildDetected(const std::string &path)
void onChooseFolder()
Definition ScanPage.cpp:431
~ScanPage() override
Definition ScanPage.cpp:125
bool isOutsideKnownDownloadDirs(const std::string &path)
Definition ScanPage.cpp:970
void onApiKeySaveClicked()
Definition ScanPage.cpp:551
void cloudCheckMalwareBazaar(const std::string &filepath)
Gtk::Box autoScanBox
Definition ScanPage.hpp:51
void scanFileAutomatically(const std::string &filepath)
Definition ScanPage.cpp:760
void addFileToList(const std::string &filepath)
Definition ScanPage.cpp:878
Gtk::Button btnApiKeySave
Definition ScanPage.hpp:71
Gtk::Box apiKeyBox
Definition ScanPage.hpp:68
Glib::RefPtr< Gio::Subprocess > subprocess
Definition ScanPage.hpp:63
Gtk::Window & parentWindow
Definition ScanPage.hpp:43
int inotifyFd
Definition ScanPage.hpp:116
Gtk::Button btnChooseFile
Definition ScanPage.hpp:48
void disableAllMonitors()
Definition ScanPage.cpp:638
Gtk::Button btnPrivacy
Definition ScanPage.hpp:72
void onChooseFile()
Definition ScanPage.cpp:463
std::vector< std::string > knownDownloadDirs
Definition ScanPage.hpp:110
void loadConfig()
Definition ScanPage.cpp:324
Gtk::Box apiKeyActionsBox
Definition ScanPage.hpp:60
void handleInotifyFileEvent(const std::string &path, uint32_t mask)
Definition ScanPage.cpp:558
void onChooseFileFinish(const Glib::RefPtr< Gio::AsyncResult > &result, Glib::RefPtr< Gtk::FileDialog > dialog)
Definition ScanPage.cpp:478
Glib::RefPtr< Gio::DataInputStream > dataStream
Definition ScanPage.hpp:65
void loadCloudConfig()
Definition ScanPage.cpp:494
ScanPage(Gtk::Window &parent)
Definition ScanPage.cpp:131
static void show_message(Gtk::Window *parent, const std::string &message, Gtk::MessageType type=Gtk::MessageType::INFO, const std::string &secondary=std::string())
Definition Utils.cpp:33
void scanPKGBUILD(const std::string &path)
Definition AurScan.cpp:258
void if_alive(const AliveFlag &alive, Fn &&fn)
std::string resource(const std::string &rel)
Definition Resource.hpp:31