From 09fc33171b408a4ab426df4ae70f0d080dc0a52d Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Thu, 25 Jun 2026 00:51:29 +0900 Subject: [PATCH] buffer: normalize lone "\r" in Blob native line endings `new Blob(parts, { endings: 'native' })` failed to normalize a standalone carriage return. The replacement regex only matched `\n` and `\r\n`, so a lone `\r` (and runs such as `\r\r`) were left untouched instead of being converted to the platform's native line ending. The WHATWG "convert line endings to native" algorithm requires `\r`, `\n`, and `\r\n` to all be normalized. Match `\r\n` before a lone `\r` so that CRLF collapses to a single native ending. Refs: https://w3c.github.io/FileAPI/#convert-line-endings-to-native Signed-off-by: Daijiro Wachi --- lib/internal/blob.js | 2 +- test/parallel/test-blob.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/internal/blob.js b/lib/internal/blob.js index dfaa24b79db5ab..fca2813b96a578 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -122,7 +122,7 @@ function getSource(source, endings) { source = new Uint8Array(source); } else if (!isArrayBufferView(source)) { if (endings === 'native') - source = RegExpPrototypeSymbolReplace(/\n|\r\n/g, source, EOL); + source = RegExpPrototypeSymbolReplace(/\r\n|\r|\n/g, source, EOL); source = enc.encode(source); } diff --git a/test/parallel/test-blob.js b/test/parallel/test-blob.js index 19ca24b0319f13..b061fa944f3cec 100644 --- a/test/parallel/test-blob.js +++ b/test/parallel/test-blob.js @@ -403,6 +403,24 @@ assert.throws(() => new Blob({}), { const b = new Blob(['hello\n'], { endings: 'native' }); assert.strictEqual(b.size, EOL.length + 5); + // The WHATWG "convert line endings to native" algorithm normalizes every + // standalone "\r", "\n", and "\r\n" sequence to the native line ending. + // Refs: https://w3c.github.io/FileAPI/#convert-line-endings-to-native + (async () => { + const cases = [ + ['a\rb', `a${EOL}b`], + ['a\nb', `a${EOL}b`], + ['a\r\nb', `a${EOL}b`], + ['a\r\rb', `a${EOL}${EOL}b`], + ['a\n\rb', `a${EOL}${EOL}b`], + ['\r\n\r', `${EOL}${EOL}`], + ]; + for (const [input, expected] of cases) { + const blob = new Blob([input], { endings: 'native' }); + assert.strictEqual(await blob.text(), expected); + } + })().then(common.mustCall()); + [1, {}, 'foo'].forEach((endings) => { assert.throws(() => new Blob([], { endings }), { code: 'ERR_INVALID_ARG_VALUE',