[d24f17c] | 1 | 'use strict'
|
---|
| 2 |
|
---|
| 3 | const { Blob, File: NativeFile } = require('buffer')
|
---|
| 4 | const { types } = require('util')
|
---|
| 5 | const { kState } = require('./symbols')
|
---|
| 6 | const { isBlobLike } = require('./util')
|
---|
| 7 | const { webidl } = require('./webidl')
|
---|
| 8 | const { parseMIMEType, serializeAMimeType } = require('./dataURL')
|
---|
| 9 | const { kEnumerableProperty } = require('../core/util')
|
---|
| 10 | const encoder = new TextEncoder()
|
---|
| 11 |
|
---|
| 12 | class File extends Blob {
|
---|
| 13 | constructor (fileBits, fileName, options = {}) {
|
---|
| 14 | // The File constructor is invoked with two or three parameters, depending
|
---|
| 15 | // on whether the optional dictionary parameter is used. When the File()
|
---|
| 16 | // constructor is invoked, user agents must run the following steps:
|
---|
| 17 | webidl.argumentLengthCheck(arguments, 2, { header: 'File constructor' })
|
---|
| 18 |
|
---|
| 19 | fileBits = webidl.converters['sequence<BlobPart>'](fileBits)
|
---|
| 20 | fileName = webidl.converters.USVString(fileName)
|
---|
| 21 | options = webidl.converters.FilePropertyBag(options)
|
---|
| 22 |
|
---|
| 23 | // 1. Let bytes be the result of processing blob parts given fileBits and
|
---|
| 24 | // options.
|
---|
| 25 | // Note: Blob handles this for us
|
---|
| 26 |
|
---|
| 27 | // 2. Let n be the fileName argument to the constructor.
|
---|
| 28 | const n = fileName
|
---|
| 29 |
|
---|
| 30 | // 3. Process FilePropertyBag dictionary argument by running the following
|
---|
| 31 | // substeps:
|
---|
| 32 |
|
---|
| 33 | // 1. If the type member is provided and is not the empty string, let t
|
---|
| 34 | // be set to the type dictionary member. If t contains any characters
|
---|
| 35 | // outside the range U+0020 to U+007E, then set t to the empty string
|
---|
| 36 | // and return from these substeps.
|
---|
| 37 | // 2. Convert every character in t to ASCII lowercase.
|
---|
| 38 | let t = options.type
|
---|
| 39 | let d
|
---|
| 40 |
|
---|
| 41 | // eslint-disable-next-line no-labels
|
---|
| 42 | substep: {
|
---|
| 43 | if (t) {
|
---|
| 44 | t = parseMIMEType(t)
|
---|
| 45 |
|
---|
| 46 | if (t === 'failure') {
|
---|
| 47 | t = ''
|
---|
| 48 | // eslint-disable-next-line no-labels
|
---|
| 49 | break substep
|
---|
| 50 | }
|
---|
| 51 |
|
---|
| 52 | t = serializeAMimeType(t).toLowerCase()
|
---|
| 53 | }
|
---|
| 54 |
|
---|
| 55 | // 3. If the lastModified member is provided, let d be set to the
|
---|
| 56 | // lastModified dictionary member. If it is not provided, set d to the
|
---|
| 57 | // current date and time represented as the number of milliseconds since
|
---|
| 58 | // the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]).
|
---|
| 59 | d = options.lastModified
|
---|
| 60 | }
|
---|
| 61 |
|
---|
| 62 | // 4. Return a new File object F such that:
|
---|
| 63 | // F refers to the bytes byte sequence.
|
---|
| 64 | // F.size is set to the number of total bytes in bytes.
|
---|
| 65 | // F.name is set to n.
|
---|
| 66 | // F.type is set to t.
|
---|
| 67 | // F.lastModified is set to d.
|
---|
| 68 |
|
---|
| 69 | super(processBlobParts(fileBits, options), { type: t })
|
---|
| 70 | this[kState] = {
|
---|
| 71 | name: n,
|
---|
| 72 | lastModified: d,
|
---|
| 73 | type: t
|
---|
| 74 | }
|
---|
| 75 | }
|
---|
| 76 |
|
---|
| 77 | get name () {
|
---|
| 78 | webidl.brandCheck(this, File)
|
---|
| 79 |
|
---|
| 80 | return this[kState].name
|
---|
| 81 | }
|
---|
| 82 |
|
---|
| 83 | get lastModified () {
|
---|
| 84 | webidl.brandCheck(this, File)
|
---|
| 85 |
|
---|
| 86 | return this[kState].lastModified
|
---|
| 87 | }
|
---|
| 88 |
|
---|
| 89 | get type () {
|
---|
| 90 | webidl.brandCheck(this, File)
|
---|
| 91 |
|
---|
| 92 | return this[kState].type
|
---|
| 93 | }
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | class FileLike {
|
---|
| 97 | constructor (blobLike, fileName, options = {}) {
|
---|
| 98 | // TODO: argument idl type check
|
---|
| 99 |
|
---|
| 100 | // The File constructor is invoked with two or three parameters, depending
|
---|
| 101 | // on whether the optional dictionary parameter is used. When the File()
|
---|
| 102 | // constructor is invoked, user agents must run the following steps:
|
---|
| 103 |
|
---|
| 104 | // 1. Let bytes be the result of processing blob parts given fileBits and
|
---|
| 105 | // options.
|
---|
| 106 |
|
---|
| 107 | // 2. Let n be the fileName argument to the constructor.
|
---|
| 108 | const n = fileName
|
---|
| 109 |
|
---|
| 110 | // 3. Process FilePropertyBag dictionary argument by running the following
|
---|
| 111 | // substeps:
|
---|
| 112 |
|
---|
| 113 | // 1. If the type member is provided and is not the empty string, let t
|
---|
| 114 | // be set to the type dictionary member. If t contains any characters
|
---|
| 115 | // outside the range U+0020 to U+007E, then set t to the empty string
|
---|
| 116 | // and return from these substeps.
|
---|
| 117 | // TODO
|
---|
| 118 | const t = options.type
|
---|
| 119 |
|
---|
| 120 | // 2. Convert every character in t to ASCII lowercase.
|
---|
| 121 | // TODO
|
---|
| 122 |
|
---|
| 123 | // 3. If the lastModified member is provided, let d be set to the
|
---|
| 124 | // lastModified dictionary member. If it is not provided, set d to the
|
---|
| 125 | // current date and time represented as the number of milliseconds since
|
---|
| 126 | // the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]).
|
---|
| 127 | const d = options.lastModified ?? Date.now()
|
---|
| 128 |
|
---|
| 129 | // 4. Return a new File object F such that:
|
---|
| 130 | // F refers to the bytes byte sequence.
|
---|
| 131 | // F.size is set to the number of total bytes in bytes.
|
---|
| 132 | // F.name is set to n.
|
---|
| 133 | // F.type is set to t.
|
---|
| 134 | // F.lastModified is set to d.
|
---|
| 135 |
|
---|
| 136 | this[kState] = {
|
---|
| 137 | blobLike,
|
---|
| 138 | name: n,
|
---|
| 139 | type: t,
|
---|
| 140 | lastModified: d
|
---|
| 141 | }
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | stream (...args) {
|
---|
| 145 | webidl.brandCheck(this, FileLike)
|
---|
| 146 |
|
---|
| 147 | return this[kState].blobLike.stream(...args)
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | arrayBuffer (...args) {
|
---|
| 151 | webidl.brandCheck(this, FileLike)
|
---|
| 152 |
|
---|
| 153 | return this[kState].blobLike.arrayBuffer(...args)
|
---|
| 154 | }
|
---|
| 155 |
|
---|
| 156 | slice (...args) {
|
---|
| 157 | webidl.brandCheck(this, FileLike)
|
---|
| 158 |
|
---|
| 159 | return this[kState].blobLike.slice(...args)
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | text (...args) {
|
---|
| 163 | webidl.brandCheck(this, FileLike)
|
---|
| 164 |
|
---|
| 165 | return this[kState].blobLike.text(...args)
|
---|
| 166 | }
|
---|
| 167 |
|
---|
| 168 | get size () {
|
---|
| 169 | webidl.brandCheck(this, FileLike)
|
---|
| 170 |
|
---|
| 171 | return this[kState].blobLike.size
|
---|
| 172 | }
|
---|
| 173 |
|
---|
| 174 | get type () {
|
---|
| 175 | webidl.brandCheck(this, FileLike)
|
---|
| 176 |
|
---|
| 177 | return this[kState].blobLike.type
|
---|
| 178 | }
|
---|
| 179 |
|
---|
| 180 | get name () {
|
---|
| 181 | webidl.brandCheck(this, FileLike)
|
---|
| 182 |
|
---|
| 183 | return this[kState].name
|
---|
| 184 | }
|
---|
| 185 |
|
---|
| 186 | get lastModified () {
|
---|
| 187 | webidl.brandCheck(this, FileLike)
|
---|
| 188 |
|
---|
| 189 | return this[kState].lastModified
|
---|
| 190 | }
|
---|
| 191 |
|
---|
| 192 | get [Symbol.toStringTag] () {
|
---|
| 193 | return 'File'
|
---|
| 194 | }
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | Object.defineProperties(File.prototype, {
|
---|
| 198 | [Symbol.toStringTag]: {
|
---|
| 199 | value: 'File',
|
---|
| 200 | configurable: true
|
---|
| 201 | },
|
---|
| 202 | name: kEnumerableProperty,
|
---|
| 203 | lastModified: kEnumerableProperty
|
---|
| 204 | })
|
---|
| 205 |
|
---|
| 206 | webidl.converters.Blob = webidl.interfaceConverter(Blob)
|
---|
| 207 |
|
---|
| 208 | webidl.converters.BlobPart = function (V, opts) {
|
---|
| 209 | if (webidl.util.Type(V) === 'Object') {
|
---|
| 210 | if (isBlobLike(V)) {
|
---|
| 211 | return webidl.converters.Blob(V, { strict: false })
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | if (
|
---|
| 215 | ArrayBuffer.isView(V) ||
|
---|
| 216 | types.isAnyArrayBuffer(V)
|
---|
| 217 | ) {
|
---|
| 218 | return webidl.converters.BufferSource(V, opts)
|
---|
| 219 | }
|
---|
| 220 | }
|
---|
| 221 |
|
---|
| 222 | return webidl.converters.USVString(V, opts)
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | webidl.converters['sequence<BlobPart>'] = webidl.sequenceConverter(
|
---|
| 226 | webidl.converters.BlobPart
|
---|
| 227 | )
|
---|
| 228 |
|
---|
| 229 | // https://www.w3.org/TR/FileAPI/#dfn-FilePropertyBag
|
---|
| 230 | webidl.converters.FilePropertyBag = webidl.dictionaryConverter([
|
---|
| 231 | {
|
---|
| 232 | key: 'lastModified',
|
---|
| 233 | converter: webidl.converters['long long'],
|
---|
| 234 | get defaultValue () {
|
---|
| 235 | return Date.now()
|
---|
| 236 | }
|
---|
| 237 | },
|
---|
| 238 | {
|
---|
| 239 | key: 'type',
|
---|
| 240 | converter: webidl.converters.DOMString,
|
---|
| 241 | defaultValue: ''
|
---|
| 242 | },
|
---|
| 243 | {
|
---|
| 244 | key: 'endings',
|
---|
| 245 | converter: (value) => {
|
---|
| 246 | value = webidl.converters.DOMString(value)
|
---|
| 247 | value = value.toLowerCase()
|
---|
| 248 |
|
---|
| 249 | if (value !== 'native') {
|
---|
| 250 | value = 'transparent'
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | return value
|
---|
| 254 | },
|
---|
| 255 | defaultValue: 'transparent'
|
---|
| 256 | }
|
---|
| 257 | ])
|
---|
| 258 |
|
---|
| 259 | /**
|
---|
| 260 | * @see https://www.w3.org/TR/FileAPI/#process-blob-parts
|
---|
| 261 | * @param {(NodeJS.TypedArray|Blob|string)[]} parts
|
---|
| 262 | * @param {{ type: string, endings: string }} options
|
---|
| 263 | */
|
---|
| 264 | function processBlobParts (parts, options) {
|
---|
| 265 | // 1. Let bytes be an empty sequence of bytes.
|
---|
| 266 | /** @type {NodeJS.TypedArray[]} */
|
---|
| 267 | const bytes = []
|
---|
| 268 |
|
---|
| 269 | // 2. For each element in parts:
|
---|
| 270 | for (const element of parts) {
|
---|
| 271 | // 1. If element is a USVString, run the following substeps:
|
---|
| 272 | if (typeof element === 'string') {
|
---|
| 273 | // 1. Let s be element.
|
---|
| 274 | let s = element
|
---|
| 275 |
|
---|
| 276 | // 2. If the endings member of options is "native", set s
|
---|
| 277 | // to the result of converting line endings to native
|
---|
| 278 | // of element.
|
---|
| 279 | if (options.endings === 'native') {
|
---|
| 280 | s = convertLineEndingsNative(s)
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | // 3. Append the result of UTF-8 encoding s to bytes.
|
---|
| 284 | bytes.push(encoder.encode(s))
|
---|
| 285 | } else if (
|
---|
| 286 | types.isAnyArrayBuffer(element) ||
|
---|
| 287 | types.isTypedArray(element)
|
---|
| 288 | ) {
|
---|
| 289 | // 2. If element is a BufferSource, get a copy of the
|
---|
| 290 | // bytes held by the buffer source, and append those
|
---|
| 291 | // bytes to bytes.
|
---|
| 292 | if (!element.buffer) { // ArrayBuffer
|
---|
| 293 | bytes.push(new Uint8Array(element))
|
---|
| 294 | } else {
|
---|
| 295 | bytes.push(
|
---|
| 296 | new Uint8Array(element.buffer, element.byteOffset, element.byteLength)
|
---|
| 297 | )
|
---|
| 298 | }
|
---|
| 299 | } else if (isBlobLike(element)) {
|
---|
| 300 | // 3. If element is a Blob, append the bytes it represents
|
---|
| 301 | // to bytes.
|
---|
| 302 | bytes.push(element)
|
---|
| 303 | }
|
---|
| 304 | }
|
---|
| 305 |
|
---|
| 306 | // 3. Return bytes.
|
---|
| 307 | return bytes
|
---|
| 308 | }
|
---|
| 309 |
|
---|
| 310 | /**
|
---|
| 311 | * @see https://www.w3.org/TR/FileAPI/#convert-line-endings-to-native
|
---|
| 312 | * @param {string} s
|
---|
| 313 | */
|
---|
| 314 | function convertLineEndingsNative (s) {
|
---|
| 315 | // 1. Let native line ending be be the code point U+000A LF.
|
---|
| 316 | let nativeLineEnding = '\n'
|
---|
| 317 |
|
---|
| 318 | // 2. If the underlying platform’s conventions are to
|
---|
| 319 | // represent newlines as a carriage return and line feed
|
---|
| 320 | // sequence, set native line ending to the code point
|
---|
| 321 | // U+000D CR followed by the code point U+000A LF.
|
---|
| 322 | if (process.platform === 'win32') {
|
---|
| 323 | nativeLineEnding = '\r\n'
|
---|
| 324 | }
|
---|
| 325 |
|
---|
| 326 | return s.replace(/\r?\n/g, nativeLineEnding)
|
---|
| 327 | }
|
---|
| 328 |
|
---|
| 329 | // If this function is moved to ./util.js, some tools (such as
|
---|
| 330 | // rollup) will warn about circular dependencies. See:
|
---|
| 331 | // https://github.com/nodejs/undici/issues/1629
|
---|
| 332 | function isFileLike (object) {
|
---|
| 333 | return (
|
---|
| 334 | (NativeFile && object instanceof NativeFile) ||
|
---|
| 335 | object instanceof File || (
|
---|
| 336 | object &&
|
---|
| 337 | (typeof object.stream === 'function' ||
|
---|
| 338 | typeof object.arrayBuffer === 'function') &&
|
---|
| 339 | object[Symbol.toStringTag] === 'File'
|
---|
| 340 | )
|
---|
| 341 | )
|
---|
| 342 | }
|
---|
| 343 |
|
---|
| 344 | module.exports = { File, FileLike, isFileLike }
|
---|