diff --git a/benchmark/buffers/buffer-btoa.js b/benchmark/buffers/buffer-btoa.js new file mode 100644 index 00000000000000..3867d5890b1d79 --- /dev/null +++ b/benchmark/buffers/buffer-btoa.js @@ -0,0 +1,20 @@ +'use strict'; +const common = require('../common.js'); +const assert = require('node:assert'); + +const bench = common.createBenchmark(main, { + size: [16, 32, 64, 128, 256, 1024], + n: [1e6], +}); + +function main({ n, size }) { + const input = 'A'.repeat(size); + let out = 0; + + bench.start(); + for (let i = 0; i < n; i++) { + out += btoa(input).length; + } + bench.end(n); + assert(out > 0); +} diff --git a/lib/buffer.js b/lib/buffer.js index ea94ebf24192f9..d4e9830bacd82f 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -69,6 +69,7 @@ const { kMaxLength, kStringMaxLength, atob: _atob, + btoa: _btoa, } = internalBinding('buffer'); const { constants: { @@ -1249,13 +1250,7 @@ function btoa(input) { if (arguments.length === 0) { throw new ERR_MISSING_ARGS('input'); } - input = `${input}`; - for (let n = 0; n < input.length; n++) { - if (input[n].charCodeAt(0) > 0xff) - throw lazyDOMException('Invalid character', 'InvalidCharacterError'); - } - const buf = Buffer.from(input, 'latin1'); - return buf.toString('base64'); + return _btoa(`${input}`); } function atob(input) { diff --git a/src/node_buffer.cc b/src/node_buffer.cc index b31beada451bc8..536b95d16e0132 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1211,6 +1211,40 @@ void DetachArrayBuffer(const FunctionCallbackInfo& args) { } } +static void Btoa(const FunctionCallbackInfo& args) { + CHECK_EQ(args.Length(), 1); + Environment* env = Environment::GetCurrent(args); + THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "argument"); + + Local input = args[0].As(); + MaybeStackBuffer buffer; + size_t written; + + if (input->IsExternalOneByte()) { // 8-bit case + auto ext = input->GetExternalOneByteStringResource(); + size_t expected_length = simdutf::base64_length_from_binary(ext->length()); + buffer.AllocateSufficientStorage(expected_length + 1); + buffer.SetLengthAndZeroTerminate(expected_length); + written = simdutf::binary_to_base64( + ext->data(), ext->length(), buffer.out(), simdutf::base64_default); + } else { // UTF-8 case + Utf8Value value(env->isolate(), input); + size_t expected_length = simdutf::base64_length_from_binary(value.length()); + buffer.AllocateSufficientStorage(expected_length + 1); + buffer.SetLengthAndZeroTerminate(expected_length); + written = simdutf::binary_to_base64( + value.out(), value.length(), buffer.out(), simdutf::base64_default); + } + + auto value = + String::NewFromOneByte(env->isolate(), + reinterpret_cast(buffer.out()), + NewStringType::kNormal, + written) + .ToLocalChecked(); + return args.GetReturnValue().Set(value); +} + // In case of success, the decoded string is returned. // In case of error, a negative value is returned: // * -1 indicates a single character remained, @@ -1329,6 +1363,7 @@ void Initialize(Local target, Isolate* isolate = env->isolate(); SetMethodNoSideEffect(context, target, "atob", Atob); + SetMethodNoSideEffect(context, target, "btoa", Btoa); SetMethod(context, target, "setBufferPrototype", SetBufferPrototype); SetMethodNoSideEffect(context, target, "createFromString", CreateFromString); @@ -1433,6 +1468,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(CopyArrayBuffer); registry->Register(Atob); + registry->Register(Btoa); } } // namespace Buffer