awesome_spawn 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of awesome_spawn might be problematic. Click here for more details.
- data/README.md +1 -0
- data/lib/awesome_spawn.rb +59 -22
- data/lib/awesome_spawn/version.rb +1 -1
- data/spec/awesome_spawn_spec.rb +54 -11
- metadata +2 -8
data/README.md
CHANGED
@@ -11,6 +11,7 @@ AwesomeSpawn is a module that provides some useful features over Ruby's Kernel.s
|
|
11
11
|
Some additional features include...
|
12
12
|
|
13
13
|
- Parameter passing as a Hash or associative Array sanitizing them to prevent command line injection.
|
14
|
+
- Ability to pass the contents of stdin as a String.
|
14
15
|
- Results returned as an object giving access to the output stream, error stream, and exit status.
|
15
16
|
- Optionally raising an exception when exit status is not 0.
|
16
17
|
|
data/lib/awesome_spawn.rb
CHANGED
@@ -31,26 +31,35 @@ module AwesomeSpawn
|
|
31
31
|
# result.exit_status # => 1
|
32
32
|
#
|
33
33
|
# @example With parameters sanitized
|
34
|
-
# result = AwesomeSpawn.run('echo', :params => {
|
34
|
+
# result = AwesomeSpawn.run('echo', :params => {:out => "; rm /some/file"})
|
35
35
|
# # => #<AwesomeSpawn::CommandResult:0x007ff64baa6650 @exit_status=0>
|
36
36
|
# result.command_line
|
37
37
|
# # => "echo --out \\;\\ rm\\ /some/file"
|
38
38
|
#
|
39
|
+
# @example With data to be passed on stdin
|
40
|
+
# result = AwesomeSpawn.run('cat', :in_data => "line1\nline2")
|
41
|
+
# => #<AwesomeSpawn::CommandResult:0x007fff05b0ab10 @exit_status=0>
|
42
|
+
# result.output
|
43
|
+
# => "line1\nline2"
|
44
|
+
#
|
39
45
|
# @param [String] command The command to run
|
40
|
-
# @param [Hash] options The options for running the command
|
46
|
+
# @param [Hash] options The options for running the command. Also accepts any
|
47
|
+
# option that can be passed to Kernel.spawn, except `:out` and `:err`.
|
41
48
|
# @option options [Hash,Array] :params The command line parameters. See
|
42
|
-
# {#build_command_line} for how to specify params.
|
43
|
-
#
|
44
|
-
#
|
49
|
+
# {#build_command_line} for how to specify params.
|
50
|
+
# @option options [String] :in_data Data to be passed on stdin. If this option
|
51
|
+
# is specified you cannot specify `:in`.
|
45
52
|
#
|
46
53
|
# @raise [NoSuchFileError] if the `command` is not found
|
47
54
|
# @return [CommandResult] the output stream, error stream, and exit status
|
48
55
|
# @see http://ruby-doc.org/core/Kernel.html#method-i-spawn Kernel.spawn
|
49
56
|
def run(command, options = {})
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
57
|
+
raise ArgumentError, "options cannot contain :out" if options.include?(:out)
|
58
|
+
raise ArgumentError, "options cannot contain :err" if options.include?(:err)
|
59
|
+
raise ArgumentError, "options cannot contain :in if :in_data is specified" if options.include?(:in) && options.include?(:in_data)
|
60
|
+
options = options.dup
|
61
|
+
params = options.delete(:params)
|
62
|
+
in_data = options.delete(:in_data)
|
54
63
|
|
55
64
|
output = ""
|
56
65
|
error = ""
|
@@ -58,7 +67,7 @@ module AwesomeSpawn
|
|
58
67
|
command_line = build_command_line(command, params)
|
59
68
|
|
60
69
|
begin
|
61
|
-
output, error = launch(command_line,
|
70
|
+
output, error = launch(command_line, in_data, options)
|
62
71
|
status = exitstatus
|
63
72
|
ensure
|
64
73
|
output ||= ""
|
@@ -99,11 +108,15 @@ module AwesomeSpawn
|
|
99
108
|
# @param [String] command The command to run
|
100
109
|
# @param [Hash,Array] params Optional command line parameters. They can
|
101
110
|
# be passed as a Hash or associative Array. The values are sanitized to
|
102
|
-
# prevent command line injection.
|
111
|
+
# prevent command line injection. Keys as symbols are prefixed with `--`,
|
112
|
+
# and `_` is replaced with `-`.
|
103
113
|
#
|
114
|
+
# - `{:key => "value"}` generates `--key value`
|
104
115
|
# - `{"--key" => "value"}` generates `--key value`
|
116
|
+
# - `{:key= => "value"}` generates `--key=value`
|
105
117
|
# - `{"--key=" => "value"}` generates `--key=value`
|
106
|
-
# - `{
|
118
|
+
# - `{:key_name => "value"}` generates `--key-name value`
|
119
|
+
# - `{:key => nil}` generates `--key`
|
107
120
|
# - `{"-f" => ["file1", "file2"]}` generates `-f file1 file2`
|
108
121
|
# - `{nil => ["file1", "file2"]}` generates `file1 file2`
|
109
122
|
#
|
@@ -118,12 +131,22 @@ module AwesomeSpawn
|
|
118
131
|
def sanitize(params)
|
119
132
|
return [] if params.nil? || params.empty?
|
120
133
|
params.collect do |k, v|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
134
|
+
[sanitize_key(k), sanitize_value(v)]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def sanitize_key(key)
|
139
|
+
case key
|
140
|
+
when Symbol then "--#{key.to_s.tr("_", "-")}"
|
141
|
+
else key
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def sanitize_value(value)
|
146
|
+
case value
|
147
|
+
when Array then value.collect { |i| i.to_s.shellescape }
|
148
|
+
when NilClass then value
|
149
|
+
else value.to_s.shellescape
|
127
150
|
end
|
128
151
|
end
|
129
152
|
|
@@ -139,20 +162,34 @@ module AwesomeSpawn
|
|
139
162
|
# http://stackoverflow.com/questions/13829830/ruby-process-spawn-stdout-pipe-buffer-size-limit/13846146#13846146
|
140
163
|
THREAD_SYNC_KEY = "#{self.name}-exitstatus"
|
141
164
|
|
142
|
-
def launch(command, spawn_options = {})
|
165
|
+
def launch(command, in_data, spawn_options = {})
|
143
166
|
out_r, out_w = IO.pipe
|
144
167
|
err_r, err_w = IO.pipe
|
145
|
-
|
146
|
-
|
168
|
+
in_r, in_w = IO.pipe if in_data
|
169
|
+
|
170
|
+
spawn_options[:out] = out_w
|
171
|
+
spawn_options[:err] = err_w
|
172
|
+
spawn_options[:in] = in_r if in_data
|
173
|
+
|
174
|
+
pid = Kernel.spawn(command, spawn_options)
|
175
|
+
|
176
|
+
write_to_input(in_w, in_data) if in_data
|
177
|
+
wait_for_process(pid, out_w, err_w, in_r)
|
147
178
|
wait_for_pipes(out_r, err_r)
|
148
179
|
end
|
149
180
|
|
150
|
-
def
|
181
|
+
def write_to_input(in_w, in_data)
|
182
|
+
in_w.write(in_data)
|
183
|
+
in_w.close
|
184
|
+
end
|
185
|
+
|
186
|
+
def wait_for_process(pid, out_w, err_w, in_r)
|
151
187
|
self.exitstatus = :not_done
|
152
188
|
Thread.new(Thread.current) do |parent_thread|
|
153
189
|
_, status = Process.wait2(pid)
|
154
190
|
out_w.close
|
155
191
|
err_w.close
|
192
|
+
in_r.close if in_r
|
156
193
|
parent_thread[THREAD_SYNC_KEY] = status.exitstatus
|
157
194
|
end
|
158
195
|
end
|
data/spec/awesome_spawn_spec.rb
CHANGED
@@ -6,11 +6,13 @@ describe AwesomeSpawn do
|
|
6
6
|
|
7
7
|
let(:params) do
|
8
8
|
{
|
9
|
-
"--user"
|
10
|
-
"--pass"
|
11
|
-
"--db"
|
12
|
-
"--desc="
|
13
|
-
|
9
|
+
"--user" => "bob",
|
10
|
+
"--pass" => "P@$sw0^& |<>/-+*d%",
|
11
|
+
"--db" => nil,
|
12
|
+
"--desc=" => "Some Description",
|
13
|
+
:symkey => nil,
|
14
|
+
:symkey_dash => nil,
|
15
|
+
nil => ["pkg1", "some pkg"]
|
14
16
|
}
|
15
17
|
end
|
16
18
|
|
@@ -19,21 +21,28 @@ describe AwesomeSpawn do
|
|
19
21
|
end
|
20
22
|
|
21
23
|
shared_examples_for "run" do
|
22
|
-
context "
|
24
|
+
context "options" do
|
23
25
|
before do
|
24
26
|
subject.stub(:exitstatus => 0)
|
25
27
|
end
|
26
28
|
|
27
|
-
it "won't
|
29
|
+
it ":params won't be modified" do
|
28
30
|
orig_params = params.dup
|
29
31
|
subject.stub(:launch)
|
30
32
|
subject.send(run_method, "true", :params => params)
|
31
33
|
expect(orig_params).to eq(params)
|
32
34
|
end
|
33
35
|
|
34
|
-
it "
|
35
|
-
subject.
|
36
|
-
|
36
|
+
it ":in_data cannot be passed with :in" do
|
37
|
+
expect { subject.send(run_method, "true", :in_data => "XXXXX", :in => "/dev/null") } .to raise_error(ArgumentError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it ":out is not supported" do
|
41
|
+
expect { subject.send(run_method, "true", :out => "/dev/null") }.to raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it ":err is not supported" do
|
45
|
+
expect { subject.send(run_method, "true", :err => "/dev/null") }.to raise_error(ArgumentError)
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
@@ -66,6 +75,20 @@ describe AwesomeSpawn do
|
|
66
75
|
expect { subject.send(run_method, "XXXXX --user=bob") }.to raise_error(Errno::ENOENT, "No such file or directory - XXXXX")
|
67
76
|
end
|
68
77
|
|
78
|
+
context "with option" do
|
79
|
+
it ":chdir" do
|
80
|
+
result = subject.send(run_method, "pwd", :chdir => "..")
|
81
|
+
expect(result.exit_status).to eq(0)
|
82
|
+
expect(result.output.chomp).to eq(File.expand_path("..", Dir.pwd))
|
83
|
+
end
|
84
|
+
|
85
|
+
it ":in_data" do
|
86
|
+
result = subject.send(run_method, "cat", :in_data => "line1\nline2")
|
87
|
+
expect(result.exit_status).to eq(0)
|
88
|
+
expect(result.output).to eq("line1\nline2")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
69
92
|
context "#exit_status" do
|
70
93
|
it "command ok exit ok" do
|
71
94
|
expect(subject.send(run_method, "true").exit_status).to eq(0)
|
@@ -113,7 +136,27 @@ describe AwesomeSpawn do
|
|
113
136
|
context ".build_command_line" do
|
114
137
|
it "sanitizes crazy params" do
|
115
138
|
cl = subject.build_command_line("true", modified_params)
|
116
|
-
expect(cl).to eq "true --user bob --pass P@\\$sw0\\^\\&\\ \\|\\<\\>/-\\+\\*d\\% --db --desc=Some\\ Description pkg1 some\\ pkg --pool 123 --pool 456"
|
139
|
+
expect(cl).to eq "true --user bob --pass P@\\$sw0\\^\\&\\ \\|\\<\\>/-\\+\\*d\\% --db --desc=Some\\ Description --symkey --symkey-dash pkg1 some\\ pkg --pool 123 --pool 456"
|
140
|
+
end
|
141
|
+
|
142
|
+
it "handles Symbol keys" do
|
143
|
+
cl = subject.build_command_line("true", :abc => "def")
|
144
|
+
expect(cl).to eq "true --abc def"
|
145
|
+
end
|
146
|
+
|
147
|
+
it "handles Symbol keys with tailing '='" do
|
148
|
+
cl = subject.build_command_line("true", :abc= => "def")
|
149
|
+
expect(cl).to eq "true --abc=def"
|
150
|
+
end
|
151
|
+
|
152
|
+
it "handles Symbol keys with underscore" do
|
153
|
+
cl = subject.build_command_line("true", :abc_def => "ghi")
|
154
|
+
expect(cl).to eq "true --abc-def ghi"
|
155
|
+
end
|
156
|
+
|
157
|
+
it "handles Symbol keys with underscore and tailing '='" do
|
158
|
+
cl = subject.build_command_line("true", :abc_def= => "ghi")
|
159
|
+
expect(cl).to eq "true --abc-def=ghi"
|
117
160
|
end
|
118
161
|
|
119
162
|
it "sanitizes Fixnum array param value" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awesome_spawn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2014-
|
15
|
+
date: 2014-02-03 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bundler
|
@@ -151,18 +151,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
151
151
|
- - ! '>='
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
|
-
segments:
|
155
|
-
- 0
|
156
|
-
hash: -1223152174146401530
|
157
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
155
|
none: false
|
159
156
|
requirements:
|
160
157
|
- - ! '>='
|
161
158
|
- !ruby/object:Gem::Version
|
162
159
|
version: '0'
|
163
|
-
segments:
|
164
|
-
- 0
|
165
|
-
hash: -1223152174146401530
|
166
160
|
requirements: []
|
167
161
|
rubyforge_project:
|
168
162
|
rubygems_version: 1.8.23
|