Add new base64_encode() / base64_decode() functions
authorThomas Jarosch <thomas.jarosch@intra2net.com>
Fri, 19 May 2017 13:31:23 +0000 (15:31 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Fri, 19 May 2017 13:33:09 +0000 (15:33 +0200)
Based upon openssl. If an error occurs, an exception
is thrown and memory cleanup is still done.

CMakeLists.txt
src/CMakeLists.txt
src/stringfunc.cpp
src/stringfunc.hxx
test/stringfunc.cpp

index f50f010..3979476 100644 (file)
@@ -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)
index 7d4c5fc..e9fe706 100644 (file)
@@ -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)
 
index 9ac7c8e..3813a1c 100644 (file)
@@ -28,6 +28,7 @@ on this file might be covered by the GNU General Public License.
 #include <stdexcept>
 #include <algorithm>
 #include <cmath>    // for round()
+#include <climits>
 
 #include <wchar.h>
 #include <stdlib.h>
@@ -37,6 +38,11 @@ on this file might be covered by the GNU General Public License.
 #include <boost/numeric/conversion/cast.hpp>
 #include <boost/foreach.hpp>
 
+#include <boost/assert.hpp>
+#include <boost/shared_ptr.hpp>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+
 #include <stringfunc.hxx>
 
 using namespace std;
@@ -677,6 +683,120 @@ string shorten_stl_types(const string &input)
     return output;
 }
 
+typedef boost::shared_ptr<BIO> 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
 
 
index 824df97..3a5d038 100644 (file)
@@ -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
 
index bf33781..604f446 100644 (file)
@@ -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()