| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| #include "iceberg/util/base64.h" | |
| #include <cstddef> | |
| #include <string> | |
| #include <gtest/gtest.h> | |
| #include "iceberg/test/matchers.h" | |
| namespace iceberg { | |
| TEST(Base64Test, Encode) { | |
| // Empty string | |
| EXPECT_EQ("", Base64::Encode("")); | |
| // Single character | |
| EXPECT_EQ("YQ==", Base64::Encode("a")); | |
| EXPECT_EQ("YWI=", Base64::Encode("ab")); | |
| EXPECT_EQ("YWJj", Base64::Encode("abc")); | |
| // Multiple of 3 characters | |
| EXPECT_EQ("YWJjZGU=", Base64::Encode("abcde")); | |
| EXPECT_EQ("YWJjZGVm", Base64::Encode("abcdef")); | |
| // Common strings | |
| EXPECT_EQ("U29tZSBkYXRhIHdpdGggY2hhcmFjdGVycw==", | |
| Base64::Encode("Some data with characters")); | |
| EXPECT_EQ("aGVsbG8=", Base64::Encode("hello")); | |
| EXPECT_EQ("dGVzdCBzdHJpbmc=", Base64::Encode("test string")); | |
| // Unicode | |
| EXPECT_EQ("8J+EgA==", Base64::Encode("\xF0\x9F\x84\x80")); | |
| // Null byte | |
| EXPECT_EQ("AA==", Base64::Encode({"\x00", 1})); | |
| } | |
| TEST(Base64Test, EncodeBinaryAcrossBlockBoundaries) { | |
| for (size_t length = 0; length <= 256; ++length) { | |
| std::string data(length, '\0'); | |
| for (size_t i = 0; i < length; ++i) { | |
| data[i] = static_cast<char>((i * 73 + length) & 0xFF); | |
| } | |
| const auto encoded = Base64::Encode(data); | |
| EXPECT_EQ(((length + 2) / 3) * 4, encoded.size()) << length; | |
| ICEBERG_UNWRAP_OR_FAIL(auto decoded, Base64::Decode(encoded)); | |
| EXPECT_EQ(data, decoded) << length; | |
| const auto url_encoded = Base64::UrlEncode(data); | |
| EXPECT_EQ((length / 3) * 4 + (length % 3 == 0 ? 0 : length % 3 + 1), | |
| url_encoded.size()) | |
| << length; | |
| ICEBERG_UNWRAP_OR_FAIL(auto url_decoded, Base64::UrlDecode(url_encoded)); | |
| EXPECT_EQ(data, url_decoded) << length; | |
| } | |
| } | |
| TEST(Base64Test, Decode) { | |
| // Empty string | |
| ICEBERG_UNWRAP_OR_FAIL(auto empty, Base64::Decode("")); | |
| EXPECT_EQ("", empty); | |
| // Round-trip with Base64::Encode | |
| ICEBERG_UNWRAP_OR_FAIL(auto a, Base64::Decode("YQ==")); | |
| EXPECT_EQ("a", a); | |
| ICEBERG_UNWRAP_OR_FAIL(auto ab, Base64::Decode("YWI=")); | |
| EXPECT_EQ("ab", ab); | |
| ICEBERG_UNWRAP_OR_FAIL(auto abc, Base64::Decode("YWJj")); | |
| EXPECT_EQ("abc", abc); | |
| ICEBERG_UNWRAP_OR_FAIL(auto abcde, Base64::Decode("YWJjZGU=")); | |
| EXPECT_EQ("abcde", abcde); | |
| ICEBERG_UNWRAP_OR_FAIL(auto abcdef, Base64::Decode("YWJjZGVm")); | |
| EXPECT_EQ("abcdef", abcdef); | |
| ICEBERG_UNWRAP_OR_FAIL(auto hello, Base64::Decode("aGVsbG8=")); | |
| EXPECT_EQ("hello", hello); | |
| ICEBERG_UNWRAP_OR_FAIL(auto test_str, Base64::Decode("dGVzdCBzdHJpbmc=")); | |
| EXPECT_EQ("test string", test_str); | |
| // Without padding (should still work) | |
| ICEBERG_UNWRAP_OR_FAIL(auto a2, Base64::Decode("YQ")); | |
| EXPECT_EQ("a", a2); | |
| ICEBERG_UNWRAP_OR_FAIL(auto ab2, Base64::Decode("YWI")); | |
| EXPECT_EQ("ab", ab2); | |
| // Invalid characters return error | |
| EXPECT_THAT(Base64::Decode("!!!"), IsError(ErrorKind::kInvalidArgument)); | |
| // Invalid padding and impossible unpadded lengths return error | |
| for (const auto* invalid : | |
| {"=", "====", "Y=Q=", "YQ=", "YQ===", "YWJj=", "aGVsbG8==", "A", "AAAAA"}) { | |
| EXPECT_THAT(Base64::Decode(invalid), IsError(ErrorKind::kInvalidArgument)) << invalid; | |
| } | |
| } | |
| TEST(Base64Test, UrlEncode) { | |
| // Empty string | |
| EXPECT_EQ("", Base64::UrlEncode("")); | |
| // No padding is emitted (unlike standard base64) | |
| EXPECT_EQ("YQ", Base64::UrlEncode("a")); | |
| EXPECT_EQ("YWI", Base64::UrlEncode("ab")); | |
| EXPECT_EQ("YWJj", Base64::UrlEncode("abc")); | |
| EXPECT_EQ("aGVsbG8", Base64::UrlEncode("hello")); | |
| // URL-safe characters: '-' and '_' instead of '+' and '/' | |
| // bytes {0xFB, 0xFF, 0xFE} encode to "+//+" in standard base64, "-__-" in base64url | |
| EXPECT_EQ("-__-", Base64::UrlEncode("\xFB\xFF\xFE")); | |
| // Round-trip with UrlDecode | |
| ICEBERG_UNWRAP_OR_FAIL(auto decoded, Base64::UrlDecode(Base64::UrlEncode("hello"))); | |
| EXPECT_EQ("hello", decoded); | |
| } | |
| TEST(Base64Test, UrlDecode) { | |
| // Empty string | |
| ICEBERG_UNWRAP_OR_FAIL(auto empty, Base64::UrlDecode("")); | |
| EXPECT_EQ("", empty); | |
| // Standard cases (same as Base64::Decode for alphanumeric) | |
| ICEBERG_UNWRAP_OR_FAIL(auto hello, Base64::UrlDecode("aGVsbG8")); | |
| EXPECT_EQ("hello", hello); | |
| ICEBERG_UNWRAP_OR_FAIL(auto abc, Base64::UrlDecode("YWJj")); | |
| EXPECT_EQ("abc", abc); | |
| // URL-safe characters: '-' and '_' instead of '+' and '/' | |
| // bytes {0xFB, 0xFF, 0xFE} encode to "+//+" in standard base64, "-__-" in base64url | |
| ICEBERG_UNWRAP_OR_FAIL(auto decoded, Base64::UrlDecode("-__-")); | |
| EXPECT_EQ(3u, decoded.size()); | |
| EXPECT_EQ('\xFB', decoded[0]); | |
| EXPECT_EQ('\xFF', decoded[1]); | |
| EXPECT_EQ('\xFE', decoded[2]); | |
| // Standard base64 chars '+' and '/' should be invalid in base64url | |
| EXPECT_THAT(Base64::UrlDecode("+//+"), IsError(ErrorKind::kInvalidArgument)); | |
| // With padding (should handle gracefully) | |
| ICEBERG_UNWRAP_OR_FAIL(auto hello2, Base64::UrlDecode("aGVsbG8=")); | |
| EXPECT_EQ("hello", hello2); | |
| // Invalid characters return error | |
| EXPECT_THAT(Base64::UrlDecode("!!!invalid!!!"), IsError(ErrorKind::kInvalidArgument)); | |
| // Invalid padding and impossible unpadded lengths return error | |
| for (const auto* invalid : | |
| {"=", "====", "Y=Q=", "YQ=", "YQ===", "YWJj=", "aGVsbG8==", "A", "AAAAA"}) { | |
| EXPECT_THAT(Base64::UrlDecode(invalid), IsError(ErrorKind::kInvalidArgument)) | |
| << invalid; | |
| } | |
| } | |
| } // namespace iceberg |