Skip to content

Commit

Permalink
Proper escape names
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmywarting committed Jun 17, 2021
1 parent 1c6fa52 commit d26bf54
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 62 deletions.
36 changes: 12 additions & 24 deletions FormData.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData
}
}

const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')

/**
* @implements {Iterable}
*/
Expand Down Expand Up @@ -327,31 +329,17 @@ if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData
* @return {Blob} [description]
*/
['_blob'] () {
const boundary = '----formdata-polyfill-' + Math.random()
const chunks = []

for (const [name, value] of this) {
chunks.push(`--${boundary}\r\n`)

if (value instanceof Blob) {
chunks.push(
`Content-Disposition: form-data; name="${name}"; filename="${value.name}"\r\n` +
`Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
value,
'\r\n'
)
} else {
chunks.push(
`Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
)
}
}

chunks.push(`--${boundary}--`)
const boundary = '----formdata-polyfill-' + Math.random(),
chunks = [],
p = `--${boundary}\r\nContent-Disposition: form-data; name="`
this.forEach((value, name) => typeof value == 'string'
? chunks.push(p + escape(name) + `"\r\n\r\n${value}\r\n`)
: chunks.push(p + escape(name) + `"; filename="${escape(value.name)}"\r\nContent-Type: ${value.type||"application/octet-stream"}\r\n\r\n`, value, `\r\n`))
chunks.push(`--${boundary}--`)
return new Blob(chunks, {
type: "multipart/form-data; boundary=" + boundary
})

return new Blob(chunks, {
type: 'multipart/form-data; boundary=' + boundary
})
}

/**
Expand Down
24 changes: 11 additions & 13 deletions esm.min.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import B from 'fetch-blob'
import C from 'fetch-blob'
import F from 'fetch-blob/file.js'

var {toStringTag:t,iterator:i,hasInstance:h}=Symbol,
m='append,set,get,getAll,delete,keys,values,entries,forEach,constructor'.split(','),
f=(a,b,c)=>(a+='',/^(Blob|File)$/.test(b?.[t])?[(c=c!==void 0?c+'':b[t]=='File'?b.name:'blob',a),b.name!==c||b[t]=='blob'?new F([b],c):b]:[a,b+''])
f=(a,b,c)=>(a+='',/^(Blob|File)$/.test(b?.[t])?[(c=c!==void 0?c+'':b[t]=='File'?b.name:'blob',a),b.name!==c||b[t]=='blob'?new F([b],c):b]:[a,b+'']),
e=c=>c.replace(/\n/g,'%0A').replace(/\r/g,'%0D').replace(/"/g,'%22')

export const File = F

Expand All @@ -20,15 +21,12 @@ forEach(a,b){for(var [c,d]of this)a.call(b,d,c,this)}
set(...a){var b=[],c=!0;a=f(...a);this.#d.forEach(d=>{d[0]===a[0]?c&&(c=!b.push(a)):b.push(d)});c&&b.push(a);this.#d=b}
*entries(){yield*this.#d}
*keys(){for(var[a]of this)yield a}
*values(){for(var[,a]of this)yield a}
}
*values(){for(var[,a]of this)yield a}}

export function formDataToBlob(formData){
var b='----formdata-'+Math.random(),c=[],d=`--${b}\r\nContent-Disposition: form-data; name="`

formData.forEach((v,n)=>typeof v==='string'
?c.push(`${d}${n}"\r\n\r\n${v}\r\n--${b}--`)
:c.push(`${d}${n}"; filename="${v.name}"\r\nContent-Type: ${v.type || 'application/octet-stream'}\r\n\r\n`,v,`\r\n--${b}--`))

return new B(c,{type:'multipart/form-data; boundary='+b})
}
export function formDataToBlob (F,B=C){
var b='----formdata-'+Math.random(),c=[],p=`--${b}\r\nContent-Disposition: form-data; name="`
F.forEach((v,n)=>typeof v=='string'
?c.push(p+e(n)+`"\r\n\r\n${v}\r\n`)
:c.push(p+e(n)+`"; filename="${e(v.name)}"\r\nContent-Type: ${v.type||"application/octet-stream"}\r\n\r\n`, v, '\r\n'))
c.push(`--${b}--`)
return new B(c,{type:"multipart/form-data; boundary="+b})}
23 changes: 14 additions & 9 deletions formdata-to-blob.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')

/**
* pure function to convert any formData instance to a Blob
* instances syncronus without reading all of the files
Expand All @@ -6,22 +8,25 @@
* @param {Blob|*} [BlobClass=Blob] the Blob class to use when constructing it
*/
export function formDataToBlob (formData, BlobClass = Blob) {
const boundary = '----formdata-' + Math.random()
const boundary = ('----formdata-polyfill-' + Math.random())
const chunks = []
const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`

for (const [name, value] of formData) {
chunks.push(`--${boundary}\r\nContent-Disposition: form-data; name="${name}"`)

typeof value === 'string'
? chunks.push(`\r\n\r\n${value}\r\n--${boundary}--`)
: chunks.push(
`; filename="${value.name}"\r\n` +
for (let [name, value] of formData) {
if (typeof value === 'string') {
chunks.push(prefix + escape(name) + `"\r\n\r\n${value}\r\n`)
} else {
chunks.push(
prefix + escape(name) + `"; filename="${escape(value.name)}"\r\n` +
`Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
value,
`\r\n--${boundary}--`
'\r\n'
)
}
}

chunks.push(`--${boundary}--`)

return new BlobClass(chunks, {
type: 'multipart/form-data; boundary=' + boundary
})
Expand Down
16 changes: 8 additions & 8 deletions formdata.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "formdata-polyfill",
"version": "4.0.3",
"version": "4.0.4",
"description": "HTML5 `FormData` for Browsers and Node.",
"type": "module",
"main": "formdata.min.js",
"scripts": {
"build": "node build",
"_prepare": "npm run build && npm run test",
"_test": "karma start --single-run --browsers ChromeHeadless karma.conf.js"
"prepare": "npm run build && npm run test",
"test": "node test/test-esm.js"
},
"repository": {
"type": "git",
Expand Down
18 changes: 17 additions & 1 deletion test/test-esm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Blob from 'fetch-blob'
import {FormData, formDataToBlob, File} from '../esm.min.js'
import File from 'fetch-blob/file.js'
import {FormData, formDataToBlob} from '../esm.min.js'

console.assert(
formDataToBlob(createFormData(['key', 'value1'])).size > 100,
Expand Down Expand Up @@ -38,6 +39,21 @@ for (let x of [undefined, null, 0, '', {}, false, true, globalThis, NaN]) {
)
}


// #120 escapes keys when encoding FormData
for (let [key, expected] of [['key\n', 'key%0A'], ['key\r', 'key%0D'], ['key"', 'key%22']]) {
const fd = createFormData([key, 'value'])
const str = await formDataToBlob(fd).text()
console.assert(str.includes(expected) === true)
}

// #120 escapes filename encoding FormData
for (let [filename, expected] of [['val\nue', 'val%0Aue'], ['val%0Aue', 'val%0Aue']]) {
const fd = createFormData(['key', new File([], filename)])
const str = await formDataToBlob(fd).text()
console.assert(str.includes(expected))
}

{
// Appending a empty blob with a name should convert it to a File
var val = createFormData(['key', new Blob(), 'blank.txt']).get('key')
Expand Down
Loading

0 comments on commit d26bf54

Please sign in to comment.