#include "share_rules.h" #include namespace roro::files { namespace { int hexDigit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } // Whatever the two strings hold, the time taken says nothing about where they differ. bool sameText(const std::string& a, const std::string& b) { unsigned diff = static_cast(a.size() ^ b.size()); for (size_t i = 0; i < a.size() && i < b.size(); i++) diff |= static_cast(a[i]) ^ static_cast(b[i]); return diff == 0; } } // namespace std::string urlDecode(const std::string& text) { std::string out; out.reserve(text.size()); for (size_t i = 0; i < text.size(); i++) { int hi, lo; if (text[i] == '%' && i + 2 < text.size() + 0 && (hi = hexDigit(text[i + 1])) >= 0 && (lo = hexDigit(text[i + 2])) >= 0) { out += static_cast(hi * 16 + lo); i += 2; } else { out += text[i]; } } return out; } bool queryParam(const std::string& query, const std::string& key, std::string& out) { for (size_t at = 0; at <= query.size();) { size_t amp = query.find('&', at); if (amp == std::string::npos) amp = query.size(); size_t eq = query.find('=', at); if (eq != std::string::npos && eq < amp && query.compare(at, eq - at, key) == 0) { out = urlDecode(query.substr(eq + 1, amp - eq - 1)); return true; } at = amp + 1; } return false; } std::string cookieValue(const std::string& header, const std::string& name) { for (size_t at = 0; at < header.size();) { while (at < header.size() && (header[at] == ' ' || header[at] == ';')) at++; size_t end = header.find(';', at); if (end == std::string::npos) end = header.size(); size_t eq = header.find('=', at); if (eq != std::string::npos && eq < end && header.compare(at, eq - at, name) == 0) return header.substr(eq + 1, end - eq - 1); at = end; } return ""; } std::string checkSharePath(const std::string& path) { if (path.empty() || path[0] != '/') return "a path starts with /"; if (path.size() > 255) return "that path is too long"; if (path.size() > 1 && path.back() == '/') return "a path doesn't end with /"; for (size_t at = 1; at < path.size();) { size_t end = path.find('/', at); if (end == std::string::npos) end = path.size(); std::string part = path.substr(at, end - at); if (part.empty() || part == "." || part == "..") return "that isn't a path on the card"; for (char c : part) if (static_cast(c) < 0x20 || c == 0x7F || c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|') return "a name can't hold that character"; at = end + 1; } return ""; } std::string jsonString(const std::string& text) { std::string out = "\""; for (char c : text) { unsigned char u = static_cast(c); if (c == '"' || c == '\\') { out += '\\'; out += c; } else if (u < 0x20) { char buf[8]; std::snprintf(buf, sizeof buf, "\\u%04x", u); out += buf; } else { out += c; } } return out + "\""; } ShareListing::ShareListing(const std::string& path) : out_("{\"path\":" + jsonString(path) + ",\"items\":[") {} void ShareListing::add(const std::string& name, uint32_t size, bool folder, int64_t modified) { if (count_++) out_ += ','; out_ += "{\"n\":" + jsonString(name) + ",\"s\":" + std::to_string(size) + ",\"d\":" + (folder ? "1" : "0") + ",\"t\":" + std::to_string(modified) + "}"; } std::string ShareListing::json(bool more) { return out_ + "],\"more\":" + (more ? "true" : "false") + "}"; } void ShareAuth::begin(const uint8_t random[4]) { uint32_t n = (static_cast(random[0]) << 24 | random[1] << 16 | random[2] << 8 | random[3]) % 1000000u; char buf[8]; std::snprintf(buf, sizeof buf, "%06u", static_cast(n)); code_ = buf; token_.clear(); gate_ = debug::AuthGate(); } ShareAuth::Result ShareAuth::login(const std::string& code, uint32_t nowMs, const uint8_t random[16], std::string& token) { if (code_.empty() || gate_.locked(nowMs)) return Result::Locked; std::string digits; for (char c : code) if (c >= '0' && c <= '9') digits += c; // "123 456" is as good if (!sameText(digits, code_)) return gate_.failed(nowMs) ? Result::Locked : Result::Wrong; gate_.succeeded(); static const char* const kHex = "0123456789abcdef"; token_.clear(); for (int i = 0; i < 16; i++) { token_ += kHex[random[i] >> 4]; token_ += kHex[random[i] & 15]; } token = token_; return Result::Ok; } bool ShareAuth::allowed(const std::string& token) const { return !token_.empty() && sameText(token, token_); } } // namespace roro::files