Skip to content
API Documentation
BastionGuard™
PhishingPage Class Reference

#include <PhishingPage.hpp>

Inheritance diagram for PhishingPage:

Classes

struct  ParsedResult

Public Member Functions

 PhishingPage ()
virtual ~PhishingPage ()

Private Member Functions

void on_analyze_requested (const Glib::ustring &url)

Static Private Member Functions

static std::string url_encode (const std::string &s)
static std::string fetch_url_body (const std::string &url)
static std::string html_entity_decode (std::string s)
static std::string strip_tags (const std::string &s)
static std::string trim_copy (const std::string &s)
static std::optional< std::string > extract_first (const std::string &text, const std::regex &re, int group=1)
static std::vector< std::string > extract_all_li_after_label (const std::string &html, const std::string &label)
static ParsedResult parse_result_html (const std::string &html)

Private Attributes

Gtk::Label lbl_title_
PhishingCheckCard phishing_card_
Gtk::ScrolledWindow scroller_
Gtk::TextView txt_output_
Gtk::Label lbl_status_
std::atomic< bool > destroyed_ {false}
BastionGuard::AliveFlag alive_ = BastionGuard::make_alive_flag()

Detailed Description

Definition at line 29 of file PhishingPage.hpp.

Constructor & Destructor Documentation

◆ PhishingPage()

PhishingPage::PhishingPage ( )

Definition at line 33 of file PhishingPage.cpp.

34 : Gtk::Box(Gtk::Orientation::VERTICAL, 14)
35{
36 add_css_class("phishing-page");
37
38 set_margin_top(20);
39 set_margin_bottom(20);
40 set_margin_start(20);
41 set_margin_end(20);
42 set_vexpand(true);
43 set_hexpand(true);
44
45 lbl_title_.set_markup("<span weight='bold' size='x-large'>Phishing Scanner</span>");
46 lbl_title_.set_halign(Gtk::Align::START);
47 lbl_title_.set_xalign(0.0f);
48 lbl_title_.add_css_class("page-title");
49 lbl_title_.set_margin_bottom(8);
50 append(lbl_title_);
51
52 phishing_card_.set_margin_bottom(10);
53 append(phishing_card_);
54
56 sigc::mem_fun(*this, &PhishingPage::on_analyze_requested));
57
58 scroller_.set_vexpand(true);
59 scroller_.set_hexpand(true);
60 scroller_.add_css_class("phishing-result-box");
61
62 txt_output_.set_editable(false);
63 txt_output_.set_cursor_visible(false);
64 txt_output_.set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
65 txt_output_.set_monospace(false);
66 txt_output_.add_css_class("phishing-result-text");
67 txt_output_.get_buffer()->set_text(_("Ready."));
68 scroller_.set_child(txt_output_);
69 append(scroller_);
70
71 lbl_status_.set_halign(Gtk::Align::START);
72 lbl_status_.set_xalign(0.0f);
73 lbl_status_.set_text(_("Ready."));
74 lbl_status_.add_css_class("phishing-status");
75 lbl_status_.set_margin_top(4);
76 append(lbl_status_);
77}
#define _(s)
sigc::signal< void(const Glib::ustring &)> & signal_analyze_requested()
PhishingCheckCard phishing_card_
Gtk::TextView txt_output_
Gtk::Label lbl_status_
void on_analyze_requested(const Glib::ustring &url)
Gtk::Label lbl_title_
Gtk::ScrolledWindow scroller_

References _, lbl_status_, lbl_title_, on_analyze_requested(), phishing_card_, scroller_, PhishingCheckCard::signal_analyze_requested(), and txt_output_.

◆ ~PhishingPage()

PhishingPage::~PhishingPage ( )
virtual

Definition at line 79 of file PhishingPage.cpp.

79 {
80 destroyed_ = true;
81 if (alive_) alive_->store(false, std::memory_order_release);
82}
std::atomic< bool > destroyed_
BastionGuard::AliveFlag alive_

References alive_, and destroyed_.

Member Function Documentation

◆ extract_all_li_after_label()

std::vector< std::string > PhishingPage::extract_all_li_after_label ( const std::string & html,
const std::string & label )
staticprivate

Definition at line 174 of file PhishingPage.cpp.

177{
178 std::vector<std::string> out;
179
180 const std::string pattern =
181 "<strong>\\s*" + label + R"(\s*:</strong>\s*<ul>([\s\S]*?)</ul>)";
182
183 std::smatch m;
184 if (!std::regex_search(html, m, std::regex(pattern, std::regex::icase)))
185 return out;
186
187 const std::string ul = m[1].str();
188
189 std::regex li_re(R"(<li>([\s\S]*?)</li>)", std::regex::icase);
190 auto begin = std::sregex_iterator(ul.begin(), ul.end(), li_re);
191 auto end = std::sregex_iterator();
192
193 for (auto it = begin; it != end; ++it) {
194 out.push_back(strip_tags((*it)[1].str()));
195 }
196
197 return out;
198}
static std::string strip_tags(const std::string &s)

References strip_tags().

Referenced by parse_result_html().

◆ extract_first()

std::optional< std::string > PhishingPage::extract_first ( const std::string & text,
const std::regex & re,
int group = 1 )
staticprivate

Definition at line 162 of file PhishingPage.cpp.

166{
167 std::smatch m;
168 if (std::regex_search(text, m, re) && m.size() > static_cast<size_t>(group))
169 return m[group].str();
170
171 return std::nullopt;
172}

Referenced by parse_result_html().

◆ fetch_url_body()

std::string PhishingPage::fetch_url_body ( const std::string & url)
staticprivate

Definition at line 111 of file PhishingPage.cpp.

111 {
112 try {
113 auto proc = Gio::Subprocess::create(
114 {
115 "/usr/bin/curl",
116 "-L",
117 "--silent",
118 "--show-error",
119 "--fail",
120 "--max-time", "20",
121 url
122 },
123 Gio::Subprocess::Flags::STDOUT_PIPE | Gio::Subprocess::Flags::STDERR_PIPE
124 );
125
126 auto result = proc->communicate_utf8("");
127
128 if (proc->get_successful())
129 return result.first.raw();
130 } catch (...) {
131 }
132
133 return {};
134}

Referenced by on_analyze_requested().

◆ html_entity_decode()

std::string PhishingPage::html_entity_decode ( std::string s)
staticprivate

Definition at line 136 of file PhishingPage.cpp.

136 {
137 auto repl = [&](const std::string& from, const std::string& to) {
138 std::size_t pos = 0;
139 while ((pos = s.find(from, pos)) != std::string::npos) {
140 s.replace(pos, from.size(), to);
141 pos += to.size();
142 }
143 };
144
145 repl("&amp;", "&");
146 repl("&lt;", "<");
147 repl("&gt;", ">");
148 repl("&quot;", "\"");
149 repl("&#039;", "'");
150 repl("&nbsp;", " ");
151
152 return s;
153}

Referenced by strip_tags().

◆ on_analyze_requested()

void PhishingPage::on_analyze_requested ( const Glib::ustring & url)
private

Definition at line 293 of file PhishingPage.cpp.

293 {
294 phishing_card_.set_feedback(_("Analysis started..."), false);
295 lbl_status_.set_text(_("Analyzing URL..."));
296 txt_output_.get_buffer()->set_text(_("Starting analysis for: ") + url + "\n");
297
298 auto alive = alive_;
299 std::thread([this, alive, url]() {
300 const std::string endpoint =
301 "https://bastionguard.eu/bastionguard-security-intelligence/?q=" +
302 url_encode(url.raw());
303
304 const std::string html = fetch_url_body(endpoint);
305
306 if (!alive || !alive->load(std::memory_order_acquire))
307 return;
308
309 if (html.empty()) {
310 Glib::signal_idle().connect_once([this, alive]() {
311 BastionGuard::if_alive(alive, [this]() {
313 _("Unable to contact BastionGuard Security Intelligence."), true);
314 lbl_status_.set_text(_("Remote analysis failed."));
315 txt_output_.get_buffer()->set_text(
316 _("The remote BastionGuard Security Intelligence page did not return valid HTML."));
317 });
318 });
319 return;
320 }
321
322 ParsedResult result = parse_result_html(html);
323
324 Glib::signal_idle().connect_once([this, alive, result]() {
325 BastionGuard::if_alive(alive, [this, &result]() {
326 if (result.classification.empty()) {
328 _("Unexpected response format from remote analyzer."), true);
329 lbl_status_.set_text(_("Parsing failed."));
330 txt_output_.get_buffer()->set_text(
331 _("The remote page responded, but no classification block was found."));
332 return;
333 }
334
335 txt_output_.get_buffer()->set_text(result.report_text);
336
337 if (result.classification.find("MALICIOUS") != Glib::ustring::npos) {
338 lbl_status_.set_text(_("Malicious result detected."));
339 phishing_card_.set_feedback(_("Malicious result detected."), true);
340 } else if (result.classification.find("SUSPICIOUS") != Glib::ustring::npos) {
341 lbl_status_.set_text(_("Suspicious result detected."));
342 phishing_card_.set_feedback(_("Suspicious result detected."), true);
343 } else {
344 lbl_status_.set_text(_("Analysis complete."));
345 phishing_card_.set_feedback(_("Analysis complete."), false);
346 }
347 });
348 });
349 }).detach();
350}
void set_feedback(const Glib::ustring &text, bool is_error=false)
static ParsedResult parse_result_html(const std::string &html)
static std::string fetch_url_body(const std::string &url)
static std::string url_encode(const std::string &s)
void if_alive(const AliveFlag &alive, Fn &&fn)

References _, alive_, PhishingPage::ParsedResult::classification, fetch_url_body(), BastionGuard::if_alive(), lbl_status_, parse_result_html(), phishing_card_, PhishingPage::ParsedResult::report_text, PhishingCheckCard::set_feedback(), txt_output_, and url_encode().

Referenced by PhishingPage().

◆ parse_result_html()

PhishingPage::ParsedResult PhishingPage::parse_result_html ( const std::string & html)
staticprivate

Definition at line 200 of file PhishingPage.cpp.

200 {
202
203 if (auto v = extract_first(
204 html,
205 std::regex(
206 R"(<span class="phishing-badge [^"]*">CLASSIFICATION</span>\s*([^<]+))",
207 std::regex::icase)); v) {
208 r.classification = trim_copy(strip_tags(*v));
209 }
210
211 if (auto v = extract_first(
212 html,
213 std::regex(
214 R"(<div class="phishing-sev-level">([^<]+)</div>)",
215 std::regex::icase)); v) {
216 r.severity = trim_copy(strip_tags(*v));
217 }
218
219 if (auto v = extract_first(
220 html,
221 std::regex(
222 R"(<strong>\s*Normalized Host:\s*</strong>\s*<code class="phishing-code">([^<]+)</code>)",
223 std::regex::icase)); v) {
224 r.normalized_host = trim_copy(strip_tags(*v));
225 }
226
227 if (auto v = extract_first(
228 html,
229 std::regex(
230 R"(<strong>\s*Evaluation Chain:\s*</strong>\s*<code class="phishing-code">([^<]+)</code>)",
231 std::regex::icase)); v) {
232 r.evaluation_chain = trim_copy(strip_tags(*v));
233 }
234
235 if (auto v = extract_first(
236 html,
237 std::regex(
238 R"(<strong>\s*Matched Indicator:\s*</strong>\s*<code class="phishing-code">([^<]+)</code>)",
239 std::regex::icase)); v) {
240 r.matched_indicator = trim_copy(strip_tags(*v));
241 }
242
243 if (auto v = extract_first(
244 html,
245 std::regex(
246 R"(<strong>\s*Link Status \‍(Live Probe\):\s*</strong>\s*<span class="phishing-badge [^"]*">\s*([^<]+)\s*</span>)",
247 std::regex::icase)); v) {
248 r.live_probe_status = trim_copy(strip_tags(*v));
249 }
250
251 if (auto v = extract_first(
252 html,
253 std::regex(
254 R"(<div style="margin-top:\.4rem; color:#475569;">\s*([\s\S]*?)\s*</div>)",
255 std::regex::icase)); v) {
256 r.live_probe_text = trim_copy(strip_tags(*v));
257 }
258
259 const auto details = extract_all_li_after_label(html, "Analysis Details");
260 std::ostringstream oss;
261
262 oss << "Classification: "
263 << (r.classification.empty() ? "N/A" : r.classification.raw()) << "\n";
264
265 if (!r.severity.empty())
266 oss << "Severity: " << r.severity << "\n";
267
268 if (!r.normalized_host.empty())
269 oss << "Normalized Host: " << r.normalized_host << "\n";
270
271 if (!r.evaluation_chain.empty())
272 oss << "Evaluation Chain: " << r.evaluation_chain << "\n";
273
274 if (!r.matched_indicator.empty())
275 oss << "Matched Indicator: " << r.matched_indicator << "\n";
276
277 if (!r.live_probe_status.empty())
278 oss << "Live Probe: " << r.live_probe_status << "\n";
279
280 if (!r.live_probe_text.empty())
281 oss << "Live Probe Details: " << r.live_probe_text << "\n";
282
283 if (!details.empty()) {
284 oss << "\nAnalysis Details:\n";
285 for (const auto& d : details)
286 oss << " - " << d << "\n";
287 }
288
289 r.report_text = oss.str();
290 return r;
291}
static std::string trim_copy(const std::string &s)
static std::vector< std::string > extract_all_li_after_label(const std::string &html, const std::string &label)
static std::optional< std::string > extract_first(const std::string &text, const std::regex &re, int group=1)

References extract_all_li_after_label(), extract_first(), strip_tags(), and trim_copy().

Referenced by on_analyze_requested().

◆ strip_tags()

std::string PhishingPage::strip_tags ( const std::string & s)
staticprivate

Definition at line 155 of file PhishingPage.cpp.

155 {
156 std::string out = std::regex_replace(s, std::regex("<[^>]*>"), " ");
157 out = html_entity_decode(out);
158 out = std::regex_replace(out, std::regex(R"(\s+)"), " ");
159 return trim_copy(out);
160}
static std::string html_entity_decode(std::string s)

References html_entity_decode(), and trim_copy().

Referenced by extract_all_li_after_label(), and parse_result_html().

◆ trim_copy()

std::string PhishingPage::trim_copy ( const std::string & s)
staticprivate

Definition at line 84 of file PhishingPage.cpp.

84 {
85 const auto begin = s.find_first_not_of(" \t\r\n");
86 if (begin == std::string::npos)
87 return {};
88
89 const auto end = s.find_last_not_of(" \t\r\n");
90 return s.substr(begin, end - begin + 1);
91}

Referenced by parse_result_html(), and strip_tags().

◆ url_encode()

std::string PhishingPage::url_encode ( const std::string & s)
staticprivate

Definition at line 93 of file PhishingPage.cpp.

93 {
94 std::ostringstream oss;
95 oss << std::hex << std::uppercase;
96
97 for (unsigned char c : s) {
98 if ((c >= 'A' && c <= 'Z') ||
99 (c >= 'a' && c <= 'z') ||
100 (c >= '0' && c <= '9') ||
101 c == '-' || c == '_' || c == '.' || c == '~') {
102 oss << c;
103 } else {
104 oss << '%' << std::setw(2) << std::setfill('0') << static_cast<int>(c);
105 }
106 }
107
108 return oss.str();
109}

Referenced by on_analyze_requested().

Member Data Documentation

◆ alive_

BastionGuard::AliveFlag PhishingPage::alive_ = BastionGuard::make_alive_flag()
private

Definition at line 75 of file PhishingPage.hpp.

Referenced by on_analyze_requested(), and ~PhishingPage().

◆ destroyed_

std::atomic<bool> PhishingPage::destroyed_ {false}
private

Definition at line 68 of file PhishingPage.hpp.

Referenced by ~PhishingPage().

◆ lbl_status_

Gtk::Label PhishingPage::lbl_status_
private

Definition at line 66 of file PhishingPage.hpp.

Referenced by on_analyze_requested(), and PhishingPage().

◆ lbl_title_

Gtk::Label PhishingPage::lbl_title_
private

Definition at line 62 of file PhishingPage.hpp.

Referenced by PhishingPage().

◆ phishing_card_

PhishingCheckCard PhishingPage::phishing_card_
private

Definition at line 63 of file PhishingPage.hpp.

Referenced by on_analyze_requested(), and PhishingPage().

◆ scroller_

Gtk::ScrolledWindow PhishingPage::scroller_
private

Definition at line 64 of file PhishingPage.hpp.

Referenced by PhishingPage().

◆ txt_output_

Gtk::TextView PhishingPage::txt_output_
private

Definition at line 65 of file PhishingPage.hpp.

Referenced by on_analyze_requested(), and PhishingPage().


The documentation for this class was generated from the following files: