// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/base64.h" #include "td/utils/common.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" #include #include namespace td { //TODO: fix copypaste static const char *const symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; string base64_encode(Slice input) { string base64; base64.reserve((input.size() + 2) / 3 * 4); for (size_t i = 0; i < input.size();) { size_t left = min(input.size() - i, static_cast(3)); int c = input.ubegin()[i++] << 16; base64 += symbols64[c >> 18]; if (left != 1) { c |= input.ubegin()[i++] << 8; } base64 += symbols64[(c >> 12) & 63]; if (left == 3) { c |= input.ubegin()[i++]; } if (left != 1) { base64 += symbols64[(c >> 6) & 63]; } else { base64 += '='; } if (left == 3) { base64 += symbols64[c & 63]; } else { base64 += '='; } } return base64; } static unsigned char char_to_value[256]; static void init_base64_table() { static bool is_inited = [] { std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast(64)); for (unsigned char i = 0; i < 64; i++) { char_to_value[static_cast(symbols64[i])] = i; } return true; }(); CHECK(is_inited); } Result base64_drop_padding(Slice base64) { if ((base64.size() & 3) != 0) { return Status::Error("Wrong string length"); } size_t padding_length = 0; while (!base64.empty() && base64.back() == '=') { base64.remove_suffix(1); padding_length++; } if (padding_length >= 3) { return Status::Error("Wrong string padding"); } return base64; } template Status base64_do_decode(Slice base64, F &&append) { for (size_t i = 0; i < base64.size();) { size_t left = min(base64.size() - i, static_cast(4)); int c = 0; for (size_t t = 0; t < left; t++) { auto value = char_to_value[base64.ubegin()[i++]]; if (value == 64) { return Status::Error("Wrong character in the string"); } c |= value << ((3 - t) * 6); } append(static_cast(static_cast(c >> 16))); // implementation-defined if (left == 2) { if ((c & ((1 << 16) - 1)) != 0) { return Status::Error("Wrong padding in the string"); } } else { append(static_cast(static_cast(c >> 8))); // implementation-defined if (left == 3) { if ((c & ((1 << 8) - 1)) != 0) { return Status::Error("Wrong padding in the string"); } } else { append(static_cast(static_cast(c))); // implementation-defined } } } return Status::OK(); } Result base64_decode(Slice base64) { init_base64_table(); TRY_RESULT_ASSIGN(base64, base64_drop_padding(base64)); string output; output.reserve(((base64.size() + 3) >> 2) * 3); TRY_STATUS(base64_do_decode(base64, [&output](char c) { output += c; })); return output; } Result base64_decode_secure(Slice base64) { init_base64_table(); TRY_RESULT_ASSIGN(base64, base64_drop_padding(base64)); SecureString output(((base64.size() + 3) >> 2) * 3); char *ptr = output.as_mutable_slice().begin(); TRY_STATUS(base64_do_decode(base64, [&ptr](char c) { *ptr++ = c; })); size_t size = ptr - output.as_mutable_slice().begin(); if (size == output.size()) { return std::move(output); } return SecureString(output.as_slice().substr(0, size)); } static const char *const url_symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; string base64url_encode(Slice input) { string base64; base64.reserve((input.size() + 2) / 3 * 4); for (size_t i = 0; i < input.size();) { size_t left = min(input.size() - i, static_cast(3)); int c = input.ubegin()[i++] << 16; base64 += url_symbols64[c >> 18]; if (left != 1) { c |= input.ubegin()[i++] << 8; } base64 += url_symbols64[(c >> 12) & 63]; if (left == 3) { c |= input.ubegin()[i++]; } if (left != 1) { base64 += url_symbols64[(c >> 6) & 63]; } if (left == 3) { base64 += url_symbols64[c & 63]; } } return base64; } static unsigned char url_char_to_value[256]; static void init_base64url_table() { static bool is_inited = [] { std::fill(std::begin(url_char_to_value), std::end(url_char_to_value), static_cast(64)); for (unsigned char i = 0; i < 64; i++) { url_char_to_value[static_cast(url_symbols64[i])] = i; } return true; }(); CHECK(is_inited); } Result base64url_decode(Slice base64) { init_base64url_table(); size_t padding_length = 0; while (!base64.empty() && base64.back() == '=') { base64.remove_suffix(1); padding_length++; } if (padding_length >= 3 || (padding_length > 0 && ((base64.size() + padding_length) & 3) != 0)) { return Status::Error("Wrong string padding"); } if ((base64.size() & 3) == 1) { return Status::Error("Wrong string length"); } string output; output.reserve(((base64.size() + 3) >> 2) * 3); for (size_t i = 0; i < base64.size();) { size_t left = min(base64.size() - i, static_cast(4)); int c = 0; for (size_t t = 0; t < left; t++) { auto value = url_char_to_value[base64.ubegin()[i++]]; if (value == 64) { return Status::Error("Wrong character in the string"); } c |= value << ((3 - t) * 6); } output += static_cast(static_cast(c >> 16)); // implementation-defined if (left == 2) { if ((c & ((1 << 16) - 1)) != 0) { return Status::Error("Wrong padding in the string"); } } else { output += static_cast(static_cast(c >> 8)); // implementation-defined if (left == 3) { if ((c & ((1 << 8) - 1)) != 0) { return Status::Error("Wrong padding in the string"); } } else { output += static_cast(static_cast(c)); // implementation-defined } } } return output; } template static bool is_base64_impl(Slice input) { size_t padding_length = 0; while (!input.empty() && input.back() == '=') { input.remove_suffix(1); padding_length++; } if (padding_length >= 3) { return false; } if ((!is_url || padding_length > 0) && ((input.size() + padding_length) & 3) != 0) { return false; } if (is_url && (input.size() & 3) == 1) { return false; } unsigned char *table; if (is_url) { init_base64url_table(); table = url_char_to_value; } else { init_base64_table(); table = char_to_value; } for (auto c : input) { if (table[static_cast(c)] == 64) { return false; } } if ((input.size() & 3) == 2) { auto value = table[static_cast(input.back())]; if ((value & 15) != 0) { return false; } } if ((input.size() & 3) == 3) { auto value = table[static_cast(input.back())]; if ((value & 3) != 0) { return false; } } return true; } bool is_base64(Slice input) { return is_base64_impl(input); } bool is_base64url(Slice input) { return is_base64_impl(input); } string base64_filter(Slice input) { string res; res.reserve(input.size()); init_base64_table(); for (auto c : input) { if (char_to_value[static_cast(c)] != 64 || c == '=') { res += c; } } return res; } } // namespace td