1 |
|
2 |
|
3 |
|
4 | if (typeof Blob === 'function' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {
|
5 | const global = typeof window === 'object'
|
6 | ? window
|
7 | : typeof self === 'object' ? self : this
|
8 |
|
9 |
|
10 | const _FormData = global.FormData
|
11 |
|
12 |
|
13 | const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
|
14 | const _fetch = global.Request && global.fetch
|
15 | const _sendBeacon = global.navigator && global.navigator.sendBeacon
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | const stringTag = global.Symbol && Symbol.toStringTag
|
23 |
|
24 |
|
25 | if (stringTag) {
|
26 | if (!Blob.prototype[stringTag]) {
|
27 | Blob.prototype[stringTag] = 'Blob'
|
28 | }
|
29 |
|
30 | if ('File' in global && !File.prototype[stringTag]) {
|
31 | File.prototype[stringTag] = 'File'
|
32 | }
|
33 | }
|
34 |
|
35 |
|
36 | try {
|
37 | new File([], '')
|
38 | } catch (a) {
|
39 | global.File = function File (b, d, c) {
|
40 | const blob = new Blob(b, c)
|
41 | const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()
|
42 |
|
43 | Object.defineProperties(blob, {
|
44 | name: {
|
45 | value: d
|
46 | },
|
47 | lastModifiedDate: {
|
48 | value: t
|
49 | },
|
50 | lastModified: {
|
51 | value: +t
|
52 | },
|
53 | toString: {
|
54 | value () {
|
55 | return '[object File]'
|
56 | }
|
57 | }
|
58 | })
|
59 |
|
60 | if (stringTag) {
|
61 | Object.defineProperty(blob, stringTag, {
|
62 | value: 'File'
|
63 | })
|
64 | }
|
65 |
|
66 | return blob
|
67 | }
|
68 | }
|
69 |
|
70 | function normalizeValue ([value, filename]) {
|
71 | if (value instanceof Blob) {
|
72 |
|
73 |
|
74 | value = new File([value], filename, {
|
75 | type: value.type,
|
76 | lastModified: value.lastModified
|
77 | })
|
78 | }
|
79 |
|
80 | return value
|
81 | }
|
82 |
|
83 | function ensureArgs (args, expected) {
|
84 | if (args.length < expected) {
|
85 | throw new TypeError(`${expected} argument required, but only ${args.length} present.`)
|
86 | }
|
87 | }
|
88 |
|
89 | function normalizeArgs (name, value, filename) {
|
90 | return value instanceof Blob
|
91 |
|
92 | ? [String(name), value, filename !== undefined
|
93 | ? filename + ''
|
94 | : typeof value.name === 'string'
|
95 | ? value.name
|
96 | : 'blob']
|
97 |
|
98 |
|
99 | : [String(name), String(value)]
|
100 | }
|
101 |
|
102 |
|
103 |
|
104 | function normalizeLinefeeds (value) {
|
105 | return value.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n')
|
106 | }
|
107 |
|
108 | function each (arr, cb) {
|
109 | for (let i = 0; i < arr.length; i++) {
|
110 | cb(arr[i])
|
111 | }
|
112 | }
|
113 |
|
114 | |
115 |
|
116 |
|
117 | class FormDataPolyfill {
|
118 | |
119 |
|
120 |
|
121 |
|
122 |
|
123 | constructor (form) {
|
124 | this._data = Object.create(null)
|
125 |
|
126 | if (!form) return this
|
127 |
|
128 | const self = this
|
129 |
|
130 | each(form.elements, elm => {
|
131 | if (!elm.name || elm.disabled || elm.type === 'submit' || elm.type === 'button') return
|
132 |
|
133 | if (elm.type === 'file') {
|
134 | const files = elm.files && elm.files.length
|
135 | ? elm.files
|
136 | : [new File([], '', { type: 'application/octet-stream' })]
|
137 |
|
138 | each(files, file => {
|
139 | self.append(elm.name, file)
|
140 | })
|
141 | } else if (elm.type === 'select-multiple' || elm.type === 'select-one') {
|
142 | each(elm.options, opt => {
|
143 | !opt.disabled && opt.selected && self.append(elm.name, opt.value)
|
144 | })
|
145 | } else if (elm.type === 'checkbox' || elm.type === 'radio') {
|
146 | if (elm.checked) self.append(elm.name, elm.value)
|
147 | } else {
|
148 | const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value
|
149 | self.append(elm.name, value)
|
150 | }
|
151 | })
|
152 | }
|
153 |
|
154 | |
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | append (name, value, filename) {
|
163 | ensureArgs(arguments, 2)
|
164 | ;[name, value, filename] = normalizeArgs.apply(null, arguments)
|
165 | const map = this._data
|
166 |
|
167 | if (!map[name]) map[name] = []
|
168 |
|
169 | map[name].push([value, filename])
|
170 | }
|
171 |
|
172 | |
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | delete (name) {
|
179 | ensureArgs(arguments, 1)
|
180 | delete this._data[String(name)]
|
181 | }
|
182 |
|
183 | |
184 |
|
185 |
|
186 |
|
187 |
|
188 | * entries () {
|
189 | const map = this._data
|
190 |
|
191 | for (let name in map) {
|
192 | for (let value of map[name]) {
|
193 | yield [name, normalizeValue(value)]
|
194 | }
|
195 | }
|
196 | }
|
197 |
|
198 | |
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | forEach (callback, thisArg) {
|
206 | ensureArgs(arguments, 1)
|
207 | for (let [name, value] of this) {
|
208 | callback.call(thisArg, value, name, this)
|
209 | }
|
210 | }
|
211 |
|
212 | |
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 | get (name) {
|
220 | ensureArgs(arguments, 1)
|
221 | const map = this._data
|
222 | name = String(name)
|
223 | return map[name] ? normalizeValue(map[name][0]) : null
|
224 | }
|
225 |
|
226 | |
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | getAll (name) {
|
233 | ensureArgs(arguments, 1)
|
234 | return (this._data[String(name)] || []).map(normalizeValue)
|
235 | }
|
236 |
|
237 | |
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 | has (name) {
|
244 | ensureArgs(arguments, 1)
|
245 | return String(name) in this._data
|
246 | }
|
247 |
|
248 | |
249 |
|
250 |
|
251 |
|
252 |
|
253 | * keys () {
|
254 | for (let [name] of this) {
|
255 | yield name
|
256 | }
|
257 | }
|
258 |
|
259 | |
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 | set (name, value, filename) {
|
268 | ensureArgs(arguments, 2)
|
269 | const args = normalizeArgs.apply(null, arguments)
|
270 | this._data[args[0]] = [[args[1], args[2]]]
|
271 | }
|
272 |
|
273 | |
274 |
|
275 |
|
276 |
|
277 |
|
278 | * values () {
|
279 | for (let [, value] of this) {
|
280 | yield value
|
281 | }
|
282 | }
|
283 |
|
284 | |
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | ['_asNative'] () {
|
291 | const fd = new _FormData()
|
292 |
|
293 | for (let [name, value] of this) {
|
294 | fd.append(name, value)
|
295 | }
|
296 |
|
297 | return fd
|
298 | }
|
299 |
|
300 | |
301 |
|
302 |
|
303 |
|
304 |
|
305 | ['_blob'] () {
|
306 | const boundary = '----formdata-polyfill-' + Math.random()
|
307 | const chunks = []
|
308 |
|
309 | for (let [name, value] of this) {
|
310 | chunks.push(`--${boundary}\r\n`)
|
311 |
|
312 | if (value instanceof Blob) {
|
313 | chunks.push(
|
314 | `Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n`,
|
315 | `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
|
316 | value,
|
317 | '\r\n'
|
318 | )
|
319 | } else {
|
320 | chunks.push(
|
321 | `Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
|
322 | )
|
323 | }
|
324 | }
|
325 |
|
326 | chunks.push(`--${boundary}--`)
|
327 |
|
328 | return new Blob(chunks, {
|
329 | type: 'multipart/form-data; boundary=' + boundary
|
330 | })
|
331 | }
|
332 |
|
333 | |
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 | [Symbol.iterator] () {
|
340 | return this.entries()
|
341 | }
|
342 |
|
343 | |
344 |
|
345 |
|
346 |
|
347 |
|
348 | toString () {
|
349 | return '[object FormData]'
|
350 | }
|
351 | }
|
352 |
|
353 | if (stringTag) {
|
354 | |
355 |
|
356 |
|
357 |
|
358 | FormDataPolyfill.prototype[stringTag] = 'FormData'
|
359 | }
|
360 |
|
361 |
|
362 | if (_send) {
|
363 | const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader
|
364 |
|
365 | |
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 | global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
|
372 | if (name.toLowerCase() === 'content-type') this._hasContentType = true
|
373 | return setRequestHeader.call(this, name, value)
|
374 | }
|
375 |
|
376 | |
377 |
|
378 |
|
379 |
|
380 |
|
381 | global.XMLHttpRequest.prototype.send = function (data) {
|
382 |
|
383 |
|
384 |
|
385 | if (data instanceof FormDataPolyfill) {
|
386 | const blob = data['_blob']()
|
387 |
|
388 |
|
389 | if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type)
|
390 | _send.call(this, blob)
|
391 | } else {
|
392 | _send.call(this, data)
|
393 | }
|
394 | }
|
395 | }
|
396 |
|
397 |
|
398 | if (_fetch) {
|
399 | const _fetch = global.fetch
|
400 |
|
401 | global.fetch = function (input, init) {
|
402 | if (init && init.body && init.body instanceof FormDataPolyfill) {
|
403 | init.body = init.body['_blob']()
|
404 | }
|
405 |
|
406 | return _fetch.call(this, input, init)
|
407 | }
|
408 | }
|
409 |
|
410 |
|
411 | if (_sendBeacon) {
|
412 | global.navigator.sendBeacon = function (url, data) {
|
413 | if (data instanceof FormDataPolyfill) {
|
414 | data = data['_asNative']()
|
415 | }
|
416 | return _sendBeacon.call(this, url, data)
|
417 | }
|
418 | }
|
419 |
|
420 | global['FormData'] = FormDataPolyfill
|
421 | }
|