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 } | |