wp/wp-includes/js/dist/vendor/wp-polyfill-formdata.js
changeset 19 3d72ae0968f4
parent 18 be944660c56a
equal deleted inserted replaced
18:be944660c56a 19:3d72ae0968f4
       
     1 /* formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
       
     2 
     1 /* global FormData self Blob File */
     3 /* global FormData self Blob File */
     2 /* eslint-disable no-inner-declarations */
     4 /* eslint-disable no-inner-declarations */
     3 
     5 
     4 if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {
     6 if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {
     5   const global = typeof globalThis === 'object'
     7   const global = typeof globalThis === 'object'
    38   // Fix so you can construct your own File
    40   // Fix so you can construct your own File
    39   try {
    41   try {
    40     new File([], '') // eslint-disable-line
    42     new File([], '') // eslint-disable-line
    41   } catch (a) {
    43   } catch (a) {
    42     global.File = function File (b, d, c) {
    44     global.File = function File (b, d, c) {
    43       const blob = new Blob(b, c)
    45       const blob = new Blob(b, c || {})
    44       const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()
    46       const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()
    45 
    47 
    46       Object.defineProperties(blob, {
    48       Object.defineProperties(blob, {
    47         name: {
    49         name: {
    48           value: d
    50           value: d
    49         },
       
    50         lastModifiedDate: {
       
    51           value: t
       
    52         },
    51         },
    53         lastModified: {
    52         lastModified: {
    54           value: +t
    53           value: +t
    55         },
    54         },
    56         toString: {
    55         toString: {
    68 
    67 
    69       return blob
    68       return blob
    70     }
    69     }
    71   }
    70   }
    72 
    71 
    73   function normalizeValue ([name, value, filename]) {
       
    74     if (value instanceof Blob) {
       
    75       // Should always returns a new File instance
       
    76       // console.assert(fd.get(x) !== fd.get(x))
       
    77       value = new File([value], filename, {
       
    78         type: value.type,
       
    79         lastModified: value.lastModified
       
    80       })
       
    81     }
       
    82 
       
    83     return [name, value]
       
    84   }
       
    85 
       
    86   function ensureArgs (args, expected) {
    72   function ensureArgs (args, expected) {
    87     if (args.length < expected) {
    73     if (args.length < expected) {
    88       throw new TypeError(`${expected} argument required, but only ${args.length} present.`)
    74       throw new TypeError(`${expected} argument required, but only ${args.length} present.`)
    89     }
    75     }
    90   }
    76   }
    91 
    77 
       
    78   /**
       
    79    * @param {string} name
       
    80    * @param {string | undefined} filename
       
    81    * @returns {[string, File|string]}
       
    82    */
    92   function normalizeArgs (name, value, filename) {
    83   function normalizeArgs (name, value, filename) {
    93     return value instanceof Blob
    84     if (value instanceof Blob) {
    94       // normalize name and filename if adding an attachment
    85       filename = filename !== undefined
    95       ? [String(name), value, filename !== undefined
    86       ? String(filename + '')
    96         ? filename + '' // Cast filename to string if 3th arg isn't undefined
    87       : typeof value.name === 'string'
    97         : typeof value.name === 'string' // if name prop exist
    88       ? value.name
    98           ? value.name // Use File.name
    89       : 'blob'
    99           : 'blob'] // otherwise fallback to Blob
    90 
   100 
    91       if (value.name !== filename || Object.prototype.toString.call(value) === '[object Blob]') {
   101       // If no attachment, just cast the args to strings
    92         value = new File([value], filename)
   102       : [String(name), String(value)]
    93       }
   103   }
    94       return [String(name), value]
   104 
    95     }
   105   // normalize linefeeds for textareas
    96     return [String(name), String(value)]
       
    97   }
       
    98 
       
    99   // normalize line feeds for textarea
   106   // https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation
   100   // https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation
   107   function normalizeLinefeeds (value) {
   101   function normalizeLinefeeds (value) {
   108     return value.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n')
   102     return value.replace(/\r?\n|\r/g, '\r\n')
   109   }
   103   }
   110 
   104 
       
   105   /**
       
   106    * @template T
       
   107    * @param {ArrayLike<T>} arr
       
   108    * @param {{ (elm: T): void; }} cb
       
   109    */
   111   function each (arr, cb) {
   110   function each (arr, cb) {
   112     for (let i = 0; i < arr.length; i++) {
   111     for (let i = 0; i < arr.length; i++) {
   113       cb(arr[i])
   112       cb(arr[i])
   114     }
   113     }
   115   }
   114   }
       
   115 
       
   116   const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
   116 
   117 
   117   /**
   118   /**
   118    * @implements {Iterable}
   119    * @implements {Iterable}
   119    */
   120    */
   120   class FormDataPolyfill {
   121   class FormDataPolyfill {
   121     /**
   122     /**
   122      * FormData class
   123      * FormData class
   123      *
   124      *
   124      * @param {HTMLElement=} form
   125      * @param {HTMLFormElement=} form
   125      */
   126      */
   126     constructor (form) {
   127     constructor (form) {
       
   128       /** @type {[string, string|File][]} */
   127       this._data = []
   129       this._data = []
   128 
   130 
   129       const self = this
   131       const self = this
   130 
   132       form && each(form.elements, (/** @type {HTMLInputElement} */ elm) => {
   131       form && each(form.elements, elm => {
       
   132         if (
   133         if (
   133           !elm.name ||
   134           !elm.name ||
   134           elm.disabled ||
   135           elm.disabled ||
   135           elm.type === 'submit' ||
   136           elm.type === 'submit' ||
   136           elm.type === 'button' ||
   137           elm.type === 'button' ||
   194      *
   195      *
   195      * @return {Iterator}
   196      * @return {Iterator}
   196      */
   197      */
   197     * entries () {
   198     * entries () {
   198       for (var i = 0; i < this._data.length; i++) {
   199       for (var i = 0; i < this._data.length; i++) {
   199         yield normalizeValue(this._data[i])
   200         yield this._data[i]
   200       }
   201       }
   201     }
   202     }
   202 
   203 
   203     /**
   204     /**
   204      * Iterate over all fields
   205      * Iterate over all fields
   205      *
   206      *
   206      * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
   207      * @param   {Function}  callback  Executed for each item with parameters (value, name, thisArg)
   207      * @param   {Object=}   thisArg   `this` context for callback function
   208      * @param   {Object=}   thisArg   `this` context for callback function
   208      * @return  {undefined}
       
   209      */
   209      */
   210     forEach (callback, thisArg) {
   210     forEach (callback, thisArg) {
   211       ensureArgs(arguments, 1)
   211       ensureArgs(arguments, 1)
   212       for (const [name, value] of this) {
   212       for (const [name, value] of this) {
   213         callback.call(thisArg, value, name, this)
   213         callback.call(thisArg, value, name, this)
   214       }
   214       }
   215     }
   215     }
   216 
   216 
   217     /**
   217     /**
   218      * Return first field value given name
   218      * Return first field value given name
   219      * or null if non existen
   219      * or null if non existent
   220      *
   220      *
   221      * @param   {string}  name      Field name
   221      * @param   {string}  name      Field name
   222      * @return  {string|File|null}  value Fields value
   222      * @return  {string|File|null}  value Fields value
   223      */
   223      */
   224     get (name) {
   224     get (name) {
   225       ensureArgs(arguments, 1)
   225       ensureArgs(arguments, 1)
   226       const entries = this._data
   226       const entries = this._data
   227       name = String(name)
   227       name = String(name)
   228       for (let i = 0; i < entries.length; i++) {
   228       for (let i = 0; i < entries.length; i++) {
   229         if (entries[i][0] === name) {
   229         if (entries[i][0] === name) {
   230           return normalizeValue(entries[i])[1]
   230           return entries[i][1]
   231         }
   231         }
   232       }
   232       }
   233       return null
   233       return null
   234     }
   234     }
   235 
   235 
   242     getAll (name) {
   242     getAll (name) {
   243       ensureArgs(arguments, 1)
   243       ensureArgs(arguments, 1)
   244       const result = []
   244       const result = []
   245       name = String(name)
   245       name = String(name)
   246       each(this._data, data => {
   246       each(this._data, data => {
   247         data[0] === name && result.push(normalizeValue(data)[1])
   247         data[0] === name && result.push(data[1])
   248       })
   248       })
   249 
   249 
   250       return result
   250       return result
   251     }
   251     }
   252 
   252 
   282      * Overwrite all values given name
   282      * Overwrite all values given name
   283      *
   283      *
   284      * @param   {string}    name      Filed name
   284      * @param   {string}    name      Filed name
   285      * @param   {string}    value     Field value
   285      * @param   {string}    value     Field value
   286      * @param   {string=}   filename  Filename (optional)
   286      * @param   {string=}   filename  Filename (optional)
   287      * @return  {undefined}
       
   288      */
   287      */
   289     set (name, value, filename) {
   288     set (name, value, filename) {
   290       ensureArgs(arguments, 2)
   289       ensureArgs(arguments, 2)
   291       name = String(name)
   290       name = String(name)
       
   291       /** @type {[string, string|File][]} */
   292       const result = []
   292       const result = []
   293       const args = normalizeArgs(name, value, filename)
   293       const args = normalizeArgs(name, value, filename)
   294       let replace = true
   294       let replace = true
   295 
   295 
   296       // - replace the first occurrence with same name
   296       // - replace the first occurrence with same name
   297       // - discards the remaning with same name
   297       // - discards the remaining with same name
   298       // - while keeping the same order items where added
   298       // - while keeping the same order items where added
   299       each(this._data, data => {
   299       each(this._data, data => {
   300         data[0] === name
   300         data[0] === name
   301           ? replace && (replace = !result.push(args))
   301           ? replace && (replace = !result.push(args))
   302           : result.push(data)
   302           : result.push(data)
   338      * [_blob description]
   338      * [_blob description]
   339      *
   339      *
   340      * @return {Blob} [description]
   340      * @return {Blob} [description]
   341      */
   341      */
   342     ['_blob'] () {
   342     ['_blob'] () {
   343       const boundary = '----formdata-polyfill-' + Math.random()
   343         const boundary = '----formdata-polyfill-' + Math.random(),
   344       const chunks = []
   344           chunks = [],
   345 
   345           p = `--${boundary}\r\nContent-Disposition: form-data; name="`
   346       for (const [name, value] of this) {
   346         this.forEach((value, name) => typeof value == 'string'
   347         chunks.push(`--${boundary}\r\n`)
   347           ? chunks.push(p + escape(normalizeLinefeeds(name)) + `"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
   348 
   348           : chunks.push(p + escape(normalizeLinefeeds(name)) + `"; filename="${escape(value.name)}"\r\nContent-Type: ${value.type||"application/octet-stream"}\r\n\r\n`, value, `\r\n`))
   349         if (value instanceof Blob) {
   349         chunks.push(`--${boundary}--`)
   350           chunks.push(
   350         return new Blob(chunks, {
   351             `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n` +
   351           type: "multipart/form-data; boundary=" + boundary
   352             `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
   352         })
   353             value,
       
   354             '\r\n'
       
   355           )
       
   356         } else {
       
   357           chunks.push(
       
   358             `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
       
   359           )
       
   360         }
       
   361       }
       
   362 
       
   363       chunks.push(`--${boundary}--`)
       
   364 
       
   365       return new Blob(chunks, {
       
   366         type: 'multipart/form-data; boundary=' + boundary
       
   367       })
       
   368     }
   353     }
   369 
   354 
   370     /**
   355     /**
   371      * The class itself is iterable
   356      * The class itself is iterable
   372      * alias for formdata.entries()
   357      * alias for formdata.entries()
   373      *
   358      *
   374      * @return  {Iterator}
   359      * @return {Iterator}
   375      */
   360      */
   376     [Symbol.iterator] () {
   361     [Symbol.iterator] () {
   377       return this.entries()
   362       return this.entries()
   378     }
   363     }
   379 
   364