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:
parent
4753cf64e3
commit
1efbfb3056
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
33
sdk/core/azure-core/inc/azure/core/base64.hpp
Normal file
33
sdk/core/azure-core/inc/azure/core/base64.hpp
Normal 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
|
||||
96
sdk/core/azure-core/src/base64.cpp
Normal file
96
sdk/core/azure-core/src/base64.cpp
Normal 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
|
||||
@ -29,6 +29,7 @@ include(GoogleTest)
|
||||
|
||||
add_executable (
|
||||
azure-core-test
|
||||
base64.cpp
|
||||
context.cpp
|
||||
${CURL_CONNECTION_POOL_TESTS}
|
||||
${CURL_OPTIONS_TESTS}
|
||||
|
||||
63
sdk/core/azure-core/test/ut/base64.cpp
Normal file
63
sdk/core/azure-core/test/ut/base64.cpp
Normal 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()));
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user