The base64 library implements Base64 encoding and decoding functions.
Its main features:
- header-only and easy to use
- written in C++20
- supports different types of STL containers:
std::string,std::vector,std::string_view,std::array - easily adaptable to use custom containers/buffers
- supports Base64 and "URL and Filename Safe" Base64 alphabets
- easily adaptable to use custom Base64 alphabet
- cross-platform, it can be used on Linux, macOS and Windows (tested on GCC, Clang and MSVC)
- no external dependencies (only the doctest library is used as part of the test subproject)
The top-level interface functions are defined in the header file base64/base64.h.
Example of encoding data in Base64:
constexpr std::string_view data = "0123456789";
// calculate the size of output buffer
const size_t encoded_size = base64::calc_encoded_size(data.size());
std::string encoded;
// allocate a buffer of sufficient size
encoded.resize(encoded_size);
// call the encoding function
base64::encode(data, encoded);
assert(encoded == "MDEyMzQ1Njc4OQ==");
Example of decoding data from Base64:
constexpr std::string_view data = "MDEyMzQ1Njc4OQ==";
// calculate the size of output buffer
const size_t decoded_size = base64::calc_decoded_size(data);
std::string decoded;
// allocate a buffer of sufficient size
decoded.resize(decoded_size);
// call the decoding function
base64::decode(data, decoded);
assert(decoded == "0123456789");
There are two encoding functions in base64.h:
template <typename raw_array, typename base64_array>
error_code_t encode(const raw_array & raw_data, base64_array & base64_data);
template <typename raw_array, typename base64_array>
error_code_t encode_url(const raw_array & raw_data, base64_array & base64_data);Both functions encode incoming data into Base64 and put the result into the output buffer. The difference between these functions lies in the alphabets used. The encode() function uses Base64 alphabet and the encode_url() uses "URL and Filename Safe" Base64 alphabet.
Parameters:
raw_data— the input buffer containing data to encode, supported buffer types: std::basic_string, std::vector, std::basic_string_view, std::arraybase64_data— the output buffer containing encoded data after successful execution, supported buffer types: std::basic_string, std::vector, std::array
Encoding functions return an error code of type error_code_t. The error_code_t::no_error() method returns true if encoding is successful. Possible error types:
error_type_t::no_error— no errorerror_type_t::insufficient_buffer_size— insufficient size of output buffer
For more information about errors, see the Error handling section.
Important: encoding functions do not allocate any dynamic memory, so you need to allocate a buffer of sufficient size before encoding. To do this, use the encoded size calculation functions:
size_t calc_encoded_size(size_t raw_size) noexcept;
size_t calc_encoded_size_url(size_t raw_size) noexcept;Both functions calculate the encoding buffer size based on the size of the input data. Similar to encoding functions, the first function uses the standard Base64 alphabet, and the second uses the "URL and Filename Safe" Base64 alphabet.
Parameters:
raw_size— the size of input buffer containing data to encode
The calculation functions never fail.
#include "base64.h"
#include <iostream>
void base64_encoding()
{
using namespace base64;
constexpr std::string_view video_attr = R"({"is_full_screen":false,"window_size":{"width":400,"height":200}})";
std::string video_base64(calc_encoded_size_url(video_attr.size()), '\0');
encode_url(video_attr, video_base64);
std::cout << "original video attributes: " << video_attr << std::endl;
std::cout << "base64 video attributes: " << video_base64 << std::endl;
}Expected output:
original video attributes: {"is_full_screen":false,"window_size":{"width":400,"height":200}}
base64 video attributes: eyJpc19mdWxsX3NjcmVlbiI6ZmFsc2UsIndpbmRvd19zaXplIjp7IndpZHRoIjo0MDAsImhlaWdodCI6MjAwfX0
There are two decoding functions in base64.h:
template <typename base64_array, typename raw_array>
error_code_t decode(const base64_array & base64_data, raw_array & raw_data);
template <typename base64_array, typename raw_array>
error_code_t decode_url(const base64_array & base64_data, raw_array & raw_data);Both functions decode incoming data from Base64 and put the result into the output buffer. The difference between these functions lies in the alphabets used. The decode() function uses Base64 alphabet and the decode_url() uses "URL and Filename Safe" Base64 alphabet.
Parameters:
base64_data— the input buffer containing encoded data, supported buffer types: std::basic_string, std::vector, std::basic_string_view, std::arrayraw_data— the output buffer containing decoded data after successful execution, supported buffer types: std::basic_string, std::vector, std::array
Encoding functions return an error code of type error_code_t. The error_code_t::no_error() method returns true if encoding is successful. Possible error types:
error_type_t::no_error— no errorerror_type_t::insufficient_buffer_size— insufficient size of output buffererror_type_t::invalid_buffer_size— invalid size of input buffer, the buffer is truncated or corruptederror_type_t::non_alphabetic_symbol— the input buffer contains a non-alphabetic symbol
For more information about errors, see the Error handling section.
Important: decoding functions do not allocate any dynamic memory, so you need to allocate a buffer of sufficient size before decoding. To do this, use the decoded size calculation functions:
template <typename base64_array>
size_t calc_decoded_size(const base64_array & base64_data) noexcept;
template <typename base64_array>
size_t calc_decoded_size_url(const base64_array & base64_data) noexcept;Both functions calculate the decoding buffer size based on the encoded data. Similar to decoding functions, the first function uses the standard Base64 alphabet, and the second uses the "URL and Filename Safe" Base64 alphabet.
Parameters:
base64_data— the input buffer containing data to decode
The calculation functions never fail.
#include "base64.h"
#include <iostream>
void base64_decoding()
{
using namespace base64;
constexpr std::string_view video_base64 = "eyJpc19mdWxsX3NjcmVlbiI6ZmFsc2UsIndpbmRvd19zaXplIjp7IndpZHRoIjo0MDAsImhlaWdodCI6MjAwfX0";
std::string video_attr(calc_decoded_size_url(video_base64), '\0');
decode_url(video_base64, video_attr);
std::cout << "base64 video attributes: " << video_base64 << std::endl;
std::cout << "decoded video attributes: " << video_attr << std::endl;
}Expected output:
base64 video attributes: eyJpc19mdWxsX3NjcmVlbiI6ZmFsc2UsIndpbmRvd19zaXplIjp7IndpZHRoIjo0MDAsImhlaWdodCI6MjAwfX0
decoded video attributes: {"is_full_screen":false,"window_size":{"width":400,"height":200}}
The encoding and decoding functions return a value of type error_code_t. The error_code_t class contains an error code and an error message. The success of the encoding/decoding operation can be determined using the methods:
bool has_error() const noexcept;
explicit operator bool() const noexcept;Both methods return true if operation fails. You can get the type of error using the method:
error_type_t type() const noexcept;The method returns the following types of errors:
error_type_t::no_error— no errorerror_type_t::insufficient_buffer_size— insufficient size of output buffererror_type_t::invalid_buffer_size— invalid size of input buffer, the buffer is truncated or corruptederror_type_t::non_alphabetic_symbol— the input buffer contains a non-alphabetic symbol
The error message can be obtained using the method:
const std::string & msg() const noexceptThe msg method returns an empty string if there is no error.
#include "base64.h"
#include <iostream>
void non_alphabetical_character()
{
using namespace base64;
// the non-alphabetical character '(' at index 3
constexpr std::string_view wrong_data = "MDE(MzQ1Njc4OUFC";
std::string decoded(calc_decoded_size(wrong_data), '\0');
const error_code_t error = decode(wrong_data, decoded);
if (error)
{
std::cout << "An error has occurred. " << error.msg() << std::endl;
return;
}
std::cout << "decoded data: " << decoded << std::endl;
}Expected output:
An error has occurred. The buffer has the non-alphabetical character 0x28 at index 3.
The base64 library provides the ability to use containers that are not among the supported ones (std::string, std::vector, std::string_view, std::array) as buffers. Low-level library functions work with containers via adapters. There are two ways to use unsupported containers:
- Create adapters directly and use them in encoding/decoding functions.
- Define adapter makers for custom container.
The base64 library has two types of adapters: const_adapter_t and mutable_adapter_t. Both are defined in the base64/impl/adapters.h header:
const_adapter_tis used for containers with input (immutable) datamutable_adapter_tis used for containers with output (mutable) data
Adapters can be created using the helper functions defined in the base64/impl/make_adapter.h header:
const_adapter_t make_const_adapter(const void * data, size_t byte_count) noexcept;
mutable_adapter_t make_mutable_adapter(void * data, size_t byte_count) noexcept;Both methods create adapters for input and output containers, respectively.
Parameters:
data— pointer to the underlying array serving as byte storage of container (can benullptr)byte_count— number of bytes available for writing (must be 0 ifdataisnullptr)
#include <cstring>
#include <iostream>
#include <memory>
#include "base64.h"
void create_adapters_directly()
{
using namespace base64;
constexpr char text[] =
"Create adapters directly and use them in encoding/decoding functions.";
const size_t text_size = strlen(text);
const size_t encoded_size = calc_encoded_size(text_size);
std::unique_ptr<char[]> encoded{ new char[encoded_size + 1] };
encoded[encoded_size] = '\0';
// make an adapter for the text buffer
const const_adapter_t text_adapter = make_const_adapter(text, text_size);
// create an adapter for the buffer that will contain encoded data
mutable_adapter_t encoded_adapter = make_mutable_adapter(encoded.get(), encoded_size);
// use text_adapter and encoded_adapter to encode data
auto err_code = encode(text_adapter, encoded_adapter);
assert(!err_code);
// you can use the encoded_adapter as a const_adapter_t
const size_t decoded_size = calc_decoded_size(encoded_adapter);
assert(decoded_size == text_size);
std::unique_ptr<char[]> decoded{ new char[decoded_size + 1] };
decoded[decoded_size] = '\0';
// make a mutable adapter for the buffer that will contain decoded data
auto decoded_adapter = make_mutable_adapter(decoded.get(), decoded_size);
// use encoded_adapter and decoded_adapter to decode data
err_code = decode(encoded_adapter, decoded_adapter);
assert(!err_code);
std::cout << "encoded data: " << encoded.get() << std::endl;
std::cout << "decoded data: " << decoded.get() << std::endl;
assert(strncmp(text, decoded.get(), text_size) == 0);
}Expected output:
encoded data: Q3JlYXRlIGFkYXB0ZXJzIGRpcmVjdGx5IGFuZCB1c2UgdGhlbSBpbiBlbmNvZGluZy9kZWNvZGluZyBmdW5jdGlvbnMu
decoded data: Create adapters directly and use them in encoding/decoding functions.
A more useful way is to implement adapter makers for your custom container. The adapter makers for custom containers are declared in the base64/impl/make_adapter.h header:
template <typename custom_buffer_type>
const_adapter_t make_const_adapter(const custom_buffer_type &);
template <typename custom_buffer_type>
mutable_adapter_t make_mutable_adapter(custom_buffer_type &);There is only a declaration without an implementation. You will get a linker error if you try to use a custom buffer in encoding/decoding functions.
TCharBuffer encoded{ encoded_size };
auto err_code = encode(text, encoded);
This code produces the linker error:
in function `base64::error_code_t base64::encode<std::basic_string_view<char, std::char_traits<char> >, TCharBuffer>(std::basic_string_view<char, std::char_traits<char> > const&, TCharBuffer&)':
/base64/base64.h:75: undefined reference to `base64::mutable_adapter_t base64::make_mutable_adapter<TCharBuffer>(TCharBuffer&)'
It is necessary to define a template specialization of adapter makers for the TCharBuffer class. Template specialization must be defined in the base64 namespace:
namespace base64
{
// the const_adapter_t maker is required for containers with input (immutable) data
template <>
inline const_adapter_t make_const_adapter<TCharBuffer>(const TCharBuffer& buffer)
{
return const_adapter_t(reinterpret_cast<const uint8_t*>(buffer.Ptr()), buffer.Size());
}
// mutable_adapter_t required for containers with output (mutable) data
template <>
inline mutable_adapter_t make_mutable_adapter<TCharBuffer>(TCharBuffer& buffer)
{
return mutable_adapter_t(reinterpret_cast<uint8_t*>(buffer.Ptr()), buffer.Size());
}
} // namespace base64And here you can see a full example.
#include <iostream>
#include <memory>
#include <string_view>
#include "base64.h"
// custom buffer
class TCharBuffer final
{
public:
explicit TCharBuffer(size_t size = 0)
: mData(size > 0 ? new char[size] : nullptr), mSize(size) {}
~TCharBuffer() noexcept = default;
char* Ptr() noexcept { return mData.get(); }
const char* Ptr() const noexcept { return mData.get(); }
size_t Size() const noexcept { return mSize; }
private:
std::unique_ptr<char[]> mData;
size_t mSize = 0;
};
namespace base64
{
// the const_adapter_t maker is required for containers containing input (immutable) data
template <>
inline const_adapter_t make_const_adapter<TCharBuffer>(const TCharBuffer& buffer)
{
return const_adapter_t(reinterpret_cast<const uint8_t*>(buffer.Ptr()), buffer.Size());
}
// mutable_adapter_t required for containers containing output (mutable) data
template <>
inline mutable_adapter_t make_mutable_adapter<TCharBuffer>(TCharBuffer& buffer)
{
return mutable_adapter_t(reinterpret_cast<uint8_t*>(buffer.Ptr()), buffer.Size());
}
} // namespace base64
void define_adapter_makers()
{
using namespace base64;
constexpr std::string_view text = "Define adapter makers for custom container.";
const size_t encoded_size = calc_encoded_size(text.size());
TCharBuffer encoded{ encoded_size };
auto err_code = encode(text, encoded);
assert(!err_code);
const size_t decoded_size = calc_decoded_size(encoded);
TCharBuffer decoded{ decoded_size };
err_code = decode(encoded, decoded);
assert(!err_code);
const std::string_view encoded_text(encoded.Ptr(), encoded.Size());
const std::string_view decoded_text(decoded.Ptr(), decoded.Size());
std::cout << "encoded data: " << encoded_text << std::endl;
std::cout << "decoded data: " << decoded_text << std::endl;
assert(text == decoded_text);
}Expected output:
encoded data: RGVmaW5lIGFkYXB0ZXIgbWFrZXJzIGZvciBjdXN0b20gY29udGFpbmVyLg==
decoded data: Define adapter makers for custom container.
As you can see, it is much more convenient to define adapter makers for custom container than to сreate adapters directly and use them in encoding/decoding functions.
The base64 library can be added as a submodule. Example for adding library to the third_party directory in your project:
git submodule add https://github.com/khva/base64 third_party/base64Another way is to download and copy the library code directly into your project:
- copy the
base64.hfile to the directory intended for third-party libraries, e.g. tothird_party/base64/base64.h - copy the
impldirectory to the same path, e.g. tothird_party/base64/impl - add
base64.hpath to project settings, e.g. for CMake project:include_directories(third_party/base64)
- the library was tested on compilers GCC 11.3, Apple Clang 13, MS Visual Studio 2019/2022
- the minimum version of CMake is 3.15
- the doctest version 2.4.9 framework is used for testing (as part of the project in the
tests/doctestdirectory)