Skip to content

Commit 96c7b4c

Browse files
authoredMay 13, 2020
fix: curl array support within multipart/form-data (#3838) (#5999)
ft: utils.createObjWithHashedKeys ft: curlify.extractKey test: curlify with array representation
1 parent ce45c37 commit 96c7b4c

File tree

3 files changed

+92
-10
lines changed

3 files changed

+92
-10
lines changed
 

‎src/core/curlify.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
import win from "./window"
22

3+
/**
4+
* if duplicate key name existed from FormData entries,
5+
* we mutated the key name by appending a hashIdx
6+
* @param {String} k - possibly mutated key name
7+
* @return {String} - src key name
8+
*/
9+
const extractKey = (k) => {
10+
const hashIdx = "_**[]"
11+
if (k.indexOf(hashIdx) < 0) {
12+
return k
13+
}
14+
return k.split(hashIdx)[0].trim()
15+
}
16+
317
export default function curl( request ){
418
let curlified = []
519
let type = ""
@@ -21,11 +35,12 @@ export default function curl( request ){
2135

2236
if(type === "multipart/form-data" && request.get("method") === "POST") {
2337
for( let [ k,v ] of request.get("body").entrySeq()) {
38+
let extractedKey = extractKey(k)
2439
curlified.push( "-F" )
2540
if (v instanceof win.File) {
26-
curlified.push( `"${k}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` )
41+
curlified.push(`"${extractedKey}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` )
2742
} else {
28-
curlified.push( `"${k}=${v}"` )
43+
curlified.push(`"${extractedKey}=${v}"` )
2944
}
3045
}
3146
} else {

‎src/core/utils.js

+55-8
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,64 @@ export function arrayify (thing) {
6767
return normalizeArray(thing)
6868
}
6969

70-
export function fromJSOrdered (js) {
71-
if(isImmutable(js))
70+
export function fromJSOrdered(js) {
71+
if (isImmutable(js)) {
7272
return js // Can't do much here
73-
74-
if (js instanceof win.File)
73+
}
74+
if (js instanceof win.File) {
75+
return js
76+
}
77+
if (!isObject(js)) {
7578
return js
79+
}
80+
if (Array.isArray(js)) {
81+
return Im.Seq(js).map(fromJSOrdered).toList()
82+
}
83+
if (js.entries) {
84+
// handle multipart/form-data
85+
const objWithHashedKeys = createObjWithHashedKeys(js)
86+
return Im.OrderedMap(objWithHashedKeys).map(fromJSOrdered)
87+
}
88+
return Im.OrderedMap(js).map(fromJSOrdered)
89+
}
7690

77-
return !isObject(js) ? js :
78-
Array.isArray(js) ?
79-
Im.Seq(js).map(fromJSOrdered).toList() :
80-
Im.OrderedMap(js).map(fromJSOrdered)
91+
/**
92+
* Convert a FormData object into plain object
93+
* Append a hashIdx and counter to the key name, if multiple exists
94+
* if single, key name = <original>
95+
* if multiple, key name = <original><hashIdx><count>
96+
* @param {FormData} fdObj - a FormData object
97+
* @return {Object} - a plain object
98+
*/
99+
export function createObjWithHashedKeys (fdObj) {
100+
if (!fdObj.entries) {
101+
return fdObj // not a FormData object with iterable
102+
}
103+
const newObj = {}
104+
const hashIdx = "_**[]" // our internal identifier
105+
const trackKeys = {}
106+
for (let pair of fdObj.entries()) {
107+
if (!newObj[pair[0]] && !(trackKeys[pair[0]] && trackKeys[pair[0]].containsMultiple)) {
108+
newObj[pair[0]] = pair[1] // first key name: no hash required
109+
} else {
110+
if (!trackKeys[pair[0]]) {
111+
// initiate tracking key for multiple
112+
trackKeys[pair[0]] = {
113+
containsMultiple: true,
114+
length: 1
115+
}
116+
// "reassign" first pair to matching hashed format for multiple
117+
let hashedKeyFirst = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}`
118+
newObj[hashedKeyFirst] = newObj[pair[0]]
119+
// remove non-hashed key of multiple
120+
delete newObj[pair[0]] // first
121+
}
122+
trackKeys[pair[0]].length += 1
123+
let hashedKeyCurrent = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}`
124+
newObj[hashedKeyCurrent] = pair[1]
125+
}
126+
}
127+
return newObj
81128
}
82129

83130
export function bindToState(obj, state) {

‎test/mocha/core/curlify.js

+20
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,26 @@ describe("curlify", function() {
143143
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"name=Sahar\"")
144144
})
145145

146+
it("should print a curl with formData that extracts array representation with hashIdx", function() {
147+
// Note: hashIdx = `_**[]${counter}`
148+
// Usage of hashIdx is an internal SwaggerUI method to convert formData array into something curlify can handle
149+
const req = {
150+
url: "http://example.com",
151+
method: "POST",
152+
headers: { "content-type": "multipart/form-data" },
153+
body: {
154+
id: "123",
155+
"fruits[]_**[]1": "apple",
156+
"fruits[]_**[]2": "banana",
157+
"fruits[]_**[]3": "grape"
158+
}
159+
}
160+
161+
let curlified = curl(Im.fromJS(req))
162+
163+
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"fruits[]=apple\" -F \"fruits[]=banana\" -F \"fruits[]=grape\"")
164+
})
165+
146166
it("should print a curl with formData and file", function() {
147167
var file = new win.File()
148168
file.name = "file.txt"

0 commit comments

Comments
 (0)
Please sign in to comment.