blob: 260b63d6307777a60d50c3aa4f6e8230fbc5f22e [file]
/*
* 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 <array>
#include <cstdint>
namespace iceberg {
namespace {
// Shared base64 encode logic. The encode alphabet maps a 6-bit value -> ASCII char.
// When `pad` is true, the output is padded with '=' to a multiple of 4 characters.
std::string Base64EncodeWithAlphabet(std::string_view input, std::string_view alphabet,
bool pad) {
std::string output;
output.reserve((input.size() + 2) / 3 * 4);
uint32_t buffer = 0;
int bits_collected = 0;
for (unsigned char byte : input) {
buffer = (buffer << 8) | byte;
bits_collected += 8;
while (bits_collected >= 6) {
bits_collected -= 6;
output.push_back(alphabet[(buffer >> bits_collected) & 0x3F]);
}
}
if (bits_collected > 0) {
// Pad the remaining bits on the right to form the final 6-bit group.
output.push_back(alphabet[(buffer << (6 - bits_collected)) & 0x3F]);
}
if (pad) {
while (output.size() % 4 != 0) {
output.push_back('=');
}
}
return output;
}
// Shared base64 decode logic. The decode table maps ASCII char -> 6-bit value.
// 0xFF means invalid character.
Result<std::string> Base64DecodeWithTable(std::string_view input,
const std::array<uint8_t, 256>& table) {
auto padded_size = input.size();
if (auto padding_pos = input.find('='); padding_pos != std::string_view::npos) {
auto padding = input.substr(padding_pos);
if (padding.find_first_not_of('=') != std::string_view::npos) {
return InvalidArgument("Invalid base64 padding");
}
auto padding_size = padding.size();
if (padding_size > 2 || input.size() % 4 != 0) {
return InvalidArgument("Invalid base64 padding");
}
}
// Strip trailing padding after validating its count and placement.
while (!input.empty() && input.back() == '=') {
input.remove_suffix(1);
}
auto unpadded_size = input.size();
if (unpadded_size % 4 == 1 ||
(padded_size != unpadded_size && unpadded_size % 4 == 0)) {
return InvalidArgument("Invalid base64 length");
}
if (input.empty()) {
return std::string{};
}
std::string output;
output.reserve((input.size() * 3) / 4);
uint32_t buffer = 0;
int bits_collected = 0;
for (char c : input) {
uint8_t val = table[static_cast<uint8_t>(c)];
if (val == 0xFF) {
return InvalidArgument("Invalid base64 character: '{}'", c);
}
buffer = (buffer << 6) | val;
bits_collected += 6;
if (bits_collected >= 8) {
bits_collected -= 8;
output.push_back(static_cast<char>((buffer >> bits_collected) & 0xFF));
}
}
return output;
}
// Standard base64 alphabet: A-Z=0-25, a-z=26-51, 0-9=52-61, +=62, /=63
constexpr std::string_view kBase64Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Base64url alphabet: same as standard but '-'=62, '_'=63 (RFC 4648 §5)
constexpr std::string_view kBase64UrlChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
// Standard base64 decode table: A-Z=0-25, a-z=26-51, 0-9=52-61, +=62, /=63
constexpr std::array<uint8_t, 256> kBase64DecodeTable = [] {
std::array<uint8_t, 256> table{};
table.fill(0xFF);
for (int i = 0; i < 26; ++i) {
table[static_cast<size_t>('A' + i)] = static_cast<uint8_t>(i);
table[static_cast<size_t>('a' + i)] = static_cast<uint8_t>(26 + i);
}
for (int i = 0; i < 10; ++i) {
table[static_cast<size_t>('0' + i)] = static_cast<uint8_t>(52 + i);
}
table[static_cast<size_t>('+')] = 62;
table[static_cast<size_t>('/')] = 63;
return table;
}();
// Base64url decode table: same as standard but '-'=62, '_'=63 (RFC 4648 §5)
constexpr std::array<uint8_t, 256> kBase64UrlDecodeTable = [] {
auto table = kBase64DecodeTable;
table[static_cast<size_t>('+')] = 0xFF; // '+' is invalid in base64url
table[static_cast<size_t>('/')] = 0xFF; // '/' is invalid in base64url
table[static_cast<size_t>('-')] = 62;
table[static_cast<size_t>('_')] = 63;
return table;
}();
} // namespace
std::string Base64::Encode(std::string_view data) {
return Base64EncodeWithAlphabet(data, kBase64Chars, /*pad=*/true);
}
Result<std::string> Base64::Decode(std::string_view encoded) {
return Base64DecodeWithTable(encoded, kBase64DecodeTable);
}
std::string Base64::UrlEncode(std::string_view data) {
return Base64EncodeWithAlphabet(data, kBase64UrlChars, /*pad=*/false);
}
Result<std::string> Base64::UrlDecode(std::string_view encoded) {
return Base64DecodeWithTable(encoded, kBase64UrlDecodeTable);
}
} // namespace iceberg