Skip to content

Commit b5116c1

Browse files
authored
Escape special characters in codegen (#734)
* CODEGEN-248 escape special characters for curl codegen * Added test * Escape special characters in double quotes * Added test for ruby * Added PowerShell test
1 parent 8a20a74 commit b5116c1

File tree

8 files changed

+141
-5
lines changed

8 files changed

+141
-5
lines changed

codegens/curl/lib/util.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ var self = module.exports = {
2929
inputString = inputString.replace(/"/g, '\\"');
3030
// Escape backslash if double quote was already escaped before call to sanitize
3131
inputString = inputString.replace(/(?<!\\)\\\\"/g, '\\\\\\"');
32+
33+
// Escape special characters to preserve their literal meaning within double quotes
34+
inputString = inputString
35+
.replace(/`/g, '\\`')
36+
.replace(/#/g, '\\#')
37+
.replace(/\$/g, '\\$')
38+
.replace(/!/g, '\\!');
3239
}
3340
else if (quoteType === '\'') {
3441
// for curl escaping of single quotes inside single quotes involves changing of ' to '\''

codegens/curl/test/unit/convert.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,40 @@ describe('curl convert function', function () {
537537
});
538538
});
539539

540+
it('should escape special characters when quoteType is "double"', function () {
541+
var request = new sdk.Request({
542+
'method': 'POST',
543+
'header': [],
544+
'body': {
545+
'mode': 'raw',
546+
'raw': '{\r\n "hello": "$(whoami)"\r\n}',
547+
'options': {
548+
'raw': {
549+
'language': 'json'
550+
}
551+
}
552+
},
553+
'url': {
554+
'raw': 'https://postman-echo.com/post',
555+
'protocol': 'https',
556+
'host': [
557+
'postman-echo',
558+
'com'
559+
],
560+
'path': [
561+
'post'
562+
]
563+
}
564+
});
565+
convert(request, { quoteType: 'double', lineContinuationCharacter: '^' }, function (error, snippet) {
566+
if (error) {
567+
expect.fail(null, null, error);
568+
}
569+
570+
expect(snippet.includes('\\"hello\\": \\"\\$(whoami)\\"')).to.be.true; // eslint-disable-line
571+
});
572+
});
573+
540574
it('should longer option for body even if longFormat is disabled if @ character is present', function () {
541575
let request = new sdk.Request({
542576
'method': 'POST',

codegens/powershell-restmethod/lib/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var _ = require('./lodash'),
22
sanitize = require('./util').sanitize,
3+
sanitizeSingleQuotes = require('./util').sanitizeSingleQuotes,
34
sanitizeOptions = require('./util').sanitizeOptions,
45
addFormParam = require('./util').addFormParam,
56
path = require('path');
@@ -289,12 +290,12 @@ function convert (request, options, callback) {
289290
}
290291

291292
if (_.includes(VALID_METHODS, request.method)) {
292-
codeSnippet += `$response = Invoke-RestMethod '${request.url.toString().replace(/'/g, '\'\'')}' -Method '` +
293+
codeSnippet += `$response = Invoke-RestMethod '${sanitizeSingleQuotes(request.url.toString())}' -Method '` +
293294
`${request.method}' -Headers $headers`;
294295
}
295296
else {
296-
codeSnippet += `$response = Invoke-RestMethod '${request.url.toString()}' -CustomMethod ` +
297-
`'${request.method}' -Headers $headers`;
297+
codeSnippet += `$response = Invoke-RestMethod '${sanitizeSingleQuotes(request.url.toString())}' -CustomMethod ` +
298+
`'${sanitizeSingleQuotes(request.method)}' -Headers $headers`;
298299
}
299300
if (bodySnippet !== '') {
300301
codeSnippet += ' -Body $body';

codegens/powershell-restmethod/lib/util.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ function sanitize (inputString, trim, shouldEscapeNewLine = true) {
2424
return trim ? inputString.trim() : inputString;
2525
}
2626

27+
/**
28+
*
29+
* @param {String} inputString - input string
30+
* @returns {String} - sanitized string
31+
*/
32+
function sanitizeSingleQuotes (inputString) {
33+
if (typeof inputString !== 'string') {
34+
return '';
35+
}
36+
inputString = inputString
37+
.replace(/'/g, '\'\'');
38+
39+
return inputString;
40+
41+
}
42+
2743
/**
2844
* sanitizes input options
2945
*
@@ -126,6 +142,7 @@ function addFormParam (array, key, type, val, disabled, contentType) {
126142

127143
module.exports = {
128144
sanitize: sanitize,
145+
sanitizeSingleQuotes: sanitizeSingleQuotes,
129146
sanitizeOptions: sanitizeOptions,
130147
addFormParam: addFormParam
131148
};

codegens/powershell-restmethod/test/unit/convert.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,45 @@ describe('Powershell-restmethod converter', function () {
498498
});
499499
});
500500

501+
it('should generate valid snippet when single quotes in custom request method', function () {
502+
var request = new sdk.Request({
503+
// eslint-disable-next-line quotes
504+
'method': "TEST';DIR;#'",
505+
'header': [],
506+
'url': {
507+
'raw': 'https://postman-echo.com/get?query1=b\'b&query2=c"c',
508+
'protocol': 'https',
509+
'host': [
510+
'postman-echo',
511+
'com'
512+
],
513+
'path': [
514+
'get'
515+
],
516+
'query': [
517+
{
518+
'key': 'query1',
519+
'value': "b'b" // eslint-disable-line quotes
520+
},
521+
{
522+
'key': 'query2',
523+
'value': 'c"c'
524+
}
525+
]
526+
}
527+
});
528+
convert(request, {}, function (error, snippet) {
529+
if (error) {
530+
expect.fail(null, null, error);
531+
}
532+
expect(snippet).to.be.a('string');
533+
// An extra single quote is placed before a single quote to escape a single quote inside a single quoted string
534+
// eslint-disable-next-line quotes
535+
expect(snippet).to.include("-CustomMethod 'TEST'';DIR;#'''");
536+
});
537+
});
538+
539+
501540
it('should generate snippet for form data params with no type key present', function () {
502541
var request = new sdk.Request({
503542
method: 'POST',

codegens/ruby/lib/util/parseBody.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ module.exports = function (request, trimRequestBody, contentType, indentCount) {
4343
if (contentType && (contentType === 'application/json' || contentType.match(/\+json$/))) {
4444
try {
4545
let jsonBody = JSON.parse(request.body[request.body.mode]);
46-
jsonBody = JSON.stringify(jsonBody, replacer, indentCount)
47-
.replace(new RegExp(`"${nullToken}"`, 'g'), 'nil');
46+
jsonBody = sanitize(JSON.stringify(jsonBody, replacer, indentCount));
47+
jsonBody = jsonBody.replace(new RegExp(`"${nullToken}"`, 'g'), 'nil');
4848
return `request.body = JSON.dump(${jsonBody})\n`;
4949
}
5050
catch (error) {

codegens/ruby/lib/util/sanitize.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ module.exports = {
1313
return '';
1414
}
1515
inputString = inputTrim && typeof inputTrim === 'boolean' ? inputString.trim() : inputString;
16+
inputString = inputString
17+
.replace(/`/g, '\\`')
18+
.replace(/#/g, '\\#')
19+
.replace(/\$/g, '\\$')
20+
.replace(/!/g, '\\!');
1621
if (escapeCharFor && typeof escapeCharFor === 'string') {
1722
switch (escapeCharFor) {
1823
case 'raw':

codegens/ruby/test/unit/converter.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,39 @@ describe('Ruby converter', function () {
5858
});
5959
});
6060

61+
it('should escape special characters inside double quotes', function () {
62+
var request = new sdk.Request({
63+
'method': 'POST',
64+
'header': [
65+
'Content-Type: application/json'
66+
],
67+
'body': {
68+
'mode': 'raw',
69+
'raw': '{\r\n "hi": "#{`curl https://postman-echo.com`}",\r\n "message": "This is a ruby Code"\r\n}',
70+
'options': {
71+
'raw': {
72+
'language': 'json'
73+
}
74+
}
75+
},
76+
'url': {
77+
'raw': 'https://google.com',
78+
'protocol': 'https',
79+
'host': [
80+
'google',
81+
'com'
82+
]
83+
}
84+
});
85+
convert(request, {}, function (error, snippet) {
86+
if (error) {
87+
expect.fail(null, null, error);
88+
}
89+
expect(snippet).to.be.a('string');
90+
expect(snippet).to.include('\\#{\\`curl https://postman-echo.com\\`}');
91+
});
92+
});
93+
6194
it('should generate snippets for no files in form data', function () {
6295
var request = new sdk.Request({
6396
'method': 'POST',

0 commit comments

Comments
 (0)