buffer: introduce File
PR-URL: https://github.com/nodejs/node/pull/45139 Fixes: https://github.com/nodejs/node/issues/39015 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
parent
e9a9e1ea5b
commit
916af4ef2d
34
benchmark/blob/file.js
Normal file
34
benchmark/blob/file.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
const common = require('../common.js');
|
||||
const { File } = require('buffer');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
bytes: [128, 1024, 1024 ** 2],
|
||||
n: [1e6],
|
||||
operation: ['text', 'arrayBuffer']
|
||||
});
|
||||
|
||||
const options = {
|
||||
lastModified: Date.now() - 1e6,
|
||||
};
|
||||
|
||||
async function run(n, bytes, operation) {
|
||||
const buff = Buffer.allocUnsafe(bytes);
|
||||
const source = new File(buff, 'dummy.txt', options);
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++) {
|
||||
switch (operation) {
|
||||
case 'text':
|
||||
await source.text();
|
||||
break;
|
||||
case 'arrayBuffer':
|
||||
await source.arrayBuffer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
bench.end(n);
|
||||
}
|
||||
|
||||
function main(conf) {
|
||||
run(conf.n, conf.bytes, conf.operation).catch(console.log);
|
||||
}
|
@ -5013,6 +5013,56 @@ 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.
|
||||
**Default:** `Date.now()`.
|
||||
|
||||
### `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
|
||||
@ -5359,6 +5409,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
|
||||
|
@ -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);
|
||||
@ -1320,6 +1324,7 @@ function atob(input) {
|
||||
|
||||
module.exports = {
|
||||
Blob,
|
||||
File,
|
||||
resolveObjectURL,
|
||||
Buffer,
|
||||
SlowBuffer,
|
||||
|
113
lib/internal/file.js
Normal file
113
lib/internal/file.js
Normal file
@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
DateNow,
|
||||
NumberIsNaN,
|
||||
ObjectDefineProperties,
|
||||
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');
|
||||
|
||||
class File extends Blob {
|
||||
/** @type {string} */
|
||||
#name;
|
||||
|
||||
/** @type {number} */
|
||||
#lastModified;
|
||||
|
||||
constructor(fileBits, fileName, options = kEmptyObject) {
|
||||
emitExperimentalWarning('buffer.File');
|
||||
|
||||
if (arguments.length < 2) {
|
||||
throw new ERR_MISSING_ARGS('fileBits', 'fileName');
|
||||
}
|
||||
|
||||
super(fileBits, options);
|
||||
|
||||
let { lastModified } = options ?? kEmptyObject;
|
||||
|
||||
if (lastModified !== undefined) {
|
||||
// Using Number(...) will not throw an error for bigints.
|
||||
lastModified = +lastModified;
|
||||
|
||||
if (NumberIsNaN(lastModified)) {
|
||||
lastModified = 0;
|
||||
}
|
||||
} else {
|
||||
lastModified = DateNow();
|
||||
}
|
||||
|
||||
this.#name = toUSVString(fileName);
|
||||
this.#lastModified = lastModified;
|
||||
}
|
||||
|
||||
get name() {
|
||||
if (!this || !(#name in this)) {
|
||||
throw new ERR_INVALID_THIS('File');
|
||||
}
|
||||
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
get lastModified() {
|
||||
if (!this || !(#name in this)) {
|
||||
throw new ERR_INVALID_THIS('File');
|
||||
}
|
||||
|
||||
return this.#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,
|
||||
};
|
155
test/fixtures/wpt/FileAPI/file/File-constructor.any.js
vendored
Normal file
155
test/fixtures/wpt/FileAPI/file/File-constructor.any.js
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
// META: title=File constructor
|
||||
|
||||
const to_string_obj = { toString: () => 'a string' };
|
||||
const to_string_throws = { toString: () => { throw new Error('expected'); } };
|
||||
|
||||
test(function() {
|
||||
assert_true("File" in globalThis, "globalThis should have a File property.");
|
||||
}, "File interface object exists");
|
||||
|
||||
test(t => {
|
||||
assert_throws_js(TypeError, () => new File(),
|
||||
'Bits argument is required');
|
||||
assert_throws_js(TypeError, () => new File([]),
|
||||
'Name argument is required');
|
||||
}, 'Required arguments');
|
||||
|
||||
function test_first_argument(arg1, expectedSize, testName) {
|
||||
test(function() {
|
||||
var file = new File(arg1, "dummy");
|
||||
assert_true(file instanceof File);
|
||||
assert_equals(file.name, "dummy");
|
||||
assert_equals(file.size, expectedSize);
|
||||
assert_equals(file.type, "");
|
||||
// assert_false(file.isClosed); XXX: File.isClosed doesn't seem to be implemented
|
||||
assert_not_equals(file.lastModified, "");
|
||||
}, testName);
|
||||
}
|
||||
|
||||
test_first_argument([], 0, "empty fileBits");
|
||||
test_first_argument(["bits"], 4, "DOMString fileBits");
|
||||
test_first_argument(["𝓽𝓮𝔁𝓽"], 16, "Unicode DOMString fileBits");
|
||||
test_first_argument([new String('string object')], 13, "String object fileBits");
|
||||
test_first_argument([new Blob()], 0, "Empty Blob fileBits");
|
||||
test_first_argument([new Blob(["bits"])], 4, "Blob fileBits");
|
||||
test_first_argument([new File([], 'world.txt')], 0, "Empty File fileBits");
|
||||
test_first_argument([new File(["bits"], 'world.txt')], 4, "File fileBits");
|
||||
test_first_argument([new ArrayBuffer(8)], 8, "ArrayBuffer fileBits");
|
||||
test_first_argument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4, "Typed array fileBits");
|
||||
test_first_argument(["bits", new Blob(["bits"]), new Blob(), new Uint8Array([0x50, 0x41]),
|
||||
new Uint16Array([0x5353]), new Uint32Array([0x53534150])], 16, "Various fileBits");
|
||||
test_first_argument([12], 2, "Number in fileBits");
|
||||
test_first_argument([[1,2,3]], 5, "Array in fileBits");
|
||||
test_first_argument([{}], 15, "Object in fileBits"); // "[object Object]"
|
||||
if (globalThis.document !== undefined) {
|
||||
test_first_argument([document.body], 24, "HTMLBodyElement in fileBits"); // "[object HTMLBodyElement]"
|
||||
}
|
||||
test_first_argument([to_string_obj], 8, "Object with toString in fileBits");
|
||||
test_first_argument({[Symbol.iterator]() {
|
||||
let i = 0;
|
||||
return {next: () => [
|
||||
{done:false, value:'ab'},
|
||||
{done:false, value:'cde'},
|
||||
{done:true}
|
||||
][i++]};
|
||||
}}, 5, 'Custom @@iterator');
|
||||
|
||||
[
|
||||
'hello',
|
||||
0,
|
||||
null
|
||||
].forEach(arg => {
|
||||
test(t => {
|
||||
assert_throws_js(TypeError, () => new File(arg, 'world.html'),
|
||||
'Constructor should throw for invalid bits argument');
|
||||
}, `Invalid bits argument: ${JSON.stringify(arg)}`);
|
||||
});
|
||||
|
||||
test(t => {
|
||||
assert_throws_js(Error, () => new File([to_string_throws], 'name.txt'),
|
||||
'Constructor should propagate exceptions');
|
||||
}, 'Bits argument: object that throws');
|
||||
|
||||
|
||||
function test_second_argument(arg2, expectedFileName, testName) {
|
||||
test(function() {
|
||||
var file = new File(["bits"], arg2);
|
||||
assert_true(file instanceof File);
|
||||
assert_equals(file.name, expectedFileName);
|
||||
}, testName);
|
||||
}
|
||||
|
||||
test_second_argument("dummy", "dummy", "Using fileName");
|
||||
test_second_argument("dummy/foo", "dummy/foo",
|
||||
"No replacement when using special character in fileName");
|
||||
test_second_argument(null, "null", "Using null fileName");
|
||||
test_second_argument(1, "1", "Using number fileName");
|
||||
test_second_argument('', '', "Using empty string fileName");
|
||||
if (globalThis.document !== undefined) {
|
||||
test_second_argument(document.body, '[object HTMLBodyElement]', "Using object fileName");
|
||||
}
|
||||
|
||||
// testing the third argument
|
||||
[
|
||||
{type: 'text/plain', expected: 'text/plain'},
|
||||
{type: 'text/plain;charset=UTF-8', expected: 'text/plain;charset=utf-8'},
|
||||
{type: 'TEXT/PLAIN', expected: 'text/plain'},
|
||||
{type: '𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫', expected: ''},
|
||||
{type: 'ascii/nonprintable\u001F', expected: ''},
|
||||
{type: 'ascii/nonprintable\u007F', expected: ''},
|
||||
{type: 'nonascii\u00EE', expected: ''},
|
||||
{type: 'nonascii\u1234', expected: ''},
|
||||
{type: 'nonparsable', expected: 'nonparsable'}
|
||||
].forEach(testCase => {
|
||||
test(t => {
|
||||
var file = new File(["bits"], "dummy", { type: testCase.type});
|
||||
assert_true(file instanceof File);
|
||||
assert_equals(file.type, testCase.expected);
|
||||
}, `Using type in File constructor: ${testCase.type}`);
|
||||
});
|
||||
test(function() {
|
||||
var file = new File(["bits"], "dummy", { lastModified: 42 });
|
||||
assert_true(file instanceof File);
|
||||
assert_equals(file.lastModified, 42);
|
||||
}, "Using lastModified");
|
||||
test(function() {
|
||||
var file = new File(["bits"], "dummy", { name: "foo" });
|
||||
assert_true(file instanceof File);
|
||||
assert_equals(file.name, "dummy");
|
||||
}, "Misusing name");
|
||||
test(function() {
|
||||
var file = new File(["bits"], "dummy", { unknownKey: "value" });
|
||||
assert_true(file instanceof File);
|
||||
assert_equals(file.name, "dummy");
|
||||
}, "Unknown properties are ignored");
|
||||
|
||||
[
|
||||
123,
|
||||
123.4,
|
||||
true,
|
||||
'abc'
|
||||
].forEach(arg => {
|
||||
test(t => {
|
||||
assert_throws_js(TypeError, () => new File(['bits'], 'name.txt', arg),
|
||||
'Constructor should throw for invalid property bag type');
|
||||
}, `Invalid property bag: ${JSON.stringify(arg)}`);
|
||||
});
|
||||
|
||||
[
|
||||
null,
|
||||
undefined,
|
||||
[1,2,3],
|
||||
/regex/,
|
||||
function() {}
|
||||
].forEach(arg => {
|
||||
test(t => {
|
||||
assert_equals(new File(['bits'], 'name.txt', arg).size, 4,
|
||||
'Constructor should accept object-ish property bag type');
|
||||
}, `Unusual but valid property bag: ${arg}`);
|
||||
});
|
||||
|
||||
test(t => {
|
||||
assert_throws_js(Error,
|
||||
() => new File(['bits'], 'name.txt', {type: to_string_throws}),
|
||||
'Constructor should propagate exceptions');
|
||||
}, 'Property bag propagates exceptions');
|
69
test/fixtures/wpt/FileAPI/file/send-file-formdata-controls.any.js
vendored
Normal file
69
test/fixtures/wpt/FileAPI/file/send-file-formdata-controls.any.js
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
// META: title=FormData: FormData: Upload files named using controls
|
||||
// META: script=../support/send-file-formdata-helper.js
|
||||
"use strict";
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-NUL-[\0].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-BS-[\b].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-VT-[\v].txt",
|
||||
});
|
||||
|
||||
// These have characters that undergo processing in name=,
|
||||
// filename=, and/or value; formDataPostFileUploadTest postprocesses
|
||||
// expectedEncodedBaseName for these internally.
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-LF-[\n].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-LF-CR-[\n\r].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-CR-[\r].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-CR-LF-[\r\n].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-HT-[\t].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-FF-[\f].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-DEL-[\x7F].txt",
|
||||
});
|
||||
|
||||
// The rest should be passed through unmodified:
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-ESC-[\x1B].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-SPACE-[ ].txt",
|
||||
});
|
144
test/fixtures/wpt/FileAPI/file/send-file-formdata-punctuation.any.js
vendored
Normal file
144
test/fixtures/wpt/FileAPI/file/send-file-formdata-punctuation.any.js
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
// META: title=FormData: FormData: Upload files named using punctuation
|
||||
// META: script=../support/send-file-formdata-helper.js
|
||||
"use strict";
|
||||
|
||||
// These have characters that undergo processing in name=,
|
||||
// filename=, and/or value; formDataPostFileUploadTest postprocesses
|
||||
// expectedEncodedBaseName for these internally.
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-QUOTATION-MARK-[\x22].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: '"file-for-upload-in-form-double-quoted.txt"',
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-REVERSE-SOLIDUS-[\\].txt",
|
||||
});
|
||||
|
||||
// The rest should be passed through unmodified:
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-EXCLAMATION-MARK-[!].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-DOLLAR-SIGN-[$].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-PERCENT-SIGN-[%].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-AMPERSAND-[&].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-APOSTROPHE-['].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-LEFT-PARENTHESIS-[(].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-RIGHT-PARENTHESIS-[)].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-ASTERISK-[*].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-PLUS-SIGN-[+].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-COMMA-[,].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-FULL-STOP-[.].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-SOLIDUS-[/].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-COLON-[:].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-SEMICOLON-[;].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-EQUALS-SIGN-[=].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-QUESTION-MARK-[?].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[]].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-LEFT-CURLY-BRACKET-[{].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-VERTICAL-LINE-[|].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form-TILDE-[~].txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "'file-for-upload-in-form-single-quoted.txt'",
|
||||
});
|
33
test/fixtures/wpt/FileAPI/file/send-file-formdata-utf-8.any.js
vendored
Normal file
33
test/fixtures/wpt/FileAPI/file/send-file-formdata-utf-8.any.js
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
// META: title=FormData: FormData: Upload files in UTF-8 fetch()
|
||||
// META: script=../support/send-file-formdata-helper.js
|
||||
"use strict";
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form.txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "x-user-defined",
|
||||
fileBaseName: "file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "windows-1252",
|
||||
fileBaseName: "file-for-upload-in-form-☺😂.txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "JIS X 0201 and JIS X 0208",
|
||||
fileBaseName: "file-for-upload-in-form-★星★.txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "Unicode",
|
||||
fileBaseName: "file-for-upload-in-form-☺😂.txt",
|
||||
});
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "Unicode",
|
||||
fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
|
||||
});
|
8
test/fixtures/wpt/FileAPI/file/send-file-formdata.any.js
vendored
Normal file
8
test/fixtures/wpt/FileAPI/file/send-file-formdata.any.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// META: title=FormData: Upload ASCII-named file in UTF-8 form
|
||||
// META: script=../support/send-file-formdata-helper.js
|
||||
"use strict";
|
||||
|
||||
formDataPostFileUploadTest({
|
||||
fileNameSource: "ASCII",
|
||||
fileBaseName: "file-for-upload-in-form.txt",
|
||||
});
|
1
test/fixtures/wpt/README.md
vendored
1
test/fixtures/wpt/README.md
vendored
@ -17,6 +17,7 @@ Last update:
|
||||
- encoding: https://github.com/web-platform-tests/wpt/tree/c1b24fce6e/encoding
|
||||
- fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources
|
||||
- FileAPI: https://github.com/web-platform-tests/wpt/tree/3b279420d4/FileAPI
|
||||
- FileAPI/file: https://github.com/web-platform-tests/wpt/tree/c01f637cca/FileAPI/file
|
||||
- hr-time: https://github.com/web-platform-tests/wpt/tree/34cafd797e/hr-time
|
||||
- html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob
|
||||
- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing
|
||||
|
4
test/fixtures/wpt/versions.json
vendored
4
test/fixtures/wpt/versions.json
vendored
@ -27,6 +27,10 @@
|
||||
"commit": "3b279420d40afea32506e823f9ac005448f4f3d8",
|
||||
"path": "FileAPI"
|
||||
},
|
||||
"FileAPI/file": {
|
||||
"commit": "c01f637cca43f0e08ce8e4269121dcd89ccbdd82",
|
||||
"path": "FileAPI/file"
|
||||
},
|
||||
"hr-time": {
|
||||
"commit": "34cafd797e58dad280d20040eee012d49ccfa91f",
|
||||
"path": "hr-time"
|
||||
|
@ -165,6 +165,7 @@ const expectedModules = new Set([
|
||||
'NativeModule internal/worker/js_transferable',
|
||||
'Internal Binding blob',
|
||||
'NativeModule internal/blob',
|
||||
'NativeModule internal/file',
|
||||
'NativeModule async_hooks',
|
||||
'NativeModule net',
|
||||
'NativeModule path',
|
||||
|
155
test/parallel/test-file.js
Normal file
155
test/parallel/test-file.js
Normal file
@ -0,0 +1,155 @@
|
||||
'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,
|
||||
'string',
|
||||
false,
|
||||
toPrimitive,
|
||||
];
|
||||
|
||||
for (const lastModified of invalidLastModified) {
|
||||
const file = new File([], '', { lastModified });
|
||||
assert.strictEqual(file.lastModified, 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const file = new File([], '', { lastModified: undefined });
|
||||
assert.notStrictEqual(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:'));
|
||||
}
|
||||
|
||||
{
|
||||
function MyClass() {}
|
||||
MyClass.prototype.lastModified = 10;
|
||||
|
||||
const file = new File([], '', new MyClass());
|
||||
assert.strictEqual(file.lastModified, 10);
|
||||
}
|
||||
|
||||
{
|
||||
let counter = 0;
|
||||
new File([], '', {
|
||||
get lastModified() {
|
||||
counter++;
|
||||
return 10;
|
||||
}
|
||||
});
|
||||
assert.strictEqual(counter, 1);
|
||||
}
|
||||
|
||||
{
|
||||
const getter = Object.getOwnPropertyDescriptor(File.prototype, 'name').get;
|
||||
assert.throws(
|
||||
() => getter.call(undefined), // eslint-disable-line no-useless-call
|
||||
{
|
||||
code: 'ERR_INVALID_THIS',
|
||||
}
|
||||
);
|
||||
}
|
17
test/wpt/status/FileAPI/file.json
Normal file
17
test/wpt/status/FileAPI/file.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"Worker-read-file-constructor.worker.js": {
|
||||
"skip": true
|
||||
},
|
||||
"send-file-formdata-punctuation.any.js": {
|
||||
"skip": true
|
||||
},
|
||||
"send-file-formdata-utf-8.any.js": {
|
||||
"skip": true
|
||||
},
|
||||
"send-file-formdata.any.js": {
|
||||
"skip": true
|
||||
},
|
||||
"send-file-formdata-controls.any.js": {
|
||||
"skip": true
|
||||
}
|
||||
}
|
13
test/wpt/test-file.js
Normal file
13
test/wpt/test-file.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const { WPTRunner } = require('../common/wpt');
|
||||
|
||||
const runner = new WPTRunner('FileAPI/file');
|
||||
|
||||
runner.setInitScript(`
|
||||
const { File } = require('buffer');
|
||||
globalThis.File = File;
|
||||
`);
|
||||
|
||||
runner.runJsTests();
|
@ -43,6 +43,7 @@ const customTypesMap = {
|
||||
`${jsDocPrefix}Reference/Global_Objects/WebAssembly/Instance`,
|
||||
|
||||
'Blob': 'buffer.html#class-blob',
|
||||
'File': 'buffer.html#class-file',
|
||||
|
||||
'BroadcastChannel':
|
||||
'worker_threads.html#class-broadcastchannel-' +
|
||||
|
Loading…
x
Reference in New Issue
Block a user