* Remove unused Boost headers from FR module source

* Declare encode/decode base64 utility methods

* Implement Encode methods

* Update packages.lock.json

* Update packages.lock.json

* Use string as return value to ensure lifetime

See https://learn.microsoft.com/en-us/cpp/code-quality/c26816?view=msvc-170

* Apply padding for Boost variant

* Remove decode from base64 wstring variant

* Cover 4*3 (plus empty) cases in unit tests

* Make output padding size match to input size % 4

* clang format

* Implement DecodeBase64

* Replace scattered usage of Boost base64_from_binary

* Remove Boost includes from BaseFrRc

* Change files

* Remove EncodeBase64(wstring_view)

* Add test for non-text values

* Pass string_view by value

* Ensure binary data lifetime in test
This commit is contained in:
Julio César Rocha 2024-02-08 12:27:30 -08:00 коммит произвёл GitHub
Родитель 0d65b8a7a5
Коммит d9bd78f3dd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
17 изменённых файлов: 317 добавлений и 78 удалений

Просмотреть файл

@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fix Base64 encoding padding",
"packageName": "react-native-windows",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}

Просмотреть файл

@ -64,7 +64,7 @@
string literals. It prevents code like
wchar_t* str = L"hello";
from compiling. -->
<AdditionalOptions>%(AdditionalOptions) /Zc:strictStrings</AdditionalOptions>
<AdditionalOptions>%(AdditionalOptions) /Zc:strictStrings /await</AdditionalOptions>
</ClCompile>
<Link>
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
@ -78,12 +78,17 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Unicode.cpp" />
<ClCompile Include="Utilities.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Unicode.h" />
<ClInclude Include="Utilities.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ItemGroup>
<PackageReference Include="boost" Version="1.76.0.0" />
<PackageReference Include="Microsoft.Windows.CppWinRT" Version="$(CppWinRTVersion)" PrivateAssets="all" />
</ItemGroup>
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
<Target Name="Deploy" />

Просмотреть файл

@ -12,6 +12,9 @@
<ClCompile Include="Unicode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utilities.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Unicode.h">

Просмотреть файл

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "Utilities.h"
// Boost Library
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>
#include <boost/archive/iterators/transform_width.hpp>
// Windows API
#include <winrt/Windows.Security.Cryptography.h>
// Standard Library
using std::string;
using std::string_view;
using std::wstring_view;
using winrt::array_view;
using winrt::Windows::Security::Cryptography::BinaryStringEncoding;
using winrt::Windows::Security::Cryptography::CryptographicBuffer;
namespace Microsoft::React::Utilities {
string DecodeBase64(string_view base64) noexcept {
typedef array_view<char const> av_t;
auto bytes = av_t(base64.data(), static_cast<av_t::size_type>(base64.size()));
using namespace boost::archive::iterators;
typedef transform_width<binary_from_base64<const char *>, 8, 6> decode_base64;
std::ostringstream oss;
std::copy(decode_base64(bytes.cbegin()), decode_base64(bytes.cend()), ostream_iterator<char>(oss));
return oss.str();
}
// https://www.boost.org/doc/libs/1_76_0/libs/serialization/doc/dataflow.html
string EncodeBase64(string_view text) noexcept {
typedef array_view<char const> av_t;
auto bytes = av_t(text.data(), static_cast<av_t::size_type>(text.size()));
using namespace boost::archive::iterators;
typedef base64_from_binary<transform_width<const char *, 6, 8>> encode_base64;
std::ostringstream oss;
std::copy(encode_base64(bytes.cbegin()), encode_base64(bytes.cend()), ostream_iterator<char>(oss));
// https://unix.stackexchange.com/questions/631501
auto padLength = (4 - (oss.tellp() % 4)) % 4;
for (auto i = 0; i < padLength; ++i) {
oss << '=';
}
return oss.str();
}
} // namespace Microsoft::React::Utilities

Просмотреть файл

@ -1,7 +1,20 @@
{
"version": 1,
"dependencies": {
"native,Version=v0.0": {},
"native,Version=v0.0": {
"boost": {
"type": "Direct",
"requested": "[1.76.0, )",
"resolved": "1.76.0",
"contentHash": "p+w3YvNdXL8Cu9Fzrmexssu0tZbWxuf6ywsQqHjDlKFE5ojXHof1HIyMC3zDLfLnh80dIeFcEUAuR2Asg/XHRA=="
},
"Microsoft.Windows.CppWinRT": {
"type": "Direct",
"requested": "[2.0.211028.7, )",
"resolved": "2.0.211028.7",
"contentHash": "JBGI0c3WLoU6aYJRy9Qo0MLDQfObEp+d4nrhR95iyzf7+HOgjRunHDp/6eGFREd7xq3OI1mll9ecJrMfzBvlyg=="
}
},
"native,Version=v0.0/win10-arm": {},
"native,Version=v0.0/win10-arm-aot": {},
"native,Version=v0.0/win10-arm64-aot": {},

Просмотреть файл

@ -2,6 +2,9 @@
// Licensed under the MIT License.
#pragma once
// Standard Library
#include <string>
#include <type_traits>
#include <utility>
@ -37,3 +40,11 @@ constexpr std::size_t ArraySize(T (&)[N]) noexcept {
}
} // namespace Microsoft::Common::Utilities
namespace Microsoft::React::Utilities {
std::string DecodeBase64(std::string_view text) noexcept;
std::string EncodeBase64(std::string_view text) noexcept;
} // namespace Microsoft::React::Utilities

Просмотреть файл

@ -54,7 +54,10 @@
"contentHash": "1tAtFgtbVpI/JgRIxy9j30R/W6B1zi9dYt0o5QwAk5V3X2mo9xrrHcbXlbczKQIftYoNHe0Mfq9ExIu9A1Cs0g=="
},
"common": {
"type": "Project"
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )"
}
},
"fmt": {
"type": "Project"

Просмотреть файл

@ -47,7 +47,10 @@
"contentHash": "WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg=="
},
"common": {
"type": "Project"
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )"
}
},
"fmt": {
"type": "Project"

Просмотреть файл

@ -50,7 +50,10 @@
"contentHash": "ksHjshj05AMAQ/v7Wet5Dwcwn9Up2BTOIrTv1yEW7+D23FQX0yILW5Zw0bmlWtV8MEtdY611z+06U3Xvu2ygSA=="
},
"common": {
"type": "Project"
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )"
}
},
"fmt": {
"type": "Project"

Просмотреть файл

@ -3,10 +3,12 @@
#include <CppUnitTest.h>
#include <Utils.h>
#include <utilities.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using std::string;
using std::string_view;
namespace Microsoft::React::Test {
@ -14,15 +16,17 @@ namespace Microsoft::React::Test {
// test macros.
// clang-format off
TEST_CLASS(UtilsTest) {
TEST_CLASS(UtilsTest)
{
void ExpectUrl(
string urlString,
string protocol,
string host,
string port = "",
string path = "/",
string query = "") {
string urlString,
string protocol,
string host,
string port = "",
string path = "/",
string query = "")
{
Url url(std::move(urlString));
@ -35,96 +39,232 @@ TEST_CLASS(UtilsTest) {
#pragma region Url Tests
TEST_METHOD(UtilsTest_ValidProtocols) {
string protocols[4]{"http", "https", "ws", "wss"};
for (auto protocol : protocols) {
TEST_METHOD(UtilsTest_ValidProtocols)
{
string protocols[4] { "http", "https", "ws", "wss" };
for (auto protocol : protocols)
{
ExpectUrl(string(protocol + "://internal"), protocol, "internal");
}
}
TEST_METHOD(UtilsTest_IntraHost) {
TEST_METHOD(UtilsTest_IntraHost)
{
ExpectUrl("ws://internal", "ws", "internal");
}
TEST_METHOD(UtilsTest_IntraHostTrailing) {
TEST_METHOD(UtilsTest_IntraHostTrailing)
{
ExpectUrl("ws://internal/", "ws", "internal");
}
TEST_METHOD(UtilsTest_IntraHostQueryLeading) {
TEST_METHOD(UtilsTest_IntraHostQueryLeading)
{
ExpectUrl("ws://internal?", "ws", "internal");
}
TEST_METHOD(UtilsTest_IntraHostTrailingQueryLeading) {
TEST_METHOD(UtilsTest_IntraHostTrailingQueryLeading)
{
ExpectUrl("ws://internal/?", "ws", "internal");
}
TEST_METHOD(UtilsTest_NormalHostQueryLeading) {
TEST_METHOD(UtilsTest_NormalHostQueryLeading)
{
ExpectUrl("ws://example.com?", "ws", "example.com");
}
TEST_METHOD(UtilsTest_IntraPort) {
TEST_METHOD(UtilsTest_IntraPort)
{
ExpectUrl("ws://internal:5000", "ws", "internal", "5000");
}
TEST_METHOD(UtilsTest_NormalPort) {
TEST_METHOD(UtilsTest_NormalPort)
{
ExpectUrl("ws://example.com:443", "ws", "example.com", "443");
}
TEST_METHOD(UtilsTest_PortPath) {
TEST_METHOD(UtilsTest_PortPath)
{
ExpectUrl("ws://example.com:5000/ws", "ws", "example.com", "5000", "/ws");
}
TEST_METHOD(UtilsTest_Query) {
TEST_METHOD(UtilsTest_Query)
{
ExpectUrl(
"ws://example.com?a=1&b=2", "ws", "example.com", "", "/", "a=1&b=2");
"ws://example.com?a=1&b=2", "ws", "example.com", "", "/", "a=1&b=2");
}
TEST_METHOD(UtilsTest_TrailingPathQuery) {
TEST_METHOD(UtilsTest_TrailingPathQuery)
{
ExpectUrl(
"ws://example.com/?a=1&b=2", "ws", "example.com", "", "/", "a=1&b=2");
"ws://example.com/?a=1&b=2", "ws", "example.com", "", "/", "a=1&b=2");
}
TEST_METHOD(UtilsTest_HyphenHostInternal) {
TEST_METHOD(UtilsTest_HyphenHostInternal)
{
ExpectUrl("wss://-my-hyphened-host--", "wss", "-my-hyphened-host--");
}
TEST_METHOD(UtilsTest_NestedPathTrailingSlashLeadingQuestionMark) {
TEST_METHOD(UtilsTest_NestedPathTrailingSlashLeadingQuestionMark)
{
ExpectUrl(
"ws://example.com/the/nested/path/?",
"ws",
"example.com",
"",
"/the/nested/path/");
"ws://example.com/the/nested/path/?",
"ws",
"example.com",
"",
"/the/nested/path/");
}
TEST_METHOD(UtilsTest_NestedSubdomain) {
TEST_METHOD(UtilsTest_NestedSubdomain)
{
ExpectUrl(
"ws://nested.sub.domain.of.example.com",
"ws",
"nested.sub.domain.of.example.com");
"ws://nested.sub.domain.of.example.com",
"ws",
"nested.sub.domain.of.example.com");
}
#pragma region Url Negative Tests
TEST_METHOD(UtilsTest_EmptyStringFails) {
Assert::ExpectException<std::exception>([]() { Url(""); });
TEST_METHOD(UtilsTest_EmptyStringFails)
{
Assert::ExpectException<std::exception>([]()
{
Url("");
});
}
TEST_METHOD(UtilsTest_WrongProtocol) {
Assert::ExpectException<std::exception>([]() { Url("foos://internal"); });
TEST_METHOD(UtilsTest_WrongProtocol)
{
Assert::ExpectException<std::exception>([]()
{
Url("foos://internal");
});
}
TEST_METHOD(UtilsTest_BadCharsInPort) {
Assert::ExpectException<std::exception>([]() { Url("ws://internal:50O0"); });
TEST_METHOD(UtilsTest_BadCharsInPort)
{
Assert::ExpectException<std::exception>([]()
{
Url("ws://internal:50O0");
});
}
TEST_METHOD(UtilsTest_SpacesInProtocol) {
Assert::ExpectException<std::exception>([]() { Url(" ws://internal"); });
TEST_METHOD(UtilsTest_SpacesInProtocol)
{
Assert::ExpectException<std::exception>([]()
{
Url(" ws://internal");
});
}
#pragma endregion
#pragma endregion
#pragma region Base64 Tests
TEST_METHOD(EncodeStdStringToBase64Succeeds)
{
string messages[]
{
"",
"a",
"ab",
"abc",
"abcd",
"abcde",
"abcdef",
"abcdefg",
"abcdefgh",
"abcdefghi",
"abcdefghij",
"abcdefghijk",
"abcdefghijkl"
};
// Computed using [System.Convert]::ToBase64String
constexpr const char* expected[] =
{
"",
"YQ==",
"YWI=",
"YWJj",
"YWJjZA==",
"YWJjZGU=",
"YWJjZGVm",
"YWJjZGVmZw==",
"YWJjZGVmZ2g=",
"YWJjZGVmZ2hp",
"YWJjZGVmZ2hpag==",
"YWJjZGVmZ2hpams=",
"YWJjZGVmZ2hpamts"
};
for (auto i = 0; i < sizeof(messages)/sizeof(string); ++i)
{
auto actual = Utilities::EncodeBase64(string_view(messages[i]));
Assert::AreEqual(expected[i], actual.data());
}
}
TEST_METHOD(EncodeArrayViewToBase64Succeeds)
{
//[System.Convert]::ToBase64String([byte[]](0,1,2,3,4))
constexpr const char* expected = "AAECAwQ=";
auto input = std::vector<uint8_t>{ 0, 1, 2, 3, 4 };
auto bytes = winrt::array_view<const uint8_t>(input.data(), input.data() + input.size());
auto chars = reinterpret_cast<const char*>(bytes.data());
auto view = string_view(chars, bytes.size());
auto actual = Utilities::EncodeBase64(view);
Assert::AreEqual(expected, actual.c_str());
}
TEST_METHOD(DecodeStdStringFromBase64Succeeds)
{
constexpr const char* messages[] =
{
"",
"YQ==",
"YWI=",
"YWJj",
"YWJjZA==",
"YWJjZGU=",
"YWJjZGVm",
"YWJjZGVmZw==",
"YWJjZGVmZ2g=",
"YWJjZGVmZ2hp",
"YWJjZGVmZ2hpag==",
"YWJjZGVmZ2hpams=",
"YWJjZGVmZ2hpamts"
};
constexpr const char* expected[]
{
"",
"a",
"ab",
"abc",
"abcd",
"abcde",
"abcdef",
"abcdefg",
"abcdefgh",
"abcdefghi",
"abcdefghij",
"abcdefghijk",
"abcdefghijkl"
};
for (auto i = 0; i < sizeof(messages) / sizeof(string); ++i)
{
auto actual = Utilities::DecodeBase64(string_view(messages[i]));
Assert::AreEqual(expected[i], actual.data());
}
}
#pragma endregion Base64 Tests
};
// clang-format on

Просмотреть файл

@ -50,7 +50,10 @@
"contentHash": "ksHjshj05AMAQ/v7Wet5Dwcwn9Up2BTOIrTv1yEW7+D23FQX0yILW5Zw0bmlWtV8MEtdY611z+06U3Xvu2ygSA=="
},
"common": {
"type": "Project"
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )"
}
},
"fmt": {
"type": "Project"

Просмотреть файл

@ -53,7 +53,10 @@
"contentHash": "WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg=="
},
"common": {
"type": "Project"
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )"
}
},
"fmt": {
"type": "Project"

Просмотреть файл

@ -9,7 +9,10 @@
"contentHash": "p+w3YvNdXL8Cu9Fzrmexssu0tZbWxuf6ywsQqHjDlKFE5ojXHof1HIyMC3zDLfLnh80dIeFcEUAuR2Asg/XHRA=="
},
"common": {
"type": "Project"
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )"
}
},
"fmt": {
"type": "Project"
@ -17,8 +20,8 @@
"folly": {
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )",
"fmt": "[1.0.0, )"
"Fmt": "[1.0.0, )",
"boost": "[1.76.0, )"
}
}
},

Просмотреть файл

@ -50,7 +50,10 @@
"contentHash": "1tAtFgtbVpI/JgRIxy9j30R/W6B1zi9dYt0o5QwAk5V3X2mo9xrrHcbXlbczKQIftYoNHe0Mfq9ExIu9A1Cs0g=="
},
"common": {
"type": "Project"
"type": "Project",
"dependencies": {
"boost": "[1.76.0, )"
}
},
"fmt": {
"type": "Project"

Просмотреть файл

@ -3,10 +3,7 @@
#include "BaseFileReaderResource.h"
// Boost Library
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <utilities.h>
// Windows API
#include <winrt/base.h>
@ -73,18 +70,9 @@ void BaseFileReaderResource::ReadAsDataUrl(
result += type;
result += ";base64,";
// https://www.boost.org/doc/libs/1_76_0/libs/serialization/doc/dataflow.html
using namespace boost::archive::iterators;
typedef base64_from_binary<transform_width<const char *, 6, 8>> encode_base64;
std::ostringstream oss;
std::copy(encode_base64(bytes.cbegin()), encode_base64(bytes.cend()), ostream_iterator<char>(oss));
result += oss.str();
// https://unix.stackexchange.com/questions/631501
auto padLength = 4 - (oss.tellp() % 4);
for (auto i = 0; i < padLength; ++i) {
result += '=';
}
auto chars = reinterpret_cast<const char *>(bytes.data());
auto view = std::string_view(chars, bytes.size());
result += Utilities::EncodeBase64(view);
resolver(std::move(result));
}

Просмотреть файл

@ -7,11 +7,6 @@
#include <ReactPropertyBag.h>
#include <sstream>
// Boost Library
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>
#include <boost/archive/iterators/transform_width.hpp>
// React Native
#include <cxxreact/JsArgumentHelpers.h>

Просмотреть файл

@ -5,6 +5,7 @@
#include <Modules/IHttpModuleProxy.h>
#include <Modules/IWebSocketModuleProxy.h>
#include <utilities.h>
// Boost Libraries
#include <boost/uuid/uuid_io.hpp>
@ -95,11 +96,9 @@ void DefaultBlobResource::SendOverSocket(string &&blobId, int64_t offset, int64_
return m_callbacks.OnError(e.what());
}
auto buffer = CryptographicBuffer::CreateFromByteArray(data);
auto base64Hstring = CryptographicBuffer::EncodeToBase64String(std::move(buffer));
auto base64String = winrt::to_string(base64Hstring);
wsProxy->SendBinary(std::move(base64String), socketId);
auto chars = reinterpret_cast<const char *>(data.data());
auto view = std::string_view(chars, data.size());
wsProxy->SendBinary(Utilities::EncodeBase64(view), socketId);
}
void DefaultBlobResource::CreateFromParts(msrn::JSValueArray &&parts, string &&blobId) noexcept /*override*/ {