262 lines
8.6 KiB
C++
262 lines
8.6 KiB
C++
/*
|
||
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
|
||
*/
|
||
|
||
/*
|
||
ver: 2.x.x - build: 2026-04-28
|
||
*/
|
||
|
||
/*
|
||
The zlib License
|
||
|
||
Copyright (c) 2026 SD-Innovation S.A.S.
|
||
|
||
This software is provided ‘as-is’, without any express or implied
|
||
warranty. In no event will the authors be held liable for any damages
|
||
arising from the use of this software.
|
||
|
||
Permission is granted to anyone to use this software for any purpose,
|
||
including commercial applications, and to alter it and redistribute it
|
||
freely, subject to the following restrictions:
|
||
|
||
1. The origin of this software must not be misrepresented; you must not
|
||
claim that you wrote the original software. If you use this software
|
||
in a product, an acknowledgment in the product documentation would be
|
||
appreciated but is not required.
|
||
|
||
2. Altered source versions must be plainly marked as such, and must not be
|
||
misrepresented as being the original software.
|
||
|
||
3. This notice may not be removed or altered from any source
|
||
distribution.
|
||
*/
|
||
|
||
#pragma once
|
||
|
||
#include <optional>
|
||
#include <span>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
namespace sdi_toolBox::desktop::utils
|
||
{
|
||
//--------------------------------------------------------------
|
||
/**
|
||
* @brief Utility class providing Base64 encoding and decoding functionality.
|
||
*
|
||
* @details This class implements the Base64 encoding scheme as defined in RFC 4648.
|
||
* All methods are static and the class is not meant to be instantiated.
|
||
* Padding is handled via the '=' character.
|
||
*
|
||
* @note The standard Base64 alphabet is used ('A-Z', 'a-z', '0-9', '+', '/').
|
||
*
|
||
* @par Example - Encoding binary data:
|
||
* @code{.cpp}
|
||
* std::vector<std::uint8_t> data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F };
|
||
* std::string encoded = Base64::encode(data);
|
||
* // encoded == "SGVsbG8="
|
||
* @endcode
|
||
*
|
||
* @par Example - Encoding a string:
|
||
* @code{.cpp}
|
||
* std::string encoded = Base64::encode("Hello, World!");
|
||
* // encoded == "SGVsbG8sIFdvcmxkIQ=="
|
||
* @endcode
|
||
*
|
||
* @par Example - Decoding:
|
||
* @code{.cpp}
|
||
* auto result = Base64::decode_to_string("SGVsbG8sIFdvcmxkIQ==");
|
||
* if (result)
|
||
* std::cout << *result; // prints: Hello, World!
|
||
* @endcode
|
||
*/
|
||
class Base64
|
||
{
|
||
public:
|
||
/**
|
||
* @brief Deleted default constructor - this class is not meant to be instantiated.
|
||
*/
|
||
Base64() = delete;
|
||
|
||
///@name Encoding
|
||
///@{
|
||
|
||
/**
|
||
* @brief Encodes binary data into a Base64 string.
|
||
*
|
||
* @param data A span of bytes to encode.
|
||
* @return A Base64-encoded string, padded with `'='` characters if necessary.
|
||
*/
|
||
[[nodiscard]] static std::string encode(std::span<const std::uint8_t> data);
|
||
|
||
/**
|
||
* @brief Encodes a text string into a Base64 string.
|
||
*
|
||
* @param text The input string to encode.
|
||
* @return A Base64-encoded string, padded with `'='` characters if necessary.
|
||
*/
|
||
[[nodiscard]] static std::string encode(std::string_view text);
|
||
|
||
///@}
|
||
///@name Decoding
|
||
///@{
|
||
|
||
/**
|
||
* @brief Decodes a Base64 string into raw binary data.
|
||
*
|
||
* @param input The Base64-encoded string to decode. Must have a length
|
||
* that is a multiple of 4.
|
||
* @return A vector of decoded bytes, or `std::nullopt` if the input is
|
||
* not a valid Base64 string.
|
||
*/
|
||
[[nodiscard]] static std::optional<std::vector<std::uint8_t>> decode(std::string_view input);
|
||
|
||
/**
|
||
* @brief Decodes a Base64 string into a text string.
|
||
*
|
||
* @param input The Base64-encoded string to decode. Must have a length
|
||
* that is a multiple of 4.
|
||
* @return The decoded string, or `std::nullopt` if the input is not a
|
||
* valid Base64 string.
|
||
*/
|
||
[[nodiscard]] static std::optional<std::string> decode_to_string(std::string_view input);
|
||
|
||
///@}
|
||
|
||
private:
|
||
///@name Internal helpers
|
||
///@{
|
||
|
||
/**
|
||
* @brief Decodes a single Base64 character into its 6-bit value.
|
||
*
|
||
* @param c The Base64 character to decode.
|
||
* @return The 6-bit value corresponding to @p c, or `std::nullopt` if
|
||
* @p c is not a valid Base64 character.
|
||
*/
|
||
[[nodiscard]] static std::optional<std::uint8_t> decode_char(char c) noexcept;
|
||
|
||
///@}
|
||
///@name Constants
|
||
///@{
|
||
|
||
/** @brief The Base64 encoding lookup table (RFC 4648 standard alphabet). */
|
||
static constexpr std::string_view ENCODE_LOOKUP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||
|
||
/** @brief The padding character used in Base64 encoding. */
|
||
static constexpr char PADDING_CAR = '=';
|
||
|
||
///@}
|
||
};
|
||
//--------------------------------------------------------------
|
||
|
||
//--------------------------------------------------------------
|
||
/* Encode binary data to Base64 string */
|
||
inline std::string Base64::encode(const std::span<const std::uint8_t> data)
|
||
{
|
||
std::string result;
|
||
result.reserve(((data.size() + 2) / 3) * 4);
|
||
|
||
for (std::size_t i = 0; i < data.size(); i += 3)
|
||
{
|
||
const std::uint32_t b0 = data[i];
|
||
const std::uint32_t b1 = (i + 1 < data.size()) ? data[i + 1] : 0u;
|
||
const std::uint32_t b2 = (i + 2 < data.size()) ? data[i + 2] : 0u;
|
||
|
||
const std::uint32_t triple = (b0 << 16) | (b1 << 8) | b2;
|
||
|
||
result += ENCODE_LOOKUP[(triple >> 18) & 0x3F];
|
||
result += ENCODE_LOOKUP[(triple >> 12) & 0x3F];
|
||
result += (i + 1 < data.size()) ? ENCODE_LOOKUP[(triple >> 6) & 0x3F] : PADDING_CAR;
|
||
result += (i + 2 < data.size()) ? ENCODE_LOOKUP[(triple >> 0) & 0x3F] : PADDING_CAR;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Encode a text string to Base64 string */
|
||
inline std::string Base64::encode(const std::string_view text)
|
||
{
|
||
return encode(std::span(reinterpret_cast<const std::uint8_t *>(text.data()), text.size()));
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Decode a Base64 string to binary data (returns std::nullopt if invalid) */
|
||
inline std::optional<std::vector<std::uint8_t>> Base64::decode(const std::string_view input)
|
||
{
|
||
if (input.size() % 4 != 0)
|
||
return std::nullopt;
|
||
|
||
if (input.empty())
|
||
return std::vector<std::uint8_t>{};
|
||
|
||
// Validate padding: '=' is only allowed in the last group, in position 2 or 3
|
||
// Valid forms: "xxx=" or "xx=="
|
||
for (std::size_t i = 0; i < input.size() - 4; ++i)
|
||
{
|
||
if (input[i] == PADDING_CAR)
|
||
return std::nullopt; // '=' found outside the last group
|
||
}
|
||
|
||
const std::string_view last = input.substr(input.size() - 4);
|
||
// "xx==" : positions 0,1 must be valid chars, positions 2,3 must be '='
|
||
// "xxx=" : positions 0,1,2 must be valid chars, position 3 must be '='
|
||
// "xxxx" : all positions must be valid chars
|
||
if (last[2] == PADDING_CAR && last[3] != PADDING_CAR)
|
||
return std::nullopt; // "xx=x" is invalid
|
||
|
||
std::vector<std::uint8_t> result;
|
||
result.reserve((input.size() / 4) * 3);
|
||
|
||
for (std::size_t i = 0; i < input.size(); i += 4)
|
||
{
|
||
const auto v0 = decode_char(input[i]);
|
||
const auto v1 = decode_char(input[i + 1]);
|
||
const auto v2 = input[i + 2] == PADDING_CAR ? std::optional<std::uint8_t>{ 0 } : decode_char(input[i + 2]);
|
||
const auto v3 = input[i + 3] == PADDING_CAR ? std::optional<std::uint8_t>{ 0 } : decode_char(input[i + 3]);
|
||
|
||
if (!v0 || !v1 || !v2 || !v3)
|
||
return std::nullopt;
|
||
|
||
const std::uint32_t triple =
|
||
(static_cast<std::uint32_t>(*v0) << 18) |
|
||
(static_cast<std::uint32_t>(*v1) << 12) |
|
||
(static_cast<std::uint32_t>(*v2) << 6) |
|
||
(static_cast<std::uint32_t>(*v3));
|
||
|
||
result.push_back(static_cast<std::uint8_t>((triple >> 16) & 0xFF));
|
||
if (input[i + 2] != PADDING_CAR)
|
||
result.push_back(static_cast<std::uint8_t>((triple >> 8) & 0xFF));
|
||
if (input[i + 3] != PADDING_CAR)
|
||
result.push_back(static_cast<std::uint8_t>((triple >> 0) & 0xFF));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Decode a Base64 string to a text string (returns std::nullopt if invalid) */
|
||
inline std::optional<std::string> Base64::decode_to_string(const std::string_view input)
|
||
{
|
||
const auto bytes = decode(input);
|
||
if (!bytes)
|
||
return std::nullopt;
|
||
return std::string(reinterpret_cast<const char *>(bytes->data()), bytes->size());
|
||
}
|
||
//--------------------------------------------------------------
|
||
/* Helper to decode a single Base64 character to its 6-bit value (returns std::nullopt if invalid) */
|
||
inline std::optional<std::uint8_t> Base64::decode_char(const char c) noexcept
|
||
{
|
||
if (c >= 'A' && c <= 'Z')
|
||
return static_cast<std::uint8_t>(c - 'A');
|
||
if (c >= 'a' && c <= 'z')
|
||
return static_cast<std::uint8_t>(c - 'a' + 26);
|
||
if (c >= '0' && c <= '9')
|
||
return static_cast<std::uint8_t>(c - '0' + 52);
|
||
if (c == '+')
|
||
return 62;
|
||
if (c == '/')
|
||
return 63;
|
||
return std::nullopt;
|
||
}
|
||
//--------------------------------------------------------------
|
||
} // namespace sdi_toolBox::desktop::utils
|