diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 836f93697a2609..0e4d437c1ea501 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1037,8 +1037,11 @@ void IndexOfString(const FunctionCallbackInfo& args) { if (needle_data == nullptr) { return args.GetReturnValue().Set(-1); } - needle->WriteOneByte( - isolate, needle_data, 0, needle_length, String::NO_NULL_TERMINATION); + StringBytes::Write(isolate, + reinterpret_cast(needle_data), + needle_length, + needle, + enc); result = nbytes::SearchString(reinterpret_cast(haystack), haystack_length, @@ -1302,11 +1305,7 @@ static void Btoa(const FunctionCallbackInfo& args) { simdutf::binary_to_base64(ext->data(), ext->length(), buffer.out()); } else if (input->IsOneByte()) { MaybeStackBuffer stack_buf(input->Length()); - input->WriteOneByte(env->isolate(), - stack_buf.out(), - 0, - input->Length(), - String::NO_NULL_TERMINATION); + input->WriteOneByteV2(env->isolate(), 0, input->Length(), stack_buf.out()); size_t expected_length = simdutf::base64_length_from_binary(input->Length()); @@ -1362,11 +1361,8 @@ static void Atob(const FunctionCallbackInfo& args) { ext->data(), ext->length(), buffer.out(), simdutf::base64_default); } else if (input->IsOneByte()) { MaybeStackBuffer stack_buf(input->Length()); - input->WriteOneByte(args.GetIsolate(), - stack_buf.out(), - 0, - input->Length(), - String::NO_NULL_TERMINATION); + input->WriteOneByteV2( + args.GetIsolate(), 0, input->Length(), stack_buf.out()); const char* data = reinterpret_cast(*stack_buf); size_t expected_length = simdutf::maximal_binary_length_from_base64(data, input->Length()); diff --git a/src/node_http2.cc b/src/node_http2.cc index 8e51129930f2cd..e53022d94f5ca1 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -483,13 +483,10 @@ Origins::Origins( CHECK_LE(origin_contents + origin_string_len, static_cast(bs_->Data()) + bs_->ByteLength()); - CHECK_EQ(origin_string->WriteOneByte( - env->isolate(), - reinterpret_cast(origin_contents), - 0, - origin_string_len, - String::NO_NULL_TERMINATION), - origin_string_len); + origin_string->WriteOneByteV2(env->isolate(), + 0, + origin_string_len, + reinterpret_cast(origin_contents)); size_t n = 0; char* p; @@ -3186,8 +3183,8 @@ void Http2Session::AltSvc(const FunctionCallbackInfo& args) { return; } - size_t origin_len = origin_str->Length(); - size_t value_len = value_str->Length(); + int origin_len = origin_str->Length(); + int value_len = value_str->Length(); CHECK_LE(origin_len + value_len, 16382); // Max permitted for ALTSVC // Verify that origin len != 0 if stream id == 0, or @@ -3196,8 +3193,13 @@ void Http2Session::AltSvc(const FunctionCallbackInfo& args) { MaybeStackBuffer origin(origin_len); MaybeStackBuffer value(value_len); - origin_str->WriteOneByte(env->isolate(), *origin); - value_str->WriteOneByte(env->isolate(), *value); + origin_str->WriteOneByteV2(env->isolate(), + 0, + origin_len, + *origin, + String::WriteFlags::kNullTerminate); + value_str->WriteOneByteV2( + env->isolate(), 0, value_len, *value, String::WriteFlags::kNullTerminate); session->AltSvc(id, *origin, origin_len, *value, value_len); } diff --git a/src/node_http_common-inl.h b/src/node_http_common-inl.h index f7f4408ecb6eaa..339e5a612312c0 100644 --- a/src/node_http_common-inl.h +++ b/src/node_http_common-inl.h @@ -2,9 +2,11 @@ #define SRC_NODE_HTTP_COMMON_INL_H_ #include "node_http_common.h" + +#include "env-inl.h" #include "node.h" #include "node_mem-inl.h" -#include "env-inl.h" +#include "string_bytes.h" #include "v8.h" #include @@ -37,13 +39,12 @@ NgHeaders::NgHeaders(Environment* env, v8::Local headers) { nv_t* const nva = reinterpret_cast(start); CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length()); - CHECK_EQ(header_string.As()->WriteOneByte( - env->isolate(), - reinterpret_cast(header_contents), - 0, - header_string_len, - v8::String::NO_NULL_TERMINATION), - header_string_len); + CHECK_EQ(StringBytes::Write(env->isolate(), + header_contents, + header_string_len, + header_string.As(), + LATIN1), + static_cast(header_string_len)); size_t n = 0; char* p; diff --git a/src/string_bytes.cc b/src/string_bytes.cc index f4411c2126f859..d78bbb237325d2 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -254,11 +254,13 @@ size_t StringBytes::Write(Isolate* isolate, nbytes = std::min(buflen, static_cast(input_view.length())); memcpy(buf, input_view.data8(), nbytes); } else { - uint8_t* const dst = reinterpret_cast(buf); - const int flags = String::HINT_MANY_WRITES_EXPECTED | - String::NO_NULL_TERMINATION | - String::REPLACE_INVALID_UTF8; - nbytes = str->WriteOneByte(isolate, dst, 0, buflen, flags); + nbytes = std::min(buflen, static_cast(input_view.length())); + // Do not use v8::String::WriteOneByteV2 as it asserts the string to be + // a one byte string. For compatibility, convert the uint16_t to uint8_t + // even though this may loose accuracy. + for (size_t i = 0; i < nbytes; i++) { + buf[i] = static_cast(input_view.data16()[i]); + } } break; diff --git a/test/cctest/test_string_bytes.cc b/test/cctest/test_string_bytes.cc new file mode 100644 index 00000000000000..bc308918680bb1 --- /dev/null +++ b/test/cctest/test_string_bytes.cc @@ -0,0 +1,100 @@ +#include "gtest/gtest.h" +#include "node.h" +#include "node_test_fixture.h" +#include "string_bytes.h" +#include "util-inl.h" + +using node::MaybeStackBuffer; +using node::StringBytes; +using v8::HandleScope; +using v8::Local; +using v8::Maybe; +using v8::String; + +class StringBytesTest : public EnvironmentTestFixture {}; + +// Data "Hello, ÆÊÎÖÿ" +static const char latin1_data[] = "Hello, \xC6\xCA\xCE\xD6\xFF"; +static const char utf8_data[] = "Hello, ÆÊÎÖÿ"; + +TEST_F(StringBytesTest, WriteLatin1WithOneByteString) { + const HandleScope handle_scope(isolate_); + const Argv argv; + Env env_{handle_scope, argv}; + + Local one_byte_str = + String::NewFromOneByte(isolate_, + reinterpret_cast(latin1_data)) + .ToLocalChecked(); + + Maybe size_maybe = + StringBytes::StorageSize(isolate_, one_byte_str, node::LATIN1); + + ASSERT_TRUE(size_maybe.IsJust()); + size_t size = size_maybe.FromJust(); + ASSERT_EQ(size, 12u); + + MaybeStackBuffer buf; + size_t written = StringBytes::Write( + isolate_, buf.out(), buf.capacity(), one_byte_str, node::LATIN1); + ASSERT_EQ(written, 12u); + + // Null-terminate the buffer and compare the contents. + buf.SetLength(13); + buf[12] = '\0'; + ASSERT_STREQ(latin1_data, buf.out()); +} + +TEST_F(StringBytesTest, WriteLatin1WithUtf8String) { + const HandleScope handle_scope(isolate_); + const Argv argv; + Env env_{handle_scope, argv}; + + Local utf8_str = + String::NewFromUtf8(isolate_, utf8_data).ToLocalChecked(); + + Maybe size_maybe = + StringBytes::StorageSize(isolate_, utf8_str, node::LATIN1); + + ASSERT_TRUE(size_maybe.IsJust()); + size_t size = size_maybe.FromJust(); + ASSERT_EQ(size, 12u); + + MaybeStackBuffer buf; + size_t written = StringBytes::Write( + isolate_, buf.out(), buf.capacity(), utf8_str, node::LATIN1); + ASSERT_EQ(written, 12u); + + // Null-terminate the buffer and compare the contents. + buf.SetLength(13); + buf[12] = '\0'; + ASSERT_STREQ(latin1_data, buf.out()); +} + +// Verify that StringBytes::Write converts two-byte characters to one-byte +// characters, even if there is no valid one-byte representation. +TEST_F(StringBytesTest, WriteLatin1WithInvalidChar) { + const HandleScope handle_scope(isolate_); + const Argv argv; + Env env_{handle_scope, argv}; + + Local utf8_str = + String::NewFromUtf8(isolate_, "Hello, 世界").ToLocalChecked(); + + Maybe size_maybe = + StringBytes::StorageSize(isolate_, utf8_str, node::LATIN1); + + ASSERT_TRUE(size_maybe.IsJust()); + size_t size = size_maybe.FromJust(); + ASSERT_EQ(size, 9u); + + MaybeStackBuffer buf; + size_t written = StringBytes::Write( + isolate_, buf.out(), buf.capacity(), utf8_str, node::LATIN1); + ASSERT_EQ(written, 9u); + + // Null-terminate the buffer and compare the contents. + buf.SetLength(10); + buf[9] = '\0'; + ASSERT_STREQ("Hello, \x16\x4C", buf.out()); +}