Skip to content

Commit 25c3570

Browse files
committed
Use proper quoting in HTTPie shell commands
Before, the resultant shell command was completely unquoted. This change introduces proper quoting of values and the URL - when necessary. It uses single quotes which provide "strong quoting" (only nested single quote characters need to be taken care of) and also make the output more readable as double quotes, ubiquitous in JSON, don't require escaping. Before: echo "{\"foo\": \"bar\"}" | \ http POST http://mockbin.com/request?foo=bar&foo=baz \ accept:application/json \ content-type:application/json \ cookie:foo=bar; bar=baz After: echo '{"foo": "bar"}' | \ http POST 'http://mockbin.com/request?foo=bar&foo=baz' \ accept:application/json \ content-type:application/json \ cookie:'foo=bar; bar=baz'
1 parent ba377be commit 25c3570

10 files changed

Lines changed: 46 additions & 20 deletions

File tree

‎src/targets/httpie.js‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
var util = require('util')
44

5+
var shellQuote = function (value) {
6+
// Unless `value` is a simple shell-safe string, quote it.
7+
var shellSafe = /^[a-z0-9-_/.@%^=:]+$/i
8+
if (!shellSafe.test(value)) {
9+
// Use "strong quoting" using single quotes so that we only need
10+
// to deal with nested single quote characters.
11+
// <http://wiki.bash-hackers.org/syntax/quoting#strong_quoting>
12+
return util.format("'%s'", value.replace(/'/g, "'\\''"))
13+
}
14+
return value
15+
}
16+
517
module.exports = function (source, options) {
618
var opts = util._extend({
719
queryParams: false,
@@ -22,7 +34,7 @@ module.exports = function (source, options) {
2234

2335
// start with body pipe
2436
if (source.postData && source.postData.text) {
25-
code.push(util.format('echo %s | ', JSON.stringify(source.postData.text)))
37+
code.push(util.format('echo %s | ', shellQuote(source.postData.text)))
2638
}
2739

2840
var flags = []
@@ -63,7 +75,7 @@ module.exports = function (source, options) {
6375
flags.push(util.format('--timeout=%s', opts.timeout))
6476
}
6577

66-
code.push(util.format('http %s%s %s', flags.length ? flags.join(' ') + ' ' : '', source.method, opts.queryParams ? source.url : source.fullUrl))
78+
code.push(util.format('http %s%s %s', flags.length ? flags.join(' ') + ' ' : '', source.method, shellQuote(opts.queryParams ? source.url : source.fullUrl)))
6779

6880
// construct query params
6981
if (opts.queryParams) {
@@ -74,23 +86,23 @@ module.exports = function (source, options) {
7486

7587
if (util.isArray(value)) {
7688
value.map(function (val) {
77-
code.push(util.format('%s==%s', name, val))
89+
code.push(util.format('%s==%s', name, shellQuote(val)))
7890
})
7991
} else {
80-
code.push(util.format('%s==%s', name, value))
92+
code.push(util.format('%s==%s', name, shellQuote(value)))
8193
}
8294
})
8395
}
8496

8597
// construct headers
8698
Object.keys(source.allHeaders).sort().map(function (key) {
87-
code.push(util.format('%s:%s', key, source.allHeaders[key]))
99+
code.push(util.format('%s:%s', key, shellQuote(source.allHeaders[key])))
88100
})
89101

90102
// construct post params
91103
if (!source.postData.text && source.postData.params && source.postData.params.length) {
92104
source.postData.params.map(function (param) {
93-
code.push(util.format('%s:%s', param.name, param.value))
105+
code.push(util.format('%s:%s', param.name, shellQuote(param.value)))
94106
})
95107
}
96108

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
echo "foo=bar&hello=world" | \
1+
echo 'foo=bar&hello=world' | \
22
http POST http://mockbin.com/har \
33
content-type:application/x-www-form-urlencoded
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
echo "{\"number\": 1, \"string\": \"f\\\"oo\", \"arr\": [1, 2, 3], \"nested\": {\"a\": \"b\"}, \"arr_mix\": [1, \"a\", {\"arr_mix_nested\": {}}] }" | \
1+
echo '{"number": 1, "string": "f\"oo", "arr": [1, 2, 3], "nested": {"a": "b"}, "arr_mix": [1, "a", {"arr_mix_nested": {}}] }' | \
22
http POST http://mockbin.com/har \
33
content-type:application/json
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
http POST http://mockbin.com/har \
2-
cookie:foo=bar; bar=baz
2+
cookie:'foo=bar; bar=baz'
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
echo "foo=bar" | \
2-
http POST http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value \
1+
echo foo=bar | \
2+
http POST 'http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value' \
33
accept:application/json \
44
content-type:application/x-www-form-urlencoded \
5-
cookie:foo=bar; bar=baz
5+
cookie:'foo=bar; bar=baz'
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
echo "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\nHello World\r\n-----011000010111000001101001--" | \
1+
echo '-----011000010111000001101001
2+
Content-Disposition: form-data; name="foo"; filename="hello.txt"
3+
Content-Type: text/plain
4+
5+
Hello World
6+
-----011000010111000001101001--' | \
27
http POST http://mockbin.com/har \
3-
content-type:multipart/form-data; boundary=---011000010111000001101001
8+
content-type:'multipart/form-data; boundary=---011000010111000001101001'
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
echo "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\n\r\n-----011000010111000001101001--" | \
1+
echo '-----011000010111000001101001
2+
Content-Disposition: form-data; name="foo"; filename="hello.txt"
3+
Content-Type: text/plain
4+
5+
6+
-----011000010111000001101001--' | \
27
http POST http://mockbin.com/har \
3-
content-type:multipart/form-data; boundary=---011000010111000001101001
8+
content-type:'multipart/form-data; boundary=---011000010111000001101001'
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
echo "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n-----011000010111000001101001--" | \
1+
echo '-----011000010111000001101001
2+
Content-Disposition: form-data; name="foo"
3+
4+
bar
5+
-----011000010111000001101001--' | \
26
http POST http://mockbin.com/har \
3-
content-type:multipart/form-data; boundary=---011000010111000001101001
7+
content-type:'multipart/form-data; boundary=---011000010111000001101001'
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
http GET http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value
1+
http GET 'http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value'

‎test/tests/httpie.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module.exports = function (HTTPSnippet, fixtures) {
3232
})
3333

3434
result.should.be.a.String
35-
result.replace(/\\\n/g, '').should.eql('echo "foo=bar" | @http POST http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value @accept:application/json @content-type:application/x-www-form-urlencoded @cookie:foo=bar; bar=baz')
35+
result.replace(/\\\n/g, '').should.eql("echo foo=bar | @http POST 'http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value' @accept:application/json @content-type:application/x-www-form-urlencoded @cookie:'foo=bar; bar=baz'")
3636
})
3737

3838
it('should use queryString parameters', function () {

0 commit comments

Comments
 (0)