| #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 <cstdint> |
| |
| 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> 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> context, |
| SnapshotCreator* creator) { |
| DCHECK_NULL(internal_field_info_); |
| internal_field_info_ = InternalFieldInfoBase::New<InternalFieldInfo>(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> context, |
| Local<Object> holder, |
| int index, |
| InternalFieldInfoBase* info) { |
| DCHECK_IS_SNAPSHOT_SLOT(index); |
| HandleScope scope(Isolate::GetCurrent()); |
| Realm* realm = Realm::GetCurrent(context); |
| // Recreate the buffer in the constructor. |
| InternalFieldInfo* casted_info = static_cast<InternalFieldInfo*>(info); |
| BindingData* binding = |
| realm->AddBindingData<BindingData>(holder, casted_info); |
| CHECK_NOT_NULL(binding); |
| } |
| |
| void BindingData::EncodeInto(const FunctionCallbackInfo<Value>& 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<BindingData>(); |
| |
| Local<String> source = args[0].As<String>(); |
| |
| Local<Uint8Array> dest = args[1].As<Uint8Array>(); |
| Local<ArrayBuffer> buf = dest->Buffer(); |
| char* write_result = static_cast<char*>(buf->Data()) + dest->ByteOffset(); |
| size_t dest_length = dest->ByteLength(); |
| |
| size_t nchars; |
| size_t written = source->WriteUtf8V2(isolate, |
| write_result, |
| dest_length, |
| String::WriteFlags::kReplaceInvalidUtf8, |
| &nchars); |
| |
| 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<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| CHECK_GE(args.Length(), 1); |
| CHECK(args[0]->IsString()); |
| |
| Local<String> str = args[0].As<String>(); |
| size_t length = str->Utf8LengthV2(isolate); |
| |
| Local<ArrayBuffer> ab; |
| { |
| std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore( |
| isolate, length, BackingStoreInitializationMode::kUninitialized); |
| |
| CHECK(bs); |
| |
| // We are certain that `data` is sufficiently large |
| str->WriteUtf8V2(isolate, |
| static_cast<char*>(bs->Data()), |
| bs->MaxByteLength(), |
| String::WriteFlags::kReplaceInvalidUtf8); |
| |
| 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<Value>& 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<char> 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<Value> ret; |
| if (StringBytes::Encode(env->isolate(), data, length, UTF8).ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| |
| void BindingData::ToASCII(const FunctionCallbackInfo<Value>& 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<Value> ret; |
| if (ToV8Value(env->context(), out, env->isolate()).ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| |
| void BindingData::ToUnicode(const FunctionCallbackInfo<Value>& 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<Value> ret; |
| if (ToV8Value(env->context(), out, env->isolate()).ToLocal(&ret)) { |
| args.GetReturnValue().Set(ret); |
| } |
| } |
| |
| void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, |
| Local<ObjectTemplate> 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<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Realm* realm = Realm::GetCurrent(context); |
| realm->AddBindingData<BindingData>(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<Value>& 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<uint8_t> 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<const char*>(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<Value> 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) |