From: Thomas Jarosch Date: Fri, 19 May 2017 13:31:23 +0000 (+0200) Subject: Add new base64_encode() / base64_decode() functions X-Git-Tag: v2.10~12 X-Git-Url: http://developer.intra2net.com/git/?a=commitdiff_plain;h=2bb723379a55acfa37cd2258f14a43914022ef41;p=libi2ncommon Add new base64_encode() / base64_decode() functions Based upon openssl. If an error occurs, an exception is thrown and memory cleanup is still done. --- diff --git a/CMakeLists.txt b/CMakeLists.txt index f50f010..3979476 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,11 @@ if (IMAP_UTF7_SUPPORT) link_directories(${ICONV_LIBRARY_DIRS}) endif(IMAP_UTF7_SUPPORT) +# Detect openssl - for base64 routines +pkg_check_modules(OPENSSL REQUIRED openssl) +INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIRS}) +LINK_DIRECTORIES(${OPENSSL_LIBRARY_DIRS}) + # pkgconfig output set(prefix ${CMAKE_INSTALL_PREFIX}) set(exec_prefix ${CMAKE_INSTALL_PREFIX}/bin) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7d4c5fc..e9fe706 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,7 +58,11 @@ SET(cpp_headers add_library(i2ncommon SHARED ${cpp_sources} ${cpp_headers}) -target_link_libraries(i2ncommon ${Boost_IOSTREAMS_LIBRARIES} ${Boost_THREAD_LIBRARIES} ${ICONV_LIBRARIES}) +target_link_libraries(i2ncommon + ${Boost_IOSTREAMS_LIBRARIES} + ${Boost_THREAD_LIBRARIES} + ${ICONV_LIBRARIES} + ${OPENSSL_LIBRARIES}) set_target_properties(i2ncommon PROPERTIES VERSION ${VERSION} SOVERSION 6) diff --git a/src/stringfunc.cpp b/src/stringfunc.cpp index 9ac7c8e..3813a1c 100644 --- a/src/stringfunc.cpp +++ b/src/stringfunc.cpp @@ -28,6 +28,7 @@ on this file might be covered by the GNU General Public License. #include #include #include // for round() +#include #include #include @@ -37,6 +38,11 @@ on this file might be covered by the GNU General Public License. #include #include +#include +#include +#include +#include + #include using namespace std; @@ -677,6 +683,120 @@ string shorten_stl_types(const string &input) return output; } +typedef boost::shared_ptr BIO_Ptr; + +/** +* @brief Converts openssl generic input/output to std::string +* +* Code adapted from keymakerd. +* +* @param bio Openssl's generic input/output +* @return :string STL string +**/ +static std::string _convert_BIO_to_string(BIO *input) +{ + std::string rtn; + + char *output = NULL; + long written = BIO_get_mem_data(input, &output); + if (written <= 0 || output == NULL) + return rtn; + + rtn.assign(output, written); //lint !e534 !e732 + return rtn; +} //lint !e1764 + +/** + * @brief base64 encode a string using OpenSSL base64 functions + * + * Data size limit is 2GB on 32 bit (LONG_MAX) + * + * @param input String to encode + * @return base64 encoded string + */ +std::string base64_encode(const std::string &input) +{ + // check for empty buffer + if (input.empty()) + return input; + + // safety check to ensure our check afer BIO_write() works + if (input.size() >= LONG_MAX) + throw runtime_error("base64 encode: Too much data"); + + // setup encoder. Note: BIO_free_all frees both BIOs. + BIO_Ptr base64_encoder(BIO_new(BIO_f_base64()), BIO_free_all); + BIO *encoder_bio = base64_encoder.get(); + BIO_set_flags(encoder_bio, BIO_FLAGS_BASE64_NO_NL); + + // chain output buffer and encoder together + BIO *encoded_result = BIO_new(BIO_s_mem()); + BIO_push(encoder_bio, encoded_result); + + // encode + long written = BIO_write(encoder_bio, input.c_str(), input.size()); + if ((unsigned)written != input.size()) + { + ostringstream out; + out << "base64 encoding failed: input size: " + << input.size() << " vs. output size: " << written; + throw runtime_error(out.str()); + } + if (BIO_flush(encoder_bio) != 1) + throw runtime_error("base64 encode: BIO_flush() failed"); + + return _convert_BIO_to_string(encoded_result); +} + +/** + * @brief base64 decode a string using OpenSSL base64 functions + * + * @param input String to decode + * @return base64 decoded string + */ +std::string base64_decode(const std::string &input) +{ + // check for empty buffer + if (input.empty()) + return input; + + // safety check for BIO_new_mem_buf() + if (input.size() >= INT_MAX) + throw runtime_error("base64 decode: Too much data"); + + // setup encoder. Note: BIO_free_all frees both BIOs. + BIO_Ptr base64_decoder(BIO_new(BIO_f_base64()), BIO_free_all); + BIO *bio_base64 = base64_decoder.get(); + BIO_set_flags(bio_base64, BIO_FLAGS_BASE64_NO_NL); + + // chain input buffer and decoder together + BIO *bio_input = BIO_new_mem_buf((void*)input.c_str(), input.size()); + bio_input = BIO_push(bio_base64, bio_input); + + BIO_Ptr decoded_result(BIO_new(BIO_s_mem()), BIO_free_all); + BIO *bio_decoded = decoded_result.get(); + const int convbuf_size = 512; + char convbuf[convbuf_size]; + + long read_bytes = 0; + while((read_bytes = BIO_read(bio_input, convbuf, convbuf_size)) > 0) + { + BOOST_ASSERT(read_bytes <= convbuf_size); + long written_bytes = BIO_write(bio_decoded, convbuf, read_bytes); + if (written_bytes != read_bytes) + { + ostringstream out; + out << "base64 decoding failed: read_bytes: " + << read_bytes << " vs. written_bytes: " << written_bytes; + throw runtime_error(out.str()); + } + } + if (read_bytes == -2 || read_bytes == -1) + throw runtime_error("base64 decode: Error during decoding"); + + return _convert_BIO_to_string(bio_decoded); +} + } // eo namespace I2n diff --git a/src/stringfunc.hxx b/src/stringfunc.hxx index 824df97..3a5d038 100644 --- a/src/stringfunc.hxx +++ b/src/stringfunc.hxx @@ -267,6 +267,8 @@ std::string to_string(const T& v) */ std::string shorten_stl_types(const std::string &input); +std::string base64_encode(const std::string &input); +std::string base64_decode(const std::string &input); } // eo namespace I2n diff --git a/test/stringfunc.cpp b/test/stringfunc.cpp index bf33781..604f446 100644 --- a/test/stringfunc.cpp +++ b/test/stringfunc.cpp @@ -849,4 +849,46 @@ BOOST_AUTO_TEST_CASE(shorten_stl_types_nothing) BOOST_CHECK_EQUAL(shorten_stl_types(text), text); } +BOOST_AUTO_TEST_CASE(base64_encode_decode) +{ + string text = "Hello World\n"; + + string encoded = base64_encode(text); + + BOOST_CHECK_EQUAL("SGVsbG8gV29ybGQK", encoded); + BOOST_CHECK_EQUAL(text, base64_decode(encoded)); +} + +BOOST_AUTO_TEST_CASE(base64_empty_string) +{ + string text = ""; + string encoded = base64_encode(text); + + BOOST_CHECK_EQUAL("", encoded); + BOOST_CHECK_EQUAL(text, base64_decode(encoded)); +} + +BOOST_AUTO_TEST_CASE(base64_large_string_with_zero) +{ + // 10 MB data + int data_size = 1024 * 1024 * 10; + + string large_binary_data(data_size, 0); + BOOST_CHECK_EQUAL(data_size, large_binary_data.size()); + + string encoded = base64_encode(large_binary_data); + + string decoded = base64_decode(encoded); + BOOST_CHECK_EQUAL(large_binary_data, decoded); +} + +BOOST_AUTO_TEST_CASE(base64_decode_garbage) +{ + std::string data = "Hello World, this is unencoded data"; + string decoded = base64_decode(data); + + // garbage turns out to be an empty string + BOOST_CHECK_EQUAL(0, decoded.size()); +} + BOOST_AUTO_TEST_SUITE_END()