From 6c48d3ef318e8628363598ec0995232a6e5f5f0b Mon Sep 17 00:00:00 2001 From: Abir Khan Date: Wed, 5 Aug 2026 17:40:37 +0800 Subject: [PATCH] feat: base64, URL encode, wildcard, indent --- include/strutil/strutil.hpp | 136 ++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/include/strutil/strutil.hpp b/include/strutil/strutil.hpp index 3b8e605..70bbe32 100644 --- a/include/strutil/strutil.hpp +++ b/include/strutil/strutil.hpp @@ -165,4 +165,140 @@ inline std::optional to_double(std::string_view str) { } } +// ── base64_encode / base64_decode ────────────────────── +inline std::string base64_encode(std::string_view data) { + static const char kEncode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::string result; + result.reserve(((data.size() + 2) / 3) * 4); + size_t i = 0; + uint32_t buf = 0; + int n = 0; + while (i < data.size()) { + buf = (buf << 8) | static_cast(data[i++]); + n++; + if (n == 3) { + result.push_back(kEncode[(buf >> 18) & 0x3F]); + result.push_back(kEncode[(buf >> 12) & 0x3F]); + result.push_back(kEncode[(buf >> 6) & 0x3F]); + result.push_back(kEncode[buf & 0x3F]); + buf = 0; + n = 0; + } + } + if (n > 0) { + buf <<= (3 - n) * 8; + result.push_back(kEncode[(buf >> 18) & 0x3F]); + result.push_back(kEncode[(buf >> 12) & 0x3F]); + result.push_back(n == 1 ? '=' : kEncode[(buf >> 6) & 0x3F]); + result.push_back('='); + } + return result; +} + +inline std::string base64_decode(std::string_view encoded) { + static const int kDecode[256] = []() { + int t[256] = {}; + for (int i = 0; i < 256; i++) t[i] = -1; + for (int i = 0; i < 64; i++) t["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; + return *t; + }(); + std::string result; + result.reserve((encoded.size() * 3) / 4); + int buf = 0, n = 0; + for (char c : encoded) { + if (c == '=') break; + int v = kDecode[static_cast(c)]; + if (v < 0) continue; + buf = (buf << 6) | v; + n++; + if (n == 4) { + result.push_back(static_cast((buf >> 16) & 0xFF)); + result.push_back(static_cast((buf >> 8) & 0xFF)); + result.push_back(static_cast(buf & 0xFF)); + buf = 0; + n = 0; + } + } + if (n >= 2) result.push_back(static_cast((buf >> 10) & 0xFF)); + if (n >= 3) result.push_back(static_cast((buf >> 2) & 0xFF)); + return result; +} + +// ── url_encode / url_decode ──────────────────────────── +inline std::string url_encode(std::string_view str) { + std::string result; + result.reserve(str.size() * 3); + for (unsigned char c : str) { + if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + result.push_back(static_cast(c)); + } else if (c == ' ') { + result.push_back('+'); + } else { + result.push_back('%'); + result.push_back("0123456789ABCDEF"[c >> 4]); + result.push_back("0123456789ABCDEF"[c & 0xF]); + } + } + return result; +} + +inline std::string url_decode(std::string_view str) { + std::string result; + result.reserve(str.size()); + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '+') { + result.push_back(' '); + } else if (str[i] == '%' && i + 2 < str.size()) { + int hi = std::isdigit(static_cast(str[i + 1])) + ? str[i + 1] - '0' + : std::toupper(static_cast(str[i + 1])) - 'A' + 10; + int lo = std::isdigit(static_cast(str[i + 2])) + ? str[i + 2] - '0' + : std::toupper(static_cast(str[i + 2])) - 'A' + 10; + result.push_back(static_cast((hi << 4) | lo)); + i += 2; + } else { + result.push_back(str[i]); + } + } + return result; +} + +// ── wildcard_match — simple glob matching with * and ? ── +inline bool wildcard_match(std::string_view pattern, std::string_view text) { + size_t pi = 0, ti = 0, star = std::string_view::npos, match = 0; + while (ti < text.size()) { + if (pi < pattern.size() && (pattern[pi] == '?' || + (pattern[pi] != '*' && pattern[pi] == text[ti]))) { + pi++; + ti++; + } else if (pi < pattern.size() && pattern[pi] == '*') { + star = pi++; + match = ti; + } else if (star != std::string_view::npos) { + pi = star + 1; + match++; + ti = match; + } else { + return false; + } + } + while (pi < pattern.size() && pattern[pi] == '*') pi++; + return pi == pattern.size(); +} + +// ── indent — indent each line ─────────────────────────── +inline std::string indent(std::string_view str, size_t spaces = 4) { + std::string prefix(spaces, ' '); + std::string result; + result.reserve(str.size() + str.size() / 10 * spaces); + result.append(prefix); + for (char c : str) { + result.push_back(c); + if (c == '\n') result.append(prefix); + } + return result; +} + } // namespace strutil