mirror of
https://github.com/fmang/opustags.git
synced 2025-01-15 12:43:17 +01:00
Implement base64 encoding and decoding
This commit is contained in:
parent
55e7e9b64e
commit
a435a28e9f
@ -36,6 +36,7 @@ include_directories(BEFORE src "${CMAKE_BINARY_DIR}" ${OGG_INCLUDE_DIRS} ${Iconv
|
||||
add_library(
|
||||
ot
|
||||
STATIC
|
||||
src/base64.cc
|
||||
src/cli.cc
|
||||
src/ogg.cc
|
||||
src/opus.cc
|
||||
|
97
src/base64.cc
Normal file
97
src/base64.cc
Normal file
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* \file src/base64.cc
|
||||
* \brief Base64 encoding/decoding (RFC 4648).
|
||||
*
|
||||
* Inspired by Jouni Malinen’s BSD implementation at
|
||||
* <http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c>.
|
||||
*
|
||||
* This implementation is used to decode the cover arts embedded in the tags. According to
|
||||
* <https://wiki.xiph.org/VorbisComment>, line feeds are not allowed and padding is required.
|
||||
*/
|
||||
|
||||
#include <opustags.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
static const unsigned char base64_table[65] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
std::string ot::encode_base64(std::string_view src)
|
||||
{
|
||||
size_t len = src.size();
|
||||
size_t num_blocks = (len + 2) / 3; // Count of 3-byte blocks, rounded up.
|
||||
size_t olen = num_blocks * 4; // Each 3-byte block becomes 4 base64 bytes.
|
||||
if (olen < len)
|
||||
throw std::overflow_error("failed to encode excessively long base64 block");
|
||||
|
||||
std::string out;
|
||||
out.resize(olen);
|
||||
|
||||
const unsigned char* in = reinterpret_cast<const unsigned char*>(src.data());
|
||||
const unsigned char* end = in + len;
|
||||
unsigned char* pos = reinterpret_cast<unsigned char*>(out.data());
|
||||
while (end - in >= 3) {
|
||||
*pos++ = base64_table[in[0] >> 2];
|
||||
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
|
||||
*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
|
||||
*pos++ = base64_table[in[2] & 0x3f];
|
||||
in += 3;
|
||||
}
|
||||
|
||||
if (end - in) {
|
||||
*pos++ = base64_table[in[0] >> 2];
|
||||
if (end - in == 1) {
|
||||
*pos++ = base64_table[(in[0] & 0x03) << 4];
|
||||
*pos++ = '=';
|
||||
} else { // end - in == 2
|
||||
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
|
||||
*pos++ = base64_table[(in[1] & 0x0f) << 2];
|
||||
}
|
||||
*pos++ = '=';
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ot::decode_base64(std::string_view src)
|
||||
{
|
||||
// Remove the padding and rely on the string length instead.
|
||||
while (src.back() == '=')
|
||||
src.remove_suffix(1);
|
||||
|
||||
size_t olen = src.size() / 4 * 3; // Whole blocks;
|
||||
switch (src.size() % 4) {
|
||||
case 1: throw status {st::error, "invalid base64 block size"};
|
||||
case 2: olen += 1; break;
|
||||
case 3: olen += 2; break;
|
||||
}
|
||||
|
||||
std::string out;
|
||||
out.resize(olen);
|
||||
unsigned char* pos = reinterpret_cast<unsigned char*>(out.data());
|
||||
|
||||
unsigned char dtable[256];
|
||||
memset(dtable, 0x80, 256);
|
||||
for (size_t i = 0; i < sizeof(base64_table) - 1; ++i)
|
||||
dtable[base64_table[i]] = (unsigned char) i;
|
||||
|
||||
unsigned char block[4];
|
||||
size_t count = 0;
|
||||
for (unsigned char c : src) {
|
||||
unsigned char tmp = dtable[c];
|
||||
if (tmp == 0x80)
|
||||
throw status {st::error, "invalid base64 character"};
|
||||
|
||||
block[count++] = tmp;
|
||||
if (count == 2) {
|
||||
*pos++ = (block[0] << 2) | (block[1] >> 4);
|
||||
} else if (count == 3) {
|
||||
*pos++ = (block[1] << 4) | (block[2] >> 2);
|
||||
} else if (count == 4) {
|
||||
*pos++ = (block[2] << 6) | block[3];
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
@ -187,6 +187,9 @@ void run_editor(std::string_view editor, std::string_view path);
|
||||
*/
|
||||
timespec get_file_timestamp(const char* path);
|
||||
|
||||
std::string encode_base64(std::string_view src);
|
||||
std::string decode_base64(std::string_view src);
|
||||
|
||||
/** \} */
|
||||
|
||||
/***********************************************************************************************//**
|
||||
|
@ -10,6 +10,9 @@ target_link_libraries(ogg.t ot)
|
||||
add_executable(cli.t EXCLUDE_FROM_ALL cli.cc)
|
||||
target_link_libraries(cli.t ot)
|
||||
|
||||
add_executable(base64.t EXCLUDE_FROM_ALL base64.cc)
|
||||
target_link_libraries(base64.t ot)
|
||||
|
||||
add_executable(oggdump EXCLUDE_FROM_ALL oggdump.cc)
|
||||
target_link_libraries(oggdump ot)
|
||||
|
||||
@ -18,5 +21,5 @@ configure_file(gobble.opus . COPYONLY)
|
||||
add_custom_target(
|
||||
check
|
||||
COMMAND prove "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
DEPENDS opustags gobble.opus system.t opus.t ogg.t cli.t
|
||||
DEPENDS opustags gobble.opus system.t opus.t ogg.t cli.t base64.t
|
||||
)
|
||||
|
46
t/base64.cc
Normal file
46
t/base64.cc
Normal file
@ -0,0 +1,46 @@
|
||||
#include <opustags.h>
|
||||
#include "tap.h"
|
||||
|
||||
static void check_encode_base64()
|
||||
{
|
||||
is(ot::encode_base64(""), "", "empty");
|
||||
is(ot::encode_base64("a"), "YQ==", "1 character");
|
||||
is(ot::encode_base64("aa"), "YWE=", "2 characters");
|
||||
is(ot::encode_base64("aaa"), "YWFh", "3 characters");
|
||||
is(ot::encode_base64("aaaa"), "YWFhYQ==", "4 characters");
|
||||
is(ot::encode_base64("\xFF\xFF\xFE"), "///+", "RFC alphabet");
|
||||
is(ot::encode_base64("\0x"sv), "AHg=", "embedded null bytes");
|
||||
}
|
||||
|
||||
static void check_decode_base64()
|
||||
{
|
||||
is(ot::decode_base64(""), "", "empty");
|
||||
is(ot::decode_base64("YQ=="), "a", "1 character");
|
||||
is(ot::decode_base64("YWE="), "aa", "2 characters");
|
||||
is(ot::decode_base64("YQ"), "a", "padless 1 character");
|
||||
is(ot::decode_base64("YWE"), "aa", "padless 2 characters");
|
||||
is(ot::decode_base64("YWFh"), "aaa", "3 characters");
|
||||
is(ot::decode_base64("YWFhYQ=="), "aaaa", "4 characters");
|
||||
is(ot::decode_base64("///+"), "\xFF\xFF\xFE", "RFC alphabet");
|
||||
is(ot::decode_base64("AHg="), "\0x"sv, "embedded null bytes");
|
||||
|
||||
try {
|
||||
ot::decode_base64("Y===");
|
||||
throw failure("accepted a bad block size");
|
||||
} catch (const ot::status& e) {
|
||||
}
|
||||
|
||||
try {
|
||||
ot::decode_base64("\xFF bad message!");
|
||||
throw failure("accepted an invalid character");
|
||||
} catch (const ot::status& e) {
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
std::cout << "1..2\n";
|
||||
run(check_encode_base64, "base64 encoding");
|
||||
run(check_decode_base64, "base64 decoding");
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user