diff --git a/src/node_api.cc b/src/node_api.cc index f3d717f3e46ab8..313481508c3330 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -1267,6 +1267,25 @@ napi_status napi_create_array_with_length(napi_env env, return GET_RETURN_STATUS(env); } +napi_status napi_create_string_latin1(napi_env env, + const char* str, + size_t length, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, result); + + auto isolate = env->isolate; + auto str_maybe = + v8::String::NewFromOneByte(isolate, + reinterpret_cast(str), + v8::NewStringType::kInternalized, + length); + CHECK_MAYBE_EMPTY(env, str_maybe, napi_generic_failure); + + *result = v8impl::JsValueFromV8LocalValue(str_maybe.ToLocalChecked()); + return GET_RETURN_STATUS(env); +} + napi_status napi_create_string_utf8(napi_env env, const char* str, size_t length, @@ -1714,9 +1733,46 @@ napi_status napi_get_value_string_length(napi_env env, return GET_RETURN_STATUS(env); } +// Copies a JavaScript string into a LATIN-1 string buffer. The result is the +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in bytes) +// via the result parameter. +// The result argument is optional unless buf is NULL. +napi_status napi_get_value_string_latin1(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result) { + NAPI_PREAMBLE(env); + + v8::Local val = v8impl::V8LocalValueFromJsValue(value); + RETURN_STATUS_IF_FALSE(env, val->IsString(), napi_string_expected); + + if (!buf) { + CHECK_ARG(env, result); + *result = val.As()->Length(); + } else { + int copied = val.As()->WriteOneByte( + reinterpret_cast(buf), 0, bufsize - 1, + v8::String::NO_NULL_TERMINATION); + + buf[copied] = '\0'; + if (result != nullptr) { + *result = copied; + } + } + + return GET_RETURN_STATUS(env); +} + // Copies a JavaScript string into a UTF-8 string buffer. The result is the -// number of bytes copied into buf, including the null terminator. If bufsize -// is insufficient, the string will be truncated, including a null terminator. +// number of bytes (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. // If buf is NULL, this method returns the length of the string (in bytes) // via the result parameter. // The result argument is optional unless buf is NULL. @@ -1735,8 +1791,10 @@ napi_status napi_get_value_string_utf8(napi_env env, *result = val.As()->Utf8Length(); } else { int copied = val.As()->WriteUtf8( - buf, bufsize, nullptr, v8::String::REPLACE_INVALID_UTF8); + buf, bufsize - 1, nullptr, v8::String::REPLACE_INVALID_UTF8 | + v8::String::NO_NULL_TERMINATION); + buf[copied] = '\0'; if (result != nullptr) { *result = copied; } @@ -1746,10 +1804,12 @@ napi_status napi_get_value_string_utf8(napi_env env, } // Copies a JavaScript string into a UTF-16 string buffer. The result is the -// number of 2-byte code units copied into buf, including the null terminator. -// If bufsize is insufficient, the string will be truncated, including a null -// terminator. If buf is NULL, this method returns the length of the string -// (in 2-byte code units) via the result parameter. +// number of 2-byte code units (excluding the null terminator) copied into buf. +// A sufficient buffer size should be greater than the length of string, +// reserving space for null terminator. +// If bufsize is insufficient, the string will be truncated and null terminated. +// If buf is NULL, this method returns the length of the string (in 2-byte +// code units) via the result parameter. // The result argument is optional unless buf is NULL. napi_status napi_get_value_string_utf16(napi_env env, napi_value value, @@ -1767,8 +1827,10 @@ napi_status napi_get_value_string_utf16(napi_env env, *result = val.As()->Length(); } else { int copied = val.As()->Write( - reinterpret_cast(buf), 0, bufsize, v8::String::NO_OPTIONS); + reinterpret_cast(buf), 0, bufsize - 1, + v8::String::NO_NULL_TERMINATION); + buf[copied] = '\0'; if (result != nullptr) { *result = copied; } diff --git a/src/node_api.h b/src/node_api.h index 8791954a377052..50b7da64c60179 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -121,6 +121,10 @@ NAPI_EXTERN napi_status napi_create_array_with_length(napi_env env, NAPI_EXTERN napi_status napi_create_number(napi_env env, double value, napi_value* result); +NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env, + const char* str, + size_t length, + napi_value* result); NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env, const char* str, size_t length, @@ -172,6 +176,13 @@ NAPI_EXTERN napi_status napi_get_value_string_length(napi_env env, napi_value value, size_t* result); +// Copies LATIN-1 encoded bytes from a string into a buffer. +NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env, + napi_value value, + char* buf, + size_t bufsize, + size_t* result); + // Copies UTF-8 encoded bytes from a string into a buffer. NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env, napi_value value, diff --git a/test/addons-napi/test_string/test.js b/test/addons-napi/test_string/test.js index 3c9334a5616523..de6f9d98baab49 100644 --- a/test/addons-napi/test_string/test.js +++ b/test/addons-napi/test_string/test.js @@ -5,22 +5,67 @@ const assert = require('assert'); // testing api calls for string const test_string = require(`./build/${common.buildType}/test_string`); +const empty = ''; +assert.strictEqual(test_string.TestLatin1(empty), empty); +assert.strictEqual(test_string.TestUtf8(empty), empty); +assert.strictEqual(test_string.TestUtf16(empty), empty); +assert.strictEqual(test_string.Length(empty), 0); +assert.strictEqual(test_string.Utf8Length(empty), 0); + const str1 = 'hello world'; -assert.strictEqual(test_string.Copy(str1), str1); +assert.strictEqual(test_string.TestLatin1(str1), str1); +assert.strictEqual(test_string.TestUtf8(str1), str1); +assert.strictEqual(test_string.TestUtf16(str1), str1); +assert.strictEqual(test_string.TestLatin1Insufficient(str1), str1.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str1), str1.slice(0, 3)); +assert.strictEqual(test_string.TestUtf16Insufficient(str1), str1.slice(0, 3)); assert.strictEqual(test_string.Length(str1), 11); assert.strictEqual(test_string.Utf8Length(str1), 11); const str2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; -assert.strictEqual(test_string.Copy(str2), str2); +assert.strictEqual(test_string.TestLatin1(str2), str2); +assert.strictEqual(test_string.TestUtf8(str2), str2); +assert.strictEqual(test_string.TestUtf16(str2), str2); +assert.strictEqual(test_string.TestLatin1Insufficient(str2), str2.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str2), str2.slice(0, 3)); +assert.strictEqual(test_string.TestUtf16Insufficient(str2), str2.slice(0, 3)); assert.strictEqual(test_string.Length(str2), 62); assert.strictEqual(test_string.Utf8Length(str2), 62); const str3 = '?!@#$%^&*()_+-=[]{}/.,<>\'"\\'; -assert.strictEqual(test_string.Copy(str3), str3); +assert.strictEqual(test_string.TestLatin1(str3), str3); +assert.strictEqual(test_string.TestUtf8(str3), str3); +assert.strictEqual(test_string.TestUtf16(str3), str3); +assert.strictEqual(test_string.TestLatin1Insufficient(str3), str3.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str3), str3.slice(0, 3)); +assert.strictEqual(test_string.TestUtf16Insufficient(str3), str3.slice(0, 3)); assert.strictEqual(test_string.Length(str3), 27); assert.strictEqual(test_string.Utf8Length(str3), 27); -const str4 = '\u{2003}\u{2101}\u{2001}'; -assert.strictEqual(test_string.Copy(str4), str4); -assert.strictEqual(test_string.Length(str4), 3); -assert.strictEqual(test_string.Utf8Length(str4), 9); +const str4 = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿'; +assert.strictEqual(test_string.TestLatin1(str4), str4); +assert.strictEqual(test_string.TestUtf8(str4), str4); +assert.strictEqual(test_string.TestUtf16(str4), str4); +assert.strictEqual(test_string.TestLatin1Insufficient(str4), str4.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str4), str4.slice(0, 1)); +assert.strictEqual(test_string.TestUtf16Insufficient(str4), str4.slice(0, 3)); +assert.strictEqual(test_string.Length(str4), 31); +assert.strictEqual(test_string.Utf8Length(str4), 62); + +const str5 = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ'; +assert.strictEqual(test_string.TestLatin1(str5), str5); +assert.strictEqual(test_string.TestUtf8(str5), str5); +assert.strictEqual(test_string.TestUtf16(str5), str5); +assert.strictEqual(test_string.TestLatin1Insufficient(str5), str5.slice(0, 3)); +assert.strictEqual(test_string.TestUtf8Insufficient(str5), str5.slice(0, 1)); +assert.strictEqual(test_string.TestUtf16Insufficient(str5), str5.slice(0, 3)); +assert.strictEqual(test_string.Length(str5), 63); +assert.strictEqual(test_string.Utf8Length(str5), 126); + +const str6 = '\u{2003}\u{2101}\u{2001}\u{202}\u{2011}'; +assert.strictEqual(test_string.TestUtf8(str6), str6); +assert.strictEqual(test_string.TestUtf16(str6), str6); +assert.strictEqual(test_string.TestUtf8Insufficient(str6), str6.slice(0, 1)); +assert.strictEqual(test_string.TestUtf16Insufficient(str6), str6.slice(0, 3)); +assert.strictEqual(test_string.Length(str6), 5); +assert.strictEqual(test_string.Utf8Length(str6), 14); diff --git a/test/addons-napi/test_string/test_string.c b/test/addons-napi/test_string/test_string.c index c7c82d7e2e7ac6..593902edd58f9d 100644 --- a/test/addons-napi/test_string/test_string.c +++ b/test/addons-napi/test_string/test_string.c @@ -1,7 +1,33 @@ #include #include "../common.h" -napi_value Copy(napi_env env, napi_callback_info info) { +napi_value TestLatin1(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_latin1(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_latin1(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf8(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value args[1]; NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); @@ -22,7 +48,111 @@ napi_value Copy(napi_env env, napi_callback_info info) { napi_get_value_string_utf8(env, args[0], buffer, buffer_size, &copied)); napi_value output; - NAPI_CALL(env, napi_create_string_utf8(env, buffer, copied-1, &output)); + NAPI_CALL(env, napi_create_string_utf8(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf16(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char16_t buffer[128]; + size_t buffer_size = 128; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_utf16(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf16(env, buffer, copied, &output)); + + return output; +} + +napi_value TestLatin1Insufficient(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_latin1(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_latin1(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf8Insufficient(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_utf8(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf8(env, buffer, copied, &output)); + + return output; +} + +napi_value TestUtf16Insufficient(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); + + NAPI_ASSERT(env, valuetype == napi_string, + "Wrong type of argment. Expects a string."); + + char16_t buffer[4]; + size_t buffer_size = 4; + size_t copied; + + NAPI_CALL(env, + napi_get_value_string_utf16(env, args[0], buffer, buffer_size, &copied)); + + napi_value output; + NAPI_CALL(env, napi_create_string_utf16(env, buffer, copied, &output)); return output; } @@ -73,7 +203,12 @@ napi_value Utf8Length(napi_env env, napi_callback_info info) { void Init(napi_env env, napi_value exports, napi_value module, void* priv) { napi_property_descriptor properties[] = { - DECLARE_NAPI_PROPERTY("Copy", Copy), + DECLARE_NAPI_PROPERTY("TestLatin1", TestLatin1), + DECLARE_NAPI_PROPERTY("TestLatin1Insufficient", TestLatin1Insufficient), + DECLARE_NAPI_PROPERTY("TestUtf8", TestUtf8), + DECLARE_NAPI_PROPERTY("TestUtf8Insufficient", TestUtf8Insufficient), + DECLARE_NAPI_PROPERTY("TestUtf16", TestUtf16), + DECLARE_NAPI_PROPERTY("TestUtf16Insufficient", TestUtf16Insufficient), DECLARE_NAPI_PROPERTY("Length", Length), DECLARE_NAPI_PROPERTY("Utf8Length", Utf8Length), };