Skip to content

Commit 12aa17e

Browse files
fix: cUrl target should encode x-www-form-urlencoded post data params (Kong#198)
Co-authored-by: Dimitri Mitropoulos <[email protected]>
1 parent 7da8c97 commit 12aa17e

5 files changed

Lines changed: 87 additions & 47 deletions

File tree

src/fixtures/runCustomFixtures.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { readFile } from 'fs/promises';
1+
import { writeFileSync } from 'fs';
2+
import { readFile, writeFile } from 'fs/promises';
23
import path from 'path';
34

45
import { HTTPSnippet, Request } from '../httpsnippet';
@@ -21,17 +22,21 @@ export interface CustomFixture {
2122
export const runCustomFixtures = ({ targetId, clientId, tests }: CustomFixture) => {
2223
describe(`custom fixtures for ${targetId}:${clientId}`, () => {
2324
tests.forEach(({ it: title, expected: fixtureFile, options, input: request }) => {
25+
const result = new HTTPSnippet(request).convert(targetId, clientId, options);
26+
const filePath = path.join(
27+
__dirname,
28+
'..',
29+
'targets',
30+
targetId,
31+
clientId,
32+
'fixtures',
33+
fixtureFile,
34+
);
35+
if (process.env.OVERWRITE_EVERYTHING) {
36+
writeFileSync(filePath, String(result));
37+
}
38+
2439
it(title, async () => {
25-
const result = new HTTPSnippet(request).convert(targetId, clientId, options);
26-
const filePath = path.join(
27-
__dirname,
28-
'..',
29-
'targets',
30-
targetId,
31-
clientId,
32-
'fixtures',
33-
fixtureFile,
34-
);
3540
const buffer = await readFile(filePath);
3641
const fixture = String(buffer);
3742

src/targets/shell/curl/client.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import applicationFormEncoded from '../../../fixtures/requests/application-form-encoded.json';
12
import full from '../../../fixtures/requests/full.json';
23
import nested from '../../../fixtures/requests/nested.json';
34
import { runCustomFixtures } from '../../../fixtures/runCustomFixtures';
@@ -71,5 +72,20 @@ runCustomFixtures({
7172
},
7273
expected: 'custom-indentation.sh',
7374
},
75+
{
76+
it: 'should url encode the params key',
77+
input: {
78+
...applicationFormEncoded,
79+
postData: {
80+
mimeType: 'application/x-www-form-urlencoded',
81+
params: [
82+
{ name: 'user name', value: 'John Doe' },
83+
{ name: '$filter', value: 'by id' },
84+
],
85+
},
86+
} as Request,
87+
options: {},
88+
expected: 'urlencode.sh',
89+
},
7490
],
7591
});

src/targets/shell/curl/client.ts

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/**
22
* @description
3+
*
34
* HTTP code snippet generator for the Shell using cURL.
45
*
56
* @author
@@ -20,39 +21,59 @@ export interface CurlOptions {
2021
indent?: string | false;
2122
}
2223

24+
/**
25+
* This is a const record with keys that correspond to the long names and values that correspond to the short names for cURL arguments.
26+
*/
27+
const params = {
28+
globoff: 'g',
29+
request: 'X',
30+
'url ': '',
31+
'http1.0': '0',
32+
header: 'H',
33+
cookie: 'b',
34+
form: 'F',
35+
data: 'd',
36+
} as const;
37+
38+
const getArg = (short: boolean) => (longName: keyof typeof params) => {
39+
if (short) {
40+
const shortName = params[longName];
41+
if (!shortName) {
42+
return '';
43+
}
44+
return `-${shortName}`;
45+
}
46+
return `--${longName}`;
47+
};
48+
2349
export const curl: Client<CurlOptions> = {
2450
info: {
2551
key: 'curl',
2652
title: 'cURL',
2753
link: 'http://curl.haxx.se/',
2854
description: 'cURL is a command line tool and library for transferring data with URL syntax',
2955
},
30-
convert: ({ fullUrl, method, httpVersion, headersObj, allHeaders, postData }, options) => {
31-
const opts = {
32-
indent: ' ',
33-
short: false,
34-
binary: false,
35-
globOff: false,
36-
...options,
37-
};
56+
convert: ({ fullUrl, method, httpVersion, headersObj, allHeaders, postData }, options = {}) => {
57+
const { indent = ' ', short = false, binary = false, globOff = false } = options;
58+
3859
const { push, join } = new CodeBuilder({
39-
...(typeof opts.indent === 'string' ? { indent: opts.indent } : {}),
40-
join: opts.indent !== false ? ` \\\n${opts.indent}` : ' ',
60+
...(typeof indent === 'string' ? { indent: indent } : {}),
61+
join: indent !== false ? ` \\\n${indent}` : ' ',
4162
});
4263

43-
const globOption = opts.short ? '-g' : '--globoff';
44-
const requestOption = opts.short ? '-X' : '--request';
64+
const arg = getArg(short);
65+
4566
let formattedUrl = quote(fullUrl);
4667

47-
push(`curl ${requestOption} ${method}`);
48-
if (opts.globOff) {
68+
push(`curl ${arg('request')} ${method}`);
69+
if (globOff) {
4970
formattedUrl = unescape(formattedUrl);
50-
push(globOption);
71+
push(arg('globoff'));
5172
}
52-
push(`${opts.short ? '' : '--url '}${formattedUrl}`);
73+
push(`${arg('url ')}${formattedUrl}`);
5374

5475
if (httpVersion === 'HTTP/1.0') {
55-
push(opts.short ? '-0' : '--http1.0');
76+
push(arg('http1.0'));
5677
}
5778

5879
// if multipart form data, we want to remove the boundary
@@ -78,11 +99,11 @@ export const curl: Client<CurlOptions> = {
7899
.sort()
79100
.forEach(key => {
80101
const header = `${key}: ${headersObj[key]}`;
81-
push(`${opts.short ? '-H' : '--header'} ${quote(header)}`);
102+
push(`${arg('header')} ${quote(header)}`);
82103
});
83104

84105
if (allHeaders.cookie) {
85-
push(`${opts.short ? '-b' : '--cookie'} ${quote(allHeaders.cookie as string)}`);
106+
push(`${arg('cookie')} ${quote(allHeaders.cookie as string)}`);
86107
}
87108

88109
// construct post params
@@ -96,36 +117,29 @@ export const curl: Client<CurlOptions> = {
96117
post = `${param.name}=${param.value}`;
97118
}
98119

99-
push(`${opts.short ? '-F' : '--form'} ${quote(post)}`);
120+
push(`${arg('form')} ${quote(post)}`);
100121
});
101122
break;
102123

103124
case 'application/x-www-form-urlencoded':
104125
if (postData.params) {
105126
postData.params.forEach(param => {
106-
push(
107-
`${opts.binary ? '--data-binary' : opts.short ? '-d' : '--data'} ${quote(
108-
`${param.name}=${param.value}`,
109-
)}`,
110-
);
127+
const unencoded = param.name;
128+
const encoded = encodeURIComponent(param.name);
129+
const needsEncoding = encoded !== unencoded;
130+
const name = needsEncoding ? encoded : unencoded;
131+
const flag = binary ? '--data-binary' : `--data${needsEncoding ? '-urlencode' : ''}`;
132+
push(`${flag} ${quote(`${name}=${param.value}`)}`);
111133
});
112134
} else {
113-
push(
114-
`${opts.binary ? '--data-binary' : opts.short ? '-d' : '--data'} ${quote(
115-
postData.text,
116-
)}`,
117-
);
135+
push(`${binary ? '--data-binary' : arg('data')} ${quote(postData.text)}`);
118136
}
119137
break;
120138

121139
default:
122140
// raw request body
123141
if (postData.text) {
124-
push(
125-
`${opts.binary ? '--data-binary' : opts.short ? '-d' : '--data'} ${quote(
126-
postData.text,
127-
)}`,
128-
);
142+
push(`${binary ? '--data-binary' : arg('data')} ${quote(postData.text)}`);
129143
}
130144
}
131145

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
curl -X POST 'http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value' -H 'accept: application/json' -H 'content-type: application/x-www-form-urlencoded' -b 'foo=bar; bar=baz' -d foo=bar
1+
curl -X POST 'http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value' -H 'accept: application/json' -H 'content-type: application/x-www-form-urlencoded' -b 'foo=bar; bar=baz' --data foo=bar
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
curl --request POST \
2+
--url http://mockbin.com/har \
3+
--header 'content-type: application/x-www-form-urlencoded' \
4+
--data-urlencode 'user%20name=John Doe' \
5+
--data-urlencode '%24filter=by id'

0 commit comments

Comments
 (0)