2026-01-07 03:58:37 -05:00
|
|
|
#include "UrlUtils.h"
|
|
|
|
|
|
2026-01-13 12:16:03 +00:00
|
|
|
#include <sstream>
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
|
2026-01-07 03:58:37 -05:00
|
|
|
namespace UrlUtils {
|
|
|
|
|
|
|
|
|
|
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 buildUrl(const std::string& serverUrl, const std::string& path) {
|
|
|
|
|
const std::string urlWithProtocol = ensureProtocol(serverUrl);
|
|
|
|
|
if (path.empty()) {
|
|
|
|
|
return urlWithProtocol;
|
|
|
|
|
}
|
|
|
|
|
if (path[0] == '/') {
|
|
|
|
|
// Absolute path - use just the host
|
|
|
|
|
return extractHost(urlWithProtocol) + path;
|
|
|
|
|
}
|
|
|
|
|
// Relative path - append to server URL
|
|
|
|
|
if (urlWithProtocol.back() == '/') {
|
|
|
|
|
return urlWithProtocol + path;
|
|
|
|
|
}
|
|
|
|
|
return urlWithProtocol + "/" + path;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 12:16:03 +00:00
|
|
|
std::string urlEncode(const std::string& value) {
|
|
|
|
|
std::ostringstream escaped;
|
|
|
|
|
escaped.fill('0');
|
|
|
|
|
escaped << std::hex;
|
|
|
|
|
|
|
|
|
|
for (char c : value) {
|
|
|
|
|
// Keep alphanumeric and other safe characters intact
|
|
|
|
|
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
|
|
|
|
escaped << c;
|
|
|
|
|
} else {
|
|
|
|
|
// Encode special characters
|
|
|
|
|
escaped << std::uppercase;
|
|
|
|
|
escaped << '%' << std::setw(2) << int((unsigned char)c);
|
|
|
|
|
escaped << std::nouppercase;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return escaped.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string buildUrlWithAuth(const std::string& serverUrl, const std::string& path,
|
|
|
|
|
const std::string& username, const std::string& password) {
|
|
|
|
|
// If no credentials, use regular buildUrl
|
|
|
|
|
if (username.empty() && password.empty()) {
|
|
|
|
|
return buildUrl(serverUrl, path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string urlWithProtocol = ensureProtocol(serverUrl);
|
|
|
|
|
|
|
|
|
|
// Find protocol end
|
|
|
|
|
const size_t protocolEnd = urlWithProtocol.find("://");
|
|
|
|
|
if (protocolEnd == std::string::npos) {
|
|
|
|
|
return buildUrl(serverUrl, path); // Fallback if no protocol
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract protocol and host parts
|
|
|
|
|
std::string protocol = urlWithProtocol.substr(0, protocolEnd + 3); // Include ://
|
|
|
|
|
std::string hostAndPath = urlWithProtocol.substr(protocolEnd + 3);
|
|
|
|
|
|
|
|
|
|
// Check if auth already exists in URL
|
|
|
|
|
const size_t atPos = hostAndPath.find('@');
|
|
|
|
|
if (atPos != std::string::npos) {
|
|
|
|
|
// Auth already in URL, remove it
|
|
|
|
|
hostAndPath = hostAndPath.substr(atPos + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build auth string with URL encoding
|
|
|
|
|
std::string auth;
|
|
|
|
|
if (!username.empty() || !password.empty()) {
|
|
|
|
|
auth = urlEncode(username) + ":" + urlEncode(password) + "@";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reconstruct URL with auth
|
|
|
|
|
std::string authenticatedUrl = protocol + auth + hostAndPath;
|
|
|
|
|
|
|
|
|
|
// Now apply path logic
|
|
|
|
|
if (path.empty()) {
|
|
|
|
|
return authenticatedUrl;
|
|
|
|
|
}
|
|
|
|
|
if (path[0] == '/') {
|
|
|
|
|
// Absolute path - extract just protocol + auth + host
|
|
|
|
|
const size_t firstSlash = hostAndPath.find('/');
|
|
|
|
|
std::string hostOnly = (firstSlash == std::string::npos) ? hostAndPath : hostAndPath.substr(0, firstSlash);
|
|
|
|
|
return protocol + auth + hostOnly + path;
|
|
|
|
|
}
|
|
|
|
|
// Relative path
|
|
|
|
|
if (authenticatedUrl.back() == '/') {
|
|
|
|
|
return authenticatedUrl + path;
|
|
|
|
|
}
|
|
|
|
|
return authenticatedUrl + "/" + path;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 03:58:37 -05:00
|
|
|
} // namespace UrlUtils
|