Skip to content

Commit 3a7f3d5

Browse files
KhafraDevdanielleadams
authored andcommittedJan 3, 2023
buffer: introduce File
PR-URL: #45139 Fixes: #39015 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 6fafec3 commit 3a7f3d5

16 files changed

+804
-0
lines changed
 

‎benchmark/blob/file.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const { File } = require('buffer');
4+
5+
const bench = common.createBenchmark(main, {
6+
bytes: [128, 1024, 1024 ** 2],
7+
n: [1e6],
8+
operation: ['text', 'arrayBuffer']
9+
});
10+
11+
const options = {
12+
lastModified: Date.now() - 1e6,
13+
};
14+
15+
async function run(n, bytes, operation) {
16+
const buff = Buffer.allocUnsafe(bytes);
17+
const source = new File(buff, 'dummy.txt', options);
18+
bench.start();
19+
for (let i = 0; i < n; i++) {
20+
switch (operation) {
21+
case 'text':
22+
await source.text();
23+
break;
24+
case 'arrayBuffer':
25+
await source.arrayBuffer();
26+
break;
27+
}
28+
}
29+
bench.end(n);
30+
}
31+
32+
function main(conf) {
33+
run(conf.n, conf.bytes, conf.operation).catch(console.log);
34+
}

‎doc/api/buffer.md

+51
Original file line numberDiff line numberDiff line change
@@ -5009,6 +5009,56 @@ changes:
50095009

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

5012+
## Class: `File`
5013+
5014+
<!-- YAML
5015+
added: REPLACEME
5016+
-->
5017+
5018+
> Stability: 1 - Experimental
5019+
5020+
* Extends: {Blob}
5021+
5022+
A [`File`][] provides information about files.
5023+
5024+
### `new buffer.File(sources, fileName[, options])`
5025+
5026+
<!-- YAML
5027+
added: REPLACEME
5028+
-->
5029+
5030+
* `sources` {string\[]|ArrayBuffer\[]|TypedArray\[]|DataView\[]|Blob\[]|File\[]}
5031+
An array of string, {ArrayBuffer}, {TypedArray}, {DataView}, {File}, or {Blob}
5032+
objects, or any mix of such objects, that will be stored within the `File`.
5033+
* `fileName` {string} The name of the file.
5034+
* `options` {Object}
5035+
* `endings` {string} One of either `'transparent'` or `'native'`. When set
5036+
to `'native'`, line endings in string source parts will be converted to
5037+
the platform native line-ending as specified by `require('node:os').EOL`.
5038+
* `type` {string} The File content-type.
5039+
* `lastModified` {number} The last modified date of the file.
5040+
**Default:** `Date.now()`.
5041+
5042+
### `file.name`
5043+
5044+
<!-- YAML
5045+
added: REPLACEME
5046+
-->
5047+
5048+
* Type: {string}
5049+
5050+
The name of the `File`.
5051+
5052+
### `file.lastModified`
5053+
5054+
<!-- YAML
5055+
added: REPLACEME
5056+
-->
5057+
5058+
* Type: {number}
5059+
5060+
The last modified date of the `File`.
5061+
50125062
## `node:buffer` module APIs
50135063

50145064
While, the `Buffer` object is available as a global, there are additional
@@ -5355,6 +5405,7 @@ introducing security vulnerabilities into an application.
53555405
[`ERR_INVALID_ARG_VALUE`]: errors.md#err_invalid_arg_value
53565406
[`ERR_INVALID_BUFFER_SIZE`]: errors.md#err_invalid_buffer_size
53575407
[`ERR_OUT_OF_RANGE`]: errors.md#err_out_of_range
5408+
[`File`]: https://developer.mozilla.org/en-US/docs/Web/API/File
53585409
[`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
53595410
[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
53605411
[`String.prototype.indexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf

‎lib/buffer.js

+5
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ const {
126126
resolveObjectURL,
127127
} = require('internal/blob');
128128

129+
const {
130+
File,
131+
} = require('internal/file');
132+
129133
FastBuffer.prototype.constructor = Buffer;
130134
Buffer.prototype = FastBuffer.prototype;
131135
addBufferPrototypeMethods(Buffer.prototype);
@@ -1320,6 +1324,7 @@ function atob(input) {
13201324

13211325
module.exports = {
13221326
Blob,
1327+
File,
13231328
resolveObjectURL,
13241329
Buffer,
13251330
SlowBuffer,

‎lib/internal/file.js

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use strict';
2+
3+
const {
4+
DateNow,
5+
NumberIsNaN,
6+
ObjectDefineProperties,
7+
SymbolToStringTag,
8+
} = primordials;
9+
10+
const {
11+
Blob,
12+
} = require('internal/blob');
13+
14+
const {
15+
customInspectSymbol: kInspect,
16+
emitExperimentalWarning,
17+
kEnumerableProperty,
18+
kEmptyObject,
19+
toUSVString,
20+
} = require('internal/util');
21+
22+
const {
23+
codes: {
24+
ERR_INVALID_THIS,
25+
ERR_MISSING_ARGS,
26+
},
27+
} = require('internal/errors');
28+
29+
const {
30+
inspect,
31+
} = require('internal/util/inspect');
32+
33+
class File extends Blob {
34+
/** @type {string} */
35+
#name;
36+
37+
/** @type {number} */
38+
#lastModified;
39+
40+
constructor(fileBits, fileName, options = kEmptyObject) {
41+
emitExperimentalWarning('buffer.File');
42+
43+
if (arguments.length < 2) {
44+
throw new ERR_MISSING_ARGS('fileBits', 'fileName');
45+
}
46+
47+
super(fileBits, options);
48+
49+
let { lastModified } = options ?? kEmptyObject;
50+
51+
if (lastModified !== undefined) {
52+
// Using Number(...) will not throw an error for bigints.
53+
lastModified = +lastModified;
54+
55+
if (NumberIsNaN(lastModified)) {
56+
lastModified = 0;
57+
}
58+
} else {
59+
lastModified = DateNow();
60+
}
61+
62+
this.#name = toUSVString(fileName);
63+
this.#lastModified = lastModified;
64+
}
65+
66+
get name() {
67+
if (!this || !(#name in this)) {
68+
throw new ERR_INVALID_THIS('File');
69+
}
70+
71+
return this.#name;
72+
}
73+
74+
get lastModified() {
75+
if (!this || !(#name in this)) {
76+
throw new ERR_INVALID_THIS('File');
77+
}
78+
79+
return this.#lastModified;
80+
}
81+
82+
[kInspect](depth, options) {
83+
if (depth < 0) {
84+
return this;
85+
}
86+
87+
const opts = {
88+
...options,
89+
depth: options.depth == null ? null : options.depth - 1,
90+
};
91+
92+
return `File ${inspect({
93+
size: this.size,
94+
type: this.type,
95+
name: this.#name,
96+
lastModified: this.#lastModified,
97+
}, opts)}`;
98+
}
99+
}
100+
101+
ObjectDefineProperties(File.prototype, {
102+
name: kEnumerableProperty,
103+
lastModified: kEnumerableProperty,
104+
[SymbolToStringTag]: {
105+
__proto__: null,
106+
configurable: true,
107+
value: 'File',
108+
}
109+
});
110+
111+
module.exports = {
112+
File,
113+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// META: title=File constructor
2+
3+
const to_string_obj = { toString: () => 'a string' };
4+
const to_string_throws = { toString: () => { throw new Error('expected'); } };
5+
6+
test(function() {
7+
assert_true("File" in globalThis, "globalThis should have a File property.");
8+
}, "File interface object exists");
9+
10+
test(t => {
11+
assert_throws_js(TypeError, () => new File(),
12+
'Bits argument is required');
13+
assert_throws_js(TypeError, () => new File([]),
14+
'Name argument is required');
15+
}, 'Required arguments');
16+
17+
function test_first_argument(arg1, expectedSize, testName) {
18+
test(function() {
19+
var file = new File(arg1, "dummy");
20+
assert_true(file instanceof File);
21+
assert_equals(file.name, "dummy");
22+
assert_equals(file.size, expectedSize);
23+
assert_equals(file.type, "");
24+
// assert_false(file.isClosed); XXX: File.isClosed doesn't seem to be implemented
25+
assert_not_equals(file.lastModified, "");
26+
}, testName);
27+
}
28+
29+
test_first_argument([], 0, "empty fileBits");
30+
test_first_argument(["bits"], 4, "DOMString fileBits");
31+
test_first_argument(["𝓽𝓮𝔁𝓽"], 16, "Unicode DOMString fileBits");
32+
test_first_argument([new String('string object')], 13, "String object fileBits");
33+
test_first_argument([new Blob()], 0, "Empty Blob fileBits");
34+
test_first_argument([new Blob(["bits"])], 4, "Blob fileBits");
35+
test_first_argument([new File([], 'world.txt')], 0, "Empty File fileBits");
36+
test_first_argument([new File(["bits"], 'world.txt')], 4, "File fileBits");
37+
test_first_argument([new ArrayBuffer(8)], 8, "ArrayBuffer fileBits");
38+
test_first_argument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4, "Typed array fileBits");
39+
test_first_argument(["bits", new Blob(["bits"]), new Blob(), new Uint8Array([0x50, 0x41]),
40+
new Uint16Array([0x5353]), new Uint32Array([0x53534150])], 16, "Various fileBits");
41+
test_first_argument([12], 2, "Number in fileBits");
42+
test_first_argument([[1,2,3]], 5, "Array in fileBits");
43+
test_first_argument([{}], 15, "Object in fileBits"); // "[object Object]"
44+
if (globalThis.document !== undefined) {
45+
test_first_argument([document.body], 24, "HTMLBodyElement in fileBits"); // "[object HTMLBodyElement]"
46+
}
47+
test_first_argument([to_string_obj], 8, "Object with toString in fileBits");
48+
test_first_argument({[Symbol.iterator]() {
49+
let i = 0;
50+
return {next: () => [
51+
{done:false, value:'ab'},
52+
{done:false, value:'cde'},
53+
{done:true}
54+
][i++]};
55+
}}, 5, 'Custom @@iterator');
56+
57+
[
58+
'hello',
59+
0,
60+
null
61+
].forEach(arg => {
62+
test(t => {
63+
assert_throws_js(TypeError, () => new File(arg, 'world.html'),
64+
'Constructor should throw for invalid bits argument');
65+
}, `Invalid bits argument: ${JSON.stringify(arg)}`);
66+
});
67+
68+
test(t => {
69+
assert_throws_js(Error, () => new File([to_string_throws], 'name.txt'),
70+
'Constructor should propagate exceptions');
71+
}, 'Bits argument: object that throws');
72+
73+
74+
function test_second_argument(arg2, expectedFileName, testName) {
75+
test(function() {
76+
var file = new File(["bits"], arg2);
77+
assert_true(file instanceof File);
78+
assert_equals(file.name, expectedFileName);
79+
}, testName);
80+
}
81+
82+
test_second_argument("dummy", "dummy", "Using fileName");
83+
test_second_argument("dummy/foo", "dummy/foo",
84+
"No replacement when using special character in fileName");
85+
test_second_argument(null, "null", "Using null fileName");
86+
test_second_argument(1, "1", "Using number fileName");
87+
test_second_argument('', '', "Using empty string fileName");
88+
if (globalThis.document !== undefined) {
89+
test_second_argument(document.body, '[object HTMLBodyElement]', "Using object fileName");
90+
}
91+
92+
// testing the third argument
93+
[
94+
{type: 'text/plain', expected: 'text/plain'},
95+
{type: 'text/plain;charset=UTF-8', expected: 'text/plain;charset=utf-8'},
96+
{type: 'TEXT/PLAIN', expected: 'text/plain'},
97+
{type: '𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫', expected: ''},
98+
{type: 'ascii/nonprintable\u001F', expected: ''},
99+
{type: 'ascii/nonprintable\u007F', expected: ''},
100+
{type: 'nonascii\u00EE', expected: ''},
101+
{type: 'nonascii\u1234', expected: ''},
102+
{type: 'nonparsable', expected: 'nonparsable'}
103+
].forEach(testCase => {
104+
test(t => {
105+
var file = new File(["bits"], "dummy", { type: testCase.type});
106+
assert_true(file instanceof File);
107+
assert_equals(file.type, testCase.expected);
108+
}, `Using type in File constructor: ${testCase.type}`);
109+
});
110+
test(function() {
111+
var file = new File(["bits"], "dummy", { lastModified: 42 });
112+
assert_true(file instanceof File);
113+
assert_equals(file.lastModified, 42);
114+
}, "Using lastModified");
115+
test(function() {
116+
var file = new File(["bits"], "dummy", { name: "foo" });
117+
assert_true(file instanceof File);
118+
assert_equals(file.name, "dummy");
119+
}, "Misusing name");
120+
test(function() {
121+
var file = new File(["bits"], "dummy", { unknownKey: "value" });
122+
assert_true(file instanceof File);
123+
assert_equals(file.name, "dummy");
124+
}, "Unknown properties are ignored");
125+
126+
[
127+
123,
128+
123.4,
129+
true,
130+
'abc'
131+
].forEach(arg => {
132+
test(t => {
133+
assert_throws_js(TypeError, () => new File(['bits'], 'name.txt', arg),
134+
'Constructor should throw for invalid property bag type');
135+
}, `Invalid property bag: ${JSON.stringify(arg)}`);
136+
});
137+
138+
[
139+
null,
140+
undefined,
141+
[1,2,3],
142+
/regex/,
143+
function() {}
144+
].forEach(arg => {
145+
test(t => {
146+
assert_equals(new File(['bits'], 'name.txt', arg).size, 4,
147+
'Constructor should accept object-ish property bag type');
148+
}, `Unusual but valid property bag: ${arg}`);
149+
});
150+
151+
test(t => {
152+
assert_throws_js(Error,
153+
() => new File(['bits'], 'name.txt', {type: to_string_throws}),
154+
'Constructor should propagate exceptions');
155+
}, 'Property bag propagates exceptions');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// META: title=FormData: FormData: Upload files named using controls
2+
// META: script=../support/send-file-formdata-helper.js
3+
"use strict";
4+
5+
formDataPostFileUploadTest({
6+
fileNameSource: "ASCII",
7+
fileBaseName: "file-for-upload-in-form-NUL-[\0].txt",
8+
});
9+
10+
formDataPostFileUploadTest({
11+
fileNameSource: "ASCII",
12+
fileBaseName: "file-for-upload-in-form-BS-[\b].txt",
13+
});
14+
15+
formDataPostFileUploadTest({
16+
fileNameSource: "ASCII",
17+
fileBaseName: "file-for-upload-in-form-VT-[\v].txt",
18+
});
19+
20+
// These have characters that undergo processing in name=,
21+
// filename=, and/or value; formDataPostFileUploadTest postprocesses
22+
// expectedEncodedBaseName for these internally.
23+
24+
formDataPostFileUploadTest({
25+
fileNameSource: "ASCII",
26+
fileBaseName: "file-for-upload-in-form-LF-[\n].txt",
27+
});
28+
29+
formDataPostFileUploadTest({
30+
fileNameSource: "ASCII",
31+
fileBaseName: "file-for-upload-in-form-LF-CR-[\n\r].txt",
32+
});
33+
34+
formDataPostFileUploadTest({
35+
fileNameSource: "ASCII",
36+
fileBaseName: "file-for-upload-in-form-CR-[\r].txt",
37+
});
38+
39+
formDataPostFileUploadTest({
40+
fileNameSource: "ASCII",
41+
fileBaseName: "file-for-upload-in-form-CR-LF-[\r\n].txt",
42+
});
43+
44+
formDataPostFileUploadTest({
45+
fileNameSource: "ASCII",
46+
fileBaseName: "file-for-upload-in-form-HT-[\t].txt",
47+
});
48+
49+
formDataPostFileUploadTest({
50+
fileNameSource: "ASCII",
51+
fileBaseName: "file-for-upload-in-form-FF-[\f].txt",
52+
});
53+
54+
formDataPostFileUploadTest({
55+
fileNameSource: "ASCII",
56+
fileBaseName: "file-for-upload-in-form-DEL-[\x7F].txt",
57+
});
58+
59+
// The rest should be passed through unmodified:
60+
61+
formDataPostFileUploadTest({
62+
fileNameSource: "ASCII",
63+
fileBaseName: "file-for-upload-in-form-ESC-[\x1B].txt",
64+
});
65+
66+
formDataPostFileUploadTest({
67+
fileNameSource: "ASCII",
68+
fileBaseName: "file-for-upload-in-form-SPACE-[ ].txt",
69+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// META: title=FormData: FormData: Upload files named using punctuation
2+
// META: script=../support/send-file-formdata-helper.js
3+
"use strict";
4+
5+
// These have characters that undergo processing in name=,
6+
// filename=, and/or value; formDataPostFileUploadTest postprocesses
7+
// expectedEncodedBaseName for these internally.
8+
9+
formDataPostFileUploadTest({
10+
fileNameSource: "ASCII",
11+
fileBaseName: "file-for-upload-in-form-QUOTATION-MARK-[\x22].txt",
12+
});
13+
14+
formDataPostFileUploadTest({
15+
fileNameSource: "ASCII",
16+
fileBaseName: '"file-for-upload-in-form-double-quoted.txt"',
17+
});
18+
19+
formDataPostFileUploadTest({
20+
fileNameSource: "ASCII",
21+
fileBaseName: "file-for-upload-in-form-REVERSE-SOLIDUS-[\\].txt",
22+
});
23+
24+
// The rest should be passed through unmodified:
25+
26+
formDataPostFileUploadTest({
27+
fileNameSource: "ASCII",
28+
fileBaseName: "file-for-upload-in-form-EXCLAMATION-MARK-[!].txt",
29+
});
30+
31+
formDataPostFileUploadTest({
32+
fileNameSource: "ASCII",
33+
fileBaseName: "file-for-upload-in-form-DOLLAR-SIGN-[$].txt",
34+
});
35+
36+
formDataPostFileUploadTest({
37+
fileNameSource: "ASCII",
38+
fileBaseName: "file-for-upload-in-form-PERCENT-SIGN-[%].txt",
39+
});
40+
41+
formDataPostFileUploadTest({
42+
fileNameSource: "ASCII",
43+
fileBaseName: "file-for-upload-in-form-AMPERSAND-[&].txt",
44+
});
45+
46+
formDataPostFileUploadTest({
47+
fileNameSource: "ASCII",
48+
fileBaseName: "file-for-upload-in-form-APOSTROPHE-['].txt",
49+
});
50+
51+
formDataPostFileUploadTest({
52+
fileNameSource: "ASCII",
53+
fileBaseName: "file-for-upload-in-form-LEFT-PARENTHESIS-[(].txt",
54+
});
55+
56+
formDataPostFileUploadTest({
57+
fileNameSource: "ASCII",
58+
fileBaseName: "file-for-upload-in-form-RIGHT-PARENTHESIS-[)].txt",
59+
});
60+
61+
formDataPostFileUploadTest({
62+
fileNameSource: "ASCII",
63+
fileBaseName: "file-for-upload-in-form-ASTERISK-[*].txt",
64+
});
65+
66+
formDataPostFileUploadTest({
67+
fileNameSource: "ASCII",
68+
fileBaseName: "file-for-upload-in-form-PLUS-SIGN-[+].txt",
69+
});
70+
71+
formDataPostFileUploadTest({
72+
fileNameSource: "ASCII",
73+
fileBaseName: "file-for-upload-in-form-COMMA-[,].txt",
74+
});
75+
76+
formDataPostFileUploadTest({
77+
fileNameSource: "ASCII",
78+
fileBaseName: "file-for-upload-in-form-FULL-STOP-[.].txt",
79+
});
80+
81+
formDataPostFileUploadTest({
82+
fileNameSource: "ASCII",
83+
fileBaseName: "file-for-upload-in-form-SOLIDUS-[/].txt",
84+
});
85+
86+
formDataPostFileUploadTest({
87+
fileNameSource: "ASCII",
88+
fileBaseName: "file-for-upload-in-form-COLON-[:].txt",
89+
});
90+
91+
formDataPostFileUploadTest({
92+
fileNameSource: "ASCII",
93+
fileBaseName: "file-for-upload-in-form-SEMICOLON-[;].txt",
94+
});
95+
96+
formDataPostFileUploadTest({
97+
fileNameSource: "ASCII",
98+
fileBaseName: "file-for-upload-in-form-EQUALS-SIGN-[=].txt",
99+
});
100+
101+
formDataPostFileUploadTest({
102+
fileNameSource: "ASCII",
103+
fileBaseName: "file-for-upload-in-form-QUESTION-MARK-[?].txt",
104+
});
105+
106+
formDataPostFileUploadTest({
107+
fileNameSource: "ASCII",
108+
fileBaseName: "file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^].txt",
109+
});
110+
111+
formDataPostFileUploadTest({
112+
fileNameSource: "ASCII",
113+
fileBaseName: "file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[].txt",
114+
});
115+
116+
formDataPostFileUploadTest({
117+
fileNameSource: "ASCII",
118+
fileBaseName: "file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[]].txt",
119+
});
120+
121+
formDataPostFileUploadTest({
122+
fileNameSource: "ASCII",
123+
fileBaseName: "file-for-upload-in-form-LEFT-CURLY-BRACKET-[{].txt",
124+
});
125+
126+
formDataPostFileUploadTest({
127+
fileNameSource: "ASCII",
128+
fileBaseName: "file-for-upload-in-form-VERTICAL-LINE-[|].txt",
129+
});
130+
131+
formDataPostFileUploadTest({
132+
fileNameSource: "ASCII",
133+
fileBaseName: "file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}].txt",
134+
});
135+
136+
formDataPostFileUploadTest({
137+
fileNameSource: "ASCII",
138+
fileBaseName: "file-for-upload-in-form-TILDE-[~].txt",
139+
});
140+
141+
formDataPostFileUploadTest({
142+
fileNameSource: "ASCII",
143+
fileBaseName: "'file-for-upload-in-form-single-quoted.txt'",
144+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// META: title=FormData: FormData: Upload files in UTF-8 fetch()
2+
// META: script=../support/send-file-formdata-helper.js
3+
"use strict";
4+
5+
formDataPostFileUploadTest({
6+
fileNameSource: "ASCII",
7+
fileBaseName: "file-for-upload-in-form.txt",
8+
});
9+
10+
formDataPostFileUploadTest({
11+
fileNameSource: "x-user-defined",
12+
fileBaseName: "file-for-upload-in-form-\uF7F0\uF793\uF783\uF7A0.txt",
13+
});
14+
15+
formDataPostFileUploadTest({
16+
fileNameSource: "windows-1252",
17+
fileBaseName: "file-for-upload-in-form-☺😂.txt",
18+
});
19+
20+
formDataPostFileUploadTest({
21+
fileNameSource: "JIS X 0201 and JIS X 0208",
22+
fileBaseName: "file-for-upload-in-form-★星★.txt",
23+
});
24+
25+
formDataPostFileUploadTest({
26+
fileNameSource: "Unicode",
27+
fileBaseName: "file-for-upload-in-form-☺😂.txt",
28+
});
29+
30+
formDataPostFileUploadTest({
31+
fileNameSource: "Unicode",
32+
fileBaseName: `file-for-upload-in-form-${kTestChars}.txt`,
33+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// META: title=FormData: Upload ASCII-named file in UTF-8 form
2+
// META: script=../support/send-file-formdata-helper.js
3+
"use strict";
4+
5+
formDataPostFileUploadTest({
6+
fileNameSource: "ASCII",
7+
fileBaseName: "file-for-upload-in-form.txt",
8+
});

‎test/fixtures/wpt/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Last update:
1717
- encoding: https://github.com/web-platform-tests/wpt/tree/c1b24fce6e/encoding
1818
- fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources
1919
- FileAPI: https://github.com/web-platform-tests/wpt/tree/3b279420d4/FileAPI
20+
- FileAPI/file: https://github.com/web-platform-tests/wpt/tree/c01f637cca/FileAPI/file
2021
- hr-time: https://github.com/web-platform-tests/wpt/tree/34cafd797e/hr-time
2122
- html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob
2223
- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing

‎test/fixtures/wpt/versions.json

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
"commit": "3b279420d40afea32506e823f9ac005448f4f3d8",
2828
"path": "FileAPI"
2929
},
30+
"FileAPI/file": {
31+
"commit": "c01f637cca43f0e08ce8e4269121dcd89ccbdd82",
32+
"path": "FileAPI/file"
33+
},
3034
"hr-time": {
3135
"commit": "34cafd797e58dad280d20040eee012d49ccfa91f",
3236
"path": "hr-time"

‎test/parallel/test-bootstrap-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ const expectedModules = new Set([
163163
'NativeModule internal/worker/js_transferable',
164164
'Internal Binding blob',
165165
'NativeModule internal/blob',
166+
'NativeModule internal/file',
166167
'NativeModule async_hooks',
167168
'NativeModule net',
168169
'NativeModule path',

‎test/parallel/test-file.js

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { Blob, File } = require('buffer');
6+
const { inspect } = require('util');
7+
8+
{
9+
// ensure File extends Blob
10+
assert.deepStrictEqual(Object.getPrototypeOf(File.prototype), Blob.prototype);
11+
}
12+
13+
{
14+
assert.throws(() => new File(), TypeError);
15+
assert.throws(() => new File([]), TypeError);
16+
}
17+
18+
{
19+
const properties = ['name', 'lastModified'];
20+
21+
for (const prop of properties) {
22+
const desc = Object.getOwnPropertyDescriptor(File.prototype, prop);
23+
assert.notStrictEqual(desc, undefined);
24+
// Ensure these properties are getters.
25+
assert.strictEqual(desc.get?.name, `get ${prop}`);
26+
assert.strictEqual(desc.set, undefined);
27+
assert.strictEqual(desc.enumerable, true);
28+
assert.strictEqual(desc.configurable, true);
29+
}
30+
}
31+
32+
{
33+
const file = new File([], '');
34+
assert.strictEqual(file[Symbol.toStringTag], 'File');
35+
assert.strictEqual(File.prototype[Symbol.toStringTag], 'File');
36+
}
37+
38+
{
39+
assert.throws(() => File.prototype.name, TypeError);
40+
assert.throws(() => File.prototype.lastModified, TypeError);
41+
}
42+
43+
{
44+
const keys = Object.keys(File.prototype).sort();
45+
assert.deepStrictEqual(keys, ['lastModified', 'name']);
46+
}
47+
48+
{
49+
const file = new File([], 'dummy.txt.exe');
50+
assert.strictEqual(file.name, 'dummy.txt.exe');
51+
assert.strictEqual(file.size, 0);
52+
assert.strictEqual(typeof file.lastModified, 'number');
53+
assert(file.lastModified <= Date.now());
54+
}
55+
56+
{
57+
const emptyFile = new File([], 'empty.txt');
58+
const blob = new Blob(['hello world']);
59+
60+
emptyFile.text.call(blob).then(common.mustCall((text) => {
61+
assert.strictEqual(text, 'hello world');
62+
}));
63+
}
64+
65+
{
66+
const toPrimitive = {
67+
[Symbol.toPrimitive]() {
68+
return 'NaN';
69+
}
70+
};
71+
72+
const invalidLastModified = [
73+
null,
74+
'string',
75+
false,
76+
toPrimitive,
77+
];
78+
79+
for (const lastModified of invalidLastModified) {
80+
const file = new File([], '', { lastModified });
81+
assert.strictEqual(file.lastModified, 0);
82+
}
83+
}
84+
85+
{
86+
const file = new File([], '', { lastModified: undefined });
87+
assert.notStrictEqual(file.lastModified, 0);
88+
}
89+
90+
{
91+
const toPrimitive = {
92+
[Symbol.toPrimitive]() {
93+
throw new TypeError('boom');
94+
}
95+
};
96+
97+
const throwValues = [
98+
BigInt(3n),
99+
toPrimitive,
100+
];
101+
102+
for (const lastModified of throwValues) {
103+
assert.throws(() => new File([], '', { lastModified }), TypeError);
104+
}
105+
}
106+
107+
{
108+
const valid = [
109+
{
110+
[Symbol.toPrimitive]() {
111+
return 10;
112+
}
113+
},
114+
new Number(10),
115+
10,
116+
];
117+
118+
for (const lastModified of valid) {
119+
assert.strictEqual(new File([], '', { lastModified }).lastModified, 10);
120+
}
121+
}
122+
123+
{
124+
const file = new File([], '');
125+
assert(inspect(file).startsWith('File { size: 0, type: \'\', name: \'\', lastModified:'));
126+
}
127+
128+
{
129+
function MyClass() {}
130+
MyClass.prototype.lastModified = 10;
131+
132+
const file = new File([], '', new MyClass());
133+
assert.strictEqual(file.lastModified, 10);
134+
}
135+
136+
{
137+
let counter = 0;
138+
new File([], '', {
139+
get lastModified() {
140+
counter++;
141+
return 10;
142+
}
143+
});
144+
assert.strictEqual(counter, 1);
145+
}
146+
147+
{
148+
const getter = Object.getOwnPropertyDescriptor(File.prototype, 'name').get;
149+
assert.throws(
150+
() => getter.call(undefined), // eslint-disable-line no-useless-call
151+
{
152+
code: 'ERR_INVALID_THIS',
153+
}
154+
);
155+
}

‎test/wpt/status/FileAPI/file.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"Worker-read-file-constructor.worker.js": {
3+
"skip": true
4+
},
5+
"send-file-formdata-punctuation.any.js": {
6+
"skip": true
7+
},
8+
"send-file-formdata-utf-8.any.js": {
9+
"skip": true
10+
},
11+
"send-file-formdata.any.js": {
12+
"skip": true
13+
},
14+
"send-file-formdata-controls.any.js": {
15+
"skip": true
16+
}
17+
}

‎test/wpt/test-file.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
require('../common');
4+
const { WPTRunner } = require('../common/wpt');
5+
6+
const runner = new WPTRunner('FileAPI/file');
7+
8+
runner.setInitScript(`
9+
const { File } = require('buffer');
10+
globalThis.File = File;
11+
`);
12+
13+
runner.runJsTests();

‎tools/doc/type-parser.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const customTypesMap = {
4343
`${jsDocPrefix}Reference/Global_Objects/WebAssembly/Instance`,
4444

4545
'Blob': 'buffer.html#class-blob',
46+
'File': 'buffer.html#class-file',
4647

4748
'BroadcastChannel':
4849
'worker_threads.html#class-broadcastchannel-' +

0 commit comments

Comments
 (0)
Please sign in to comment.