Add Base64 encoding and decoding utility APIs to the Azure::Core namespace available from azure/core/base64.hpp. (#1297)

* Add Base64 encode and decode APIs to Azure::Core (from Azure::Storage).

* Revert changes to crypt.hpp to leave callsite updates for auto-gen
storage component for later.

* Add entry to changelog.

* Add basic unit tests.

* Remove definition of NOMINMAX since it is unnecessary.

* Include missing header from openssl.

* Include missing open ssl buffer header.

* Address feedback around angle brackets, and move implementation into a
namespace block.

* Remove redundant namespace qualifier in the signature.

* Link openssl to azure-core for non-Windows platforms, to fix the MacOS
linking issue.
This commit is contained in:
Ahson Khan 2021-01-08 21:20:44 -08:00 committed by GitHub
parent 4753cf64e3
commit 1efbfb3056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 202 additions and 0 deletions

View File

@ -9,6 +9,7 @@
- Added support for long-running operations with `Operation<T>`.
- Added support for setting a custom transport adapter by implementing the method `std::shared_ptr<HttpTransport> ::AzureSdkGetCustomHttpTransport()`.
- Added default constructor to `DateTime`.
- Added Base64 encoding and decoding utility APIs to the `Azure::Core` namespace available from `azure/core/base64.hpp`.
### Breaking Changes

View File

@ -64,6 +64,7 @@ set(
inc/azure/core/internal/contract.hpp
inc/azure/core/internal/log.hpp
inc/azure/core/logging/logging.hpp
inc/azure/core/base64.hpp
inc/azure/core/context.hpp
inc/azure/core/credentials.hpp
inc/azure/core/datetime.hpp
@ -95,6 +96,7 @@ set(
src/http/transport_policy.cpp
src/http/url.cpp
src/logging/logging.cpp
src/base64.cpp
src/context.cpp
src/datetime.cpp
src/operation_status.cpp
@ -122,6 +124,12 @@ target_include_directories(azure-core PUBLIC ${CURL_INCLUDE_DIRS})
target_include_directories(azure-core INTERFACE ${nlohmann_json_INCLUDE_DIRS})
target_link_libraries(azure-core INTERFACE Threads::Threads nlohmann_json::nlohmann_json)
if(NOT MSVC)
message("Find the OpenSSL package on MacOS, used for Base64 encoding.")
find_package(OpenSSL REQUIRED)
target_link_libraries(azure-core PRIVATE OpenSSL::SSL)
endif()
if(BUILD_TRANSPORT_CURL)
target_link_libraries(azure-core PRIVATE CURL::libcurl)
endif()

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Utility functions to help convert between binary data and UTF-8 encoded text that is
* represented in base 64.
*/
#pragma once
#include <string>
#include <vector>
namespace Azure { namespace Core {
/**
* @brief Encodes the vector of binary data into UTF-8 encoded text represented as base 64.
*
* @param data The input vector that contains binary data that needs to be encoded.
* @return The UTF-8 encoded text in base 64.
*/
std::string Base64Encode(const std::vector<uint8_t>& data);
/**
* @brief Decodes the UTF-8 encoded text represented as base 64 into binary data.
*
* @param text The input UTF-8 encoded text in base 64 that needs to be decoded.
* @return The decoded binary data.
*/
std::vector<uint8_t> Base64Decode(const std::string& text);
}} // namespace Azure::Core

View File

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/base64.hpp"
#include "azure/core/platform.hpp"
#if defined(AZ_PLATFORM_WINDOWS)
#include <windows.h>
#elif defined(AZ_PLATFORM_POSIX)
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#endif
#include <string>
#include <vector>
namespace Azure { namespace Core {
#if defined(AZ_PLATFORM_WINDOWS)
std::string Base64Encode(const std::vector<uint8_t>& data)
{
std::string encoded;
// According to RFC 4648, the encoded length should be ceiling(n / 3) * 4
DWORD encodedLength = static_cast<DWORD>((data.size() + 2) / 3 * 4);
encoded.resize(encodedLength);
CryptBinaryToStringA(
reinterpret_cast<const BYTE*>(data.data()),
static_cast<DWORD>(data.size()),
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
static_cast<LPSTR>(&encoded[0]),
&encodedLength);
return encoded;
}
std::vector<uint8_t> Base64Decode(const std::string& text)
{
std::vector<uint8_t> decoded;
// According to RFC 4648, the encoded length should be ceiling(n / 3) * 4, so we can infer an
// upper bound here
DWORD decodedLength = DWORD(text.length() / 4 * 3);
decoded.resize(decodedLength);
CryptStringToBinaryA(
text.data(),
static_cast<DWORD>(text.length()),
CRYPT_STRING_BASE64 | CRYPT_STRING_STRICT,
reinterpret_cast<BYTE*>(decoded.data()),
&decodedLength,
nullptr,
nullptr);
decoded.resize(decodedLength);
return decoded;
}
#elif defined(AZ_PLATFORM_POSIX)
std::string Base64Encode(const std::vector<uint8_t>& data)
{
BIO* bio = BIO_new(BIO_s_mem());
bio = BIO_push(BIO_new(BIO_f_base64()), bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, data.data(), static_cast<int>(data.size()));
BIO_flush(bio);
BUF_MEM* bufferPtr;
BIO_get_mem_ptr(bio, &bufferPtr);
BIO_set_close(bio, BIO_NOCLOSE);
BIO_free_all(bio);
return std::string(bufferPtr->data, bufferPtr->length);
}
std::vector<uint8_t> Base64Decode(const std::string& text)
{
std::vector<uint8_t> decoded;
// According to RFC 4648, the encoded length should be ceiling(n / 3) * 4, so we can infer an
// upper bound here
std::size_t maxDecodedLength = text.length() / 4 * 3;
decoded.resize(maxDecodedLength);
BIO* bio = BIO_new_mem_buf(text.data(), -1);
bio = BIO_push(BIO_new(BIO_f_base64()), bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
int decodedLength = BIO_read(bio, &decoded[0], static_cast<int>(text.length()));
BIO_free_all(bio);
decoded.resize(decodedLength);
return decoded;
}
#endif
}} // namespace Azure::Core

View File

@ -29,6 +29,7 @@ include(GoogleTest)
add_executable (
azure-core-test
base64.cpp
context.cpp
${CURL_CONNECTION_POOL_TESTS}
${CURL_OPTIONS_TESTS}

View File

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <azure/core/base64.hpp>
#include <gtest/gtest.h>
#include <string>
#include <vector>
using namespace Azure::Core;
TEST(Base64, Basic)
{
int maxLength = 7;
std::vector<uint8_t> data;
for (int i = 0; i < maxLength; i++)
{
data.push_back(i + 1);
}
std::string result = Base64Encode(data);
EXPECT_PRED2(
[](std::string expect, std::string actual) { return expect == actual; },
result,
"AQIDBAUGBw==");
EXPECT_TRUE(std::equal(data.begin(), data.end(), Base64Decode(result).begin()));
std::vector<uint8_t> subsection = std::vector<uint8_t>(data.begin(), data.begin() + 1);
result = Base64Encode(subsection);
EXPECT_PRED2(
[](std::string expect, std::string actual) { return expect == actual; }, result, "AQ==");
EXPECT_TRUE(std::equal(subsection.begin(), subsection.end(), Base64Decode(result).begin()));
subsection = std::vector<uint8_t>(data.begin(), data.begin() + 2);
result = Base64Encode(subsection);
EXPECT_PRED2(
[](std::string expect, std::string actual) { return expect == actual; }, result, "AQI=");
EXPECT_TRUE(std::equal(subsection.begin(), subsection.end(), Base64Decode(result).begin()));
subsection = std::vector<uint8_t>(data.begin(), data.begin() + 3);
result = Base64Encode(subsection);
EXPECT_PRED2(
[](std::string expect, std::string actual) { return expect == actual; }, result, "AQID");
EXPECT_TRUE(std::equal(subsection.begin(), subsection.end(), Base64Decode(result).begin()));
subsection = std::vector<uint8_t>(data.begin(), data.begin() + 4);
result = Base64Encode(subsection);
EXPECT_PRED2(
[](std::string expect, std::string actual) { return expect == actual; }, result, "AQIDBA==");
EXPECT_TRUE(std::equal(subsection.begin(), subsection.end(), Base64Decode(result).begin()));
subsection = std::vector<uint8_t>(data.begin(), data.begin() + 5);
result = Base64Encode(subsection);
EXPECT_PRED2(
[](std::string expect, std::string actual) { return expect == actual; }, result, "AQIDBAU=");
EXPECT_TRUE(std::equal(subsection.begin(), subsection.end(), Base64Decode(result).begin()));
subsection = std::vector<uint8_t>(data.begin(), data.begin() + 6);
result = Base64Encode(subsection);
EXPECT_PRED2(
[](std::string expect, std::string actual) { return expect == actual; }, result, "AQIDBAUG");
EXPECT_TRUE(std::equal(subsection.begin(), subsection.end(), Base64Decode(result).begin()));
}