Skip to content
Open
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
38 changes: 36 additions & 2 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,40 @@ function lazyLoadUtf8Stream() {
Utf8Stream ??= require('internal/streams/fast-utf8-stream');
}

// Check if encoding is a UTF-8 variant that should use the fast path.
// This covers all valid UTF-8 encoding names: utf8, utf-8, UTF8, UTF-8,
// and case-insensitive variations like Utf8, Utf-8, etc.
function isUtf8Encoding(enc) {
if (enc === 'utf8' || enc === 'utf-8') return true;
if (enc.length === 4) {
// Check for UTF8 (case-insensitive)
const c1 = StringPrototypeCharCodeAt(enc, 0);
const c2 = StringPrototypeCharCodeAt(enc, 1);
const c3 = StringPrototypeCharCodeAt(enc, 2);
const c4 = StringPrototypeCharCodeAt(enc, 3);
// U/u, T/t, F/f, 8
return (c1 === 85 || c1 === 117) && // U/u
(c2 === 84 || c2 === 116) && // T/t
(c3 === 70 || c3 === 102) && // F/f
c4 === 56; // 8
}
if (enc.length === 5) {
// Check for UTF-8 (case-insensitive)
const c1 = StringPrototypeCharCodeAt(enc, 0);
const c2 = StringPrototypeCharCodeAt(enc, 1);
const c3 = StringPrototypeCharCodeAt(enc, 2);
const c4 = StringPrototypeCharCodeAt(enc, 3);
const c5 = StringPrototypeCharCodeAt(enc, 4);
// U/u, T/t, F/f, -, 8
return (c1 === 85 || c1 === 117) && // U/u
(c2 === 84 || c2 === 116) && // T/t
(c3 === 70 || c3 === 102) && // F/f
c4 === 45 && // -
c5 === 56; // 8
}
return false;
}

// Ensure that callbacks run in the global context. Only use this function
// for callbacks that are passed to the binding layer, callbacks that are
// invoked from JS already run in the proper scope.
Expand Down Expand Up @@ -429,7 +463,7 @@ function tryReadSync(fd, isUserFd, buffer, pos, len) {
function readFileSync(path, options) {
options = getOptions(options, { flag: 'r' });

if (options.encoding === 'utf8' || options.encoding === 'utf-8') {
if (isUtf8Encoding(options.encoding)) {
if (!isInt32(path)) {
path = getValidatedPath(path);
}
Expand Down Expand Up @@ -2390,7 +2424,7 @@ function writeFileSync(path, data, options) {
const flag = options.flag || 'w';

// C++ fast path for string data and UTF8 encoding
if (typeof data === 'string' && (options.encoding === 'utf8' || options.encoding === 'utf-8')) {
if (typeof data === 'string' && isUtf8Encoding(options.encoding)) {
if (!isInt32(path)) {
path = getValidatedPath(path);
}
Expand Down
74 changes: 74 additions & 0 deletions test/parallel/test-fs-utf8-encoding-case-insensitive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

// Test that fs.readFileSync and fs.writeFileSync accept all valid
// UTF-8 encoding names (case-insensitive) for the fast path.
// Refs: https://github.com/nodejs/node/issues/49888

const common = require('../common');
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const tmpdir = require('../common/tmpdir');

tmpdir.refresh();

const testFile = path.join(tmpdir.path, 'test-utf8-encoding.txt');
const testContent = 'Hello, World! 你好,世界!';

// All valid UTF-8 encoding variants that should use the fast path
const utf8Variants = [
'utf8',
'utf-8',
'UTF8',
'UTF-8',
'Utf8',
'Utf-8',
'uTf8',
'uTf-8',
'utF8',
'utF-8',
'UTf8',
'UTf-8',
'uTF8',
'uTF-8',
];

// Test writeFileSync with all UTF-8 variants
for (const encoding of utf8Variants) {
const testPath = path.join(tmpdir.path, `test-write-${encoding}.txt`);

// Should not throw and should write the file correctly
fs.writeFileSync(testPath, testContent, { encoding });

// Verify the file was written correctly
const content = fs.readFileSync(testPath, 'utf8');
assert.strictEqual(content, testContent,
`writeFileSync should work with encoding "${encoding}"`);
}

// Test readFileSync with all UTF-8 variants
for (const encoding of utf8Variants) {
const testPath = path.join(tmpdir.path, `test-read-${encoding}.txt`);

// Create a test file
fs.writeFileSync(testPath, testContent, 'utf8');

// Should read the file correctly with any UTF-8 variant
const content = fs.readFileSync(testPath, { encoding });
assert.strictEqual(content, testContent,
`readFileSync should work with encoding "${encoding}"`);
}

// Test that non-UTF-8 encodings still work correctly
const otherEncodings = ['ascii', 'base64', 'hex', 'latin1'];
for (const encoding of otherEncodings) {
const testPath = path.join(tmpdir.path, `test-${encoding}.txt`);

// These should not use the UTF-8 fast path but should still work
fs.writeFileSync(testPath, testContent, { encoding });
const content = fs.readFileSync(testPath, { encoding });
assert.strictEqual(content, testContent,
`Should work with encoding "${encoding}"`);
}

console.log('All tests passed!');
49 changes: 49 additions & 0 deletions test/parallel/test-fs-utf8-fast-path-casing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

// This test ensures that fs.readFileSync and fs.writeFileSync
// accept all valid UTF8 encoding variants (utf8, utf-8, UTF8, UTF-8).
// Refs: https://github.com/nodejs/node/issues/49888

const common = require('../common');
const tmpdir = require('../../test/common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const path = require('path');

tmpdir.refresh();

const testContent = 'Hello, World! 你好,世界!';
const encodings = ['utf8', 'utf-8', 'UTF8', 'UTF-8'];

// Test writeFileSync and readFileSync with different UTF8 variants
for (const encoding of encodings) {
const testFile = tmpdir.resolve(`test_utf8_fast_path_${encoding}.txt`);

// Test writeFileSync
fs.writeFileSync(testFile, testContent, { encoding });

// Test readFileSync
const result = fs.readFileSync(testFile, { encoding });
assert.strictEqual(result, testContent,
`readFileSync should return correct content for encoding "${encoding}"`);
}

// Test with file descriptor
for (const encoding of encodings) {
const testFile = tmpdir.resolve(`test_utf8_fast_path_fd_${encoding}.txt`);

// Write with fd
const fdWrite = fs.openSync(testFile, 'w');
fs.writeFileSync(fdWrite, testContent, { encoding });
fs.closeSync(fdWrite);

// Read with fd
const fdRead = fs.openSync(testFile, 'r');
const result = fs.readFileSync(fdRead, { encoding });
fs.closeSync(fdRead);

assert.strictEqual(result, testContent,
`readFileSync with fd should return correct content for encoding "${encoding}"`);
}

console.log('All UTF8 fast path casing tests passed!');