Files
retoor 3c4334cec9 feat: implement MoloApp plans 01-15 with integration
Full C++23 framework tree: runtime, observability, security,
data, app, realtime, supervisor, clients, CLI, example plus
enterprise tenancy, audit, operability, governance,
supplychain. Postgres via docker compose for tests.
2026-10-23 18:20:04 +02:00

273 lines
7.6 KiB
C++

#include "Data.hpp"
#include <cstdio>
#include "Caps.hpp"
namespace molo::example::fw {
std::string SessionStore::create() {
std::lock_guard<std::mutex> lock(mu_);
std::string id = "sess-" + std::to_string(next_id_++);
sessions_[id] = {};
return id;
}
bool SessionStore::get(const std::string& id,
std::map<std::string, std::string>& out) {
std::lock_guard<std::mutex> lock(mu_);
auto it = sessions_.find(id);
if (it == sessions_.end()) return false;
out = it->second;
return true;
}
void SessionStore::put(const std::string& id, const std::string& key,
const std::string& value) {
std::lock_guard<std::mutex> lock(mu_);
if (!sessions_.count(id) && sessions_.size() >= caps::kMaxSessions) return;
sessions_[id][key] = value;
}
void SessionStore::rotate(const std::string& old_id, std::string& new_id) {
std::lock_guard<std::mutex> lock(mu_);
new_id = "sess-" + std::to_string(next_id_++);
auto it = sessions_.find(old_id);
if (it != sessions_.end()) {
sessions_[new_id] = it->second;
sessions_.erase(it);
} else {
sessions_[new_id] = {};
}
}
std::string demo_hash_password(const std::string& password) {
unsigned long long h = 1469598103934665603ULL;
std::string salted = "demo-salt|" + password;
for (unsigned char c : salted) {
h ^= c;
h *= 1099511628211ULL;
}
char buf[32];
std::snprintf(buf, sizeof(buf), "demo$%016llx", (unsigned long long)h);
return buf;
}
namespace {
int b64val(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
}
std::string b64decode(const std::string& in) {
std::string out;
int acc = 0, bits = 0;
for (char c : in) {
if (c == '=') break;
int v = b64val(c);
if (v < 0) continue;
acc = (acc << 6) | v;
bits += 6;
if (bits >= 8) {
bits -= 8;
out.push_back((char)((acc >> bits) & 0xFF));
}
}
return out;
}
} // namespace
bool parse_basic_auth(const std::string& header, std::string& user,
std::string& password) {
const std::string prefix = "Basic ";
if (header.compare(0, prefix.size(), prefix) != 0) return false;
std::string decoded = b64decode(header.substr(prefix.size()));
std::size_t colon = decoded.find(':');
if (colon == std::string::npos) return false;
user = decoded.substr(0, colon);
password = decoded.substr(colon + 1);
return true;
}
void Auth::add_user(const std::string& username, const std::string& password) {
std::lock_guard<std::mutex> lock(mu_);
users_[username] = demo_hash_password(password);
}
bool Auth::check(const std::string& username,
const std::string& password) const {
std::lock_guard<std::mutex> lock(mu_);
auto it = users_.find(username);
if (it == users_.end()) return false;
return it->second == demo_hash_password(password);
}
std::string backend_name(OrmBackend backend) {
return backend == OrmBackend::Sqlite ? "sqlite" : "postgres";
}
OrmBackend parse_backend(const std::string& name) {
return name == "postgres" ? OrmBackend::Postgres : OrmBackend::Sqlite;
}
OrmDb::OrmDb(OrmBackend backend) : backend_(backend) {}
OrmBackend OrmDb::backend() const { return backend_; }
OrmResult OrmDb::insert(const std::string& table,
const std::map<std::string, std::string>& values) {
std::lock_guard<std::mutex> lock(mu_);
OrmResult res;
auto& cols = schema_[table];
if (cols.empty()) {
if (schema_.size() > caps::kMaxOrmTables) {
res.ok = false;
return res;
}
for (const auto& [k, v] : values) cols.push_back(k);
res.healed = true;
res.healed_note = "auto-created table " + table;
} else {
for (const auto& [k, v] : values) {
bool known = false;
for (const auto& c : cols) {
if (c == k) {
known = true;
break;
}
}
if (!known) {
cols.push_back(k);
res.healed = true;
res.healed_note = "auto-added column " + k + " to " + table;
}
}
}
auto& vec = rows_[table];
if (vec.size() >= caps::kMaxOrmRows) {
res.ok = false;
return res;
}
OrmRow row;
row.columns = values;
vec.push_back(row);
res.rows = {row};
return res;
}
OrmResult OrmDb::select_all(const std::string& table) {
std::lock_guard<std::mutex> lock(mu_);
OrmResult res;
auto it = rows_.find(table);
if (it == rows_.end()) {
if (!schema_.count(table)) {
schema_[table] = {"id"};
res.healed = true;
res.healed_note = "auto-created table " + table;
}
return res;
}
res.rows = it->second;
return res;
}
OrmResult OrmDb::ensure_column(const std::string& table,
const std::string& column) {
std::lock_guard<std::mutex> lock(mu_);
OrmResult res;
auto& cols = schema_[table];
for (const auto& c : cols) {
if (c == column) return res;
}
cols.push_back(column);
res.healed = true;
res.healed_note = "auto-added column " + column + " to " + table;
return res;
}
std::vector<std::string> OrmDb::tables() const {
std::lock_guard<std::mutex> lock(mu_);
std::vector<std::string> out;
for (const auto& [k, v] : schema_) out.push_back(k);
return out;
}
std::vector<std::string> OrmDb::columns(const std::string& table) const {
std::lock_guard<std::mutex> lock(mu_);
auto it = schema_.find(table);
if (it == schema_.end()) return {};
return it->second;
}
bool UploadStore::store(const std::string& filename,
const std::string& content_type,
const std::string& data, UploadInfo& out,
std::string& error) {
if (data.size() > caps::kMaxUploadBytes) {
error = "file too large";
return false;
}
bool allowed = content_type == "text/plain" || content_type == "image/png" ||
content_type == "application/json";
if (!allowed) {
error = "content type not allowed";
return false;
}
if (filename.find("..") != std::string::npos ||
filename.find('/') != std::string::npos) {
error = "unsafe filename";
return false;
}
std::lock_guard<std::mutex> lock(mu_);
out = UploadInfo{filename, content_type, data.size()};
uploads_.push_back(out);
return true;
}
std::vector<UploadInfo> UploadStore::list() const {
std::lock_guard<std::mutex> lock(mu_);
return uploads_;
}
void WebdavStore::put(const std::string& path, const std::string& content) {
std::lock_guard<std::mutex> lock(mu_);
if (!files_.count(path) && files_.size() >= caps::kMaxWebdavEntries) return;
files_[path] = content;
}
bool WebdavStore::get(const std::string& path, std::string& out) const {
std::lock_guard<std::mutex> lock(mu_);
auto it = files_.find(path);
if (it == files_.end()) return false;
out = it->second;
return true;
}
bool WebdavStore::remove(const std::string& path) {
std::lock_guard<std::mutex> lock(mu_);
return files_.erase(path) > 0;
}
std::string WebdavStore::propfind_xml(const std::string& root) const {
std::lock_guard<std::mutex> lock(mu_);
std::string out =
"<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">";
for (const auto& [k, v] : files_) {
if (k.compare(0, root.size(), root) != 0) continue;
out += "<D:response><D:href>" + k +
"</D:href><D:propstat><D:prop><D:getcontentlength>" +
std::to_string(v.size()) +
"</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK"
"</D:status></D:propstat></D:response>";
}
return out + "</D:multistatus>";
}
} // namespace molo::example::fw