Skip to content

Commit

Permalink
Escape special characters in codegen (#734)
Browse files Browse the repository at this point in the history
* CODEGEN-248 escape special characters for curl codegen

* Added test

* Escape special characters in double quotes

* Added test for ruby

* Added PowerShell test
  • Loading branch information
aman-v-singh committed Apr 26, 2024
1 parent 8a20a74 commit b5116c1
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 5 deletions.
7 changes: 7 additions & 0 deletions codegens/curl/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ var self = module.exports = {
inputString = inputString.replace(/"/g, '\\"');
// Escape backslash if double quote was already escaped before call to sanitize
inputString = inputString.replace(/(?<!\\)\\\\"/g, '\\\\\\"');

// Escape special characters to preserve their literal meaning within double quotes
inputString = inputString
.replace(/`/g, '\\`')
.replace(/#/g, '\\#')
.replace(/\$/g, '\\$')
.replace(/!/g, '\\!');
}
else if (quoteType === '\'') {
// for curl escaping of single quotes inside single quotes involves changing of ' to '\''
Expand Down
34 changes: 34 additions & 0 deletions codegens/curl/test/unit/convert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,40 @@ describe('curl convert function', function () {
});
});

it('should escape special characters when quoteType is "double"', function () {
var request = new sdk.Request({
'method': 'POST',
'header': [],
'body': {
'mode': 'raw',
'raw': '{\r\n "hello": "$(whoami)"\r\n}',
'options': {
'raw': {
'language': 'json'
}
}
},
'url': {
'raw': 'https://postman-echo.com/post',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'post'
]
}
});
convert(request, { quoteType: 'double', lineContinuationCharacter: '^' }, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
}

expect(snippet.includes('\\"hello\\": \\"\\$(whoami)\\"')).to.be.true; // eslint-disable-line
});
});

it('should longer option for body even if longFormat is disabled if @ character is present', function () {
let request = new sdk.Request({
'method': 'POST',
Expand Down
7 changes: 4 additions & 3 deletions codegens/powershell-restmethod/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var _ = require('./lodash'),
sanitize = require('./util').sanitize,
sanitizeSingleQuotes = require('./util').sanitizeSingleQuotes,
sanitizeOptions = require('./util').sanitizeOptions,
addFormParam = require('./util').addFormParam,
path = require('path');
Expand Down Expand Up @@ -289,12 +290,12 @@ function convert (request, options, callback) {
}

if (_.includes(VALID_METHODS, request.method)) {
codeSnippet += `$response = Invoke-RestMethod '${request.url.toString().replace(/'/g, '\'\'')}' -Method '` +
codeSnippet += `$response = Invoke-RestMethod '${sanitizeSingleQuotes(request.url.toString())}' -Method '` +
`${request.method}' -Headers $headers`;
}
else {
codeSnippet += `$response = Invoke-RestMethod '${request.url.toString()}' -CustomMethod ` +
`'${request.method}' -Headers $headers`;
codeSnippet += `$response = Invoke-RestMethod '${sanitizeSingleQuotes(request.url.toString())}' -CustomMethod ` +
`'${sanitizeSingleQuotes(request.method)}' -Headers $headers`;
}
if (bodySnippet !== '') {
codeSnippet += ' -Body $body';
Expand Down
17 changes: 17 additions & 0 deletions codegens/powershell-restmethod/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ function sanitize (inputString, trim, shouldEscapeNewLine = true) {
return trim ? inputString.trim() : inputString;
}

/**
*
* @param {String} inputString - input string
* @returns {String} - sanitized string
*/
function sanitizeSingleQuotes (inputString) {
if (typeof inputString !== 'string') {
return '';
}
inputString = inputString
.replace(/'/g, '\'\'');

return inputString;

}

/**
* sanitizes input options
*
Expand Down Expand Up @@ -126,6 +142,7 @@ function addFormParam (array, key, type, val, disabled, contentType) {

module.exports = {
sanitize: sanitize,
sanitizeSingleQuotes: sanitizeSingleQuotes,
sanitizeOptions: sanitizeOptions,
addFormParam: addFormParam
};
39 changes: 39 additions & 0 deletions codegens/powershell-restmethod/test/unit/convert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,45 @@ describe('Powershell-restmethod converter', function () {
});
});

it('should generate valid snippet when single quotes in custom request method', function () {
var request = new sdk.Request({
// eslint-disable-next-line quotes
'method': "TEST';DIR;#'",
'header': [],
'url': {
'raw': 'https://postman-echo.com/get?query1=b\'b&query2=c"c',
'protocol': 'https',
'host': [
'postman-echo',
'com'
],
'path': [
'get'
],
'query': [
{
'key': 'query1',
'value': "b'b" // eslint-disable-line quotes
},
{
'key': 'query2',
'value': 'c"c'
}
]
}
});
convert(request, {}, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
}
expect(snippet).to.be.a('string');
// An extra single quote is placed before a single quote to escape a single quote inside a single quoted string
// eslint-disable-next-line quotes
expect(snippet).to.include("-CustomMethod 'TEST'';DIR;#'''");
});
});


it('should generate snippet for form data params with no type key present', function () {
var request = new sdk.Request({
method: 'POST',
Expand Down
4 changes: 2 additions & 2 deletions codegens/ruby/lib/util/parseBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ module.exports = function (request, trimRequestBody, contentType, indentCount) {
if (contentType && (contentType === 'application/json' || contentType.match(/\+json$/))) {
try {
let jsonBody = JSON.parse(request.body[request.body.mode]);
jsonBody = JSON.stringify(jsonBody, replacer, indentCount)
.replace(new RegExp(`"${nullToken}"`, 'g'), 'nil');
jsonBody = sanitize(JSON.stringify(jsonBody, replacer, indentCount));
jsonBody = jsonBody.replace(new RegExp(`"${nullToken}"`, 'g'), 'nil');
return `request.body = JSON.dump(${jsonBody})\n`;
}
catch (error) {
Expand Down
5 changes: 5 additions & 0 deletions codegens/ruby/lib/util/sanitize.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ module.exports = {
return '';
}
inputString = inputTrim && typeof inputTrim === 'boolean' ? inputString.trim() : inputString;
inputString = inputString
.replace(/`/g, '\\`')
.replace(/#/g, '\\#')
.replace(/\$/g, '\\$')
.replace(/!/g, '\\!');
if (escapeCharFor && typeof escapeCharFor === 'string') {
switch (escapeCharFor) {
case 'raw':
Expand Down
33 changes: 33 additions & 0 deletions codegens/ruby/test/unit/converter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,39 @@ describe('Ruby converter', function () {
});
});

it('should escape special characters inside double quotes', function () {
var request = new sdk.Request({
'method': 'POST',
'header': [
'Content-Type: application/json'
],
'body': {
'mode': 'raw',
'raw': '{\r\n "hi": "#{`curl https://postman-echo.com`}",\r\n "message": "This is a ruby Code"\r\n}',
'options': {
'raw': {
'language': 'json'
}
}
},
'url': {
'raw': 'https://google.com',
'protocol': 'https',
'host': [
'google',
'com'
]
}
});
convert(request, {}, function (error, snippet) {
if (error) {
expect.fail(null, null, error);
}
expect(snippet).to.be.a('string');
expect(snippet).to.include('\\#{\\`curl https://postman-echo.com\\`}');
});
});

it('should generate snippets for no files in form data', function () {
var request = new sdk.Request({
'method': 'POST',
Expand Down

0 comments on commit b5116c1

Please sign in to comment.