1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

AK+LibJS: Use simdutf for all base64 operations

We were previously unable to use simdutf for base64 decoding operations
other than "loose". Upstream has added support for the "strict" and
"stop-before-partial" operations, so let's make use of them!
This commit is contained in:
Timothy Flynn 2025-05-03 07:08:26 -04:00 committed by Tim Flynn
parent e04545979f
commit 8a80ff7b3b
Notes: github-actions[bot] 2025-05-03 15:22:03 +00:00
6 changed files with 88 additions and 370 deletions

View file

@ -17,38 +17,70 @@ size_t size_required_to_decode_base64(StringView input)
return simdutf::maximal_binary_length_from_base64(input.characters_without_null_termination(), input.length());
}
static ErrorOr<size_t, InvalidBase64> decode_base64_into_impl(StringView input, ByteBuffer& output, simdutf::base64_options options)
static constexpr simdutf::last_chunk_handling_options to_simdutf_last_chunk_handling(LastChunkHandling last_chunk_handling)
{
size_t output_length = output.size();
switch (last_chunk_handling) {
case LastChunkHandling::Loose:
return simdutf::last_chunk_handling_options::loose;
case LastChunkHandling::Strict:
return simdutf::last_chunk_handling_options::strict;
case LastChunkHandling::StopBeforePartial:
return simdutf::last_chunk_handling_options::stop_before_partial;
}
VERIFY_NOT_REACHED();
}
static ErrorOr<size_t, InvalidBase64> decode_base64_into_impl(StringView input, ByteBuffer& output, LastChunkHandling last_chunk_handling, simdutf::base64_options options)
{
static constexpr auto decode_up_to_bad_character = true;
auto output_length = output.size();
auto result = simdutf::base64_to_binary_safe(
input.characters_without_null_termination(),
input.length(),
reinterpret_cast<char*>(output.data()),
output_length,
options);
options,
to_simdutf_last_chunk_handling(last_chunk_handling),
decode_up_to_bad_character);
if (result.error != simdutf::SUCCESS && result.error != simdutf::OUTPUT_BUFFER_TOO_SMALL) {
if (result.error == simdutf::BASE64_INPUT_REMAINDER && last_chunk_handling == LastChunkHandling::StopBeforePartial) {
result.error = simdutf::SUCCESS;
} else if (result.error != simdutf::SUCCESS && result.error != simdutf::OUTPUT_BUFFER_TOO_SMALL) {
output.resize((result.count / 4) * 3);
return InvalidBase64 {
.error = Error::from_string_literal("Invalid base64-encoded data"),
.valid_input_bytes = result.count,
};
auto error = [&]() {
switch (result.error) {
case simdutf::BASE64_EXTRA_BITS:
return Error::from_string_literal("Extra bits found at end of chunk");
case simdutf::BASE64_INPUT_REMAINDER:
return Error::from_string_literal("Invalid trailing data");
case simdutf::INVALID_BASE64_CHARACTER:
return Error::from_string_literal("Invalid base64 character");
default:
return Error::from_string_literal("Invalid base64-encoded data");
}
}();
return InvalidBase64 { .error = move(error), .valid_input_bytes = result.count };
}
VERIFY(output_length <= output.size());
output.resize(output_length);
if (last_chunk_handling == LastChunkHandling::StopBeforePartial)
return input.length() - (input.length() % 4);
return result.error == simdutf::SUCCESS ? input.length() : result.count;
}
static ErrorOr<ByteBuffer> decode_base64_impl(StringView input, simdutf::base64_options options)
static ErrorOr<ByteBuffer> decode_base64_impl(StringView input, LastChunkHandling last_chunk_handling, simdutf::base64_options options)
{
ByteBuffer output;
TRY(output.try_resize(size_required_to_decode_base64(input)));
if (auto result = decode_base64_into_impl(input, output, options); result.is_error())
if (auto result = decode_base64_into_impl(input, output, last_chunk_handling, options); result.is_error())
return result.release_error().error;
return output;
@ -68,24 +100,24 @@ static ErrorOr<String> encode_base64_impl(StringView input, simdutf::base64_opti
return String::from_utf8_without_validation(output);
}
ErrorOr<ByteBuffer> decode_base64(StringView input)
ErrorOr<ByteBuffer> decode_base64(StringView input, LastChunkHandling last_chunk_handling)
{
return decode_base64_impl(input, simdutf::base64_default);
return decode_base64_impl(input, last_chunk_handling, simdutf::base64_default);
}
ErrorOr<ByteBuffer> decode_base64url(StringView input)
ErrorOr<ByteBuffer> decode_base64url(StringView input, LastChunkHandling last_chunk_handling)
{
return decode_base64_impl(input, simdutf::base64_url);
return decode_base64_impl(input, last_chunk_handling, simdutf::base64_url);
}
ErrorOr<size_t, InvalidBase64> decode_base64_into(StringView input, ByteBuffer& output)
ErrorOr<size_t, InvalidBase64> decode_base64_into(StringView input, ByteBuffer& output, LastChunkHandling last_chunk_handling)
{
return decode_base64_into_impl(input, output, simdutf::base64_default);
return decode_base64_into_impl(input, output, last_chunk_handling, simdutf::base64_default);
}
ErrorOr<size_t, InvalidBase64> decode_base64url_into(StringView input, ByteBuffer& output)
ErrorOr<size_t, InvalidBase64> decode_base64url_into(StringView input, ByteBuffer& output, LastChunkHandling last_chunk_handling)
{
return decode_base64_into_impl(input, output, simdutf::base64_url);
return decode_base64_into_impl(input, output, last_chunk_handling, simdutf::base64_url);
}
ErrorOr<String> encode_base64(ReadonlyBytes input, OmitPadding omit_padding)

View file

@ -15,8 +15,14 @@ namespace AK {
size_t size_required_to_decode_base64(StringView);
ErrorOr<ByteBuffer> decode_base64(StringView);
ErrorOr<ByteBuffer> decode_base64url(StringView);
enum class LastChunkHandling {
Loose,
Strict,
StopBeforePartial,
};
ErrorOr<ByteBuffer> decode_base64(StringView, LastChunkHandling = LastChunkHandling::Loose);
ErrorOr<ByteBuffer> decode_base64url(StringView, LastChunkHandling = LastChunkHandling::Loose);
struct InvalidBase64 {
Error error;
@ -25,8 +31,8 @@ struct InvalidBase64 {
// On success, these return the number of input bytes that were decoded. This might be less than the
// string length if the output buffer was not large enough.
ErrorOr<size_t, InvalidBase64> decode_base64_into(StringView, ByteBuffer&);
ErrorOr<size_t, InvalidBase64> decode_base64url_into(StringView, ByteBuffer&);
ErrorOr<size_t, InvalidBase64> decode_base64_into(StringView, ByteBuffer&, LastChunkHandling = LastChunkHandling::Loose);
ErrorOr<size_t, InvalidBase64> decode_base64url_into(StringView, ByteBuffer&, LastChunkHandling = LastChunkHandling::Loose);
enum class OmitPadding {
No,

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Base64.h>
#include <AK/StringBuilder.h>
#include <AK/StringUtils.h>
#include <LibJS/Runtime/AbstractOperations.h>
@ -55,23 +54,23 @@ static ThrowCompletionOr<Alphabet> parse_alphabet(VM& vm, Object& options)
return vm.throw_completion<TypeError>(ErrorType::OptionIsNotValidValue, alphabet, "alphabet"sv);
}
static ThrowCompletionOr<LastChunkHandling> parse_last_chunk_handling(VM& vm, Object& options)
static ThrowCompletionOr<AK::LastChunkHandling> parse_last_chunk_handling(VM& vm, Object& options)
{
// Let lastChunkHandling be ? Get(opts, "lastChunkHandling").
auto last_chunk_handling = TRY(options.get(vm.names.lastChunkHandling));
// If lastChunkHandling is undefined, set lastChunkHandling to "loose".
if (last_chunk_handling.is_undefined())
return LastChunkHandling::Loose;
return AK::LastChunkHandling::Loose;
// If lastChunkHandling is not one of "loose", "strict", or "stop-before-partial", throw a TypeError exception.
if (last_chunk_handling.is_string()) {
if (last_chunk_handling.as_string().utf8_string_view() == "loose"sv)
return LastChunkHandling::Loose;
return AK::LastChunkHandling::Loose;
if (last_chunk_handling.as_string().utf8_string_view() == "strict"sv)
return LastChunkHandling::Strict;
return AK::LastChunkHandling::Strict;
if (last_chunk_handling.as_string().utf8_string_view() == "stop-before-partial"sv)
return LastChunkHandling::StopBeforePartial;
return AK::LastChunkHandling::StopBeforePartial;
}
return vm.throw_completion<TypeError>(ErrorType::OptionIsNotValidValue, last_chunk_handling, "lastChunkHandling"sv);
@ -451,107 +450,16 @@ void set_uint8_array_bytes(TypedArrayBase& into, ReadonlyBytes bytes)
}
}
// 10.1 SkipAsciiWhitespace ( string, index ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-skipasciiwhitespace
static size_t skip_ascii_whitespace(StringView string, size_t index)
{
// 1. Let length be the length of string.
auto length = string.length();
// 2. Repeat, while index < length,
while (index < length) {
// a. Let char be the code unit at index index of string.
auto ch = string[index];
// b. If char is neither 0x0009 (TAB), 0x000A (LF), 0x000C (FF), 0x000D (CR), nor 0x0020 (SPACE), then
if (ch != '\t' && ch != '\n' && ch != '\f' && ch != '\r' && ch != ' ') {
// i. Return index.
return index;
}
// c. Set index to index + 1.
++index;
}
// 3. Return index.
return index;
}
// 10.2 DecodeBase64Chunk ( chunk [ , throwOnExtraBits ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64
static ThrowCompletionOr<ByteBuffer> decode_base64_chunk(VM& vm, StringBuilder& chunk, Optional<bool> throw_on_extra_bits = {})
{
// 1. Let chunkLength be the length of chunk.
auto chunk_length = chunk.length();
// 2. If chunkLength is 2, then
if (chunk_length == 2) {
// a. Set chunk to the string-concatenation of chunk and "AA".
chunk.append("AA"sv);
}
// 3. Else if chunkLength is 3, then
else if (chunk_length == 3) {
// a. Set chunk to the string-concatenation of chunk and "A".
chunk.append("A"sv);
}
// 4. Else,
else {
// a. Assert: chunkLength is 4.
VERIFY(chunk_length == 4);
}
// 5. Let byteSequence be the unique sequence of 3 bytes resulting from decoding chunk as base64 (such that applying
// the base64 encoding specified in section 4 of RFC 4648 to byteSequence would produce chunk).
// 6. Let bytes be a List whose elements are the elements of byteSequence, in order.
auto bytes = MUST(decode_base64(chunk.string_view()));
// 7. If chunkLength is 2, then
if (chunk_length == 2) {
// a. Assert: throwOnExtraBits is present.
VERIFY(throw_on_extra_bits.has_value());
// b. If throwOnExtraBits is true and bytes[1] ≠ 0, then
if (*throw_on_extra_bits && bytes[1] != 0) {
// i. Throw a SyntaxError exception.
return vm.throw_completion<SyntaxError>("Extra bits found at end of chunk"sv);
}
// c. Return « bytes[0] ».
return MUST(bytes.slice(0, 1));
}
// 8. Else if chunkLength is 3, then
if (chunk_length == 3) {
// a. Assert: throwOnExtraBits is present.
VERIFY(throw_on_extra_bits.has_value());
// b. If throwOnExtraBits is true and bytes[2] ≠ 0, then
if (*throw_on_extra_bits && bytes[2] != 0) {
// i. Throw a SyntaxError exception.
return vm.throw_completion<SyntaxError>("Extra bits found at end of chunk"sv);
}
// c. Return « bytes[0], bytes[1] ».
return MUST(bytes.slice(0, 2));
}
// 9. Else,
// a. Return bytes.
return bytes;
}
// 10.3 FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64
DecodeResult from_base64(VM& vm, StringView string, Alphabet alphabet, LastChunkHandling last_chunk_handling, Optional<size_t> max_length)
DecodeResult from_base64(VM& vm, StringView string, Alphabet alphabet, AK::LastChunkHandling last_chunk_handling, Optional<size_t> max_length)
{
// FIXME: We can only use simdutf when the last-chunk-handling parameter is "loose". Upstream is planning to implement
// the remaining options. When that is complete, we should be able to remove the slow implementation below. See:
// https://github.com/simdutf/simdutf/issues/440
if (last_chunk_handling == LastChunkHandling::Loose) {
auto output = MUST(ByteBuffer::create_uninitialized(max_length.value_or_lazy_evaluated([&]() {
return AK::size_required_to_decode_base64(string);
})));
auto result = alphabet == Alphabet::Base64
? AK::decode_base64_into(string, output)
: AK::decode_base64url_into(string, output);
? AK::decode_base64_into(string, output, last_chunk_handling)
: AK::decode_base64url_into(string, output, last_chunk_handling);
if (result.is_error()) {
auto error = vm.throw_completion<SyntaxError>(result.error().error.string_literal());
@ -561,229 +469,6 @@ DecodeResult from_base64(VM& vm, StringView string, Alphabet alphabet, LastChunk
return { .read = result.value(), .bytes = move(output), .error = {} };
}
// 1. If maxLength is not present, then
if (!max_length.has_value()) {
// a. Let maxLength be 2**53 - 1.
max_length = MAX_ARRAY_LIKE_INDEX;
// b. NOTE: Because the input is a string, the length of strings is limited to 2**53 - 1 characters, and the
// output requires no more bytes than the input has characters, this limit can never be reached. However, it
// is editorially convenient to use a finite value here.
}
// 2. NOTE: The order of validation and decoding in the algorithm below is not observable. Implementations are
// encouraged to perform them in whatever order is most efficient, possibly interleaving validation with decoding,
// as long as the behaviour is observably equivalent.
// 3. If maxLength is 0, then
if (max_length == 0uz) {
// a. Return the Record { [[Read]]: 0, [[Bytes]]: « », [[Error]]: none }.
return { .read = 0, .bytes = {}, .error = {} };
}
// 4. Let read be 0.
size_t read = 0;
// 5. Let bytes be « ».
ByteBuffer bytes;
// 6. Let chunk be the empty String.
StringBuilder chunk;
// 7. Let chunkLength be 0.
size_t chunk_length = 0;
// 8. Let index be 0.
size_t index = 0;
// 9. Let length be the length of string.
auto length = string.length();
// 10. Repeat,
while (true) {
// a. Set index to SkipAsciiWhitespace(string, index).
index = skip_ascii_whitespace(string, index);
// b. If index = length, then
if (index == length) {
// i. If chunkLength > 0, then
if (chunk_length > 0) {
// 1. If lastChunkHandling is "stop-before-partial", then
if (last_chunk_handling == LastChunkHandling::StopBeforePartial) {
// a. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
return { .read = read, .bytes = move(bytes), .error = {} };
}
// 2. Else if lastChunkHandling is "loose", then
else if (last_chunk_handling == LastChunkHandling::Loose) {
VERIFY_NOT_REACHED();
}
// 3. Else,
else {
// a. Assert: lastChunkHandling is "strict".
VERIFY(last_chunk_handling == LastChunkHandling::Strict);
// b. Let error be a new SyntaxError exception.
auto error = vm.throw_completion<SyntaxError>("Invalid trailing data"sv);
// c. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
return { .read = read, .bytes = move(bytes), .error = move(error) };
}
}
// ii. Return the Record { [[Read]]: length, [[Bytes]]: bytes, [[Error]]: none }.
return { .read = length, .bytes = move(bytes), .error = {} };
}
// c. Let char be the substring of string from index to index + 1.
auto ch = string[index];
// d. Set index to index + 1.
++index;
// e. If char is "=", then
if (ch == '=') {
// i. If chunkLength < 2, then
if (chunk_length < 2) {
// 1. Let error be a new SyntaxError exception.
auto error = vm.throw_completion<SyntaxError>("Unexpected padding character"sv);
// 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
return { .read = read, .bytes = move(bytes), .error = move(error) };
}
// ii. Set index to SkipAsciiWhitespace(string, index).
index = skip_ascii_whitespace(string, index);
// iii. If chunkLength = 2, then
if (chunk_length == 2) {
// 1. If index = length, then
if (index == length) {
// a. If lastChunkHandling is "stop-before-partial", then
if (last_chunk_handling == LastChunkHandling::StopBeforePartial) {
// i. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
return { .read = read, .bytes = move(bytes), .error = {} };
}
// b. Let error be a new SyntaxError exception.
auto error = vm.throw_completion<SyntaxError>("Incomplete number of padding characters"sv);
// c. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
return { .read = read, .bytes = move(bytes), .error = move(error) };
}
// 2. Set char to the substring of string from index to index + 1.
ch = string[index];
// 3. If char is "=", then
if (ch == '=') {
// a. Set index to SkipAsciiWhitespace(string, index + 1).
index = skip_ascii_whitespace(string, index + 1);
}
}
// iv. If index < length, then
if (index < length) {
// 1. Let error be a new SyntaxError exception.
auto error = vm.throw_completion<SyntaxError>("Unexpected padding character"sv);
// 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
return { .read = read, .bytes = move(bytes), .error = move(error) };
}
// v. If lastChunkHandling is "strict", let throwOnExtraBits be true.
// vi. Else, let throwOnExtraBits be false.
auto throw_on_extra_bits = last_chunk_handling == LastChunkHandling::Strict;
// vii. Let decodeResult be Completion(DecodeBase64Chunk(chunk, throwOnExtraBits)).
auto decode_result = decode_base64_chunk(vm, chunk, throw_on_extra_bits);
// viii. If decodeResult is an abrupt completion, then
if (decode_result.is_error()) {
// 1. Let error be decodeResult.[[Value]].
auto error = decode_result.release_error();
// 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
return { .read = read, .bytes = move(bytes), .error = move(error) };
}
// ix. Set bytes to the list-concatenation of bytes and ! decodeResult.
bytes.append(decode_result.release_value());
// x. Return the Record { [[Read]]: length, [[Bytes]]: bytes, [[Error]]: none }.
return { .read = length, .bytes = move(bytes), .error = {} };
}
// f. If alphabet is "base64url", then
if (alphabet == Alphabet::Base64URL) {
// i. If char is either "+" or "/", then
if (ch == '+' || ch == '/') {
// 1. Let error be a new SyntaxError exception.
auto error = vm.throw_completion<SyntaxError>(MUST(String::formatted("Invalid character '{}'", ch)));
// 2. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
return { .read = read, .bytes = move(bytes), .error = move(error) };
}
// ii. Else if char is "-", then
else if (ch == '-') {
// 1. Set char to "+".
ch = '+';
}
// iii. Else if char is "_", then
else if (ch == '_') {
// 1. Set char to "/".
ch = '/';
}
}
// g. If the sole code unit of char is not an element of the standard base64 alphabet, then
static constexpr auto standard_base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"sv;
if (!standard_base64_alphabet.contains(ch)) {
// i. Let error be a new SyntaxError exception.
auto error = vm.throw_completion<SyntaxError>(MUST(String::formatted("Invalid character '{}'", ch)));
// ii. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: error }.
return { .read = read, .bytes = move(bytes), .error = move(error) };
}
// h. Let remaining be maxLength - the length of bytes.
auto remaining = *max_length - bytes.size();
// i. If remaining = 1 and chunkLength = 2, or if remaining = 2 and chunkLength = 3, then
if ((remaining == 1 && chunk_length == 2) || (remaining == 2 && chunk_length == 3)) {
// i. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
return { .read = read, .bytes = move(bytes), .error = {} };
}
// j. Set chunk to the string-concatenation of chunk and char.
chunk.append(ch);
// k. Set chunkLength to the length of chunk.
chunk_length = chunk.length();
// l. If chunkLength = 4, then
if (chunk_length == 4) {
// i. Set bytes to the list-concatenation of bytes and ! DecodeBase64Chunk(chunk).
bytes.append(MUST(decode_base64_chunk(vm, chunk)));
// ii. Set chunk to the empty String.
chunk.clear();
// iii. Set chunkLength to 0.
chunk_length = 0;
// iv. Set read to index.
read = index;
// v. If the length of bytes = maxLength, then
if (bytes.size() == max_length) {
// 1. Return the Record { [[Read]]: read, [[Bytes]]: bytes, [[Error]]: none }.
return { .read = read, .bytes = move(bytes), .error = {} };
}
}
}
}
// 10.4 FromHex ( string [ , maxLength ] ), https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex
DecodeResult from_hex(VM& vm, StringView string, Optional<size_t> max_length)
{

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/Base64.h>
#include <AK/ByteBuffer.h>
#include <AK/Optional.h>
#include <AK/StringView.h>
@ -41,12 +42,6 @@ enum class Alphabet {
Base64URL,
};
enum class LastChunkHandling {
Loose,
Strict,
StopBeforePartial,
};
struct DecodeResult {
size_t read { 0 }; // [[Read]]
ByteBuffer bytes; // [[Bytes]]
@ -56,7 +51,7 @@ struct DecodeResult {
ThrowCompletionOr<GC::Ref<TypedArrayBase>> validate_uint8_array(VM&);
ThrowCompletionOr<ByteBuffer> get_uint8_array_bytes(VM&, TypedArrayBase const&);
void set_uint8_array_bytes(TypedArrayBase&, ReadonlyBytes);
DecodeResult from_base64(VM&, StringView string, Alphabet alphabet, LastChunkHandling last_chunk_handling, Optional<size_t> max_length = {});
DecodeResult from_base64(VM&, StringView string, Alphabet alphabet, AK::LastChunkHandling last_chunk_handling, Optional<size_t> max_length = {});
DecodeResult from_hex(VM&, StringView string, Optional<size_t> max_length = {});
}

View file

@ -40,25 +40,25 @@ describe("errors", () => {
test("invalid padding", () => {
expect(() => {
Uint8Array.fromBase64("Zm9v=", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Unexpected padding character");
}).toThrowWithMessage(SyntaxError, "Invalid trailing data");
expect(() => {
Uint8Array.fromBase64("Zm9vaa=", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Incomplete number of padding characters");
}).toThrowWithMessage(SyntaxError, "Invalid trailing data");
expect(() => {
Uint8Array.fromBase64("Zm9vaa=a", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Unexpected padding character");
}).toThrowWithMessage(SyntaxError, "Invalid base64 character");
});
test("invalid alphabet", () => {
expect(() => {
Uint8Array.fromBase64("-", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Invalid character '-'");
}).toThrowWithMessage(SyntaxError, "Invalid base64 character");
expect(() => {
Uint8Array.fromBase64("+", { alphabet: "base64url", lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Invalid character '+'");
}).toThrowWithMessage(SyntaxError, "Invalid base64 character");
});
test("overlong chunk", () => {

View file

@ -79,28 +79,28 @@ describe("errors", () => {
test("invalid padding", () => {
expect(() => {
new Uint8Array(10).setFromBase64("Zm9v=", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Unexpected padding character");
}).toThrowWithMessage(SyntaxError, "Invalid trailing data");
expect(() => {
new Uint8Array(10).setFromBase64("Zm9vaa=", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Incomplete number of padding characters");
}).toThrowWithMessage(SyntaxError, "Invalid trailing data");
expect(() => {
new Uint8Array(10).setFromBase64("Zm9vaa=a", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Unexpected padding character");
}).toThrowWithMessage(SyntaxError, "Invalid base64 character");
});
test("invalid alphabet", () => {
expect(() => {
new Uint8Array(10).setFromBase64("-", { lastChunkHandling: "strict" });
}).toThrowWithMessage(SyntaxError, "Invalid character '-'");
}).toThrowWithMessage(SyntaxError, "Invalid base64 character");
expect(() => {
new Uint8Array(10).setFromBase64("+", {
alphabet: "base64url",
lastChunkHandling: "strict",
});
}).toThrowWithMessage(SyntaxError, "Invalid character '+'");
}).toThrowWithMessage(SyntaxError, "Invalid base64 character");
});
test("overlong chunk", () => {