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
Next Next commit
buffer: introduce File
  • Loading branch information
KhafraDev committed Nov 1, 2022
commit f8940e9a81a0af8bfb099266ef9786defed843f0
46 changes: 46 additions & 0 deletions doc/api/buffer.md
Original file line number Diff line number Diff line change
Expand Up @@ -5013,6 +5013,51 @@ changes:

See [`Buffer.from(string[, encoding])`][`Buffer.from(string)`].

## Class: `File`
<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

* Extends: {Blob}

A [`File`][] provides information about files.

### `new buffer.File(sources, fileName[, options])`
<!-- YAML
added: REPLACEME
-->

* `sources` {string\[]|ArrayBuffer\[]|TypedArray\[]|DataView\[]|Blob\[]|File\[]}
An array of string, {ArrayBuffer}, {TypedArray}, {DataView}, {File}, or {Blob}
objects, or any mix of such objects, that will be stored within the `File`.
* `fileName` {string} The name of the file.
* `options` {Object}
* `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('node:os').EOL`.
* `type` {string} The File content-type.
* `lastModified` {number} The last modified date of the file.
Comment thread
KhafraDev marked this conversation as resolved.

### `file.name`
<!-- YAML
added: REPLACEME
-->

* Type: {string}

The name of the `File`.

### `file.lastModified`
<!-- YAML
added: REPLACEME
-->

* Type: {number}

The last modified date of the `File`.

## `node:buffer` module APIs

While, the `Buffer` object is available as a global, there are additional
Expand Down Expand Up @@ -5359,6 +5404,7 @@ introducing security vulnerabilities into an application.
[`ERR_INVALID_ARG_VALUE`]: errors.md#err_invalid_arg_value
[`ERR_INVALID_BUFFER_SIZE`]: errors.md#err_invalid_buffer_size
[`ERR_OUT_OF_RANGE`]: errors.md#err_out_of_range
[`File`]: https://developer.mozilla.org/en-US/docs/Web/API/File
[`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
[`String.prototype.indexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf
Expand Down
5 changes: 5 additions & 0 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ const {
resolveObjectURL,
} = require('internal/blob');

const {
File,
} = require('internal/file');

FastBuffer.prototype.constructor = Buffer;
Buffer.prototype = FastBuffer.prototype;
addBufferPrototypeMethods(Buffer.prototype);
Expand Down Expand Up @@ -1320,6 +1324,7 @@ function atob(input) {

module.exports = {
Blob,
File,
resolveObjectURL,
Buffer,
SlowBuffer,
Expand Down
117 changes: 117 additions & 0 deletions lib/internal/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use strict';

const {
DateNow,
NumberIsNaN,
ObjectDefineProperties,
ObjectPrototypeHasOwnProperty,
Symbol,
SymbolToStringTag,
} = primordials;

const {
Blob,
} = require('internal/blob');

const {
customInspectSymbol: kInspect,
emitExperimentalWarning,
kEnumerableProperty,
kEmptyObject,
toUSVString,
} = require('internal/util');

const {
codes: {
ERR_INVALID_THIS,
ERR_MISSING_ARGS,
},
} = require('internal/errors');

const {
inspect,
} = require('internal/util/inspect');

const kState = Symbol('kState');

function isFile(object) {
return object?.[kState] !== undefined || object instanceof Blob;
Comment thread
KhafraDev marked this conversation as resolved.
Outdated
}

class File extends Blob {
constructor(fileBits, fileName, options = kEmptyObject) {
emitExperimentalWarning('buffer.File');

if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('fileBits', 'fileName');
}

super(fileBits, options);
Comment thread
anonrig marked this conversation as resolved.

let lastModified;

if (ObjectPrototypeHasOwnProperty(options, 'lastModified')) {
Comment thread
KhafraDev marked this conversation as resolved.
Outdated
// Using Number(...) will not throw an error for bigints.
lastModified = +options.lastModified;
Comment thread
KhafraDev marked this conversation as resolved.
Outdated

if (NumberIsNaN(lastModified)) {
lastModified = 0;
}
} else {
lastModified = DateNow();
}

this[kState] = {
name: toUSVString(fileName),
lastModified: lastModified,
Comment thread
KhafraDev marked this conversation as resolved.
Outdated
};
}

get name() {
if (!isFile(this)) {
throw new ERR_INVALID_THIS('File');
}
Comment thread
KhafraDev marked this conversation as resolved.
Outdated

return this[kState].name;
}

get lastModified() {
if (!isFile(this)) {
throw new ERR_INVALID_THIS('File');
}

return this[kState].lastModified;
}

[kInspect](depth, options) {
if (depth < 0) {
return this;
}

const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};

return `File ${inspect({
size: this.size,
type: this.type,
name: this.name,
lastModified: this.lastModified,
}, opts)}`;
}
}

ObjectDefineProperties(File.prototype, {
name: kEnumerableProperty,
lastModified: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'File',
}
});

module.exports = {
File,
};
122 changes: 122 additions & 0 deletions test/parallel/test-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use strict';

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

{
// ensure File extends Blob
assert.deepStrictEqual(Object.getPrototypeOf(File.prototype), Blob.prototype);
}

{
assert.throws(() => new File(), TypeError);
assert.throws(() => new File([]), TypeError);
}

{
const properties = ['name', 'lastModified'];

for (const prop of properties) {
const desc = Object.getOwnPropertyDescriptor(File.prototype, prop);
assert.notStrictEqual(desc, undefined);
// Ensure these properties are getters.
assert.strictEqual(desc.get?.name, `get ${prop}`);
assert.strictEqual(desc.set, undefined);
assert.strictEqual(desc.enumerable, true);
assert.strictEqual(desc.configurable, true);
}
}

{
const file = new File([], '');
assert.strictEqual(file[Symbol.toStringTag], 'File');
assert.strictEqual(File.prototype[Symbol.toStringTag], 'File');
}

{
assert.throws(() => File.prototype.name, TypeError);
assert.throws(() => File.prototype.lastModified, TypeError);
}

{
const keys = Object.keys(File.prototype).sort();
assert.deepStrictEqual(keys, ['lastModified', 'name']);
}

{
const file = new File([], 'dummy.txt.exe');
assert.strictEqual(file.name, 'dummy.txt.exe');
assert.strictEqual(file.size, 0);
assert.strictEqual(typeof file.lastModified, 'number');
assert(file.lastModified <= Date.now());
}

{
const emptyFile = new File([], 'empty.txt');
const blob = new Blob(['hello world']);

emptyFile.text.call(blob).then(common.mustCall((text) => {
assert.strictEqual(text, 'hello world');
}));
}

{
const toPrimitive = {
[Symbol.toPrimitive]() {
return 'NaN';
}
};

const invalidLastModified = [
null,
undefined,
'string',
false,
toPrimitive,
];

for (const lastModified of invalidLastModified) {
const file = new File([], '', { lastModified });
assert.strictEqual(file.lastModified, 0);
}
}

{
const toPrimitive = {
[Symbol.toPrimitive]() {
throw new TypeError('boom');
}
};

const throwValues = [
BigInt(3n),
toPrimitive,
];

for (const lastModified of throwValues) {
assert.throws(() => new File([], '', { lastModified }), TypeError);
}
}

{
const valid = [
{
[Symbol.toPrimitive]() {
return 10;
}
},
new Number(10),
10,
];

for (const lastModified of valid) {
assert.strictEqual(new File([], '', { lastModified }).lastModified, 10);
}
}

{
const file = new File([], '');
assert(inspect(file).startsWith('File { size: 0, type: \'\', name: \'\', lastModified:'));
}