Skip to content
API Documentation
BastionGuard™
block_page.hpp
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#pragma once
22
23#include <string>
24#include <fstream>
25#include <sstream>
26#include <glib/gi18n.h>
27
28static inline std::string load_logo_base64() {
29 static std::string cached;
30 static bool loaded = false;
31 if (loaded) return cached;
32 loaded = true;
33
34 std::ifstream f("/usr/share/BastionGuard/data/logo.png", std::ios::binary);
35 if (!f) return cached;
36
37 std::ostringstream ss;
38 ss << f.rdbuf();
39 const std::string raw = ss.str();
40 if (raw.empty()) return cached;
41
42 static constexpr char b64[] =
43 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
44 std::string out;
45 out.reserve(((raw.size() + 2) / 3) * 4);
46 for (std::size_t i = 0; i < raw.size(); i += 3) {
47 const unsigned char a = static_cast<unsigned char>(raw[i]);
48 const unsigned char b = (i+1 < raw.size()) ? static_cast<unsigned char>(raw[i+1]) : 0;
49 const unsigned char c = (i+2 < raw.size()) ? static_cast<unsigned char>(raw[i+2]) : 0;
50 out += b64[(a >> 2) & 0x3F];
51 out += b64[((a << 4) | (b >> 4)) & 0x3F];
52 out += (i+1 < raw.size()) ? b64[((b << 2) | (c >> 6)) & 0x3F] : '=';
53 out += (i+2 < raw.size()) ? b64[c & 0x3F] : '=';
54 }
55 cached = "data:image/png;base64," + out;
56 return cached;
57}
58
59namespace BlockPage {
60
61inline std::string make(const std::string& host, const std::string& type) {
62
63 const std::string logo_uri = load_logo_base64();
64 const std::string logo_html = logo_uri.empty()
65 ? R"(<span style="font-size:28px;line-height:40px;">&#x1F6E1;</span>)"
66 : R"(<img src=")" + logo_uri + R"(" alt="BastionGuard" width="36" height="36" style="border-radius:6px;">)";
67
68 const std::string js_type = (type == "bank") ? "bank" : "payment";
69
70 std::string body = R"(<!DOCTYPE html>
71<html lang="en" dir="ltr">
72<head>
73<meta charset="UTF-8">
74<meta name="viewport" content="width=device-width,initial-scale=1">
75<title>BastionGuard</title>
76<style>
77 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
78 :root {
79 --sidebar: #3a6ea5;
80 --sidebar-dk: #2c5d8a;
81 --sidebar-lt: rgba(255,255,255,0.15);
82 --bg: #f8f9fa;
83 --card-bg: #ffffff;
84 --border: #dee2e6;
85 --text: #212529;
86 --muted: #6c757d;
87 --green: #198754;
88 --green-bg: rgba(25,135,84,0.10);
89 --blue-chip: #eef3fb;
90 --blue-chip-border: #c6d9f0;
91 --font: system-ui,-apple-system,sans-serif;
92 }
93 html,body { height:100%; font-family:var(--font); font-size:14px;
94 color:var(--text); background:var(--bg); }
95
96 /* ── Layout ── */
97 .layout { display:flex; min-height:100vh; }
98
99 /* ── Sidebar ── */
100 .sidebar {
101 width:220px; flex-shrink:0;
102 background:var(--sidebar);
103 display:flex; flex-direction:column;
104 }
105 .sidebar-header {
106 display:flex; align-items:center; gap:10px;
107 padding:14px 16px;
108 border-bottom:1px solid var(--sidebar-lt);
109 }
110 .sidebar-brand {
111 font-weight:700; font-size:15px; color:#fff;
112 }
113 .sidebar-section-title {
114 padding:18px 16px 8px;
115 font-size:11px; font-weight:700;
116 color:rgba(255,255,255,0.5);
117 letter-spacing:.08em; text-transform:uppercase;
118 }
119 .sidebar-active-item {
120 display:flex; align-items:center; gap:10px;
121 margin:0 10px; padding:8px 10px;
122 border-radius:8px;
123 background:rgba(255,255,255,0.22);
124 color:#fff; font-weight:600; font-size:14px;
125 }
126 .sidebar-active-item svg {
127 flex-shrink:0; opacity:.9;
128 }
129
130 /* ── Main ── */
131 .main {
132 flex:1; display:flex; flex-direction:column;
133 align-items:center; justify-content:center;
134 padding:40px 32px;
135 }
136 .card {
137 background:var(--card-bg);
138 border:1px solid var(--border);
139 border-radius:12px;
140 box-shadow:0 1px 3px rgba(0,0,0,0.08);
141 padding:32px 28px 24px;
142 width:100%; max-width:520px;
143 text-align:center;
144 }
145 .type-icon { font-size:48px; line-height:1; margin-bottom:16px; display:block; }
146 .card-heading { font-size:17px; font-weight:700; color:var(--text); margin-bottom:12px; }
147 .domain-chip {
148 display:inline-flex; align-items:center; gap:6px;
149 background:var(--blue-chip); border:1px solid var(--blue-chip-border);
150 border-radius:8px; padding:5px 14px;
151 font-family:'Menlo','Consolas','Liberation Mono',monospace;
152 font-size:13px; color:var(--sidebar); font-weight:600;
153 margin-bottom:16px;
154 }
155 .badge {
156 display:inline-flex; align-items:center; gap:7px;
157 background:var(--green-bg); border:1px solid rgba(25,135,84,0.25);
158 color:var(--green); border-radius:999px; padding:4px 14px;
159 font-size:12px; font-weight:600;
160 letter-spacing:.04em; text-transform:uppercase;
161 margin-bottom:20px;
162 }
163 .badge-dot {
164 width:7px; height:7px; border-radius:50%;
165 background:var(--green); box-shadow:0 0 0 2px var(--green-bg);
166 animation:pulse 2s ease-in-out infinite;
167 }
168 @keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
169 .description {
170 color:var(--muted); line-height:1.65; font-size:13.5px; margin-bottom:20px;
171 }
172 .description strong { color:var(--text); font-weight:600; }
173 .tip {
174 background:var(--bg); border:1px solid var(--border);
175 border-left:3px solid var(--sidebar);
176 border-radius:0 8px 8px 0;
177 padding:10px 14px; text-align:left;
178 font-size:12.5px; color:var(--muted); line-height:1.5;
179 }
180 [dir=rtl] .tip {
181 border-left:1px solid var(--border);
182 border-right:3px solid var(--sidebar);
183 border-radius:8px 0 0 8px; text-align:right;
184 }
185 .watermark {
186 margin-top:18px; font-size:11px; color:#adb5bd;
187 letter-spacing:.06em; text-transform:uppercase;
188 }
189</style>
190</head>
191<body>
192<div class="layout">
193
194 <!-- Sidebar -->
195 <aside class="sidebar">
196 <div class="sidebar-header">
197 )" + logo_html + R"(
198 <span class="sidebar-brand">BastionGuard</span>
199 </div>
200 <div class="sidebar-section-title" id="sb-section"></div>
201 <div class="sidebar-active-item">
202 <!-- icona lucchetto -->
203 <svg width="18" height="18" viewBox="0 0 24 24" fill="none"
204 stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
205 <rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
206 <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
207 </svg>
208 <span id="sb-label"></span>
209 </div>
210 </aside>
211
212 <!-- Contenuto -->
213 <main class="main">
214 <div class="card">
215 <span class="type-icon" id="bg-icon"></span>
216 <div class="card-heading" id="bg-heading"></div>
217 <div class="domain-chip" id="bg-domain"></div>
218 <div style="margin-bottom:4px">
219 <span class="badge">
220 <span class="badge-dot"></span>
221 <span id="bg-badge"></span>
222 </span>
223 </div>
224 <p class="description" id="bg-desc"></p>
225 <div class="tip" id="bg-tip"></div>
226 <div class="watermark">BastionGuard&#x2122; &middot; Secure Isolation Layer</div>
227 </div>
228 </main>
229
230</div>
231<script>
232(function(){
233var HOST=')" + host + R"(';
234var TYPE=')" + js_type + R"(';
235var ICON=TYPE==='bank'?'\uD83C\uDFE6':'\uD83D\uDCB3';
236
237var T={
238 en:{
239 section:'Security',
240 sidebarLabel:'Protected Session',
241 pageTitle:'Protected Session',
242 heading:'Protected session opened',
243 bank:'Banking institution', payment:'Secure payment',
244 verified:'Verified',
245 desc:'BastionGuard has opened <strong>'+HOST+'</strong> in an isolated, protected window.<br><br>This tab has no access to the site. Use the BastionGuard window that just opened.',
246 tip:'\uD83D\uDCA1 You can safely close this tab. The secure session is already active in the sandbox.',
247 dir:'ltr'
248 },
249 it:{
250 section:'Sicurezza',
251 sidebarLabel:'Sessione protetta',
252 pageTitle:'Sessione protetta',
253 heading:'Sessione protetta aperta',
254 bank:'Istituto bancario', payment:'Pagamento sicuro',
255 verified:'Verificato',
256 desc:'BastionGuard ha aperto <strong>'+HOST+'</strong> in una finestra isolata e protetta.<br><br>Questa scheda non ha accesso al sito. Usa la finestra BastionGuard appena aperta.',
257 tip:'\uD83D\uDCA1 Puoi chiudere tranquillamente questa scheda. La sessione sicura \u00e8 gi\u00e0 attiva nella sandbox.',
258 dir:'ltr'
259 },
260 de:{
261 section:'Sicherheit',
262 sidebarLabel:'Gesch\u00fctzte Sitzung',
263 pageTitle:'Gesch\u00fctzte Sitzung',
264 heading:'Gesch\u00fctzte Sitzung ge\u00f6ffnet',
265 bank:'Kreditinstitut', payment:'Sicherer Zahlungsvorgang',
266 verified:'Verifiziert',
267 desc:'BastionGuard hat <strong>'+HOST+'</strong> in einem isolierten, gesch\u00fctzten Fenster ge\u00f6ffnet.<br><br>Dieser Tab hat keinen Zugriff auf die Website. Verwende das soeben ge\u00f6ffnete BastionGuard-Fenster.',
268 tip:'\uD83D\uDCA1 Du kannst diesen Tab sicher schlie\u00dfen. Die sichere Sitzung ist bereits in der Sandbox aktiv.',
269 dir:'ltr'
270 },
271 ar:{
272 section:'\u0627\u0644\u0623\u0645\u0627\u0646',
273 sidebarLabel:'\u062c\u0644\u0633\u0629 \u0645\u062d\u0645\u064a\u0629',
274 pageTitle:'\u062c\u0644\u0633\u0629 \u0645\u062d\u0645\u064a\u0629',
275 heading:'\u062a\u0645 \u0641\u062a\u062d \u062c\u0644\u0633\u0629 \u0645\u062d\u0645\u064a\u0629',
276 bank:'\u0645\u0624\u0633\u0633\u0629 \u0645\u0635\u0631\u0641\u064a\u0629', payment:'\u062f\u0641\u0639 \u0622\u0645\u0646',
277 verified:'\u0645\u0648\u062b\u0648\u0642',
278 desc:'\u0641\u062a\u062d BastionGuard \u0645\u0648\u0642\u0639 <strong>'+HOST+'</strong> \u0641\u064a \u0646\u0627\u0641\u0630\u0629 \u0645\u0639\u0632\u0648\u0644\u0629 \u0648\u0645\u062d\u0645\u064a\u0629.<br><br>\u0644\u0627 \u064a\u0645\u0643\u0646 \u0644\u0647\u0630\u0627 \u0627\u0644\u062a\u0628\u0648\u064a\u0628 \u0627\u0644\u0648\u0635\u0648\u0644 \u0625\u0644\u0649 \u0627\u0644\u0645\u0648\u0642\u0639. \u0627\u0633\u062a\u062e\u062f\u0645 \u0646\u0627\u0641\u0630\u0629 BastionGuard \u0627\u0644\u062a\u064a \u062a\u0645 \u0641\u062a\u062d\u0647\u0627 \u0644\u0644\u062a\u0648\u060c.',
279 tip:'\uD83D\uDCA1 \u064a\u0645\u0643\u0646\u0643 \u0625\u063a\u0644\u0627\u0642 \u0647\u0630\u0627 \u0627\u0644\u062a\u0628\u0648\u064a\u0628 \u0628\u0623\u0645\u0627\u0646. \u0627\u0644\u062c\u0644\u0633\u0629 \u0627\u0644\u0622\u0645\u0646\u0629 \u0646\u0634\u0637\u0629 \u0628\u0627\u0644\u0641\u0639\u0644 \u0641\u064a \u0628\u064a\u0626\u0629 \u0627\u0644\u062a\u0637\u0628\u064a\u0642 \u0627\u0644\u0627\u0641\u062a\u0631\u0627\u0636\u064a.',
280 dir:'rtl'
281 },
282 fr:{
283 section:'S\u00e9curit\u00e9',
284 sidebarLabel:'Session prot\u00e9g\u00e9e',
285 pageTitle:'Session prot\u00e9g\u00e9e',
286 heading:'Session prot\u00e9g\u00e9e ouverte',
287 bank:'\u00c9tablissement bancaire', payment:'Paiement s\u00e9curis\u00e9',
288 verified:'V\u00e9rifi\u00e9',
289 desc:'BastionGuard a ouvert <strong>'+HOST+'</strong> dans une fen\u00eatre isol\u00e9e et prot\u00e9g\u00e9e.<br><br>Cet onglet n\u2019a pas acc\u00e8s au site. Utilisez la fen\u00eatre BastionGuard qui vient de s\u2019ouvrir.',
290 tip:'\uD83D\uDCA1 Vous pouvez fermer cet onglet en toute s\u00e9curit\u00e9. La session s\u00e9curis\u00e9e est d\u00e9j\u00e0 active dans le bac \u00e0 sable.',
291 dir:'ltr'
292 },
293 ja:{
294 section:'\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3',
295 sidebarLabel:'\u4fdd\u8b77\u30bb\u30c3\u30b7\u30e7\u30f3',
296 pageTitle:'\u4fdd\u8b77\u30bb\u30c3\u30b7\u30e7\u30f3',
297 heading:'\u4fdd\u8b77\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u958b\u304d\u307e\u3057\u305f',
298 bank:'\u91d1\u878d\u6a5f\u95a2', payment:'\u5b89\u5168\u306a\u6c7a\u6e08',
299 verified:'\u8a8d\u8a3c\u6e08\u307f',
300 desc:'BastionGuard \u306f <strong>'+HOST+'</strong> \u3092\u9694\u96e2\u3055\u308c\u305f\u4fdd\u8b77\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u958b\u3044\u305f\u307e\u3057\u305f\u3002<br><br>\u3053\u306e\u30bf\u30d6\u306f\u30b5\u30a4\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u305b\u3093\u3002\u958b\u3044\u305f BastionGuard \u30a6\u30a3\u30f3\u30c9\u30a6\u3092\u3054\u5229\u7528\u304f\u3060\u3055\u3044\u3002',
301 tip:'\uD83D\uDCA1 \u3053\u306e\u30bf\u30d6\u306f\u5b89\u5168\u306b\u9589\u3058\u3089\u308c\u307e\u3059\u3002\u5b89\u5168\u306a\u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u3067\u3059\u3067\u306b\u30a2\u30af\u30c6\u30a3\u30d6\u3067\u3059\u3002',
302 dir:'ltr'
303 },
304 nl:{
305 section:'Beveiliging',
306 sidebarLabel:'Beveiligde sessie',
307 pageTitle:'Beveiligde sessie',
308 heading:'Beveiligde sessie geopend',
309 bank:'Bankinstelling', payment:'Veilige betaling',
310 verified:'Geverifieerd',
311 desc:'BastionGuard heeft <strong>'+HOST+'</strong> geopend in een ge\u00efsole\u00ebrd, beveiligd venster.<br><br>Dit tabblad heeft geen toegang tot de site. Gebruik het BastionGuard-venster dat zojuist is geopend.',
312 tip:'\uD83D\uDCA1 Je kunt dit tabblad veilig sluiten. De beveiligde sessie is al actief in de sandbox.',
313 dir:'ltr'
314 },
315 pl:{
316 section:'Bezpiecze\u0144stwo',
317 sidebarLabel:'Chroniona sesja',
318 pageTitle:'Chroniona sesja',
319 heading:'Chroniona sesja otwarta',
320 bank:'Instytucja bankowa', payment:'Bezpieczna p\u0142atno\u015b\u0107',
321 verified:'Zweryfikowano',
322 desc:'BastionGuard otworzy\u0142 <strong>'+HOST+'</strong> w izolowanym, chronionym oknie.<br><br>Ta karta nie ma dost\u0119pu do witryny. U\u017cyj okna BastionGuard, kt\u00f3re w\u0142a\u015bnie si\u0119 otworzy\u0142o.',
323 tip:'\uD83D\uDCA1 Mo\u017cesz bezpiecznie zamkn\u0105\u0107 t\u0119 kart\u0119. Bezpieczna sesja jest ju\u017c aktywna w sandboksie.',
324 dir:'ltr'
325 },
326 pt:{
327 section:'Seguran\u00e7a',
328 sidebarLabel:'Sess\u00e3o protegida',
329 pageTitle:'Sess\u00e3o protegida',
330 heading:'Sess\u00e3o protegida aberta',
331 bank:'Institui\u00e7\u00e3o banc\u00e1ria', payment:'Pagamento seguro',
332 verified:'Verificado',
333 desc:'O BastionGuard abriu <strong>'+HOST+'</strong> numa janela isolada e protegida.<br><br>Este separador n\u00e3o tem acesso ao site. Utilize a janela BastionGuard que acabou de abrir.',
334 tip:'\uD83D\uDCA1 Pode fechar este separador com seguran\u00e7a. A sess\u00e3o segura j\u00e1 est\u00e1 ativa na sandbox.',
335 dir:'ltr'
336 },
337 ru:{
338 section:'\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c',
339 sidebarLabel:'\u0417\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u0430\u044f \u0441\u0435\u0441\u0441\u0438\u044f',
340 pageTitle:'\u0417\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u0430\u044f \u0441\u0435\u0441\u0441\u0438\u044f',
341 heading:'\u0417\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u0430\u044f \u0441\u0435\u0441\u0441\u0438\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0430',
342 bank:'\u0411\u0430\u043d\u043a\u043e\u0432\u0441\u043a\u043e\u0435 \u0443\u0447\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435', payment:'\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043b\u0430\u0442\u0451\u0436',
343 verified:'\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e',
344 desc:'BastionGuard \u043e\u0442\u043a\u0440\u044b\u043b <strong>'+HOST+'</strong> \u0432 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c, \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u043e\u043c \u043e\u043a\u043d\u0435.<br><br>\u042d\u0442\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u0430\u0439\u0442\u0443. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u043a\u043d\u043e BastionGuard, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e \u043e\u0442\u043a\u0440\u044b\u043b\u043e\u0441\u044c.',
345 tip:'\uD83D\uDCA1 \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0437\u0430\u043a\u0440\u044b\u0442\u044c \u044d\u0442\u0443 \u0432\u043a\u043b\u0430\u0434\u043a\u0443. \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u0441\u0435\u0441\u0441\u0438\u044f \u0443\u0436\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u0430 \u0432 \u0441\u0430\u043d\u0434\u0431\u043e\u043a\u0441\u0435.',
346 dir:'ltr'
347 }
348};
349
350function getLang(){
351 var ls=navigator.languages||[navigator.language||'en'];
352 for(var i=0;i<ls.length;i++){
353 var c=ls[i].toLowerCase().split('-')[0];
354 if(T[c]) return c;
355 }
356 return 'en';
357}
358
359var lang=getLang(), t=T[lang];
360document.documentElement.lang=lang;
361document.documentElement.dir=t.dir||'ltr';
362document.title='BastionGuard \u2014 '+t.pageTitle;
363
364document.getElementById('sb-section').textContent=t.section;
365document.getElementById('sb-label').textContent=t.sidebarLabel;
366document.getElementById('bg-icon').textContent=ICON;
367document.getElementById('bg-heading').textContent=t.heading;
368document.getElementById('bg-domain').textContent=HOST;
369document.getElementById('bg-badge').textContent=t.verified+' \u00b7 '+(TYPE==='bank'?t.bank:t.payment);
370document.getElementById('bg-desc').innerHTML=t.desc;
371document.getElementById('bg-tip').textContent=t.tip;
372})();
373</script>
374</body>
375</html>)";
376
377 const std::string len = std::to_string(body.size());
378 return "HTTP/1.1 403 Forbidden\r\n"
379 "Content-Type: text/html; charset=UTF-8\r\n"
380 "Content-Length: " + len + "\r\n"
381 "Connection: close\r\n"
382 "X-BastionGuard: intercepted\r\n"
383 "\r\n" + body;
384}
385
386} // namespace BlockPage
static std::string load_logo_base64()
std::string make(const std::string &host, const std::string &type)