#include "encoding_binding.h" #include "ada.h" #include "env-inl.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::Context; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Isolate; using v8::Local; using v8::MaybeLocal; using v8::Object; using v8::ObjectTemplate; 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, v8::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, v8::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_EQ(index, BaseObject::kEmbedderType); InternalFieldInfo* info = internal_field_info_; internal_field_info_ = nullptr; return info; } void BindingData::Deserialize(Local context, Local holder, int index, InternalFieldInfoBase* info) { DCHECK_EQ(index, BaseObject::kEmbedderType); v8::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(context, holder, casted_info); CHECK_NOT_NULL(binding); } void BindingData::EncodeInto(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); CHECK_GE(args.Length(), 2); CHECK(args[0]->IsString()); CHECK(args[1]->IsUint8Array()); BindingData* binding_data = Realm::GetBindingData(args); 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; { NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); std::unique_ptr bs = ArrayBuffer::NewBackingStore(isolate, length); 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)); } auto array = Uint8Array::New(ab, 0, length); args.GetReturnValue().Set(array); } // 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 error; MaybeLocal maybe_ret = StringBytes::Encode(env->isolate(), data, length, UTF8, &error); Local ret; if (!maybe_ret.ToLocal(&ret)) { CHECK(!error.IsEmpty()); env->isolate()->ThrowException(error); return; } args.GetReturnValue().Set(ret); } void BindingData::ToASCII(const v8::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()); args.GetReturnValue().Set( String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked()); } void BindingData::ToUnicode(const v8::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()); args.GetReturnValue().Set( String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked()); } void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, Local ctor) { Isolate* isolate = isolate_data->isolate(); Local target = ctor->InstanceTemplate(); 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); } void BindingData::CreatePerContextProperties(Local target, Local unused, Local context, void* priv) { Realm* realm = Realm::GetCurrent(context); realm->AddBindingData(context, target); } void BindingData::RegisterTimerExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(EncodeInto); registry->Register(EncodeUtf8String); registry->Register(DecodeUTF8); registry->Register(ToASCII); registry->Register(ToUnicode); } } // 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)