Skip to content

Commit a107032

Browse files
committed
Correctly escape single & double quotes within header values
1 parent a5107ea commit a107032

File tree

57 files changed

+201
-99
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+201
-99
lines changed

src/helpers/code-builder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const formatString = require('./format')
3+
const { format: formatString } = require('./format')
44

55
/**
66
* Helper object to format and aggragate lines of code.

src/helpers/format.js

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,87 @@
11
const util = require('util')
22

3-
function quote (value) {
4-
if (typeof value !== 'string') value = JSON.stringify(value)
5-
return JSON.stringify(value)
3+
exports.escape = function escape (value, options) {
4+
// The JSON-stringify string serialization algorithm, but generalized for string delimiters
5+
// (e.g. " or ') and different escape characters (e.g. Powershell uses `)
6+
// https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
7+
const {
8+
delimiter = '"',
9+
escapeChar = '\\',
10+
escapeNewlines = true
11+
} = options || {}
12+
13+
return [...value].map((c) => {
14+
if (c === '\b') {
15+
return escapeChar + 'b'
16+
} else if (c === '\t') {
17+
return escapeChar + 't'
18+
} else if (c === '\n') {
19+
if (escapeNewlines) {
20+
return escapeChar + 'n'
21+
} else {
22+
return c // Don't just continue, or this is caught by < \u0020
23+
}
24+
} else if (c === '\f') {
25+
return escapeChar + 'f'
26+
} else if (c === '\r') {
27+
if (escapeNewlines) {
28+
return escapeChar + 'r'
29+
} else {
30+
return c // Don't just continue, or this is caught by < \u0020
31+
}
32+
} else if (c === escapeChar) {
33+
return escapeChar + escapeChar
34+
} else if (c === delimiter) {
35+
return escapeChar + delimiter
36+
} else if (c < '\u0020' || c > '\u007E') {
37+
// Delegate the trickier non-ASCII cases to the normal algorithm. Some of these are escaped as
38+
// \uXXXX, whilst others are represented literally. Since we're using this primarily for header
39+
// values that are generally (though not strictly?) ASCII-only, this should almost never happen.
40+
return JSON.stringify(c).slice(1, -1)
41+
} else {
42+
return c
43+
}
44+
}).join('')
645
}
746

8-
function escape (value) {
9-
const q = quote(value)
10-
return q && q.slice(1, -1)
47+
function doubleQuoteEscape (value) {
48+
return exports.escape(value, { delimiter: '"' })
49+
}
50+
51+
function singleQuoteEscape (value) {
52+
return exports.escape(value, { delimiter: "'" })
1153
}
1254

1355
/**
14-
* Wraps the `util.format` function and adds the %q and %v format options,
15-
* where `%q` - escape tricky characters, like newline or quotes
16-
* and `%v` - JSON-stringify-if-necessary
56+
* Wraps the `util.format` function and adds the %qd, %qs and %v format options,
57+
* where `%qd` escapes characters for a single-line double-quoted string, `%qs`
58+
* escapes characters for a single-line single-quoted string, and `%v`
59+
* JSON-stringifies the value.
1760
*
1861
* @param {string} value
1962
* @param {...string} format
2063
*
21-
* @example
22-
* format('foo("%q")', { bar: 'baz' })
23-
* // output: foo("{\"bar\":\"baz\"}")
24-
*
25-
* format('foo(%v)', { bar: 'baz' })
26-
* // output: foo({"bar":"baz"})
27-
*
2864
* @returns {string} Formatted string
2965
*/
30-
function format (value, ...format) {
66+
exports.format = function format (value, ...format) {
3167
if (typeof value !== 'string') return ''
3268

3369
let i = 0
34-
value = value.replace(/(?<!%)%[sdifjoOcqv]/g, (m) => {
70+
value = value.replace(/(?<!%)%([sdifjoOcv]|q[sd])/g, (m) => {
3571
// JSON-stringify
3672
if (m === '%v') {
3773
const [elem] = format.splice(i, 1)
3874
return JSON.stringify(elem)
3975
}
40-
// JSON-stringify, remove quotes (means, escape)
41-
if (m === '%q') {
76+
// Escape for double-quoted string
77+
if (m === '%qd') {
78+
const [elem] = format.splice(i, 1)
79+
return doubleQuoteEscape(elem)
80+
}
81+
// Escape for single-quoted string
82+
if (m === '%qs') {
4283
const [elem] = format.splice(i, 1)
43-
return escape(elem)
84+
return singleQuoteEscape(elem)
4485
}
4586
i += 1
4687
return m
@@ -49,5 +90,3 @@ function format (value, ...format) {
4990
const ret = util.format(value, ...format)
5091
return ret
5192
}
52-
53-
module.exports = format

src/targets/c/libcurl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module.exports = function (source, options) {
1919
.push('struct curl_slist *headers = NULL;')
2020

2121
headers.forEach(function (key) {
22-
code.push('headers = curl_slist_append(headers, "%s: %s");', key, source.headersObj[key])
22+
code.push('headers = curl_slist_append(headers, "%s: %qd");', key, source.headersObj[key])
2323
})
2424

2525
code.push('curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);')

src/targets/csharp/httpclient.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ module.exports = function (source, options) {
8585
code.push(1, 'Headers =')
8686
code.push(1, '{')
8787
headers.forEach(function (key) {
88-
code.push(2, '{ "%s", "%s" },', key, source.allHeaders[key])
88+
code.push(2, '{ "%s", "%qd" },', key, source.allHeaders[key])
8989
})
9090
code.push(1, '},')
9191
}

src/targets/csharp/restsharp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module.exports = function (source, options) {
2020
// construct headers
2121
if (headers.length) {
2222
headers.forEach(function (key) {
23-
code.push('request.AddHeader("%s", "%s");', key, source.headersObj[key])
23+
code.push('request.AddHeader("%s", "%qd");', key, source.headersObj[key])
2424
})
2525
}
2626

src/targets/go/native.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ module.exports = function (source, options) {
112112
// Add headers
113113
if (Object.keys(source.allHeaders).length) {
114114
Object.keys(source.allHeaders).forEach(function (key) {
115-
code.push(indent, 'req.Header.Add("%s", "%s")', key, source.allHeaders[key])
115+
code.push(indent, 'req.Header.Add("%s", "%qd")', key, source.allHeaders[key])
116116
})
117117

118118
code.blank()

src/targets/java/asynchttp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = function (source, options) {
2929
// construct headers
3030
if (headers.length) {
3131
headers.forEach(function (key) {
32-
code.push(1, '.setHeader("%s", "%s")', key, source.allHeaders[key])
32+
code.push(1, '.setHeader("%s", "%qd")', key, source.allHeaders[key])
3333
})
3434
}
3535

src/targets/java/nethttp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module.exports = function (source, options) {
3030
// construct headers
3131
if (headers.length) {
3232
headers.forEach(function (key) {
33-
code.push(2, '.header("%s", "%s")', key, source.allHeaders[key])
33+
code.push(2, '.header("%s", "%qd")', key, source.allHeaders[key])
3434
})
3535
}
3636

src/targets/java/okhttp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ module.exports = function (source, options) {
5959
// construct headers
6060
if (headers.length) {
6161
headers.forEach(function (key) {
62-
code.push(1, '.addHeader("%s", "%s")', key, source.allHeaders[key])
62+
code.push(1, '.addHeader("%s", "%qd")', key, source.allHeaders[key])
6363
})
6464
}
6565

src/targets/java/unirest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = function (source, options) {
3333
// construct headers
3434
if (headers.length) {
3535
headers.forEach(function (key) {
36-
code.push(1, '.header("%s", "%s")', key, source.allHeaders[key])
36+
code.push(1, '.header("%s", "%qd")', key, source.allHeaders[key])
3737
})
3838
}
3939

0 commit comments

Comments
 (0)