1. Overview
The PhishingPage module implements BastionGuard’s interactive phishing analysis page using GTKmm (GTK4). It provides a compact analysis workflow composed of a page title, an input/action card for URL submission, a scrollable result viewer, and a status label for real-time feedback.
The page is designed as a remote-analysis client: it accepts a URL from the user, sends it to the BastionGuard Security Intelligence endpoint, downloads the resulting HTML report, parses relevant indicators from the returned page, and renders a normalized text summary inside the UI.
The module integrates with:
- GTKmm / GTK4 – layout containers, labels, text view, scrolled window, and widget lifecycle
- glibmm i18n – localized labels and runtime messages through
_() - glibmm main loop – deferred UI updates via
Glib::signal_idle() - Gio::Subprocess – remote HTML retrieval through an external
curlsubprocess - std::regex – structured extraction of phishing analysis fields from returned HTML
- Background threading – asynchronous remote analysis without blocking the GTK main thread
- Phishing input card component – emits
signal_analyze_requested()and receives visual feedback updates
2. Page Construction and Layout
2.1 Base Container
PhishingPage derives from Gtk::Box and is initialized as a vertical container with spacing 14:
PhishingPage::PhishingPage()
: Gtk::Box(Gtk::Orientation::VERTICAL, 14)
The page applies the CSS class:
phishing-page
and is configured with symmetric margins and full expansion in both directions.
2.2 Main Visual Sections
The page is composed of four primary UI sections:
- Title label – a bold, large heading reading Phishing Scanner
- Analysis card – an interactive card widget used to request URL analysis
- Scrollable result area – a read-only text view used to display normalized analysis output
- Status label – a compact runtime state indicator shown below the result viewer
The title label uses Pango markup and applies the CSS class:
page-title
The result scroller uses:
phishing-result-box
The embedded text view uses:
phishing-result-text
The status label uses:
phishing-status
Both the result buffer and status label are initialized to:
Ready.
3. Analyze Workflow and Signal Binding
3.1 Analyze Request Signal
The phishing analysis workflow starts from the embedded card widget. During construction, the page connects the card’s analyze signal to:
PhishingPage::on_analyze_requested
This method receives the user-provided URL as a Glib::ustring and becomes the main orchestration point for the remote lookup and parsing process.
3.2 Immediate UI Feedback
When a request begins, the page updates all visible UI state immediately:
- The card feedback is set to
Analysis started... - The status label is set to
Analyzing URL... - The output area is reset with a line showing the URL being analyzed
This gives the user immediate confirmation before the background network operation starts.
3.3 Background Execution Model
The actual network request is executed in a detached background thread to prevent blocking the GTK main loop:
std::thread([this, url]() { ... }).detach();
The target endpoint is constructed by appending the URL-encoded user input to the BastionGuard Security Intelligence query parameter:
https://bastionguard.eu/bastionguard-security-intelligence/?q=<encoded-url>
Once the HTML is downloaded and parsed, all widget updates are marshaled back to the GTK main loop through Glib::signal_idle().connect_once(...).
4. Remote Fetch and URL Handling
4.1 Input Normalization Helpers
The module provides several internal helpers used during request construction and parsing:
trim_copy(s)– removes leading and trailing whitespaceurl_encode(s)– percent-encodes arbitrary URL input for safe query-string transporthtml_entity_decode(s)– decodes a limited set of common HTML entitiesstrip_tags(s)– strips HTML tags, decodes entities, compresses whitespace, and trims the result
The URL encoder preserves RFC-safe characters and percent-encodes all others in uppercase hexadecimal form.
4.2 HTML Download via curl
Remote content retrieval is implemented by fetch_url_body(url), which launches an external curl process through Gio::Subprocess.
The current invocation is equivalent to:
/usr/bin/curl -L --silent --show-error --fail --max-time 20 <url>
Behavioral characteristics:
-Lfollows redirects--silentsuppresses progress output--show-errorpreserves diagnostic stderr on failure--failtreats HTTP errors as command failure--max-time 20limits the remote fetch duration
If the subprocess exits successfully, the function returns the response body; otherwise it returns an empty string.
5. HTML Parsing Strategy
5.1 Parsed Result Model
The page parses remote HTML into a structured internal result object (ParsedResult). The extracted fields include:
classificationseveritynormalized_hostevaluation_chainmatched_indicatorlive_probe_statuslive_probe_textreport_text
The final report_text is a normalized, human-readable summary generated from the extracted fields.
5.2 Regex-Based Extraction
The parser uses targeted regular expressions to extract specific blocks from the returned BastionGuard Security Intelligence HTML.
Field extraction is centralized through:
extract_first(text, regex, group)– returns the first matching capture group as an optional stringextract_all_li_after_label(html, label)– extracts list items from a labeled HTML block such as Analysis Details
The parser currently looks for structured fragments such as:
- Classification inside a
phishing-badgeclassification block - Severity inside
phishing-sev-level - Normalized Host inside a labeled
phishing-codeblock - Evaluation Chain inside a labeled
phishing-codeblock - Matched Indicator inside a labeled
phishing-codeblock - Live Probe Status inside a badge-style labeled block
- Live Probe Details inside a styled informational
<div> - Analysis Details inside a labeled unordered list
The parser is therefore tightly coupled to the current HTML structure of the BastionGuard remote analyzer page.
5.3 HTML Cleanup
Before values are returned to the UI, extracted fragments are normalized through:
- HTML tag removal
- Entity decoding
- Whitespace collapsing
- Trim normalization
This makes the final report independent from inline markup present in the remote response.
6. Result Rendering
6.1 Normalized Text Report
After extraction, the module composes a plain-text summary in a fixed reporting format.
The generated report may contain:
ClassificationSeverityNormalized HostEvaluation ChainMatched IndicatorLive ProbeLive Probe Details- A bullet-style
Analysis Detailssection
If a field is absent, it is either omitted or replaced with N/A in the case of the classification header.
6.2 Output Widget Behavior
The rendered report is written directly into the text buffer of txt_output_. The result view is configured as:
- Read-only
- Cursor hidden
- Word-wrapped
- Non-monospace
This makes the result area suitable for readable narrative output rather than raw log-style text.
7. Classification Logic and UI Feedback
7.1 Success Path
If parsing succeeds and a classification is found, the report is shown in the result area and the page updates the status/feedback according to the classification content.
The current logic checks for classification substrings:
- If classification contains
MALICIOUS, the UI reports a malicious result and marks the card with error-style feedback - If classification contains
SUSPICIOUS, the UI reports a suspicious result and marks the card with error-style feedback - Otherwise, the UI reports generic completion and uses non-error feedback
7.2 Empty or Invalid Remote Response
If the HTML body is empty, the page reports that the BastionGuard Security Intelligence service could not be contacted.
In that case:
- The card shows an error feedback message
- The status label changes to
Remote analysis failed. - The output area shows a message indicating that no valid HTML was returned
7.3 Unexpected HTML Format
If the remote page responds but the expected classification block is missing, the page treats the result as a parsing failure.
In that case:
- The card shows
Unexpected response format from remote analyzer. - The status label changes to
Parsing failed. - The output area explains that no classification block was found
8. Lifecycle and Thread-Safety Considerations
8.1 Destruction Guard
The page maintains an internal destroyed_ flag. The destructor sets:
destroyed_ = true;
This guard is checked both inside the worker thread and inside idle callbacks before touching the UI, reducing the risk of use-after-destroy behavior when the page is closed during an in-flight request.
8.2 Main-Loop UI Updates
All UI mutations after the background fetch are routed through Glib::signal_idle().connect_once(...). This ensures GTK widgets are updated only from the main loop, preserving thread safety.
The network and parsing work itself remains offloaded to a detached worker thread.
9. Security and Runtime Considerations
- Asynchronous execution: remote analysis is performed in a background thread to keep the UI responsive
- Controlled network access: HTML retrieval is delegated to
curlwith redirect support, timeout enforcement, and failure-aware behavior - Bounded request time: the remote fetch uses
--max-time 20to reduce indefinite blocking risk - UI-thread safety: all widget updates are marshaled back to the GTK main thread
- Graceful failure handling: transport failures and malformed remote responses are surfaced with explicit user feedback
- HTML normalization: tags and common entities are stripped before display, producing a clean textual report
- Structure-sensitive parsing: because the implementation relies on regex over HTML, changes to the remote page markup may require parser updates
- Lifecycle robustness: the
destroyed_flag helps prevent invalid UI access after widget destruction