Files
radiantwave/src/validation.cpp
T
NeilClack e7df02344b Kiosk: System Name storage, validation, sync, and sent on license validation
Adds local System Name persistence (trimmed, 1-40) in system_kv, an
injectable sync module with conflict/invalid revert and a pending-sync
flag, a background SystemNameSync worker (retries on boot and on a timer),
and api_client support for PUT /licenses/{key}/system-name plus the
additive systemName field/systemNameStatus on license validation. The
license service sends the stored name and applies the status. Covers
validation, pending retry, and conflict revert with a faked HTTP layer.
2026-09-29 09:57:52 -05:00

108 lines
2.8 KiB
C++

#include "validation.h"
#include <cctype>
namespace radiantwave::validation {
bool isValidEmail(const std::string& email) {
if (email.size() < 3 || email.size() > 254) {
return false;
}
const size_t at = email.find('@');
if (at == std::string::npos || at == 0 || at == email.size() - 1) {
return false;
}
if (email.find('@', at + 1) != std::string::npos) {
return false;
}
for (unsigned char c : email) {
if (std::isspace(c)) {
return false;
}
}
const std::string local = email.substr(0, at);
const std::string domain = email.substr(at + 1);
// Local part: alphanumerics plus ._%+- , but never leading/trailing dots.
if (local.front() == '.' || local.back() == '.') {
return false;
}
for (unsigned char c : local) {
if (!(std::isalnum(c) || c == '.' || c == '_' || c == '%' || c == '+' || c == '-')) {
return false;
}
}
// Domain part: dot-separated labels of alphanumerics/hyphens.
if (domain.front() == '.' || domain.back() == '.') {
return false;
}
int labels = 0;
size_t start = 0;
for (size_t i = 0; i <= domain.size(); ++i) {
if (i == domain.size() || domain[i] == '.') {
const size_t len = i - start;
if (len == 0) {
return false;
}
for (size_t j = start; j < i; ++j) {
const unsigned char c = static_cast<unsigned char>(domain[j]);
if (!(std::isalnum(c) || c == '-')) {
return false;
}
}
++labels;
start = i + 1;
}
}
return labels >= 2;
}
std::string sanitizeLicenseKey(const std::string& input) {
std::string out;
out.reserve(16);
for (unsigned char c : input) {
if (out.size() >= 16) {
break;
}
if (std::isalnum(c)) {
out += static_cast<char>(std::toupper(c));
}
}
return out;
}
std::string formatLicenseKey(const std::string& sanitized) {
std::string out;
out.reserve(19);
for (size_t i = 0; i < sanitized.size(); ++i) {
if (i > 0 && i % 4 == 0) {
out += '-';
}
out += sanitized[i];
}
return out;
}
std::string trim(const std::string& input) {
size_t start = 0;
size_t end = input.size();
while (start < end && std::isspace(static_cast<unsigned char>(input[start]))) {
++start;
}
while (end > start && std::isspace(static_cast<unsigned char>(input[end - 1]))) {
--end;
}
return input.substr(start, end - start);
}
bool isValidSystemName(const std::string& name) {
const std::string trimmed = trim(name);
return !trimmed.empty() && trimmed.size() <= SYSTEM_NAME_MAX_LENGTH;
}
} // namespace radiantwave::validation