Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fixup! buffer: add buffer.isUtf8 for utf8 validation
  • Loading branch information
anonrig committed Dec 23, 2022
commit bcb19ec2a9b0972023cdfe429c5f2bfcde28df98
2 changes: 1 addition & 1 deletion doc/api/buffer.md
Original file line number Diff line number Diff line change
Expand Up @@ -5137,7 +5137,7 @@ added: REPLACEME
-->

* input {Buffer | ArrayBuffer | TypedArray} The input to validate.
* Returns: {boolean} Returns true if and only if the input is valid UTF-8.
* Returns: {boolean} Returns `true` if and only if the input is valid UTF-8.

This function is used to check if input contains UTF-8 code points (characters).

Expand Down
8 changes: 2 additions & 6 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1317,15 +1317,11 @@ function atob(input) {
}

function isUtf8(input) {
if (isTypedArray(input) || Buffer.isBuffer(input)) {
return bindingIsUtf8(input.buffer);
}

if (isAnyArrayBuffer(input)) {
if (isTypedArray(input) || isAnyArrayBuffer(input)) {
return bindingIsUtf8(input);
}

return false;
throw new ERR_INVALID_ARG_TYPE('input', ['TypedArray', 'Buffer'], input);
}

module.exports = {
Expand Down
32 changes: 26 additions & 6 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1224,12 +1224,32 @@ static void EncodeInto(const FunctionCallbackInfo<Value>& args) {
}

static void IsUtf8(const FunctionCallbackInfo<Value>& args) {
CHECK_GE(args.Length(), 1);
CHECK(args[0]->IsArrayBuffer());
Local<ArrayBuffer> input = args[0].As<ArrayBuffer>();
auto external = static_cast<const char*>(input->Data());
args.GetReturnValue().Set(
simdutf::validate_utf8(external, input->ByteLength()));
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();

CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsTypedArray() || args[0]->IsArrayBuffer());
Comment thread
anonrig marked this conversation as resolved.
Outdated

Local<ArrayBuffer> buf;
size_t offset = 0;
size_t length = 0;

if (args[0]->IsTypedArray()) {
Local<v8::TypedArray> input = args[0].As<v8::TypedArray>();
buf = input->Buffer();
offset = input->ByteOffset();
length = input->ByteLength();
} else {
buf = args[0].As<ArrayBuffer>();
length = buf->ByteLength();
}

if (buf->WasDetached()) {
return node::THROW_ERR_BUFFER_CONTEXT_NOT_AVAILABLE(isolate);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's after the fact but why does the buffer being detached matter here? It would be otherwise indistinguishable from zero-length which we should just return false for anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detached buffers create false sense of UTF8 validation, if there isn’t an error in here, since there is no way of accessing the underlying data store, and validating for UTF-8, I believe this error is valid.


const char* external = static_cast<const char*>(buf->Data()) + offset;
args.GetReturnValue().Set(simdutf::validate_utf8(external, length));
Comment thread
anonrig marked this conversation as resolved.
Outdated
}

void SetBufferPrototype(const FunctionCallbackInfo<Value>& args) {
Expand Down
31 changes: 28 additions & 3 deletions test/parallel/test-buffer-isutf8.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,39 @@

require('../common');
const assert = require('assert');
const { isUtf8 } = require('buffer');
const { isUtf8, Buffer } = require('buffer');
const { TextEncoder } = require('util');

const encoder = new TextEncoder();

assert.strictEqual(isUtf8(encoder.encode('hello')), true);
assert.strictEqual(isUtf8(encoder.encode('ğ')), true);
assert.strictEqual(isUtf8(Buffer.from([0xf8])), false);
Comment thread
anonrig marked this conversation as resolved.
Outdated
assert.strictEqual(isUtf8(encoder.encode('aé日')), true);
Comment thread
anonrig marked this conversation as resolved.
Outdated

assert.strictEqual(isUtf8(null), false);
assert.strictEqual(isUtf8(undefined), false);
[
null,
undefined,
'hello',
true,
false
].forEach((input) => {
assert.throws(
() => { isUtf8(input); },
{
code: 'ERR_INVALID_ARG_TYPE',
},
);
})
Comment thread
anonrig marked this conversation as resolved.
Outdated

{
// Test with detached array buffers
const arrayBuffer = new ArrayBuffer(1024);
structuredClone(arrayBuffer, { transfer: [arrayBuffer] });
assert.throws(
() => { isUtf8(arrayBuffer); },
{
Comment thread
anonrig marked this conversation as resolved.
Outdated
code: 'ERR_BUFFER_CONTEXT_NOT_AVAILABLE'
}
)
Comment thread
anonrig marked this conversation as resolved.
Outdated
}