mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
7.2 KiB
259 lines
7.2 KiB
'use strict'
|
|
|
|
const { iteratorMixin } = require('./util')
|
|
const { kEnumerableProperty } = require('../../core/util')
|
|
const { webidl } = require('../webidl')
|
|
const nodeUtil = require('node:util')
|
|
|
|
// https://xhr.spec.whatwg.org/#formdata
|
|
class FormData {
|
|
#state = []
|
|
|
|
constructor (form = undefined) {
|
|
webidl.util.markAsUncloneable(this)
|
|
|
|
if (form !== undefined) {
|
|
throw webidl.errors.conversionFailed({
|
|
prefix: 'FormData constructor',
|
|
argument: 'Argument 1',
|
|
types: ['undefined']
|
|
})
|
|
}
|
|
}
|
|
|
|
append (name, value, filename = undefined) {
|
|
webidl.brandCheck(this, FormData)
|
|
|
|
const prefix = 'FormData.append'
|
|
webidl.argumentLengthCheck(arguments, 2, prefix)
|
|
|
|
name = webidl.converters.USVString(name)
|
|
|
|
if (arguments.length === 3 || webidl.is.Blob(value)) {
|
|
value = webidl.converters.Blob(value, prefix, 'value')
|
|
|
|
if (filename !== undefined) {
|
|
filename = webidl.converters.USVString(filename)
|
|
}
|
|
} else {
|
|
value = webidl.converters.USVString(value)
|
|
}
|
|
|
|
// 1. Let value be value if given; otherwise blobValue.
|
|
|
|
// 2. Let entry be the result of creating an entry with
|
|
// name, value, and filename if given.
|
|
const entry = makeEntry(name, value, filename)
|
|
|
|
// 3. Append entry to this’s entry list.
|
|
this.#state.push(entry)
|
|
}
|
|
|
|
delete (name) {
|
|
webidl.brandCheck(this, FormData)
|
|
|
|
const prefix = 'FormData.delete'
|
|
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
|
|
name = webidl.converters.USVString(name)
|
|
|
|
// The delete(name) method steps are to remove all entries whose name
|
|
// is name from this’s entry list.
|
|
this.#state = this.#state.filter(entry => entry.name !== name)
|
|
}
|
|
|
|
get (name) {
|
|
webidl.brandCheck(this, FormData)
|
|
|
|
const prefix = 'FormData.get'
|
|
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
|
|
name = webidl.converters.USVString(name)
|
|
|
|
// 1. If there is no entry whose name is name in this’s entry list,
|
|
// then return null.
|
|
const idx = this.#state.findIndex((entry) => entry.name === name)
|
|
if (idx === -1) {
|
|
return null
|
|
}
|
|
|
|
// 2. Return the value of the first entry whose name is name from
|
|
// this’s entry list.
|
|
return this.#state[idx].value
|
|
}
|
|
|
|
getAll (name) {
|
|
webidl.brandCheck(this, FormData)
|
|
|
|
const prefix = 'FormData.getAll'
|
|
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
|
|
name = webidl.converters.USVString(name)
|
|
|
|
// 1. If there is no entry whose name is name in this’s entry list,
|
|
// then return the empty list.
|
|
// 2. Return the values of all entries whose name is name, in order,
|
|
// from this’s entry list.
|
|
return this.#state
|
|
.filter((entry) => entry.name === name)
|
|
.map((entry) => entry.value)
|
|
}
|
|
|
|
has (name) {
|
|
webidl.brandCheck(this, FormData)
|
|
|
|
const prefix = 'FormData.has'
|
|
webidl.argumentLengthCheck(arguments, 1, prefix)
|
|
|
|
name = webidl.converters.USVString(name)
|
|
|
|
// The has(name) method steps are to return true if there is an entry
|
|
// whose name is name in this’s entry list; otherwise false.
|
|
return this.#state.findIndex((entry) => entry.name === name) !== -1
|
|
}
|
|
|
|
set (name, value, filename = undefined) {
|
|
webidl.brandCheck(this, FormData)
|
|
|
|
const prefix = 'FormData.set'
|
|
webidl.argumentLengthCheck(arguments, 2, prefix)
|
|
|
|
name = webidl.converters.USVString(name)
|
|
|
|
if (arguments.length === 3 || webidl.is.Blob(value)) {
|
|
value = webidl.converters.Blob(value, prefix, 'value')
|
|
|
|
if (filename !== undefined) {
|
|
filename = webidl.converters.USVString(filename)
|
|
}
|
|
} else {
|
|
value = webidl.converters.USVString(value)
|
|
}
|
|
|
|
// The set(name, value) and set(name, blobValue, filename) method steps
|
|
// are:
|
|
|
|
// 1. Let value be value if given; otherwise blobValue.
|
|
|
|
// 2. Let entry be the result of creating an entry with name, value, and
|
|
// filename if given.
|
|
const entry = makeEntry(name, value, filename)
|
|
|
|
// 3. If there are entries in this’s entry list whose name is name, then
|
|
// replace the first such entry with entry and remove the others.
|
|
const idx = this.#state.findIndex((entry) => entry.name === name)
|
|
if (idx !== -1) {
|
|
this.#state = [
|
|
...this.#state.slice(0, idx),
|
|
entry,
|
|
...this.#state.slice(idx + 1).filter((entry) => entry.name !== name)
|
|
]
|
|
} else {
|
|
// 4. Otherwise, append entry to this’s entry list.
|
|
this.#state.push(entry)
|
|
}
|
|
}
|
|
|
|
[nodeUtil.inspect.custom] (depth, options) {
|
|
const state = this.#state.reduce((a, b) => {
|
|
if (a[b.name]) {
|
|
if (Array.isArray(a[b.name])) {
|
|
a[b.name].push(b.value)
|
|
} else {
|
|
a[b.name] = [a[b.name], b.value]
|
|
}
|
|
} else {
|
|
a[b.name] = b.value
|
|
}
|
|
|
|
return a
|
|
}, { __proto__: null })
|
|
|
|
options.depth ??= depth
|
|
options.colors ??= true
|
|
|
|
const output = nodeUtil.formatWithOptions(options, state)
|
|
|
|
// remove [Object null prototype]
|
|
return `FormData ${output.slice(output.indexOf(']') + 2)}`
|
|
}
|
|
|
|
/**
|
|
* @param {FormData} formData
|
|
*/
|
|
static getFormDataState (formData) {
|
|
return formData.#state
|
|
}
|
|
|
|
/**
|
|
* @param {FormData} formData
|
|
* @param {any[]} newState
|
|
*/
|
|
static setFormDataState (formData, newState) {
|
|
formData.#state = newState
|
|
}
|
|
}
|
|
|
|
const { getFormDataState, setFormDataState } = FormData
|
|
Reflect.deleteProperty(FormData, 'getFormDataState')
|
|
Reflect.deleteProperty(FormData, 'setFormDataState')
|
|
|
|
iteratorMixin('FormData', FormData, getFormDataState, 'name', 'value')
|
|
|
|
Object.defineProperties(FormData.prototype, {
|
|
append: kEnumerableProperty,
|
|
delete: kEnumerableProperty,
|
|
get: kEnumerableProperty,
|
|
getAll: kEnumerableProperty,
|
|
has: kEnumerableProperty,
|
|
set: kEnumerableProperty,
|
|
[Symbol.toStringTag]: {
|
|
value: 'FormData',
|
|
configurable: true
|
|
}
|
|
})
|
|
|
|
/**
|
|
* @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry
|
|
* @param {string} name
|
|
* @param {string|Blob} value
|
|
* @param {?string} filename
|
|
* @returns
|
|
*/
|
|
function makeEntry (name, value, filename) {
|
|
// 1. Set name to the result of converting name into a scalar value string.
|
|
// Note: This operation was done by the webidl converter USVString.
|
|
|
|
// 2. If value is a string, then set value to the result of converting
|
|
// value into a scalar value string.
|
|
if (typeof value === 'string') {
|
|
// Note: This operation was done by the webidl converter USVString.
|
|
} else {
|
|
// 3. Otherwise:
|
|
|
|
// 1. If value is not a File object, then set value to a new File object,
|
|
// representing the same bytes, whose name attribute value is "blob"
|
|
if (!webidl.is.File(value)) {
|
|
value = new File([value], 'blob', { type: value.type })
|
|
}
|
|
|
|
// 2. If filename is given, then set value to a new File object,
|
|
// representing the same bytes, whose name attribute is filename.
|
|
if (filename !== undefined) {
|
|
/** @type {FilePropertyBag} */
|
|
const options = {
|
|
type: value.type,
|
|
lastModified: value.lastModified
|
|
}
|
|
|
|
value = new File([value], filename, options)
|
|
}
|
|
}
|
|
|
|
// 4. Return an entry whose name is name and whose value is value.
|
|
return { name, value }
|
|
}
|
|
|
|
webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData)
|
|
|
|
module.exports = { FormData, makeEntry, setFormDataState }
|
|
|