Skip to content
Closed
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: 11 additions & 3 deletions doc/api/buffer.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,14 +459,20 @@ multiple worker threads.
### `new buffer.Blob([sources[, options]])`
<!-- YAML
added: v15.7.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/39708
description: Added the standard `endings` option to replace line-endings,
and removed the non-standard `encoding` option.
-->

* `sources` {string[]|ArrayBuffer[]|TypedArray[]|DataView[]|Blob[]} An array
of string, {ArrayBuffer}, {TypedArray}, {DataView}, or {Blob} objects, or
any mix of such objects, that will be stored within the `Blob`.
* `options` {Object}
* `encoding` {string} The character encoding to use for string sources.
**Default:** `'utf8'`.
* `endings` {string} One of either `'transparent'` or `'native'`. When set
to `'native'`, line endings in string source parts will be converted to
the platform native line-ending as specified by `require('os').EOL`.
* `type` {string} The Blob content-type. The intent is for `type` to convey
the MIME media type of the data, however no validation of the type format
is performed.
Expand All @@ -476,7 +482,9 @@ Creates a new `Blob` object containing a concatenation of the given sources.
{ArrayBuffer}, {TypedArray}, {DataView}, and {Buffer} sources are copied into
the 'Blob' and can therefore be safely modified after the 'Blob' is created.

String sources are also copied into the `Blob`.
String sources are encoded as UTF-8 byte sequences and copied into the Blob.
Unmatched surrogate pairs within each string part will be replaced by Unicode
U+FFFD replacement characters.

### `blob.arrayBuffer()`
<!-- YAML
Expand Down
52 changes: 40 additions & 12 deletions lib/internal/blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
PromiseReject,
SafePromisePrototypeFinally,
ReflectConstruct,
RegExpPrototypeSymbolReplace,
RegExpPrototypeTest,
StringPrototypeToLowerCase,
StringPrototypeSplit,
Expand All @@ -24,7 +25,10 @@ const {
getDataObject,
} = internalBinding('blob');

const { TextDecoder } = require('internal/encoding');
const {
TextDecoder,
TextEncoder,
} = require('internal/encoding');

const {
makeTransferable,
Expand All @@ -48,6 +52,7 @@ const {
AbortError,
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_THIS,
ERR_BUFFER_TOO_LARGE,
}
Expand All @@ -68,10 +73,11 @@ const kMaxChunkSize = 65536;

const disallowedTypeCharacters = /[^\u{0020}-\u{007E}]/u;

let Buffer;
let ReadableStream;
let URL;
let EOL;

const enc = new TextEncoder();

// Yes, lazy loading is annoying but because of circular
// references between the url, internal/blob, and buffer
Expand All @@ -82,29 +88,35 @@ function lazyURL(id) {
return new URL(id);
}

function lazyBuffer() {
Buffer ??= require('buffer').Buffer;
return Buffer;
}

function lazyReadableStream(options) {
ReadableStream ??=
require('internal/webstreams/readablestream').ReadableStream;
return new ReadableStream(options);
}

// TODO(@jasnell): This is annoying but this has to be lazy because
// requiring the 'os' module too early causes building Node.js to
// fail with an unknown reference failure.
function lazyEOL() {
EOL ??= require('os').EOL;
return EOL;
}

function isBlob(object) {
return object?.[kHandle] !== undefined;
}

function getSource(source, encoding) {
function getSource(source, endings) {
if (isBlob(source))
return [source.size, source[kHandle]];

if (isAnyArrayBuffer(source)) {
source = new Uint8Array(source);
} else if (!isArrayBufferView(source)) {
source = lazyBuffer().from(`${source}`, encoding);
source = `${source}`;
if (endings === 'native')
source = RegExpPrototypeSymbolReplace(/\n|\r\n/g, source, lazyEOL());
source = enc.encode(source);
}

// We copy into a new Uint8Array because the underlying
Expand All @@ -116,6 +128,16 @@ function getSource(source, encoding) {
}

class Blob {
/**
* @typedef {string|ArrayBuffer|ArrayBufferView|Blob} SourcePart
*
* @param {SourcePart[]} [sources]
* @param {{
* endings? : string,
* type? : string,
* }} [options]
* @returns
*/
constructor(sources = [], options = {}) {
emitExperimentalWarning('buffer.Blob');
if (sources === null ||
Expand All @@ -124,12 +146,18 @@ class Blob {
throw new ERR_INVALID_ARG_TYPE('sources', 'Iterable', sources);
}
validateObject(options, 'options');
const { encoding = 'utf8' } = options;
let { type = '' } = options;
let {
type = '',
endings = 'transparent',
} = options;

endings = `${endings}`;
if (endings !== 'transparent' && endings !== 'native')
throw new ERR_INVALID_ARG_VALUE('options.endings', endings);

let length = 0;
const sources_ = ArrayFrom(sources, (source) => {
const { 0: len, 1: src } = getSource(source, encoding);
const { 0: len, 1: src } = getSource(source, endings);
length += len;
return src;
});
Expand Down
22 changes: 13 additions & 9 deletions test/parallel/test-blob.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Flags: --no-warnings
'use strict';

const common = require('../common');
const assert = require('assert');
const { Blob } = require('buffer');
const { inspect } = require('util');
const { EOL } = require('os');

{
const b = new Blob();
Expand Down Expand Up @@ -45,15 +47,6 @@ assert.throws(() => new Blob({}), {
assert.strictEqual(new Blob([], { type: {} }).type, '[object object]');
}

{
const b = new Blob(['616263'], { encoding: 'hex', type: 'foo' });
assert.strictEqual(b.size, 3);
assert.strictEqual(b.type, 'foo');
b.text().then(common.mustCall((text) => {
assert.strictEqual(text, 'abc');
}));
}

{
const b = new Blob([Buffer.from('abc')]);
assert.strictEqual(b.size, 3);
Expand Down Expand Up @@ -216,3 +209,14 @@ assert.throws(() => new Blob({}), {
res = await reader.read();
assert(res.done);
})().then(common.mustCall());

{
const b = new Blob(['hello\n'], { endings: 'native' });
assert.strictEqual(b.size, EOL.length + 5);

[1, {}, 'foo'].forEach((endings) => {
assert.throws(() => new Blob([], { endings }), {
code: 'ERR_INVALID_ARG_VALUE',
});
});
}