Skip to content

Commit

Permalink
Merge branch 'better-escaping'
Browse files Browse the repository at this point in the history
  • Loading branch information
pimterry committed Jul 2, 2024
2 parents 17ce2ef + ebf709c commit 9899b73
Show file tree
Hide file tree
Showing 79 changed files with 1,418 additions and 125 deletions.
62 changes: 1 addition & 61 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
"form-data": "3.0.0",
"fs-readfile-promise": "^2.0.1",
"fs-writefile-promise": "^1.0.3",
"har-validator": "^5.0.0",
"stringify-object": "^3.3.0"
"har-validator": "^5.0.0"
}
}
134 changes: 134 additions & 0 deletions src/helpers/stringify-js-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Closely based on v5.0.0 of stringify-object (BSD-2-Clause licensed), with
// modifications to simplify it for our use case (simpler obj & regex checks
// + object key retrieval) and no awkwardly incompatible ESM.

module.exports = (input, options, pad) => {
const seen = []

return (function stringify (input, options = {}, pad = '') {
const indent = options.indent || '\t'

let tokens
if (options.inlineCharacterLimit === undefined) {
tokens = {
newline: '\n',
newlineOrSpace: '\n',
pad,
indent: pad + indent
}
} else {
tokens = {
newline: '@@__STRINGIFY_OBJECT_NEW_LINE__@@',
newlineOrSpace: '@@__STRINGIFY_OBJECT_NEW_LINE_OR_SPACE__@@',
pad: '@@__STRINGIFY_OBJECT_PAD__@@',
indent: '@@__STRINGIFY_OBJECT_INDENT__@@'
}
}

const expandWhiteSpace = string => {
if (options.inlineCharacterLimit === undefined) {
return string
}

const oneLined = string
.replace(new RegExp(tokens.newline, 'g'), '')
.replace(new RegExp(tokens.newlineOrSpace, 'g'), ' ')
.replace(new RegExp(tokens.pad + '|' + tokens.indent, 'g'), '')

if (oneLined.length <= options.inlineCharacterLimit) {
return oneLined
}

return string
.replace(new RegExp(tokens.newline + '|' + tokens.newlineOrSpace, 'g'), '\n')
.replace(new RegExp(tokens.pad, 'g'), pad)
.replace(new RegExp(tokens.indent, 'g'), pad + indent)
}

if (seen.includes(input)) {
return '"[Circular]"'
}

if (
input === null ||
input === undefined ||
typeof input === 'number' ||
typeof input === 'boolean' ||
typeof input === 'function' ||
typeof input === 'symbol' ||
input instanceof RegExp
) {
return String(input)
}

if (input instanceof Date) {
return `new Date('${input.toISOString()}')`
}

if (Array.isArray(input)) {
if (input.length === 0) {
return '[]'
}

seen.push(input)

const returnValue = '[' + tokens.newline + input.map((element, i) => {
const eol = input.length - 1 === i ? tokens.newline : ',' + tokens.newlineOrSpace

let value = stringify(element, options, pad + indent)
if (options.transform) {
value = options.transform(input, i, value)
}

return tokens.indent + value + eol
}).join('') + tokens.pad + ']'

seen.pop()

return expandWhiteSpace(returnValue)
}

if (typeof input === 'object') {
let objectKeys = Object.keys(input)

if (options.filter) {
objectKeys = objectKeys.filter(element => options.filter(input, element))
}

if (objectKeys.length === 0) {
return '{}'
}

seen.push(input)

const returnValue = '{' + tokens.newline + objectKeys.map((element, index) => {
const eol = objectKeys.length - 1 === index ? tokens.newline : ',' + tokens.newlineOrSpace
const isSymbol = typeof element === 'symbol'
const isClassic = !isSymbol && /^[a-z$_][$\w]*$/i.test(element)
const key = isSymbol || isClassic ? element : stringify(element, options)

let value = stringify(input[element], options, pad + indent)
if (options.transform) {
value = options.transform(input, element, value)
}

return tokens.indent + String(key) + ': ' + value + eol
}).join('') + tokens.pad + '}'

seen.pop()

return expandWhiteSpace(returnValue)
}

input = input.replace(/\\/g, '\\\\')
input = String(input).replace(/[\r\n]/g, x => x === '\n' ? '\\n' : '\\r')

if (options.singleQuotes === false) {
input = input.replace(/"/g, '\\"')
return `"${input}"`
}

input = input.replace(/'/g, '\\\'')
return `'${input}'`
})(input, options, pad)
}
12 changes: 10 additions & 2 deletions src/targets/clojure/clj_http.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

const CodeBuilder = require('../../helpers/code-builder')
const helpers = require('../../helpers/headers')
const { escape } = require('../../helpers/format')

const Keyword = function (name) {
this.name = name
Expand Down Expand Up @@ -60,7 +61,7 @@ const padBlock = function (x, s) {
const jsToEdn = function (js) {
switch (jsType(js)) {
case 'string':
return '"' + js.replace(/"/g, '\\"') + '"'
return '"' + escape(js, { delimiter: '"' }) + '"'
case 'file':
return js.toString()
case 'keyword':
Expand All @@ -73,7 +74,14 @@ const jsToEdn = function (js) {
const obj = Object.keys(js)
.reduce(function (acc, key) {
const val = padBlock(key.length + 2, jsToEdn(js[key]))
return acc + ':' + key + ' ' + val + '\n '

// This check is overly strict, but good enough for us for
// all typical HTTP values we care about
const safeKey = key.match(/^[a-zA-Z_][\w-]*$/)
? ':' + key
: jsToEdn(key)

return acc + safeKey + ' ' + val + '\n '
}, '')
.trim()
return '{' + padBlock(1, obj) + '}'
Expand Down
2 changes: 1 addition & 1 deletion src/targets/javascript/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
'use strict'

const util = require('util')
const stringifyObject = require('stringify-object')
const stringifyObject = require('../../helpers/stringify-js-object')
const CodeBuilder = require('../../helpers/code-builder')

module.exports = function (source, options) {
Expand Down
4 changes: 2 additions & 2 deletions src/targets/javascript/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ module.exports = function (source, options) {
options
)

const stringifyObject = require('stringify-object')
const stringifyObject = require('../../helpers/stringify-js-object')
const code = new CodeBuilder(opts.indent)

options = {
Expand Down Expand Up @@ -85,7 +85,7 @@ module.exports = function (source, options) {
.blank()
}

code.push("fetch('%s', options)", source.fullUrl)
code.push('fetch(%s, options)', stringifyObject(source.fullUrl))
.push(1, '.then(response => response.json())')
.push(1, '.then(response => console.log(response))')
.push(1, '.catch(err => console.error(err));')
Expand Down
2 changes: 1 addition & 1 deletion src/targets/node/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
'use strict'

const util = require('util')
const stringifyObject = require('stringify-object')
const stringifyObject = require('../../helpers/stringify-js-object')
const CodeBuilder = require('../../helpers/code-builder')

module.exports = function (source, options) {
Expand Down
4 changes: 2 additions & 2 deletions src/targets/node/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

'use strict'

const stringifyObject = require('stringify-object')
const stringifyObject = require('../../helpers/stringify-js-object')
const CodeBuilder = require('../../helpers/code-builder')

module.exports = function (source, options) {
Expand Down Expand Up @@ -88,7 +88,7 @@ module.exports = function (source, options) {
}
}
code.blank()
code.push('let url = \'' + url + '\';')
code.push('let url = ' + stringifyObject(url) + ';')
.blank()
code.push('let options = %s;', stringifyObject(reqOpts, { indent: ' ', inlineCharacterLimit: 80 }))
.blank()
Expand Down
2 changes: 1 addition & 1 deletion src/targets/node/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

'use strict'

const stringifyObject = require('stringify-object')
const stringifyObject = require('../../helpers/stringify-js-object')
const CodeBuilder = require('../../helpers/code-builder')

module.exports = function (source, options) {
Expand Down
2 changes: 1 addition & 1 deletion src/targets/node/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
'use strict'

const util = require('util')
const stringifyObject = require('stringify-object')
const stringifyObject = require('../../helpers/stringify-js-object')
const CodeBuilder = require('../../helpers/code-builder')

module.exports = function (source, options) {
Expand Down
4 changes: 3 additions & 1 deletion src/targets/objc/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const util = require('util')

const { escape } = require('../../helpers/format')

module.exports = {
/**
* Create an string of given length filled with blank spaces
Expand Down Expand Up @@ -73,7 +75,7 @@ module.exports = {
if (value === null || value === undefined) {
return ''
}
return '@"' + value.toString().replace(/"/g, '\\"') + '"'
return '@"' + escape(value.toString(), { delimiter: '"' }) + '"'
}
}
}
Loading

0 comments on commit 9899b73

Please sign in to comment.