123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- 'use strict'
- 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')
- 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(new TextEncoder().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 }
|