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.
273 lines
7.6 KiB
C++
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
|