| #include "utils/url.h" |
| |
| #include <cctype> |
| #include <cstddef> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| |
| namespace milotic { |
| |
| std::string UrlDecode(absl::string_view encoded) { |
| std::string decoded; |
| decoded.reserve(encoded.size()); |
| for (size_t i = 0; i < encoded.size(); ++i) { |
| if (encoded[i] != '%' || i + 2 >= encoded.size()) { |
| decoded.push_back(encoded[i]); |
| continue; |
| } |
| char hex1 = encoded[i + 1]; |
| char hex2 = encoded[i + 2]; |
| if (std::isxdigit(static_cast<unsigned char>(hex1)) == 0 || |
| std::isxdigit(static_cast<unsigned char>(hex2)) == 0) { |
| decoded.push_back(encoded[i]); |
| continue; |
| } |
| |
| char val = 0; |
| if (hex1 >= '0' && hex1 <= '9') { |
| val += (hex1 - '0') * 16; |
| } else if (hex1 >= 'a' && hex1 <= 'f') { |
| val += (hex1 - 'a' + 10) * 16; |
| } else if (hex1 >= 'A' && hex1 <= 'F') { |
| val += (hex1 - 'A' + 10) * 16; |
| } |
| |
| if (hex2 >= '0' && hex2 <= '9') { |
| val += (hex2 - '0'); |
| } else if (hex2 >= 'a' && hex2 <= 'f') { |
| val += (hex2 - 'a' + 10); |
| } else if (hex2 >= 'A' && hex2 <= 'F') { |
| val += (hex2 - 'A' + 10); |
| } |
| |
| decoded.push_back(val); |
| i += 2; |
| } |
| return decoded; |
| } |
| |
| namespace { |
| |
| std::string NormalizePath(absl::string_view path) { |
| std::vector<absl::string_view> segments; |
| size_t start = 0; |
| while (start < path.size()) { |
| size_t end = path.find('/', start); |
| if (end == absl::string_view::npos) { |
| end = path.size(); |
| } |
| absl::string_view segment = path.substr(start, end - start); |
| if (!segment.empty() && segment != ".") { |
| if (segment == "..") { |
| if (!segments.empty()) { |
| segments.pop_back(); |
| } |
| } else { |
| segments.push_back(segment); |
| } |
| } |
| start = end + 1; |
| } |
| bool has_trailing_slash = !path.empty() && path.back() == '/'; |
| std::string normalized; |
| normalized.reserve(path.size()); |
| for (absl::string_view segment : segments) { |
| normalized.push_back('/'); |
| normalized.append(segment.data(), segment.size()); |
| } |
| if (normalized.empty()) { |
| return "/"; |
| } |
| if (has_trailing_slash) { |
| normalized.push_back('/'); |
| } |
| return normalized; |
| } |
| |
| } // namespace |
| |
| absl::StatusOr<std::string> SanitizeUrl(absl::string_view redfish_id) { |
| size_t query_pos = redfish_id.find('?'); |
| absl::string_view path_part = redfish_id.substr(0, query_pos); |
| absl::string_view query_part = (query_pos == absl::string_view::npos) |
| ? "" |
| : redfish_id.substr(query_pos); |
| |
| std::string decoded_path = UrlDecode(path_part); |
| |
| if (absl::StrContains(decoded_path, '?') || |
| absl::StrContains(decoded_path, '#') || |
| absl::StrContains(decoded_path, '\0')) { |
| return absl::InvalidArgumentError("Path contains invalid characters"); |
| } |
| |
| return absl::StrCat(NormalizePath(decoded_path), query_part); |
| } |
| |
| } // namespace milotic |