// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "transaction_csv.h" #include #include namespace dragonx { namespace data { std::string escapeCsvField(const std::string& field) { // Neutralize spreadsheet formula injection: a field beginning with '=', '+', '-', '@', // tab, or CR is interpreted as a formula by Excel/LibreOffice, letting an attacker-supplied // memo/address execute on open. Prefix such fields with a single quote so they render as text. std::string safe = field; if (!safe.empty()) { const char c0 = safe.front(); if (c0 == '=' || c0 == '+' || c0 == '-' || c0 == '@' || c0 == '\t' || c0 == '\r') safe.insert(safe.begin(), '\''); } if (safe.find(',') != std::string::npos || safe.find('"') != std::string::npos || safe.find('\n') != std::string::npos) { std::string escaped; escaped.reserve(safe.size() + 4); escaped += '"'; for (char c : safe) { if (c == '"') escaped += "\"\""; else escaped += c; } escaped += '"'; return escaped; } return safe; } std::string formatTransactionsCsv(const std::vector& txs) { std::string out; out.reserve(64 + txs.size() * 200); out += "Date,Type,Amount,Address,TXID,Confirmations,Memo\n"; for (const auto& tx : txs) { std::time_t t = static_cast(tx.timestamp); std::tm tmv{}; #ifdef _WIN32 const bool okTm = localtime_s(&tmv, &t) == 0; #else const bool okTm = localtime_r(&t, &tmv) != nullptr; #endif char datebuf[32] = ""; if (okTm) std::strftime(datebuf, sizeof(datebuf), "%Y-%m-%d %H:%M:%S", &tmv); char amt[48]; std::snprintf(amt, sizeof(amt), "%.8f", tx.amount); out += datebuf; out += ','; out += escapeCsvField(tx.type); out += ','; out += amt; out += ','; out += escapeCsvField(tx.address); out += ','; out += escapeCsvField(tx.txid); out += ','; out += std::to_string(tx.confirmations); out += ','; out += escapeCsvField(tx.memo); out += '\n'; } return out; } } // namespace data } // namespace dragonx