#include "UrlUtils.h" #include namespace UrlUtils { namespace { bool isHexDigit(const char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } bool shouldEncode(const unsigned char c) { if (c <= 0x20 || c >= 0x7f) return true; switch (c) { case '"': case '<': case '>': case '\\': case '^': case '`': case '{': case '|': case '}': return true; default: return false; } } } // namespace std::string ensureProtocol(const std::string& url) { if (url.find("://") == std::string::npos) { return "http://" + url; } return url; } std::string extractHost(const std::string& url) { const size_t protocolEnd = url.find("://"); if (protocolEnd == std::string::npos) { // No protocol, find first slash const size_t firstSlash = url.find('/'); return firstSlash == std::string::npos ? url : url.substr(0, firstSlash); } // Find the first slash after the protocol const size_t hostStart = protocolEnd + 3; const size_t pathStart = url.find('/', hostStart); return pathStart == std::string::npos ? url : url.substr(0, pathStart); } std::string encodeUnsafeUrlChars(const std::string& url) { std::string out; out.reserve(url.size()); for (size_t i = 0; i < url.size(); ++i) { const unsigned char c = static_cast(url[i]); if (c == '%' && i + 2 < url.size() && isHexDigit(url[i + 1]) && isHexDigit(url[i + 2])) { out += url[i]; out += url[i + 1]; out += url[i + 2]; i += 2; } else if (c == '%' || shouldEncode(c)) { char encoded[4]; snprintf(encoded, sizeof(encoded), "%%%02X", c); out += encoded; } else { out += static_cast(c); } } return out; } std::string buildUrl(const std::string& serverUrl, const std::string& path) { // If path is already an absolute URL (has protocol), use it directly if (path.find("://") != std::string::npos) { return encodeUnsafeUrlChars(path); } const std::string urlWithProtocol = ensureProtocol(serverUrl); if (path.empty()) { return encodeUnsafeUrlChars(urlWithProtocol); } if (path[0] == '/') { // Absolute path - use just the host return encodeUnsafeUrlChars(extractHost(urlWithProtocol) + path); } // Relative path - strip query string from base before appending std::string base = urlWithProtocol; const size_t queryPos = base.find('?'); if (queryPos != std::string::npos) { base.resize(queryPos); } if (base.back() == '/') { return encodeUnsafeUrlChars(base + path); } return encodeUnsafeUrlChars(base + "/" + path); } } // namespace UrlUtils