[PercentEncoding] Add minimal percent encoding/decoding functions
This commit is contained in:
parent
ed6dc601cc
commit
2eefad4152
4 changed files with 180 additions and 0 deletions
91
Garbage/Include/Garbage/PercentEncoding.hpp
Normal file
91
Garbage/Include/Garbage/PercentEncoding.hpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#ifndef GARBAGE_PERCENT_ENCODING_HPP
|
||||||
|
#define GARBAGE_PERCENT_ENCODING_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Garbage::Percent
|
||||||
|
{
|
||||||
|
namespace Internal
|
||||||
|
{
|
||||||
|
static constexpr auto EncodeLUT = std::array {
|
||||||
|
'0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7',
|
||||||
|
'8', '9', 'A', 'B',
|
||||||
|
'C', 'D', 'E', 'F',
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
std::uint8_t DecodeNibble(char ch) {
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
return ch - '0';
|
||||||
|
}
|
||||||
|
if (ch >= 'A' && ch <= 'F') {
|
||||||
|
return ch - 'A' + 10;
|
||||||
|
}
|
||||||
|
if (ch >= 'a' && ch <= 'f') {
|
||||||
|
return ch - 'a' + 10;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
std::uint8_t DecodeChar(char upper, char lower) {
|
||||||
|
return (DecodeNibble(upper) << 4) | DecodeNibble(lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
bool IsHex(char ch) {
|
||||||
|
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
std::string Encode(std::span<std::uint8_t> data)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result.reserve(data.size() * 3);
|
||||||
|
|
||||||
|
for (std::uint8_t byte : data) {
|
||||||
|
result.push_back('%');
|
||||||
|
result.push_back(Internal::EncodeLUT[byte >> 4]);
|
||||||
|
result.push_back(Internal::EncodeLUT[byte & 15]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
std::vector<std::uint8_t> Decode(std::string_view data)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> result;
|
||||||
|
result.reserve(data.size() / 3);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < data.size(); i += 3) {
|
||||||
|
result.push_back(Internal::DecodeChar(data[i + 1], data[i + 2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
bool Validate(std::string_view data)
|
||||||
|
{
|
||||||
|
if (data.size() % 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < data.size(); i += 3) {
|
||||||
|
if (data[i] != '%' || !Internal::IsHex(data[i + 1]) || !Internal::IsHex(data[i + 2])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GARBAGE_PERCENT_ENCODING_HPP
|
|
@ -1,5 +1,6 @@
|
||||||
include(${PROJECT_SOURCE_DIR}/CMake/Catch2.cmake)
|
include(${PROJECT_SOURCE_DIR}/CMake/Catch2.cmake)
|
||||||
|
|
||||||
add_subdirectory(Base64)
|
add_subdirectory(Base64)
|
||||||
|
add_subdirectory(PercentEncoding)
|
||||||
add_subdirectory(SQLite)
|
add_subdirectory(SQLite)
|
||||||
add_subdirectory(SimpleConf)
|
add_subdirectory(SimpleConf)
|
||||||
|
|
17
Tests/PercentEncoding/CMakeLists.txt
Normal file
17
Tests/PercentEncoding/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
add_executable(TestPercentEncoding)
|
||||||
|
|
||||||
|
target_compile_features(TestPercentEncoding
|
||||||
|
PRIVATE
|
||||||
|
cxx_std_20
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(TestPercentEncoding
|
||||||
|
PRIVATE
|
||||||
|
Garbage
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(TestPercentEncoding
|
||||||
|
PRIVATE
|
||||||
|
Tests.cpp
|
||||||
|
)
|
71
Tests/PercentEncoding/Tests.cpp
Normal file
71
Tests/PercentEncoding/Tests.cpp
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <Garbage/PercentEncoding.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("Percent encoding/decoding tests")
|
||||||
|
{
|
||||||
|
SECTION("Empty span")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> data;
|
||||||
|
|
||||||
|
std::string encoded = Garbage::Percent::Encode(data);
|
||||||
|
REQUIRE(encoded.size() == 0);
|
||||||
|
|
||||||
|
REQUIRE(Garbage::Percent::Validate(encoded));
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> decoded = Garbage::Percent::Decode(encoded);
|
||||||
|
REQUIRE(encoded.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Validation rejections")
|
||||||
|
{
|
||||||
|
constexpr std::string_view invalid1 = "Random chars";
|
||||||
|
REQUIRE_FALSE(Garbage::Percent::Validate(invalid1));
|
||||||
|
|
||||||
|
constexpr std::string_view invalid2 = "%AFFAF%AF";
|
||||||
|
REQUIRE_FALSE(Garbage::Percent::Validate(invalid2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Encoding short data")
|
||||||
|
{
|
||||||
|
constexpr std::string_view source("日");
|
||||||
|
std::vector<std::uint8_t> data(source.begin(), source.end());
|
||||||
|
|
||||||
|
std::string encoded = Garbage::Percent::Encode(data);
|
||||||
|
|
||||||
|
REQUIRE(encoded == "%E6%97%A5");
|
||||||
|
REQUIRE(Garbage::Percent::Validate(encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Decoding short data")
|
||||||
|
{
|
||||||
|
std::string data("%E6%97%A5");
|
||||||
|
|
||||||
|
REQUIRE(Garbage::Percent::Validate(data));
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> decoded = Garbage::Percent::Decode(data);
|
||||||
|
std::string destination(decoded.begin(), decoded.end());
|
||||||
|
REQUIRE(destination == "日");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Encoding longer data")
|
||||||
|
{
|
||||||
|
constexpr std::string_view source("日男昼持賃竹田");
|
||||||
|
std::vector<std::uint8_t> data(source.begin(), source.end());
|
||||||
|
|
||||||
|
std::string encoded = Garbage::Percent::Encode(data);
|
||||||
|
|
||||||
|
REQUIRE(encoded == "%E6%97%A5%E7%94%B7%E6%98%BC%E6%8C%81%E8%B3%83%E7%AB%B9%E7%94%B0");
|
||||||
|
REQUIRE(Garbage::Percent::Validate(encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Decoding longer data")
|
||||||
|
{
|
||||||
|
std::string data("%E6%97%A5%E7%94%B7%E6%98%BC%E6%8C%81%E8%B3%83%E7%AB%B9%E7%94%B0");
|
||||||
|
|
||||||
|
REQUIRE(Garbage::Percent::Validate(data));
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> decoded = Garbage::Percent::Decode(data);
|
||||||
|
std::string destination(decoded.begin(), decoded.end());
|
||||||
|
REQUIRE(destination == "日男昼持賃竹田");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue