See More

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #include "graphqlservice/internal/Base64.h" #include #include #include namespace graphql::internal { std::uint8_t Base64::verifyFromBase64(char ch) { const std::uint8_t result = fromBase64(ch); if ((result & 0xC0) != 0) { throw std::logic_error { "invalid character in base64 encoded string" }; } return result; } std::vector<:uint8_t> Base64::fromBase64(std::string_view encoded) { std::vector<:uint8_t> result; if (encoded.empty()) { return result; } result.reserve((encoded.size() + (encoded.size() % 4)) * 3 / 4); // First decode all of the full unpadded segments 24 bits at a time while (encoded.size() >= 4 && encoded[3] != padding) { const uint32_t segment = (static_cast(verifyFromBase64(encoded[0])) << 18) | (static_cast(verifyFromBase64(encoded[1])) << 12) | (static_cast(verifyFromBase64(encoded[2])) << 6) | static_cast(verifyFromBase64(encoded[3])); result.emplace_back(static_cast<:uint8_t>((segment & 0xFF0000) >> 16)); result.emplace_back(static_cast<:uint8_t>((segment & 0xFF00) >> 8)); result.emplace_back(static_cast<:uint8_t>(segment & 0xFF)); encoded = encoded.substr(4); } // Get any leftover partial segment with 2 or 3 non-padding characters if (encoded.size() > 1) { const bool triplet = (encoded.size() > 2 && padding != encoded[2]); const std::uint8_t tail = (triplet ? verifyFromBase64(encoded[2]) : 0); const uint16_t segment = (static_cast(verifyFromBase64(encoded[0])) << 10) | (static_cast(verifyFromBase64(encoded[1])) << 4) | (static_cast(tail) >> 2); if (triplet) { if (tail & 0x3) { throw std::logic_error { "invalid padding at the end of a base64 encoded string" }; } result.emplace_back(static_cast<:uint8_t>((segment & 0xFF00) >> 8)); result.emplace_back(static_cast<:uint8_t>(segment & 0xFF)); encoded = encoded.substr(3); } else { if (segment & 0xFF) { throw std::logic_error { "invalid padding at the end of a base64 encoded string" }; } result.emplace_back(static_cast<:uint8_t>((segment & 0xFF00) >> 8)); encoded = encoded.substr(2); } } // Make sure anything that's left is 0 - 2 characters of padding if ((encoded.size() > 0 && padding != encoded[0]) || (encoded.size() > 1 && padding != encoded[1]) || encoded.size() > 2) { throw std::logic_error { "invalid padding at the end of a base64 encoded string" }; } return result; } char Base64::verifyToBase64(std::uint8_t i) { unsigned char result = toBase64(i); if (result == padding) { throw std::logic_error { "invalid 6-bit value" }; } return result; } std::string Base64::toBase64(const std::vector<:uint8_t>& bytes) { std::string result; if (bytes.empty()) { return result; } std::size_t count = bytes.size(); const std::uint8_t* data = bytes.data(); result.reserve((count + (count % 3)) * 4 / 3); // First encode all of the full unpadded segments 24 bits at a time while (count >= 3) { const uint32_t segment = (static_cast(data[0]) << 16) | (static_cast(data[1]) << 8) | static_cast(data[2]); result.append({ verifyToBase64(static_cast<:uint8_t>((segment & 0xFC0000) >> 18)), verifyToBase64(static_cast<:uint8_t>((segment & 0x3F000) >> 12)), verifyToBase64(static_cast<:uint8_t>((segment & 0xFC0) >> 6)), verifyToBase64(static_cast<:uint8_t>(segment & 0x3F)) }); data += 3; count -= 3; } // Get any leftover partial segment with 1 or 2 bytes if (count > 0) { const bool pair = (count > 1); const uint16_t segment = (static_cast(data[0]) << 8) | (pair ? static_cast(data[1]) : 0); const std::array remainder { verifyToBase64(static_cast<:uint8_t>( (segment & 0xFC00) >> 10)), verifyToBase64(static_cast<:uint8_t>((segment & 0x3F0) >> 4)), (pair ? verifyToBase64(static_cast<:uint8_t>((segment & 0xF) << 2)) : padding), padding }; result.append(remainder.data(), remainder.size()); } return result; } Base64::Comparison Base64::compareBase64( const std::vector<:uint8_t>& bytes, std::string_view maybeEncoded) noexcept { if (bytes.empty()) { if (maybeEncoded.empty()) { return Comparison::EqualTo; } } else if (maybeEncoded.empty()) { return Comparison::GreaterThan; } auto result = Comparison::EqualTo; auto itr = bytes.cbegin(); const auto itrEnd = bytes.cend(); // First decode and compare all of the full unpadded segments 24 bits at a time while (maybeEncoded.size() >= 4 && maybeEncoded[3] != padding) { const auto a = fromBase64(maybeEncoded[0]); const auto b = fromBase64(maybeEncoded[1]); const auto c = fromBase64(maybeEncoded[2]); const auto d = fromBase64(maybeEncoded[3]); if (((a | b | c | d) & 0xC0) != 0) { // Invalid Base64 characters return Comparison::InvalidBase64; } if (Comparison::EqualTo == result) { const uint32_t segment = (static_cast(a) << 18) | (static_cast(b) << 12) | (static_cast(c) << 6) | static_cast(d); const std::array decoded { static_cast<:uint8_t>((segment & 0xFF0000) >> 16), static_cast<:uint8_t>((segment & 0xFF00) >> 8), static_cast<:uint8_t>(segment & 0xFF) }; for (auto value : decoded) { if (itr == itrEnd) { result = Comparison::LessThan; break; } if (*itr != value) { result = *itr < value ? Comparison::LessThan : Comparison::GreaterThan; break; } ++itr; } } maybeEncoded = maybeEncoded.substr(4); } // Compare any leftover partial segment with 2 or 3 non-padding characters if (maybeEncoded.size() > 1) { const bool triplet = (maybeEncoded.size() > 2 && padding != maybeEncoded[2]); const auto a = fromBase64(maybeEncoded[0]); const auto b = fromBase64(maybeEncoded[1]); const auto c = triplet ? fromBase64(maybeEncoded[2]) : std::uint8_t {}; if (((a | b | c) & 0xC0) != 0 || (c & 0x3) != 0) { // Invalid Base64 characters or padding return Comparison::InvalidBase64; } const uint16_t segment = (static_cast(a) << 10) | (static_cast(b) << 4) | (static_cast(c) >> 2); const std::array decoded { static_cast<:uint8_t>((segment & 0xFF00) >> 8), static_cast<:uint8_t>(segment & 0xFF) }; if (triplet) { if (Comparison::EqualTo == result) { for (auto value : decoded) { if (itr == itrEnd) { result = Comparison::LessThan; break; } if (*itr != value) { result = *itr < value ? Comparison::LessThan : Comparison::GreaterThan; break; } ++itr; } } maybeEncoded = maybeEncoded.substr(3); } else { if (decoded[1] != 0) { // Invalid padding return Comparison::InvalidBase64; } if (Comparison::EqualTo == result) { if (itr == itrEnd) { result = Comparison::LessThan; } else if (*itr != decoded[0]) { result = *itr < decoded[0] ? Comparison::LessThan : Comparison::GreaterThan; } ++itr; } maybeEncoded = maybeEncoded.substr(2); } } // Make sure anything that's left is 0 - 2 characters of padding if ((maybeEncoded.size() > 0 && padding != maybeEncoded[0]) || (maybeEncoded.size() > 1 && padding != maybeEncoded[1]) || maybeEncoded.size() > 2) { return Comparison::InvalidBase64; } if (Comparison::EqualTo == result) { // We should reach the end of the byte vector if (itr != itrEnd) { result = Comparison::GreaterThan; } } return result; } bool Base64::validateBase64(std::string_view maybeEncoded) noexcept { if (maybeEncoded.empty()) { return true; } // First decode and compare all of the full unpadded segments 24 bits at a time while (maybeEncoded.size() >= 4 && maybeEncoded[3] != padding) { const auto a = fromBase64(maybeEncoded[0]); const auto b = fromBase64(maybeEncoded[1]); const auto c = fromBase64(maybeEncoded[2]); const auto d = fromBase64(maybeEncoded[3]); if (((a | b | c | d) & 0xC0) != 0) { // Invalid Base64 characters return false; } maybeEncoded = maybeEncoded.substr(4); } // Compare any leftover partial segment with 2 or 3 non-padding characters if (maybeEncoded.size() > 1) { const bool triplet = (maybeEncoded.size() > 2 && padding != maybeEncoded[2]); const auto a = fromBase64(maybeEncoded[0]); const auto b = fromBase64(maybeEncoded[1]); const auto c = triplet ? fromBase64(maybeEncoded[2]) : std::uint8_t {}; if (((a | b | c) & 0xC0) != 0 || (c & 0x3) != 0) { // Invalid Base64 characters or padding return false; } if (triplet) { maybeEncoded = maybeEncoded.substr(3); } else { const uint16_t segment = (static_cast(a) << 10) | (static_cast(b) << 4) | (static_cast(c) >> 2); if ((segment & 0xFF) != 0) { // Invalid padding return false; } maybeEncoded = maybeEncoded.substr(2); } } // Make sure anything that's left is 0 - 2 characters of padding if ((maybeEncoded.size() > 0 && padding != maybeEncoded[0]) || (maybeEncoded.size() > 1 && padding != maybeEncoded[1]) || maybeEncoded.size() > 2) { return false; } return true; } } // namespace graphql::internal