Serenity Operating System
at master 124 lines 3.7 kB view raw
1/* 2 * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Array.h> 8#include <AK/Assertions.h> 9#include <AK/Base64.h> 10#include <AK/CharacterTypes.h> 11#include <AK/Error.h> 12#include <AK/StringBuilder.h> 13#include <AK/Types.h> 14#include <AK/Vector.h> 15 16namespace AK { 17 18size_t calculate_base64_decoded_length(StringView input) 19{ 20 return input.length() * 3 / 4; 21} 22 23size_t calculate_base64_encoded_length(ReadonlyBytes input) 24{ 25 return ((4 * input.size() / 3) + 3) & ~3; 26} 27 28ErrorOr<ByteBuffer> decode_base64(StringView input) 29{ 30 auto alphabet_lookup_table = base64_lookup_table(); 31 32 auto get = [&](size_t& offset, bool* is_padding, bool& parsed_something) -> ErrorOr<u8> { 33 while (offset < input.length() && is_ascii_space(input[offset])) 34 ++offset; 35 if (offset >= input.length()) 36 return 0; 37 auto ch = static_cast<unsigned char>(input[offset++]); 38 parsed_something = true; 39 if (ch == '=') { 40 if (!is_padding) 41 return Error::from_string_literal("Invalid '=' character outside of padding in base64 data"); 42 *is_padding = true; 43 return 0; 44 } 45 i16 result = alphabet_lookup_table[ch]; 46 if (result < 0) 47 return Error::from_string_literal("Invalid character in base64 data"); 48 VERIFY(result < 256); 49 return { result }; 50 }; 51 52 Vector<u8> output; 53 output.ensure_capacity(calculate_base64_decoded_length(input)); 54 55 size_t offset = 0; 56 while (offset < input.length()) { 57 bool in2_is_padding = false; 58 bool in3_is_padding = false; 59 60 bool parsed_something = false; 61 62 const u8 in0 = TRY(get(offset, nullptr, parsed_something)); 63 const u8 in1 = TRY(get(offset, nullptr, parsed_something)); 64 const u8 in2 = TRY(get(offset, &in2_is_padding, parsed_something)); 65 const u8 in3 = TRY(get(offset, &in3_is_padding, parsed_something)); 66 67 if (!parsed_something) 68 break; 69 70 const u8 out0 = (in0 << 2) | ((in1 >> 4) & 3); 71 const u8 out1 = ((in1 & 0xf) << 4) | ((in2 >> 2) & 0xf); 72 const u8 out2 = ((in2 & 0x3) << 6) | in3; 73 74 output.append(out0); 75 if (!in2_is_padding) 76 output.append(out1); 77 if (!in3_is_padding) 78 output.append(out2); 79 } 80 81 return ByteBuffer::copy(output); 82} 83 84ErrorOr<String> encode_base64(ReadonlyBytes input) 85{ 86 StringBuilder output(calculate_base64_encoded_length(input)); 87 88 auto get = [&](const size_t offset, bool* need_padding = nullptr) -> u8 { 89 if (offset >= input.size()) { 90 if (need_padding) 91 *need_padding = true; 92 return 0; 93 } 94 return input[offset]; 95 }; 96 97 for (size_t i = 0; i < input.size(); i += 3) { 98 bool is_8bit = false; 99 bool is_16bit = false; 100 101 const u8 in0 = get(i); 102 const u8 in1 = get(i + 1, &is_16bit); 103 const u8 in2 = get(i + 2, &is_8bit); 104 105 const u8 index0 = (in0 >> 2) & 0x3f; 106 const u8 index1 = ((in0 << 4) | (in1 >> 4)) & 0x3f; 107 const u8 index2 = ((in1 << 2) | (in2 >> 6)) & 0x3f; 108 const u8 index3 = in2 & 0x3f; 109 110 char const out0 = base64_alphabet[index0]; 111 char const out1 = base64_alphabet[index1]; 112 char const out2 = is_16bit ? '=' : base64_alphabet[index2]; 113 char const out3 = is_8bit ? '=' : base64_alphabet[index3]; 114 115 TRY(output.try_append(out0)); 116 TRY(output.try_append(out1)); 117 TRY(output.try_append(out2)); 118 TRY(output.try_append(out3)); 119 } 120 121 return output.to_string(); 122} 123 124}