#include "encoding_binding.h" #include "ada.h" #include "env-inl.h" #include "node_buffer.h" #include "node_errors.h" #include "node_external_reference.h" #include "simdutf.h" #include "string_bytes.h" #include "v8.h" #include namespace node { namespace encoding_binding { using v8::ArrayBuffer; using v8::BackingStore; using v8::BackingStoreInitializationMode; using v8::Context; using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::Object; using v8::ObjectTemplate; using v8::SnapshotCreator; using v8::String; using v8::Uint8Array; using v8::Value; void BindingData::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("encode_into_results_buffer", encode_into_results_buffer_); } BindingData::BindingData(Realm* realm, Local object, InternalFieldInfo* info) : SnapshotableObject(realm, object, type_int), encode_into_results_buffer_( realm->isolate(), kEncodeIntoResultsLength, MAYBE_FIELD_PTR(info, encode_into_results_buffer)) { if (info == nullptr) { object ->Set(realm->context(), FIXED_ONE_BYTE_STRING(realm->isolate(), "encodeIntoResults"), encode_into_results_buffer_.GetJSArray()) .Check(); } else { encode_into_results_buffer_.Deserialize(realm->context()); } encode_into_results_buffer_.MakeWeak(); } bool BindingData::PrepareForSerialization(Local context, SnapshotCreator* creator) { DCHECK_NULL(internal_field_info_); internal_field_info_ = InternalFieldInfoBase::New(type()); internal_field_info_->encode_into_results_buffer = encode_into_results_buffer_.Serialize(context, creator); // Return true because we need to maintain the reference to the binding from // JS land. return true; } InternalFieldInfoBase* BindingData::Serialize(int index) { DCHECK_IS_SNAPSHOT_SLOT(index); InternalFieldInfo* info = internal_field_info_; internal_field_info_ = nullptr; return info; } void BindingData::Deserialize(Local context, Local holder, int index, InternalFieldInfoBase* info) { DCHECK_IS_SNAPSHOT_SLOT(index); HandleScope scope(context->GetIsolate()); Realm* realm = Realm::GetCurrent(context); // Recreate the buffer in the constructor. InternalFieldInfo* casted_info = static_cast(info); BindingData* binding = realm->AddBindingData(holder, casted_info); CHECK_NOT_NULL(binding); } void BindingData::EncodeInto(const FunctionCallbackInfo& args) { CHECK_GE(args.Length(), 2); CHECK(args[0]->IsString()); CHECK(args[1]->IsUint8Array()); Realm* realm = Realm::GetCurrent(args); Isolate* isolate = realm->isolate(); BindingData* binding_data = realm->GetBindingData(); Local source = args[0].As(); Local dest = args[1].As(); Local buf = dest->Buffer(); char* write_result = static_cast(buf->Data()) + dest->ByteOffset(); size_t dest_length = dest->ByteLength(); int nchars; int written = source->WriteUtf8( isolate, write_result, dest_length, &nchars, String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8); binding_data->encode_into_results_buffer_[0] = nchars; binding_data->encode_into_results_buffer_[1] = written; } // Encode a single string to a UTF-8 Uint8Array (not Buffer). // Used in TextEncoder.prototype.encode. void BindingData::EncodeUtf8String(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); Local str = args[0].As(); size_t length = str->Utf8Length(isolate); Local ab; { std::unique_ptr bs = ArrayBuffer::NewBackingStore( isolate, length, BackingStoreInitializationMode::kUninitialized); CHECK(bs); str->WriteUtf8(isolate, static_cast(bs->Data()), -1, // We are certain that `data` is sufficiently large nullptr, String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8); ab = ArrayBuffer::New(isolate, std::move(bs)); } args.GetReturnValue().Set(Uint8Array::New(ab, 0, length)); } // Convert the input into an encoded string void BindingData::DecodeUTF8(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); // list, flags CHECK_GE(args.Length(), 1); if (!(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer() || args[0]->IsArrayBufferView())) { return node::THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"list\" argument must be an instance of SharedArrayBuffer, " "ArrayBuffer or ArrayBufferView."); } ArrayBufferViewContents buffer(args[0]); bool ignore_bom = args[1]->IsTrue(); bool has_fatal = args[2]->IsTrue(); const char* data = buffer.data(); size_t length = buffer.length(); if (has_fatal) { auto result = simdutf::validate_utf8_with_errors(data, length); if (result.error) { return node::THROW_ERR_ENCODING_INVALID_ENCODED_DATA( env->isolate(), "The encoded data was not valid for encoding utf-8"); } } if (!ignore_bom && length >= 3) { if (memcmp(data, "\xEF\xBB\xBF", 3) == 0) { data += 3; length -= 3; } } if (length == 0) return args.GetReturnValue().SetEmptyString(); Local ret; if (StringBytes::Encode(env->isolate(), data, length, UTF8).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void BindingData::ToASCII(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); Utf8Value input(env->isolate(), args[0]); auto out = ada::idna::to_ascii(input.ToStringView()); Local ret; if (ToV8Value(env->context(), out, env->isolate()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void BindingData::ToUnicode(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); Utf8Value input(env->isolate(), args[0]); auto out = ada::idna::to_unicode(input.ToStringView()); Local ret; if (ToV8Value(env->context(), out, env->isolate()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); SetMethod(isolate, target, "encodeInto", EncodeInto); SetMethodNoSideEffect(isolate, target, "encodeUtf8String", EncodeUtf8String); SetMethodNoSideEffect(isolate, target, "decodeUTF8", DecodeUTF8); SetMethodNoSideEffect(isolate, target, "toASCII", ToASCII); SetMethodNoSideEffect(isolate, target, "toUnicode", ToUnicode); SetMethodNoSideEffect(isolate, target, "decodeLatin1", DecodeLatin1); } void BindingData::CreatePerContextProperties(Local target, Local unused, Local context, void* priv) { Realm* realm = Realm::GetCurrent(context); realm->AddBindingData(target); } void BindingData::RegisterTimerExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(EncodeInto); registry->Register(EncodeUtf8String); registry->Register(DecodeUTF8); registry->Register(ToASCII); registry->Register(ToUnicode); registry->Register(DecodeLatin1); } void BindingData::DecodeLatin1(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_GE(args.Length(), 1); if (!(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer() || args[0]->IsArrayBufferView())) { return node::THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"input\" argument must be an instance of ArrayBuffer, " "SharedArrayBuffer, or ArrayBufferView."); } bool ignore_bom = args[1]->IsTrue(); bool has_fatal = args[2]->IsTrue(); ArrayBufferViewContents buffer(args[0]); const uint8_t* data = buffer.data(); size_t length = buffer.length(); if (ignore_bom && length > 0 && data[0] == 0xFF) { data++; length--; } if (length == 0) { return args.GetReturnValue().SetEmptyString(); } std::string result(length * 2, '\0'); size_t written = simdutf::convert_latin1_to_utf8( reinterpret_cast(data), length, result.data()); if (has_fatal && written == 0) { return node::THROW_ERR_ENCODING_INVALID_ENCODED_DATA( env->isolate(), "The encoded data was not valid for encoding latin1"); } std::string_view view(result.c_str(), written); Local ret; if (ToV8Value(env->context(), view, env->isolate()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } } // namespace encoding_binding } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL( encoding_binding, node::encoding_binding::BindingData::CreatePerContextProperties) NODE_BINDING_PER_ISOLATE_INIT( encoding_binding, node::encoding_binding::BindingData::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE( encoding_binding, node::encoding_binding::BindingData::RegisterTimerExternalReferences)