[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 /* formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */ 2 3 /* global FormData self Blob File */ 4 /* eslint-disable no-inner-declarations */ 5 6 if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) { 7 const global = typeof globalThis === 'object' 8 ? globalThis 9 : typeof window === 'object' 10 ? window 11 : typeof self === 'object' ? self : this 12 13 // keep a reference to native implementation 14 const _FormData = global.FormData 15 16 // To be monkey patched 17 const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send 18 const _fetch = global.Request && global.fetch 19 const _sendBeacon = global.navigator && global.navigator.sendBeacon 20 // Might be a worker thread... 21 const _match = global.Element && global.Element.prototype 22 23 // Unable to patch Request/Response constructor correctly #109 24 // only way is to use ES6 class extend 25 // https://github.com/babel/babel/issues/1966 26 27 const stringTag = global.Symbol && Symbol.toStringTag 28 29 // Add missing stringTags to blob and files 30 if (stringTag) { 31 if (!Blob.prototype[stringTag]) { 32 Blob.prototype[stringTag] = 'Blob' 33 } 34 35 if ('File' in global && !File.prototype[stringTag]) { 36 File.prototype[stringTag] = 'File' 37 } 38 } 39 40 // Fix so you can construct your own File 41 try { 42 new File([], '') // eslint-disable-line 43 } catch (a) { 44 global.File = function File (b, d, c) { 45 const blob = new Blob(b, c || {}) 46 const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date() 47 48 Object.defineProperties(blob, { 49 name: { 50 value: d 51 }, 52 lastModified: { 53 value: +t 54 }, 55 toString: { 56 value () { 57 return '[object File]' 58 } 59 } 60 }) 61 62 if (stringTag) { 63 Object.defineProperty(blob, stringTag, { 64 value: 'File' 65 }) 66 } 67 68 return blob 69 } 70 } 71 72 function ensureArgs (args, expected) { 73 if (args.length < expected) { 74 throw new TypeError(`$expected} argument required, but only $args.length} present.`) 75 } 76 } 77 78 /** 79 * @param {string} name 80 * @param {string | undefined} filename 81 * @returns {[string, File|string]} 82 */ 83 function normalizeArgs (name, value, filename) { 84 if (value instanceof Blob) { 85 filename = filename !== undefined 86 ? String(filename + '') 87 : typeof value.name === 'string' 88 ? value.name 89 : 'blob' 90 91 if (value.name !== filename || Object.prototype.toString.call(value) === '[object Blob]') { 92 value = new File([value], filename) 93 } 94 return [String(name), value] 95 } 96 return [String(name), String(value)] 97 } 98 99 // normalize line feeds for textarea 100 // https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation 101 function normalizeLinefeeds (value) { 102 return value.replace(/\r?\n|\r/g, '\r\n') 103 } 104 105 /** 106 * @template T 107 * @param {ArrayLike<T>} arr 108 * @param {{ (elm: T): void; }} cb 109 */ 110 function each (arr, cb) { 111 for (let i = 0; i < arr.length; i++) { 112 cb(arr[i]) 113 } 114 } 115 116 const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22') 117 118 /** 119 * @implements {Iterable} 120 */ 121 class FormDataPolyfill { 122 /** 123 * FormData class 124 * 125 * @param {HTMLFormElement=} form 126 */ 127 constructor (form) { 128 /** @type {[string, string|File][]} */ 129 this._data = [] 130 131 const self = this 132 form && each(form.elements, (/** @type {HTMLInputElement} */ elm) => { 133 if ( 134 !elm.name || 135 elm.disabled || 136 elm.type === 'submit' || 137 elm.type === 'button' || 138 elm.matches('form fieldset[disabled] *') 139 ) return 140 141 if (elm.type === 'file') { 142 const files = elm.files && elm.files.length 143 ? elm.files 144 : [new File([], '', { type: 'application/octet-stream' })] // #78 145 146 each(files, file => { 147 self.append(elm.name, file) 148 }) 149 } else if (elm.type === 'select-multiple' || elm.type === 'select-one') { 150 each(elm.options, opt => { 151 !opt.disabled && opt.selected && self.append(elm.name, opt.value) 152 }) 153 } else if (elm.type === 'checkbox' || elm.type === 'radio') { 154 if (elm.checked) self.append(elm.name, elm.value) 155 } else { 156 const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value 157 self.append(elm.name, value) 158 } 159 }) 160 } 161 162 /** 163 * Append a field 164 * 165 * @param {string} name field name 166 * @param {string|Blob|File} value string / blob / file 167 * @param {string=} filename filename to use with blob 168 * @return {undefined} 169 */ 170 append (name, value, filename) { 171 ensureArgs(arguments, 2) 172 this._data.push(normalizeArgs(name, value, filename)) 173 } 174 175 /** 176 * Delete all fields values given name 177 * 178 * @param {string} name Field name 179 * @return {undefined} 180 */ 181 delete (name) { 182 ensureArgs(arguments, 1) 183 const result = [] 184 name = String(name) 185 186 each(this._data, entry => { 187 entry[0] !== name && result.push(entry) 188 }) 189 190 this._data = result 191 } 192 193 /** 194 * Iterate over all fields as [name, value] 195 * 196 * @return {Iterator} 197 */ 198 * entries () { 199 for (var i = 0; i < this._data.length; i++) { 200 yield this._data[i] 201 } 202 } 203 204 /** 205 * Iterate over all fields 206 * 207 * @param {Function} callback Executed for each item with parameters (value, name, thisArg) 208 * @param {Object=} thisArg `this` context for callback function 209 */ 210 forEach (callback, thisArg) { 211 ensureArgs(arguments, 1) 212 for (const [name, value] of this) { 213 callback.call(thisArg, value, name, this) 214 } 215 } 216 217 /** 218 * Return first field value given name 219 * or null if non existent 220 * 221 * @param {string} name Field name 222 * @return {string|File|null} value Fields value 223 */ 224 get (name) { 225 ensureArgs(arguments, 1) 226 const entries = this._data 227 name = String(name) 228 for (let i = 0; i < entries.length; i++) { 229 if (entries[i][0] === name) { 230 return entries[i][1] 231 } 232 } 233 return null 234 } 235 236 /** 237 * Return all fields values given name 238 * 239 * @param {string} name Fields name 240 * @return {Array} [{String|File}] 241 */ 242 getAll (name) { 243 ensureArgs(arguments, 1) 244 const result = [] 245 name = String(name) 246 each(this._data, data => { 247 data[0] === name && result.push(data[1]) 248 }) 249 250 return result 251 } 252 253 /** 254 * Check for field name existence 255 * 256 * @param {string} name Field name 257 * @return {boolean} 258 */ 259 has (name) { 260 ensureArgs(arguments, 1) 261 name = String(name) 262 for (let i = 0; i < this._data.length; i++) { 263 if (this._data[i][0] === name) { 264 return true 265 } 266 } 267 return false 268 } 269 270 /** 271 * Iterate over all fields name 272 * 273 * @return {Iterator} 274 */ 275 * keys () { 276 for (const [name] of this) { 277 yield name 278 } 279 } 280 281 /** 282 * Overwrite all values given name 283 * 284 * @param {string} name Filed name 285 * @param {string} value Field value 286 * @param {string=} filename Filename (optional) 287 */ 288 set (name, value, filename) { 289 ensureArgs(arguments, 2) 290 name = String(name) 291 /** @type {[string, string|File][]} */ 292 const result = [] 293 const args = normalizeArgs(name, value, filename) 294 let replace = true 295 296 // - replace the first occurrence with same name 297 // - discards the remaining with same name 298 // - while keeping the same order items where added 299 each(this._data, data => { 300 data[0] === name 301 ? replace && (replace = !result.push(args)) 302 : result.push(data) 303 }) 304 305 replace && result.push(args) 306 307 this._data = result 308 } 309 310 /** 311 * Iterate over all fields 312 * 313 * @return {Iterator} 314 */ 315 * values () { 316 for (const [, value] of this) { 317 yield value 318 } 319 } 320 321 /** 322 * Return a native (perhaps degraded) FormData with only a `append` method 323 * Can throw if it's not supported 324 * 325 * @return {FormData} 326 */ 327 ['_asNative'] () { 328 const fd = new _FormData() 329 330 for (const [name, value] of this) { 331 fd.append(name, value) 332 } 333 334 return fd 335 } 336 337 /** 338 * [_blob description] 339 * 340 * @return {Blob} [description] 341 */ 342 ['_blob'] () { 343 const boundary = '----formdata-polyfill-' + Math.random(), 344 chunks = [], 345 p = `--$boundary}\r\nContent-Disposition: form-data; name="` 346 this.forEach((value, name) => typeof value == 'string' 347 ? chunks.push(p + escape(normalizeLinefeeds(name)) + `"\r\n\r\n$normalizeLinefeeds(value)}\r\n`) 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 chunks.push(`--$boundary}--`) 350 return new Blob(chunks, { 351 type: "multipart/form-data; boundary=" + boundary 352 }) 353 } 354 355 /** 356 * The class itself is iterable 357 * alias for formdata.entries() 358 * 359 * @return {Iterator} 360 */ 361 [Symbol.iterator] () { 362 return this.entries() 363 } 364 365 /** 366 * Create the default string description. 367 * 368 * @return {string} [object FormData] 369 */ 370 toString () { 371 return '[object FormData]' 372 } 373 } 374 375 if (_match && !_match.matches) { 376 _match.matches = 377 _match.matchesSelector || 378 _match.mozMatchesSelector || 379 _match.msMatchesSelector || 380 _match.oMatchesSelector || 381 _match.webkitMatchesSelector || 382 function (s) { 383 var matches = (this.document || this.ownerDocument).querySelectorAll(s) 384 var i = matches.length 385 while (--i >= 0 && matches.item(i) !== this) {} 386 return i > -1 387 } 388 } 389 390 if (stringTag) { 391 /** 392 * Create the default string description. 393 * It is accessed internally by the Object.prototype.toString(). 394 */ 395 FormDataPolyfill.prototype[stringTag] = 'FormData' 396 } 397 398 // Patch xhr's send method to call _blob transparently 399 if (_send) { 400 const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader 401 402 global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) { 403 setRequestHeader.call(this, name, value) 404 if (name.toLowerCase() === 'content-type') this._hasContentType = true 405 } 406 407 global.XMLHttpRequest.prototype.send = function (data) { 408 // need to patch send b/c old IE don't send blob's type (#44) 409 if (data instanceof FormDataPolyfill) { 410 const blob = data['_blob']() 411 if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type) 412 _send.call(this, blob) 413 } else { 414 _send.call(this, data) 415 } 416 } 417 } 418 419 // Patch fetch's function to call _blob transparently 420 if (_fetch) { 421 global.fetch = function (input, init) { 422 if (init && init.body && init.body instanceof FormDataPolyfill) { 423 init.body = init.body['_blob']() 424 } 425 426 return _fetch.call(this, input, init) 427 } 428 } 429 430 // Patch navigator.sendBeacon to use native FormData 431 if (_sendBeacon) { 432 global.navigator.sendBeacon = function (url, data) { 433 if (data instanceof FormDataPolyfill) { 434 data = data['_asNative']() 435 } 436 return _sendBeacon.call(this, url, data) 437 } 438 } 439 440 global['FormData'] = FormDataPolyfill 441 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Dec 25 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |