file.js 9.3 KB

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