diff --git a/CMakeLists.txt b/CMakeLists.txt index bebca1e..ae4ee7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ add_library(webfuse-core STATIC lib/webfuse/core/status.c lib/webfuse/core/string.c lib/webfuse/core/path.c + lib/webfuse/core/base64.c lib/webfuse/core/lws_log.c ) @@ -283,6 +284,7 @@ add_executable(alltests test/core/test_string.cc test/core/test_slist.cc test/core/test_path.cc + test/core/test_base64.cc test/core/test_status.cc test/core/test_message.cc test/core/test_message_queue.cc diff --git a/lib/webfuse/adapter/impl/operation/read.c b/lib/webfuse/adapter/impl/operation/read.c index f30902b..ec434c5 100644 --- a/lib/webfuse/adapter/impl/operation/read.c +++ b/lib/webfuse/adapter/impl/operation/read.c @@ -4,9 +4,9 @@ #include #include #include -#include #include "webfuse/adapter/impl/jsonrpc/proxy.h" +#include "webfuse/core/base64.h" #define WF_MAX_READ_LENGTH 4096 @@ -17,7 +17,7 @@ static char * wf_impl_fill_buffer( wf_status * status) { *status = WF_GOOD; - char * buffer = malloc(count + 4); // FixMe: lws 3.2.0 needs more buffer to decode (bug?) + char * buffer = malloc(count); if ((NULL != buffer) && (0 < count)) { @@ -27,7 +27,7 @@ static char * wf_impl_fill_buffer( } else if (0 == strcmp("base64", format)) { - lws_b64_decode_string(data, buffer, count + 4); + wf_base64_decode(data, strlen(data), (uint8_t *) buffer, count); } else { diff --git a/lib/webfuse/core/base64.c b/lib/webfuse/core/base64.c new file mode 100644 index 0000000..2b24688 --- /dev/null +++ b/lib/webfuse/core/base64.c @@ -0,0 +1,183 @@ +#include "webfuse/core/base64.h" + +static const uint8_t wf_base64_decode_table[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 1 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 62, 0x80, 0x80, 0x80, 63, // 2 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0x80, 0x80, 0x80, 0, 0x80, 0x80, // 3 + 0x80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 4 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0x80, 0x80, 0x80, 0x80, 0x80, // 5 + 0x80, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 6 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0x80, 0x80, 0x80, 0x80, 0x80, // 7 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 8 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 9 + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // A + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // B + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // C + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // D + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // E + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // F +}; + + + +size_t wf_base64_encoded_size(size_t length) +{ + return ((length + 2) / 3) * 4; +} + +size_t wf_base64_encode( + uint8_t const * data, + size_t length, + char * buffer, + size_t buffer_size) +{ + // 0 1 2 3 4 5 6 + // 0123456789012345678901234567890123456789012345678901234567890123 + static char const table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t const length_needed = wf_base64_encoded_size(length); + if (buffer_size < length_needed) + { + return 0; + } + + size_t pos = 0; + size_t out_pos = 0; + for(; (length - pos) >= 3; pos += 3) + { + buffer[out_pos++] = table[ data[pos] >> 2 ]; + buffer[out_pos++] = table[ ((data[pos ] & 0x03) << 4) | (data[pos + 1] >> 4) ]; + buffer[out_pos++] = table[ ((data[pos + 1] & 0x0f) << 2) | (data[pos + 2] >> 6) ]; + buffer[out_pos++] = table[ data[pos + 2] & 0x3f ]; + } + + switch((length - pos)) + { + case 1: + buffer[out_pos++] = table[ data[pos] >> 2 ]; + buffer[out_pos++] = table[ ((data[pos] & 0x03) << 4) ]; + buffer[out_pos++] = '='; + buffer[out_pos++] = '='; + break; + case 2: + buffer[out_pos++] = table[ data[pos] >> 2 ]; + buffer[out_pos++] = table[ ((data[pos ] & 0x03) << 4) | (data[pos + 1] >> 4) ]; + buffer[out_pos++] = table[ ((data[pos + 1] & 0x0f) << 2) ]; + buffer[out_pos++] = '='; + break; + default: + break; + } + + if (buffer_size > out_pos) + { + buffer[out_pos] = '\0'; + } + + return out_pos; +} + +size_t wf_base64_decoded_size(char const * data, size_t length) +{ + size_t result = 0; + if ((length > 0) && ((length % 4) == 0)) + { + result = (length / 4) * 3; + + if ('=' == data[length - 1]) + { + result--; + if ('=' == data[length - 2]) + { + result--; + } + } + } + + return result; +} + +size_t wf_base64_decode( + char const * data, + size_t length, + uint8_t * buffer, + size_t buffer_size) +{ + uint8_t const * table = wf_base64_decode_table; + size_t needed_size = wf_base64_decoded_size(data, length); + if ((0 == needed_size) || (buffer_size < needed_size)) + { + return 0; + } + + size_t out_pos = 0; + size_t pos = 0; + for(; pos < length - 4; pos += 4) + { + uint8_t a = table[ (unsigned char) data[pos ] ]; + uint8_t b = table[ (unsigned char) data[pos + 1] ]; + uint8_t c = table[ (unsigned char) data[pos + 2] ]; + uint8_t d = table[ (unsigned char) data[pos + 3] ]; + + buffer[out_pos++] = (a << 2) | (b >> 4); + buffer[out_pos++] = (b << 4) | (c >> 2); + buffer[out_pos++] = (c << 6) | d; + } + + // decode last block + { + uint8_t a = table[ (unsigned char) data[pos ] ]; + uint8_t b = table[ (unsigned char) data[pos + 1] ]; + uint8_t c = table[ (unsigned char) data[pos + 2] ]; + uint8_t d = table[ (unsigned char) data[pos + 3] ]; + + buffer[out_pos++] = (a << 2) | (b >> 4); + if ('=' != data[pos + 2]) + { + buffer[out_pos++] = (b << 4) | (c >> 2); + if ('=' != data[pos + 3]) + { + buffer[out_pos++] = (c << 6) | d; + } + } + } + + return out_pos; +} + +extern bool wf_base64_isvalid(char const * data, size_t length) +{ + uint8_t const * table = wf_base64_decode_table; + + if ((length == 0) || ((length % 4) != 0)) + { + return false; + } + + size_t pos = 0; + for(; pos < (length - 2); pos++) + { + unsigned char c = (unsigned char) data[pos]; + if (('=' == c) || (0x80 == table[c])) + { + return false; + } + } + + if (('=' == data[pos]) && ('=' != data[pos + 1])) + { + return false; + } + + for(;pos < length; pos++) + { + char c = data[pos]; + if (0x80 == table[ (unsigned char) c]) + { + return false; + } + } + + return true; +} diff --git a/lib/webfuse/core/base64.h b/lib/webfuse/core/base64.h new file mode 100644 index 0000000..6803afb --- /dev/null +++ b/lib/webfuse/core/base64.h @@ -0,0 +1,40 @@ +#ifndef WF_BASE64_H +#define WF_BASE64_H + +#ifndef __cplusplus +#include +#include +#include +#else +#include +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern size_t wf_base64_encoded_size(size_t length); + +extern size_t wf_base64_encode( + uint8_t const * data, + size_t length, + char * buffer, + size_t buffer_size); + +extern size_t wf_base64_decoded_size(char const * data, size_t length); + +extern size_t wf_base64_decode( + char const * data, + size_t length, + uint8_t * buffer, + size_t buffer_size); + +extern bool wf_base64_isvalid(char const * data, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/webfuse/provider/impl/operation/read.c b/lib/webfuse/provider/impl/operation/read.c index 64188b4..3101ea0 100644 --- a/lib/webfuse/provider/impl/operation/read.c +++ b/lib/webfuse/provider/impl/operation/read.c @@ -1,11 +1,11 @@ #include "webfuse/provider/impl/operation/read.h" #include -#include #include "webfuse/provider/impl/operation/error.h" #include "webfuse/provider/impl/request.h" #include "webfuse/core/util.h" +#include "webfuse/core/base64.h" void wfp_impl_read( struct wfp_impl_invokation_context * context, @@ -54,11 +54,11 @@ void wfp_impl_respond_read( { if (0 < length) { - size_t const size = 4 * ((length / 3) + 2); + size_t const size = wf_base64_encoded_size(length) + 1; char * buffer = malloc(size); if (NULL != buffer) { - lws_b64_encode_string(data, length, buffer, size); + wf_base64_encode((uint8_t const *) data, length, buffer, size); json_t * result = json_object(); json_object_set_new(result, "data", json_string(buffer)); diff --git a/test/core/test_base64.cc b/test/core/test_base64.cc new file mode 100644 index 0000000..e6ae97d --- /dev/null +++ b/test/core/test_base64.cc @@ -0,0 +1,122 @@ +#include +#include "webfuse/core/base64.h" + +TEST(Base64, EncodedSize) +{ + ASSERT_EQ(4, wf_base64_encoded_size(1)); + ASSERT_EQ(4, wf_base64_encoded_size(2)); + ASSERT_EQ(4, wf_base64_encoded_size(3)); + + ASSERT_EQ(8, wf_base64_encoded_size(4)); + ASSERT_EQ(8, wf_base64_encoded_size(5)); + ASSERT_EQ(8, wf_base64_encoded_size(6)); + + ASSERT_EQ(120, wf_base64_encoded_size(90)); +} + +TEST(Base64, Encode) +{ + char buffer[42]; + + std::string in = "Hello"; + size_t length = wf_base64_encode((uint8_t const*) in.c_str(), in.size(), buffer, 42); + ASSERT_EQ(8, length); + ASSERT_STREQ("SGVsbG8=", buffer); + + in = "Hello\n"; + length = wf_base64_encode((uint8_t const*) in.c_str(), in.size(), buffer, 42); + ASSERT_EQ(8, length); + ASSERT_STREQ("SGVsbG8K", buffer); + + in = "Blue"; + length = wf_base64_encode((uint8_t const*) in.c_str(), in.size(), buffer, 42); + ASSERT_EQ(8, length); + ASSERT_STREQ("Qmx1ZQ==", buffer); +} + +TEST(Base64, FailedToEncodeBufferTooSmall) +{ + char buffer[1]; + + std::string in = "Hello"; + size_t length = wf_base64_encode((uint8_t const*) in.c_str(), in.size(), buffer, 1); + ASSERT_EQ(0, length); +} + +TEST(Base64, DecodedSize) +{ + std::string in = "SGVsbG8="; // Hello + size_t length = wf_base64_decoded_size(in.c_str(), in.size()); + ASSERT_EQ(5, length); + + in = "SGVsbG8K"; // Hello\n + length = wf_base64_decoded_size(in.c_str(), in.size()); + ASSERT_EQ(6, length); + + in = "Qmx1ZQ=="; // Blue + length = wf_base64_decoded_size(in.c_str(), in.size()); + ASSERT_EQ(4, length); +} + +TEST(Base64, IsValid) +{ + std::string in = "SGVsbG8="; // Hello + ASSERT_TRUE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "SGVsbG8K"; // Hello\n + ASSERT_TRUE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "Qmx1ZQ=="; // Blue + ASSERT_TRUE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "Qmx1ZQ=a"; + ASSERT_FALSE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "Qmx1ZQ"; + ASSERT_FALSE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "Qmx1ZQ="; + ASSERT_FALSE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "Qmx1Z==="; + ASSERT_FALSE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "Qmx1ZQ?="; + ASSERT_FALSE(wf_base64_isvalid(in.c_str(), in.size())); + + in = "Qm?1ZQ=="; + ASSERT_FALSE(wf_base64_isvalid(in.c_str(), in.size())); +} + +TEST(Base64, Decode) +{ + char buffer[42]; + + std::string in = "SGVsbG8="; // Hello + size_t length = wf_base64_decode(in.c_str(), in.size(), (uint8_t*) buffer, 42); + ASSERT_EQ(5, length); + buffer[length] = '\0'; + ASSERT_STREQ("Hello", buffer); + + in = "SGVsbG8K"; // Hello\n + length = wf_base64_decode(in.c_str(), in.size(), (uint8_t*) buffer, 42); + ASSERT_EQ(6, length); + buffer[length] = '\0'; + ASSERT_STREQ("Hello\n", buffer); + + in = "Qmx1ZQ=="; // Blue + length = wf_base64_decode(in.c_str(), in.size(), (uint8_t*) buffer, 42); + ASSERT_EQ(4, length); + buffer[length] = '\0'; + ASSERT_STREQ("Blue", buffer); +} + +TEST(Base64, FailToDecodeBufferTooSmall) +{ + char buffer[1]; + + std::string in = "SGVsbG8="; // Hello + size_t length = wf_base64_decode(in.c_str(), in.size(), (uint8_t*) buffer, 1); + ASSERT_EQ(0, length); +} +