23#include <glibmm/miscutils.h>
26#include <unordered_set>
31namespace fs = std::filesystem;
38 return Glib::get_home_dir() +
"/.config/BastionGuard";
47 j[
"security"] = nlohmann::json::object();
48 j[
"security"][
"secure_payments_enabled"] =
false;
50 j[
"ui"] = nlohmann::json::object();
51 j[
"ui"][
"language"] =
"";
57 std::lock_guard<std::mutex> lk(
mtx_);
62 std::lock_guard<std::mutex> lk(
mtx_);
69 std::lock_guard<std::mutex> lk(
mtx_);
88 if (!fs::exists(path)) {
93 std::ifstream in(path, std::ios::binary);
94 if (!in.good())
return false;
109 const auto tmp = path +
".tmp";
112 fs::create_directories(dir);
114 std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
115 if (!out.good())
return false;
118 if (!out.good())
return false;
122 fs::rename(tmp, path, ec);
124 fs::remove(path, ec);
126 fs::rename(tmp, path, ec);
140 std::lock_guard<std::mutex> lk(
mtx_);
145static std::vector<std::string>
split_dotted(
const std::string& dotted_key) {
146 std::vector<std::string> parts;
148 for (
char c : dotted_key) {
150 if (!cur.empty()) parts.push_back(cur);
156 if (!cur.empty()) parts.push_back(cur);
162 if (parts.empty())
return nullptr;
164 nlohmann::json* node = &root;
165 for (
size_t i = 0; i + 1 < parts.size(); ++i) {
166 const auto& k = parts[i];
167 if (!node->contains(k) || !(*node)[k].is_object()) {
168 (*node)[k] = nlohmann::json::object();
177 if (parts.empty())
return nullptr;
179 const nlohmann::json* node = &root;
180 for (
const auto& k : parts) {
181 if (!node->is_object() || !node->contains(k))
return nullptr;
188 std::lock_guard<std::mutex> lk(
mtx_);
192 if (!n)
return fallback_default;
193 if (n->is_boolean())
return n->get<
bool>();
194 return fallback_default;
198 std::lock_guard<std::mutex> lk(
mtx_);
202 if (parts.empty())
return;
207 (*parent)[parts.back()] = value;
212 std::lock_guard<std::mutex> lk(
mtx_);
216 if (!n)
return fallback_default;
217 if (n->is_number_integer())
return n->get<
int>();
218 if (n->is_number())
return static_cast<int>(n->get<
double>());
219 return fallback_default;
223 std::lock_guard<std::mutex> lk(
mtx_);
227 if (parts.empty())
return;
232 (*parent)[parts.back()] = value;
237 std::lock_guard<std::mutex> lk(
mtx_);
241 if (!n)
return fallback_default;
242 if (n->is_string())
return n->get<std::string>();
243 return fallback_default;
247 std::lock_guard<std::mutex> lk(
mtx_);
251 if (parts.empty())
return;
256 (*parent)[parts.back()] = value;
263 return get_bool(
"security.secure_payments_enabled",
false);
267 set_bool(
"security.secure_payments_enabled", enabled);
279 const std::string lang_conf =
280 Glib::get_home_dir() +
"/.config/BastionGuard/lang.conf";
282 std::filesystem::create_directories(
283 std::filesystem::path(lang_conf).parent_path());
284 std::ofstream f(lang_conf, std::ios::trunc);
286 f <<
"LANG=" << lang <<
".UTF-8\n"
287 <<
"LC_ALL=" << lang <<
".UTF-8\n"
288 <<
"LANGUAGE=" << lang <<
":" << lang.substr(0, 2) <<
"\n";
298 const std::string lang_conf =
299 Glib::get_home_dir() +
"/.config/BastionGuard/lang.conf";
302 std::string from_lang_conf;
304 std::ifstream f(lang_conf);
306 while (std::getline(f, line)) {
308 if (line.rfind(
"LANGUAGE=", 0) == 0) {
309 from_lang_conf = line.substr(9);
311 const auto colon = from_lang_conf.find(
':');
312 if (colon != std::string::npos)
313 from_lang_conf = from_lang_conf.substr(0, colon);
323 const bool settings_is_default =
324 from_settings.empty() || from_settings ==
"en_US";
326 if (settings_is_default && !from_lang_conf.empty()
327 && from_lang_conf !=
"en_US") {
328 lang = from_lang_conf;
331 std::cerr <<
"[i18n] migrated language from lang.conf to settings.json: "
333 }
else if (!from_settings.empty()) {
334 lang = from_settings;
335 std::cerr <<
"[i18n] language from settings.json: " << lang <<
"\n";
336 }
else if (!from_lang_conf.empty()) {
337 lang = from_lang_conf;
338 std::cerr <<
"[i18n] language from lang.conf: " << lang <<
"\n";
340 std::cerr <<
"[i18n] no language configured, using system locale\n";
341 setlocale(LC_ALL,
"");
345 setenv(
"LANGUAGE", lang.c_str(), 1);
346 setenv(
"LANG", (lang +
".UTF-8").c_str(), 1);
347 setenv(
"LC_ALL", (lang +
".UTF-8").c_str(), 1);
348 if (!setlocale(LC_ALL, (lang +
".UTF-8").c_str()))
349 if (!setlocale(LC_ALL, lang.c_str()))
350 setlocale(LC_ALL,
"");
354 return Glib::get_home_dir() +
"/.config/BastionGuard/payments.json";
358 return "/usr/share/BastionGuard/data/payments/payments.json";
362 s.erase(0, s.find_first_not_of(
" \t\r\n"));
363 s.erase(s.find_last_not_of(
" \t\r\n") + 1);
364 std::transform(s.begin(), s.end(), s.begin(),
365 [](
unsigned char c){ return (char)std::tolower(c); });
366 if (!s.empty() && s[0] ==
'.') s.erase(0, 1);
367 if (s.rfind(
"www.", 0) == 0) s.erase(0, 4);
368 auto p = s.find(
':');
369 if (p != std::string::npos) s.erase(p);
377 j[
"updated_at"] =
"2026-01-13";
378 j[
"list"] = nlohmann::json::array({
385 "braintreepayments.com",
386 "braintreegateway.com",
392 "payments.amazon.com"
399 if (!fs::exists(path))
return false;
400 std::ifstream in(path, std::ios::binary);
401 if (!in.good())
return false;
411 fs::create_directories(fs::path(path).parent_path());
412 const auto tmp = path +
".tmp";
415 std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
416 if (!out.good())
return false;
419 if (!out.good())
return false;
423 fs::rename(tmp, path, ec);
425 fs::remove(path, ec);
427 fs::rename(tmp, path, ec);
440 std::lock_guard<std::mutex> lk(
mtx_);
445 if (fs::exists(up))
return;
446 if (fs::exists(sp)) {
448 fs::create_directories(fs::path(up).parent_path());
449 std::ifstream in(sp, std::ios::binary);
450 std::ofstream out(up, std::ios::binary | std::ios::trunc);
451 if (in.good() && out.good()) {
464 if (j.contains(
"list") && j[
"list"].is_array()) {
465 for (
const auto& v : j[
"list"]) {
468 }
else if (j.contains(
"domains") && j[
"domains"].is_array()) {
469 for (
const auto& v : j[
"domains"]) {
473 }
else if (j.is_array()) {
474 for (
const auto& v : j) {
483 std::lock_guard<std::mutex> lk(
mtx_);
485 std::vector<std::string> tmp;
486 nlohmann::json jsys, jusr;
491 std::unordered_set<std::string> uniq;
492 std::vector<std::string> out;
493 out.reserve(tmp.size());
495 for (
auto& d : tmp) {
497 if (d.empty())
continue;
498 if (uniq.insert(d).second) out.push_back(d);
501 std::sort(out.begin(), out.end());
506 std::lock_guard<std::mutex> lk(
mtx_);
510 j[
"updated_at"] =
"2026-01-13";
511 j[
"list"] = nlohmann::json::array();
513 std::unordered_set<std::string> uniq;
514 for (
auto d : domains) {
516 if (d.empty())
continue;
517 if (uniq.insert(d).second) j[
"list"].push_back(d);
524 std::lock_guard<std::mutex> lk(
mtx_);
529 if (!fs::exists(sp))
return false;
532 fs::create_directories(fs::path(up).parent_path());
533 std::ifstream in(sp, std::ios::binary);
534 std::ofstream out(up, std::ios::binary | std::ios::trunc);
535 if (!in.good() || !out.good())
return false;
static std::string normalize_domain(std::string s)
static std::string config_dir_path()
static std::vector< std::string > split_dotted(const std::string &dotted_key)
static void extract_domains_from_payments_json(const nlohmann::json &j, std::vector< std::string > &out)
static std::string user_payments_path()
static void ensure_loaded_locked()
static std::string get_string(const std::string &dotted_key, const std::string &fallback_default)
static nlohmann::json cache_
static bool write_to_disk_locked(const nlohmann::json &j)
static void set_language(const std::string &lang)
static void set_secure_payments_enabled(bool enabled)
static nlohmann::json default_payments_seed()
static std::string settings_file_path()
static bool get_secure_payments_enabled()
static bool save_payment_domains_user(const std::vector< std::string > &domains)
static nlohmann::json defaults()
static bool get_bool(const std::string &dotted_key, bool fallback_default)
static void ensure_payments_json_exists()
static nlohmann::json * get_or_create_node_locked(nlohmann::json &root, const std::string &dotted_key)
static bool reset_to_defaults()
static const nlohmann::json * get_node_locked(const nlohmann::json &root, const std::string &dotted_key)
static std::vector< std::string > get_payment_domains()
static void apply_language()
static void set_int(const std::string &dotted_key, int value)
static std::string get_language()
static bool read_json_file(const std::string &path, nlohmann::json &out)
static void set_bool(const std::string &dotted_key, bool value)
static void set_string(const std::string &dotted_key, const std::string &value)
static std::string system_payments_path()
static int get_int(const std::string &dotted_key, int fallback_default)
static bool reset_payment_domains_from_system()
static bool write_json_atomic(const std::string &path, const nlohmann::json &j)
static bool load_from_disk_locked()