Skip to content

Commit

Permalink
New base64 encoding/decoding to remove dependency w/stringencoders lib
Browse files Browse the repository at this point in the history
  • Loading branch information
dacap committed Aug 24, 2022
1 parent 858acb2 commit 50409e1
Show file tree
Hide file tree
Showing 13 changed files with 279 additions and 103 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[submodule "third_party/stringencoders"]
path = third_party/stringencoders
url = https://github.com/aseprite/stringencoders.git
[submodule "third_party/googletest"]
path = third_party/googletest
url = https://github.com/aseprite/googletest.git
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ ctest
Some functions in *laf* depends on third party libraries (you should
include these license notices when you distribute your software):

* [base::encode/decode_base64](base/base64.cpp) functions use
[stringencoders](https://github.com/client9/stringencoders) by
[Nick Galbreath](https://github.com/client9)
([MIT license](https://github.com/aseprite/stringencoders/blob/master/LICENSE)).
* Tests use the [Google Test](https://github.com/aseprite/googletest/tree/master/googletest)
framework by Google Inc. licensed under
[a BSD-like license](https://github.com/aseprite/googletest/blob/master/googletest/LICENSE).
Expand Down
1 change: 0 additions & 1 deletion base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ else()
endif()

add_library(laf-base ${BASE_SOURCES})
target_link_libraries(laf-base modpbase64)

# To #include "base/base.h" and "base/config.h"
target_include_directories(laf-base PUBLIC
Expand Down
129 changes: 115 additions & 14 deletions base/base64.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// LAF Base Library
// Copyright (c) 2022 Igara Studio S.A.
// Copyright (c) 2015-2016 David Capello
//
// This file is released under the terms of the MIT license.
Expand All @@ -9,30 +10,130 @@
#endif

#include "base/base64.h"
#include "base/debug.h"
#include "base/ints.h"

#include "modp_b64.h"
#include <cmath>

namespace base {

void encode_base64(const buffer& input, std::string& output)
static const char base64Table[] = "ABCDEFGHIJKLMNOP"
"QRSTUVWXYZabcdef"
"ghijklmnopqrstuv"
"wxyz0123456789+/";

static const int invBase64Table[] = {
// From 32 to 47 '+' '/'
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,62, 0, 0, 0,63,
// From 48 to 63 '9'
52,53,54,55,56,57,58,59,60,61, 0, 0, 0, 0, 0, 0,
// From 64 to 79
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
// From 80 to 95
15,16,17,18,19,20,21,22,23,24,25, 0, 0, 0, 0, 0,
// From 96 to 111 'o'
0,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
// From 112 to 122 'z'
41,42,43,44,45,46,47,48,49,50,51 };

static inline char base64Char(int index)
{
size_t size = modp_b64_encode_len(input.size());
output.resize(size);
size = modp_b64_encode(&output[0], (const char*)&input[0], input.size());
if (size != (size_t)-1)
output.erase(size, std::string::npos);
ASSERT(index >= 0 && index < 64);
return base64Table[index];
}

static inline int base64Inv(int asciiChar)
{
asciiChar -= 32;
if (asciiChar >= 0 && asciiChar < sizeof(invBase64Table))
return invBase64Table[asciiChar];
else
output.clear();
return 0;
}

void encode_base64(const char* input, size_t n, std::string& output)
{
size_t size = 4*int(std::ceil(n/3.0)); // Estimate encoded string size
output.resize(size);

auto outIt = output.begin();
auto outEnd = output.end();
uint8_t next = 0;
size_t i = 0;
for (; i<n; ++i, ++input) {
auto inputValue = *input;
switch (i%3) {
case 0:
*outIt = base64Char((inputValue & 0b11111100) >> 2);
++outIt;
next |= (inputValue & 0b00000011) << 4;
break;
case 1:
*outIt = base64Char(((inputValue & 0b11110000) >> 4) | next);
++outIt;
next = (inputValue & 0b00001111) << 2;
break;
case 2:
*outIt = base64Char(((inputValue & 0b11000000) >> 6) | next);
++outIt;
*outIt = base64Char(inputValue & 0b00111111);
++outIt;
next = 0;
break;
}
}

if (outIt != outEnd) {
if (next) {
*outIt = base64Char(next);
++outIt;
}
for (; outIt!=outEnd; ++outIt)
*outIt = '='; // Padding
}
}

void decode_base64(const std::string& input, buffer& output)
void decode_base64(const char* input, size_t n, buffer& output)
{
output.resize(modp_b64_decode_len(input.size()));
size_t size = modp_b64_decode((char*)&output[0], input.c_str(), input.size());
if (size != (size_t)-1)
size_t size = 3*int(std::ceil(n/4.0)); // Estimate decoded buffer size
output.resize(size);

auto outIt = output.begin();
auto outEnd = output.end();
size_t i = 0;
for (; i+3<n; i+=4, input+=4) {
*outIt = (((base64Inv(input[0]) ) << 2) |
((base64Inv(input[1]) & 0b110000) >> 4));
++outIt;

if (input[2] == '=') {
size -= 2;
break;
}

*outIt = (((base64Inv(input[1]) & 0b001111) << 4) |
((base64Inv(input[2]) & 0b111100) >> 2));
++outIt;

if (input[3] == '=') {
--size;
break;
}

*outIt = (((base64Inv(input[2]) & 0b000011) << 6) |
((base64Inv(input[3]) )));
++outIt;
}

if (output.size() > size)
output.resize(size);
else
output.resize(0);
}

void decode_base64(const char* input, size_t n, std::string& output)
{
buffer tmp;
decode_base64(input, n, tmp);
output = std::string((const char*)&tmp[0], tmp.size());
}

} // namespace base
51 changes: 49 additions & 2 deletions base/base64.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// LAF Base Library
// Copyright (c) 2022 Igara Studio S.A.
// Copyright (c) 2015-2016 David Capello
//
// This file is released under the terms of the MIT license.
Expand All @@ -14,8 +15,54 @@

namespace base {

void encode_base64(const buffer& input, std::string& output);
void decode_base64(const std::string& input, buffer& output);
void encode_base64(const char* input, size_t n, std::string& output);
void decode_base64(const char* input, size_t n, buffer& output);

inline void encode_base64(const buffer& input, std::string& output) {
if (!input.empty())
encode_base64((const char*)&input[0], input.size(), output);
}

inline std::string encode_base64(const buffer& input) {
std::string output;
if (!input.empty())
encode_base64((const char*)&input[0], input.size(), output);
return output;
}

inline std::string encode_base64(const std::string& input) {
std::string output;
if (!input.empty())
encode_base64((const char*)input.c_str(), input.size(), output);
return output;
}

inline void decode_base64(const std::string& input, buffer& output) {
if (!input.empty())
decode_base64(input.c_str(), input.size(), output);
}

inline buffer decode_base64(const std::string& input) {
buffer output;
if (!input.empty())
decode_base64(input.c_str(), input.size(), output);
return output;
}

inline std::string decode_base64s(const std::string& input) {
if (input.empty())
return std::string();
buffer tmp;
decode_base64(input.c_str(), input.size(), tmp);
return std::string((const char*)&tmp[0], tmp.size());
}

inline buffer decode_base64(const buffer& input) {
buffer output;
if (!input.empty())
decode_base64((const char*)&input[0], input.size(), output);
return output;
}

} // namespace base

Expand Down
34 changes: 16 additions & 18 deletions base/base64_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// LAF Base Library
// Copyright (c) 2022 Igara Studio S.A.
// Copyright (c) 2015-2016 David Capello
//
// This file is released under the terms of the MIT license.
Expand All @@ -7,33 +8,30 @@
#include <gtest/gtest.h>

#include "base/base64.h"
#include "base/string.h"

using namespace base;

TEST(Base64, Encode)
{
buffer data;
data.push_back('a');
data.push_back('b');
data.push_back('c');
data.push_back('d');
data.push_back('e');

std::string output;
encode_base64(data, output);
EXPECT_EQ("YWJjZGU=", output);
EXPECT_EQ("", encode_base64(buffer()));
EXPECT_EQ("Cg==", encode_base64(buffer{'\n'}));
EXPECT_EQ("YQ==", encode_base64(buffer{'a'}));
EXPECT_EQ("YWJjZGU=", encode_base64(buffer{'a', 'b', 'c', 'd', 'e'}));
EXPECT_EQ("YWJjZGU=", encode_base64("abcde"));
EXPECT_EQ("YWJj", encode_base64("abc"));
EXPECT_EQ("5pel5pys6Kqe", encode_base64("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E")); // "日本語"
}

TEST(Base64, Decode)
{
buffer output;
decode_base64("YWJjZGU=", output);

std::string output_str;
for (auto chr : output)
output_str.push_back(chr);

EXPECT_EQ("abcde", output_str);
EXPECT_EQ(buffer(), decode_base64(""));
EXPECT_EQ(buffer{'\n'}, decode_base64("Cg=="));
EXPECT_EQ(buffer{'a'}, decode_base64("YQ=="));
EXPECT_EQ(buffer({'a', 'b', 'c', 'd', 'e'}), decode_base64("YWJjZGU="));
EXPECT_EQ("abcde", decode_base64s("YWJjZGU="));
EXPECT_EQ("abc", decode_base64s("YWJj"));
EXPECT_EQ("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E", decode_base64s("5pel5pys6Kqe")); // "日本語"
}

int main(int argc, char** argv)
Expand Down
44 changes: 31 additions & 13 deletions base/file_content.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// LAF Base Library
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
Expand All @@ -16,20 +16,23 @@
#include <cstdio>
#include <stdexcept>

#if LAF_WINDOWS
#include <fcntl.h>
#include <io.h>
#endif

namespace base {

const size_t kChunkSize = 1024*64; // 64k

buffer read_file_content(const std::string& filename)
buffer read_file_content(FILE* file)
{
FileHandle f(open_file(filename, "rb"));

buffer buf;
size_t pos = 0;

while (!std::feof(f.get())) {
while (!std::feof(file)) {
buf.resize(buf.size() + kChunkSize);
size_t read_bytes = std::fread(&buf[pos], 1, kChunkSize, f.get());
size_t read_bytes = std::fread(&buf[pos], 1, kChunkSize, file);
pos += read_bytes;
if (read_bytes < kChunkSize) {
buf.resize(pos);
Expand All @@ -40,24 +43,39 @@ buffer read_file_content(const std::string& filename)
return buf;
}


void write_file_content(const std::string& filename, const buffer& buf)
buffer read_file_content(const std::string& filename)
{
write_file_content(filename, &buf[0], buf.size());
FileHandle f(open_file(filename, "rb"));
if (f)
return read_file_content(f.get());
else
return buffer();
}

void write_file_content(const std::string& filename, const uint8_t* buf, size_t size)
void write_file_content(FILE* file, const uint8_t* buf, size_t size)
{
FileHandle f(open_file(filename, "wb"));

for (size_t pos=0; pos < size; ) {
const int write_bytes = std::min(kChunkSize, size-pos);
const size_t written_bytes = std::fwrite(buf, 1, write_bytes, f.get());
const size_t written_bytes = std::fwrite(buf, 1, write_bytes, file);
if (written_bytes < write_bytes)
throw std::runtime_error("Cannot write all bytes");
pos += written_bytes;
buf += written_bytes;
}
}

void write_file_content(const std::string& filename, const uint8_t* buf, size_t size)
{
FileHandle f(open_file(filename, "wb"));
if (f)
write_file_content(f.get(), buf, size);
}

void set_write_binary_file_content(FILE* file)
{
#if LAF_WINDOWS
_setmode(_fileno(file), O_BINARY);
#endif
}

} // namespace base
Loading

0 comments on commit 50409e1

Please sign in to comment.