diff --git a/FormData.js b/FormData.js index 32bd5fc..68c80fe 100644 --- a/FormData.js +++ b/FormData.js @@ -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} */ @@ -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 - }) } /** diff --git a/esm.min.js b/esm.min.js index c040f38..a86e6fa 100644 --- a/esm.min.js +++ b/esm.min.js @@ -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 @@ -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})} diff --git a/formdata-to-blob.js b/formdata-to-blob.js index db5b74c..cae087b 100644 --- a/formdata-to-blob.js +++ b/formdata-to-blob.js @@ -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 @@ -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 }) diff --git a/formdata.min.js b/formdata.min.js index e30958f..890829c 100644 --- a/formdata.min.js +++ b/formdata.min.js @@ -1,7 +1,7 @@ ;(function(){var h;function l(a){var c=0;return function(){return c>>0)+"_",e=0;return c}); -r("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var c="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),b=0;b 100, @@ -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') diff --git a/test/test-polyfill.html b/test/test-polyfill.html index f0144be..c4127ef 100644 --- a/test/test-polyfill.html +++ b/test/test-polyfill.html @@ -12,14 +12,16 @@ -