Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 7 additions & 7 deletions lib/internal/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const kEncoding = Symbol('encoding');
const kDecoder = Symbol('decoder');
const kFatal = Symbol('kFatal');
const kUTF8FastPath = Symbol('kUTF8FastPath');
const kLatin1FastPath = Symbol('kLatin1FastPath');
const kWindows1252FastPath = Symbol('kWindows1252FastPath');
const kIgnoreBOM = Symbol('kIgnoreBOM');

const {
Expand All @@ -55,7 +55,7 @@ const {
encodeIntoResults,
encodeUtf8String,
decodeUTF8,
decodeLatin1,
decodeWindows1252,
} = binding;

const { Buffer } = require('buffer');
Expand Down Expand Up @@ -420,10 +420,10 @@ function makeTextDecoderICU() {
this[kFatal] = Boolean(options?.fatal);
// Only support fast path for UTF-8.
this[kUTF8FastPath] = enc === 'utf-8';
this[kLatin1FastPath] = enc === 'windows-1252';
this[kWindows1252FastPath] = enc === 'windows-1252';
this[kHandle] = undefined;

if (!this[kUTF8FastPath] && !this[kLatin1FastPath]) {
if (!this[kUTF8FastPath] && !this[kWindows1252FastPath]) {
this.#prepareConverter();
}
}
Expand All @@ -440,14 +440,14 @@ function makeTextDecoderICU() {
validateDecoder(this);

this[kUTF8FastPath] &&= !(options?.stream);
this[kLatin1FastPath] &&= !(options?.stream);
this[kWindows1252FastPath] &&= !(options?.stream);

if (this[kUTF8FastPath]) {
return decodeUTF8(input, this[kIgnoreBOM], this[kFatal]);
}

if (this[kLatin1FastPath]) {
return decodeLatin1(input, this[kIgnoreBOM], this[kFatal]);
if (this[kWindows1252FastPath]) {
return decodeWindows1252(input, this[kIgnoreBOM], this[kFatal]);
}

this.#prepareConverter();
Expand Down
3 changes: 1 addition & 2 deletions lib/internal/http2/compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const {
ObjectHasOwn,
ObjectKeys,
Proxy,
ReflectApply,
ReflectGetPrototypeOf,
Symbol,
} = primordials;
Expand Down Expand Up @@ -846,7 +845,7 @@ class Http2ServerResponse extends Stream {
this.writeHead(this[kState].statusCode);

if (this[kState].closed || stream.destroyed)
ReflectApply(onStreamCloseResponse, stream, []);
onStreamCloseResponse.call(stream);
else
stream.end();

Expand Down
13 changes: 6 additions & 7 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -1960,7 +1960,7 @@ function shutdownWritable(callback) {
req.handle = handle;
const err = handle.shutdown(req);
if (err === 1) // synchronous finish
return ReflectApply(afterShutdown, req, [0]);
return afterShutdown.call(req, 0);
}

function finishSendTrailers(stream, headersList) {
Expand Down Expand Up @@ -2327,7 +2327,7 @@ class Http2Stream extends Duplex {
return;
}
debugStreamObj(this, 'shutting down writable on _final');
ReflectApply(shutdownWritable, this, [cb]);
shutdownWritable.call(this, cb);

if (this.session[kType] === NGHTTP2_SESSION_CLIENT && onClientStreamBodySentChannel.hasSubscribers) {
onClientStreamBodySentChannel.publish({ stream: this });
Expand Down Expand Up @@ -2741,8 +2741,7 @@ function doSendFD(session, options, fd, headers, streamOptions, err, stat) {
// response is canceled. The user code may also send a separate type
// of response so check again for the HEADERS_SENT flag
if ((typeof options.statCheck === 'function' &&
ReflectApply(options.statCheck, this,
[stat, headers, statOptions]) === false) ||
options.statCheck.call(this, stat, headers, statOptions) === false) ||
(this[kState].flags & STREAM_FLAGS_HEADERS_SENT)) {
return;
}
Expand Down Expand Up @@ -2801,7 +2800,7 @@ function doSendFileFD(session, options, fd, headers, streamOptions, err, stat) {
// response is canceled. The user code may also send a separate type
// of response so check again for the HEADERS_SENT flag
if ((typeof options.statCheck === 'function' &&
ReflectApply(options.statCheck, this, [stat, headers]) === false) ||
options.statCheck.call(this, stat, headers) === false) ||
(this[kState].flags & STREAM_FLAGS_HEADERS_SENT)) {
tryClose(fd);
return;
Expand Down Expand Up @@ -3633,9 +3632,9 @@ function getUnpackedSettings(buf, options = kEmptyObject) {
const settings = {};
let offset = 0;
while (offset < buf.length) {
const id = ReflectApply(readUInt16BE, buf, [offset]);
const id = readUInt16BE.call(buf, offset);
offset += 2;
const value = ReflectApply(readUInt32BE, buf, [offset]);
const value = readUInt32BE.call(buf, offset);
switch (id) {
case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
settings.headerTableSize = value;
Expand Down
25 changes: 12 additions & 13 deletions lib/zlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const {
ObjectFreeze,
ObjectKeys,
ObjectSetPrototypeOf,
ReflectApply,
Symbol,
Uint32Array,
} = primordials;
Expand Down Expand Up @@ -255,7 +254,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
}
}

ReflectApply(Transform, this, [{ autoDestroy: true, ...opts }]);
Transform.call(this, { autoDestroy: true, ...opts });
this[kError] = null;
this.bytesWritten = 0;
this._handle = handle;
Expand Down Expand Up @@ -681,7 +680,7 @@ function Zlib(opts, mode) {
processCallback,
dictionary);

ReflectApply(ZlibBase, this, [opts, mode, handle, zlibDefaultOpts]);
ZlibBase.call(this, opts, mode, handle, zlibDefaultOpts);

this._level = level;
this._strategy = strategy;
Expand Down Expand Up @@ -724,7 +723,7 @@ function Deflate(opts) {
if (!(this instanceof Deflate)) {
return deprecateInstantiation(Deflate, 'DEP0184', opts);
}
ReflectApply(Zlib, this, [opts, DEFLATE]);
Zlib.call(this, opts, DEFLATE);
}
ObjectSetPrototypeOf(Deflate.prototype, Zlib.prototype);
ObjectSetPrototypeOf(Deflate, Zlib);
Expand All @@ -733,7 +732,7 @@ function Inflate(opts) {
if (!(this instanceof Inflate)) {
return deprecateInstantiation(Inflate, 'DEP0184', opts);
}
ReflectApply(Zlib, this, [opts, INFLATE]);
Zlib.call(this, opts, INFLATE);
}
ObjectSetPrototypeOf(Inflate.prototype, Zlib.prototype);
ObjectSetPrototypeOf(Inflate, Zlib);
Expand All @@ -742,7 +741,7 @@ function Gzip(opts) {
if (!(this instanceof Gzip)) {
return deprecateInstantiation(Gzip, 'DEP0184', opts);
}
ReflectApply(Zlib, this, [opts, GZIP]);
Zlib.call(this, opts, GZIP);
}
ObjectSetPrototypeOf(Gzip.prototype, Zlib.prototype);
ObjectSetPrototypeOf(Gzip, Zlib);
Expand All @@ -751,7 +750,7 @@ function Gunzip(opts) {
if (!(this instanceof Gunzip)) {
return deprecateInstantiation(Gunzip, 'DEP0184', opts);
}
ReflectApply(Zlib, this, [opts, GUNZIP]);
Zlib.call(this, opts, GUNZIP);
}
ObjectSetPrototypeOf(Gunzip.prototype, Zlib.prototype);
ObjectSetPrototypeOf(Gunzip, Zlib);
Expand All @@ -761,7 +760,7 @@ function DeflateRaw(opts) {
if (!(this instanceof DeflateRaw)) {
return deprecateInstantiation(DeflateRaw, 'DEP0184', opts);
}
ReflectApply(Zlib, this, [opts, DEFLATERAW]);
Zlib.call(this, opts, DEFLATERAW);
}
ObjectSetPrototypeOf(DeflateRaw.prototype, Zlib.prototype);
ObjectSetPrototypeOf(DeflateRaw, Zlib);
Expand All @@ -770,7 +769,7 @@ function InflateRaw(opts) {
if (!(this instanceof InflateRaw)) {
return deprecateInstantiation(InflateRaw, 'DEP0184', opts);
}
ReflectApply(Zlib, this, [opts, INFLATERAW]);
Zlib.call(this, opts, INFLATERAW);
}
ObjectSetPrototypeOf(InflateRaw.prototype, Zlib.prototype);
ObjectSetPrototypeOf(InflateRaw, Zlib);
Expand All @@ -779,7 +778,7 @@ function Unzip(opts) {
if (!(this instanceof Unzip)) {
return deprecateInstantiation(Unzip, 'DEP0184', opts);
}
ReflectApply(Zlib, this, [opts, UNZIP]);
Zlib.call(this, opts, UNZIP);
}
ObjectSetPrototypeOf(Unzip.prototype, Zlib.prototype);
ObjectSetPrototypeOf(Unzip, Zlib);
Expand Down Expand Up @@ -837,7 +836,7 @@ function Brotli(opts, mode) {
this._writeState = new Uint32Array(2);
handle.init(brotliInitParamsArray, this._writeState, processCallback);

ReflectApply(ZlibBase, this, [opts, mode, handle, brotliDefaultOpts]);
ZlibBase.call(this, opts, mode, handle, brotliDefaultOpts);
}
ObjectSetPrototypeOf(Brotli.prototype, Zlib.prototype);
ObjectSetPrototypeOf(Brotli, Zlib);
Expand All @@ -846,7 +845,7 @@ function BrotliCompress(opts) {
if (!(this instanceof BrotliCompress)) {
return deprecateInstantiation(BrotliCompress, 'DEP0184', opts);
}
ReflectApply(Brotli, this, [opts, BROTLI_ENCODE]);
Brotli.call(this, opts, BROTLI_ENCODE);
}
ObjectSetPrototypeOf(BrotliCompress.prototype, Brotli.prototype);
ObjectSetPrototypeOf(BrotliCompress, Brotli);
Expand All @@ -855,7 +854,7 @@ function BrotliDecompress(opts) {
if (!(this instanceof BrotliDecompress)) {
return deprecateInstantiation(BrotliDecompress, 'DEP0184', opts);
}
ReflectApply(Brotli, this, [opts, BROTLI_DECODE]);
Brotli.call(this, opts, BROTLI_DECODE);
}
ObjectSetPrototypeOf(BrotliDecompress.prototype, Brotli.prototype);
ObjectSetPrototypeOf(BrotliDecompress, Brotli);
Expand Down
53 changes: 39 additions & 14 deletions src/encoding_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethodNoSideEffect(isolate, target, "decodeUTF8", DecodeUTF8);
SetMethodNoSideEffect(isolate, target, "toASCII", ToASCII);
SetMethodNoSideEffect(isolate, target, "toUnicode", ToUnicode);
SetMethodNoSideEffect(isolate, target, "decodeLatin1", DecodeLatin1);
SetMethodNoSideEffect(
isolate, target, "decodeWindows1252", DecodeWindows1252);
}

void BindingData::CreatePerContextProperties(Local<Object> target,
Expand All @@ -432,10 +433,10 @@ void BindingData::RegisterTimerExternalReferences(
registry->Register(DecodeUTF8);
registry->Register(ToASCII);
registry->Register(ToUnicode);
registry->Register(DecodeLatin1);
registry->Register(DecodeWindows1252);
}

void BindingData::DecodeLatin1(const FunctionCallbackInfo<Value>& args) {
void BindingData::DecodeWindows1252(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

CHECK_GE(args.Length(), 1);
Expand All @@ -448,7 +449,6 @@ void BindingData::DecodeLatin1(const FunctionCallbackInfo<Value>& args) {
}

bool ignore_bom = args[1]->IsTrue();
bool has_fatal = args[2]->IsTrue();

ArrayBufferViewContents<uint8_t> buffer(args[0]);
const uint8_t* data = buffer.data();
Expand All @@ -463,20 +463,45 @@ void BindingData::DecodeLatin1(const FunctionCallbackInfo<Value>& args) {
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());
// Windows-1252 specific mapping for bytes 128-159
// These differ from Latin-1/ISO-8859-1
static const uint16_t windows1252_mapping[32] = {
0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, // 80-87
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, // 88-8F
0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, // 90-97
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178 // 98-9F
};

std::string result;
result.reserve(length * 3); // Reserve space for UTF-8 output

for (size_t i = 0; i < length; i++) {
uint8_t byte = data[i];
uint32_t codepoint;

// Check if byte is in the special Windows-1252 range (128-159)
if (byte >= 0x80 && byte <= 0x9F) {
codepoint = windows1252_mapping[byte - 0x80];
} else {
// For all other bytes, Windows-1252 is identical to Latin-1
codepoint = byte;
}

if (has_fatal && written == 0) {
return node::THROW_ERR_ENCODING_INVALID_ENCODED_DATA(
env->isolate(), "The encoded data was not valid for encoding latin1");
// Convert codepoint to UTF-8
if (codepoint < 0x80) {
result.push_back(static_cast<char>(codepoint));
} else if (codepoint < 0x800) {
result.push_back(static_cast<char>(0xC0 | (codepoint >> 6)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else {
result.push_back(static_cast<char>(0xE0 | (codepoint >> 12)));
result.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
}
}

std::string_view view(result.c_str(), written);

Local<Value> ret;
if (ToV8Value(env->context(), view, env->isolate()).ToLocal(&ret)) {
if (ToV8Value(env->context(), result, env->isolate()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/encoding_binding.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class BindingData : public SnapshotableObject {
static void EncodeInto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EncodeUtf8String(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DecodeUTF8(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DecodeLatin1(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DecodeWindows1252(
const v8::FunctionCallbackInfo<v8::Value>& args);

static void ToASCII(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ToUnicode(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand Down
47 changes: 26 additions & 21 deletions test/parallel/test-internal-encoding-binding.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,46 @@ const assert = require('node:assert');
const { internalBinding } = require('internal/test/binding');
const binding = internalBinding('encoding_binding');

// Windows-1252 specific tests
{
// Valid input
const buf = Uint8Array.from([0xC1, 0xE9, 0xF3]);
assert.strictEqual(binding.decodeLatin1(buf, false, false), 'Áéó');
// Test Windows-1252 special characters in 128-159 range
// These differ from Latin-1
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x80), false, false), '€');
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x82), false, false), '‚');
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x83), false, false), 'ƒ');
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x9F), false, false), 'Ÿ');
}

{
// Empty input
const buf = Uint8Array.from([]);
assert.strictEqual(binding.decodeLatin1(buf, false, false), '');
// Test Windows-1252 characters outside 128-159 range (same as Latin-1)
const buf = Uint8Array.from([0xC1, 0xE9, 0xF3]);
assert.strictEqual(binding.decodeWindows1252(buf, false, false), 'Áéó');
}

{
// Invalid input, but Latin1 has no invalid chars and should never throw.
const buf = new TextEncoder().encode('Invalid Latin1 🧑‍🧑‍🧒‍🧒');
assert.strictEqual(
binding.decodeLatin1(buf, false, false),
'Invalid Latin1 ð\x9F§\x91â\x80\x8Dð\x9F§\x91â\x80\x8Dð\x9F§\x92â\x80\x8Dð\x9F§\x92'
);
// Empty input
const buf = Uint8Array.from([]);
assert.strictEqual(binding.decodeWindows1252(buf, false, false), '');
}

// Windows-1252 specific tests
{
// IgnoreBOM with BOM
const buf = Uint8Array.from([0xFE, 0xFF, 0xC1, 0xE9, 0xF3]);
assert.strictEqual(binding.decodeLatin1(buf, true, false), 'þÿÁéó');
// Test Windows-1252 special characters in 128-159 range
// These differ from Latin-1
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x80), false, false), '€');
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x82), false, false), '‚');
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x83), false, false), 'ƒ');
assert.strictEqual(binding.decodeWindows1252(Uint8Array.of(0x9F), false, false), 'Ÿ');
}

{
// Fatal and InvalidInput, but Latin1 has no invalid chars and should never throw.
const buf = Uint8Array.from([0xFF, 0xFF, 0xFF]);
assert.strictEqual(binding.decodeLatin1(buf, false, true), 'ÿÿÿ');
// Test Windows-1252 characters outside 128-159 range (same as Latin-1)
const buf = Uint8Array.from([0xC1, 0xE9, 0xF3]);
assert.strictEqual(binding.decodeWindows1252(buf, false, false), 'Áéó');
}

{
// IgnoreBOM and Fatal, but Latin1 has no invalid chars and should never throw.
const buf = Uint8Array.from([0xFE, 0xFF, 0xC1, 0xE9, 0xF3]);
assert.strictEqual(binding.decodeLatin1(buf, true, true), 'þÿÁéó');
// Empty input
const buf = Uint8Array.from([]);
assert.strictEqual(binding.decodeWindows1252(buf, false, false), '');
}
Loading
Loading