diff --git a/Garbage/Include/Garbage/Base64.hpp b/Garbage/Include/Garbage/Base64.hpp index e7536e8..9eea07f 100644 --- a/Garbage/Include/Garbage/Base64.hpp +++ b/Garbage/Include/Garbage/Base64.hpp @@ -138,6 +138,33 @@ namespace Garbage::Base64 return result; } + + [[nodiscard]] constexpr + bool Validate(std::string_view data) + { + // Technically superfluous - the decoder can deal with unpadded data. + if (data.size() % 4 != 0) { + return false; + } + + std::size_t i = 0; + for (char ch : data) { + bool condition = (ch == '+') || + (ch >= '/' && ch <= '9') || + (ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z'); + if (!condition) { + // Allow '=' for the last two character + if (i >= data.size() - 2 && ch == '=') { + continue; + } + return false; + } + ++i; + } + + return true; + } } #endif // GARBAGE_BASE64_HPP diff --git a/README.md b/README.md index 5ce7791..0552635 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,108 @@ # Garbage -A collection of various single-header libraries of questionable quality. \ No newline at end of file +A collection of various single-header libraries of questionable quality. + +## Usage + +To use these libraries (please don't), just add them into your project's include path. +Ideally, use CMake with FetchContent pinned to a specific commit hash so you don't encounter +nasty surprises when the libraries update. + +```cmake +include(FetchContent) + +FetchContent_Declare( + Garbage + GIT_REPOSITORY https://code.3011.io/TennesseeTrash/Garbage + GIT_TAG 72f02ff856ddfd1b817d4f95096ec1be0ed11a49 +) + +FetchContent_MakeAvailable( + Garbage +) +``` + +Once you've added the `Include` directory into your include path, you can use the headers you need, +e.g. +```cpp +#include + +// Now you can use +Garbage::Base64::Decode(/* some base64 string */); +``` + +## Base64 + +A very simple library composed of 3 functions in the `Garbage::Base64` namespace. + +- `Encode()` takes a `std::span` (i.e. an array of bytes), and produces a + padded Base64 encoded string out of it. +- `Decode()` takes an encoded `std::string_view`, and converts it into an array of bytes + (specifically a `std::vector`). +- `Validate()` performs validation of an untrusted `std::string_view`. This is necessary + because `Decode()` does not do any checking and running it on unverified data is + dangerous. This function is not completely robust, but it is good enough to make sure + the `Decode()` call is safe. + +## SimpleConf + +A small config file library. Provides a `Garbage::SimpleConf` class that reads files +with key-value pairs. This library makes use of exceptions, it will throw `Garbage::SimpleConfError` +when an error is encountered. These exceptions inerit from `std::exception`, so using those to catch +them works fine. + +```cpp +/////////////////////////////////////////////////////////////////////////////// +// INIT + +// Read a file from disk +Garbage::SimpleConf config(std::filesystem::path(/*path to the config*/)); + +// Use an externally provided string +std::string rawConfig = /* whatever procedure to obtain a string */; +Garbage::SimpleConf config(std::move(rawConfig)); + +/////////////////////////////////////////////////////////////////////////////// +// GETTING VALUES + +// Optional values + +// Get a std::optional +auto value = config.GetOptional("SomeKey"); +// Get a default value +int value = config.Get("SomeKey", 0 /* Optional default value */); + +// Required values +using Required = Garbage::SimpleConf::Required; +int value = config.Get("SomeKey"); +``` + +The config files are structured such that a single line contains a key-value pair separated by a `=` +character. +Some specifics: +- Leading and trailing whitespace is removed from both keys and values. +- Whitespace is is allowed inside both keys and values. +- Values may be empty. +- The `#` character denotes the start of a comment, everything after (and including) + this character is ignored until the end of the line. + +Example: +```conf +# Simplest scenario +SomeKey=SomeValue + +# Also works + SomeKey = SomeValue + +# Also supported, but note that the key is now "Some Key" +Some Key = Some Value + +Comments = After # The pair are also fine + +# Also fine +KeyOnly= +``` + +## SQLite + +WIP diff --git a/Tests/Base64/Tests.cpp b/Tests/Base64/Tests.cpp index 67a6784..bb940eb 100644 --- a/Tests/Base64/Tests.cpp +++ b/Tests/Base64/Tests.cpp @@ -10,6 +10,8 @@ TEST_CASE("Base64 encoding/decoding tests") std::string encoded = Garbage::Base64::Encode(data); REQUIRE(encoded.size() == 0); + REQUIRE(Garbage::Base64::Validate(encoded)); + std::vector decoded = Garbage::Base64::Decode(encoded); REQUIRE(encoded.size() == 0); } @@ -21,12 +23,15 @@ TEST_CASE("Base64 encoding/decoding tests") std::string encoded = Garbage::Base64::Encode(data); REQUIRE(encoded == "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu"); + REQUIRE(Garbage::Base64::Validate(encoded)); } SECTION("Decoding short data") { std::string data("TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu"); + REQUIRE(Garbage::Base64::Validate(data)); + std::vector decoded = Garbage::Base64::Decode(data); std::string destination(decoded.begin(), decoded.end()); REQUIRE(destination == "Many hands make light work."); @@ -39,12 +44,15 @@ TEST_CASE("Base64 encoding/decoding tests") std::string encoded = Garbage::Base64::Encode(data); REQUIRE(encoded == "V2l0aCBhIHBhZGRpbmc="); + REQUIRE(Garbage::Base64::Validate(encoded)); } SECTION("Decoding with a padding char") { std::string data("V2l0aCBhIHBhZGRpbmc="); + REQUIRE(Garbage::Base64::Validate(data)); + std::vector decoded = Garbage::Base64::Decode(data); std::string destination(decoded.begin(), decoded.end()); REQUIRE(destination == "With a padding"); @@ -57,12 +65,15 @@ TEST_CASE("Base64 encoding/decoding tests") std::string encoded = Garbage::Base64::Encode(data); REQUIRE(encoded == "V2l0aCB0d28gcGFkZGluZw=="); + REQUIRE(Garbage::Base64::Validate(encoded)); } SECTION("Decoding with two padding chars") { std::string data("V2l0aCB0d28gcGFkZGluZw=="); + REQUIRE(Garbage::Base64::Validate(data)); + std::vector decoded = Garbage::Base64::Decode(data); std::string destination(decoded.begin(), decoded.end()); REQUIRE(destination == "With two padding");