Spaces:
Runtime error
Runtime error
const { Blob, File: NativeFile } = require('buffer') | |
const { types } = require('util') | |
const { kState } = require('./symbols') | |
const { isBlobLike } = require('./util') | |
const { webidl } = require('./webidl') | |
const { parseMIMEType, serializeAMimeType } = require('./dataURL') | |
const { kEnumerableProperty } = require('../core/util') | |
const encoder = new TextEncoder() | |
class File extends Blob { | |
constructor (fileBits, fileName, options = {}) { | |
// The File constructor is invoked with two or three parameters, depending | |
// on whether the optional dictionary parameter is used. When the File() | |
// constructor is invoked, user agents must run the following steps: | |
webidl.argumentLengthCheck(arguments, 2, { header: 'File constructor' }) | |
fileBits = webidl.converters['sequence<BlobPart>'](fileBits) | |
fileName = webidl.converters.USVString(fileName) | |
options = webidl.converters.FilePropertyBag(options) | |
// 1. Let bytes be the result of processing blob parts given fileBits and | |
// options. | |
// Note: Blob handles this for us | |
// 2. Let n be the fileName argument to the constructor. | |
const n = fileName | |
// 3. Process FilePropertyBag dictionary argument by running the following | |
// substeps: | |
// 1. If the type member is provided and is not the empty string, let t | |
// be set to the type dictionary member. If t contains any characters | |
// outside the range U+0020 to U+007E, then set t to the empty string | |
// and return from these substeps. | |
// 2. Convert every character in t to ASCII lowercase. | |
let t = options.type | |
let d | |
// eslint-disable-next-line no-labels | |
substep: { | |
if (t) { | |
t = parseMIMEType(t) | |
if (t === 'failure') { | |
t = '' | |
// eslint-disable-next-line no-labels | |
break substep | |
} | |
t = serializeAMimeType(t).toLowerCase() | |
} | |
// 3. If the lastModified member is provided, let d be set to the | |
// lastModified dictionary member. If it is not provided, set d to the | |
// current date and time represented as the number of milliseconds since | |
// the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]). | |
d = options.lastModified | |
} | |
// 4. Return a new File object F such that: | |
// F refers to the bytes byte sequence. | |
// F.size is set to the number of total bytes in bytes. | |
// F.name is set to n. | |
// F.type is set to t. | |
// F.lastModified is set to d. | |
super(processBlobParts(fileBits, options), { type: t }) | |
this[kState] = { | |
name: n, | |
lastModified: d, | |
type: t | |
} | |
} | |
get name () { | |
webidl.brandCheck(this, File) | |
return this[kState].name | |
} | |
get lastModified () { | |
webidl.brandCheck(this, File) | |
return this[kState].lastModified | |
} | |
get type () { | |
webidl.brandCheck(this, File) | |
return this[kState].type | |
} | |
} | |
class FileLike { | |
constructor (blobLike, fileName, options = {}) { | |
// TODO: argument idl type check | |
// The File constructor is invoked with two or three parameters, depending | |
// on whether the optional dictionary parameter is used. When the File() | |
// constructor is invoked, user agents must run the following steps: | |
// 1. Let bytes be the result of processing blob parts given fileBits and | |
// options. | |
// 2. Let n be the fileName argument to the constructor. | |
const n = fileName | |
// 3. Process FilePropertyBag dictionary argument by running the following | |
// substeps: | |
// 1. If the type member is provided and is not the empty string, let t | |
// be set to the type dictionary member. If t contains any characters | |
// outside the range U+0020 to U+007E, then set t to the empty string | |
// and return from these substeps. | |
// TODO | |
const t = options.type | |
// 2. Convert every character in t to ASCII lowercase. | |
// TODO | |
// 3. If the lastModified member is provided, let d be set to the | |
// lastModified dictionary member. If it is not provided, set d to the | |
// current date and time represented as the number of milliseconds since | |
// the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]). | |
const d = options.lastModified ?? Date.now() | |
// 4. Return a new File object F such that: | |
// F refers to the bytes byte sequence. | |
// F.size is set to the number of total bytes in bytes. | |
// F.name is set to n. | |
// F.type is set to t. | |
// F.lastModified is set to d. | |
this[kState] = { | |
blobLike, | |
name: n, | |
type: t, | |
lastModified: d | |
} | |
} | |
stream (...args) { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].blobLike.stream(...args) | |
} | |
arrayBuffer (...args) { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].blobLike.arrayBuffer(...args) | |
} | |
slice (...args) { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].blobLike.slice(...args) | |
} | |
text (...args) { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].blobLike.text(...args) | |
} | |
get size () { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].blobLike.size | |
} | |
get type () { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].blobLike.type | |
} | |
get name () { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].name | |
} | |
get lastModified () { | |
webidl.brandCheck(this, FileLike) | |
return this[kState].lastModified | |
} | |
get [Symbol.toStringTag] () { | |
return 'File' | |
} | |
} | |
Object.defineProperties(File.prototype, { | |
[Symbol.toStringTag]: { | |
value: 'File', | |
configurable: true | |
}, | |
name: kEnumerableProperty, | |
lastModified: kEnumerableProperty | |
}) | |
webidl.converters.Blob = webidl.interfaceConverter(Blob) | |
webidl.converters.BlobPart = function (V, opts) { | |
if (webidl.util.Type(V) === 'Object') { | |
if (isBlobLike(V)) { | |
return webidl.converters.Blob(V, { strict: false }) | |
} | |
if ( | |
ArrayBuffer.isView(V) || | |
types.isAnyArrayBuffer(V) | |
) { | |
return webidl.converters.BufferSource(V, opts) | |
} | |
} | |
return webidl.converters.USVString(V, opts) | |
} | |
webidl.converters['sequence<BlobPart>'] = webidl.sequenceConverter( | |
webidl.converters.BlobPart | |
) | |
// https://www.w3.org/TR/FileAPI/#dfn-FilePropertyBag | |
webidl.converters.FilePropertyBag = webidl.dictionaryConverter([ | |
{ | |
key: 'lastModified', | |
converter: webidl.converters['long long'], | |
get defaultValue () { | |
return Date.now() | |
} | |
}, | |
{ | |
key: 'type', | |
converter: webidl.converters.DOMString, | |
defaultValue: '' | |
}, | |
{ | |
key: 'endings', | |
converter: (value) => { | |
value = webidl.converters.DOMString(value) | |
value = value.toLowerCase() | |
if (value !== 'native') { | |
value = 'transparent' | |
} | |
return value | |
}, | |
defaultValue: 'transparent' | |
} | |
]) | |
/** | |
* @see https://www.w3.org/TR/FileAPI/#process-blob-parts | |
* @param {(NodeJS.TypedArray|Blob|string)[]} parts | |
* @param {{ type: string, endings: string }} options | |
*/ | |
function processBlobParts (parts, options) { | |
// 1. Let bytes be an empty sequence of bytes. | |
/** @type {NodeJS.TypedArray[]} */ | |
const bytes = [] | |
// 2. For each element in parts: | |
for (const element of parts) { | |
// 1. If element is a USVString, run the following substeps: | |
if (typeof element === 'string') { | |
// 1. Let s be element. | |
let s = element | |
// 2. If the endings member of options is "native", set s | |
// to the result of converting line endings to native | |
// of element. | |
if (options.endings === 'native') { | |
s = convertLineEndingsNative(s) | |
} | |
// 3. Append the result of UTF-8 encoding s to bytes. | |
bytes.push(encoder.encode(s)) | |
} else if ( | |
types.isAnyArrayBuffer(element) || | |
types.isTypedArray(element) | |
) { | |
// 2. If element is a BufferSource, get a copy of the | |
// bytes held by the buffer source, and append those | |
// bytes to bytes. | |
if (!element.buffer) { // ArrayBuffer | |
bytes.push(new Uint8Array(element)) | |
} else { | |
bytes.push( | |
new Uint8Array(element.buffer, element.byteOffset, element.byteLength) | |
) | |
} | |
} else if (isBlobLike(element)) { | |
// 3. If element is a Blob, append the bytes it represents | |
// to bytes. | |
bytes.push(element) | |
} | |
} | |
// 3. Return bytes. | |
return bytes | |
} | |
/** | |
* @see https://www.w3.org/TR/FileAPI/#convert-line-endings-to-native | |
* @param {string} s | |
*/ | |
function convertLineEndingsNative (s) { | |
// 1. Let native line ending be be the code point U+000A LF. | |
let nativeLineEnding = '\n' | |
// 2. If the underlying platform’s conventions are to | |
// represent newlines as a carriage return and line feed | |
// sequence, set native line ending to the code point | |
// U+000D CR followed by the code point U+000A LF. | |
if (process.platform === 'win32') { | |
nativeLineEnding = '\r\n' | |
} | |
return s.replace(/\r?\n/g, nativeLineEnding) | |
} | |
// If this function is moved to ./util.js, some tools (such as | |
// rollup) will warn about circular dependencies. See: | |
// https://github.com/nodejs/undici/issues/1629 | |
function isFileLike (object) { | |
return ( | |
(NativeFile && object instanceof NativeFile) || | |
object instanceof File || ( | |
object && | |
(typeof object.stream === 'function' || | |
typeof object.arrayBuffer === 'function') && | |
object[Symbol.toStringTag] === 'File' | |
) | |
) | |
} | |
module.exports = { File, FileLike, isFileLike } | |