ion1-mischacks 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +6 -0
- data/README.txt +62 -1
- data/lib/mischacks.rb +73 -13
- data/mischacks.gemspec +3 -3
- data/spec/mischacks_spec.rb +124 -34
- metadata +3 -3
data/History.txt
CHANGED
data/README.txt
CHANGED
@@ -4,7 +4,30 @@
|
|
4
4
|
|
5
5
|
== DESCRIPTION:
|
6
6
|
|
7
|
-
|
7
|
+
Miscellaneous methods that may or may not be useful.
|
8
|
+
|
9
|
+
sh:: Safely pass untrusted parameters to sh scripts.
|
10
|
+
|
11
|
+
fork_and_check:: Run a block in a forked process and raise an exception if the
|
12
|
+
process returns a non-zero value.
|
13
|
+
|
14
|
+
do_and_exit, do_and_exit!:: Run a block. If the block does not run exit!, a
|
15
|
+
successful exec or equivalent, run exit(1) or exit!(1) ourselves. Useful to
|
16
|
+
make sure a forked block either runs a successful exec or dies.
|
17
|
+
|
18
|
+
Any exceptions from the block are printed to standard error.
|
19
|
+
|
20
|
+
overwrite:: Safely replace a file. Writes to a temporary file and then moves it
|
21
|
+
over the old file.
|
22
|
+
|
23
|
+
tempname_for:: Generates an unique temporary path based on a filename. The
|
24
|
+
generated filename resides in the same directory as the original one.
|
25
|
+
|
26
|
+
try_n_times:: Retries a block of code until it succeeds or a maximum number of
|
27
|
+
attempts (default 10) is exceeded.
|
28
|
+
|
29
|
+
Exception#to_formatted_string:: Returns a string that looks like how Ruby would
|
30
|
+
dump an uncaught exception.
|
8
31
|
|
9
32
|
== FEATURES/PROBLEMS:
|
10
33
|
|
@@ -14,6 +37,8 @@ ever use an untrusted variable as a command.
|
|
14
37
|
|
15
38
|
== SYNOPSIS:
|
16
39
|
|
40
|
+
# sh
|
41
|
+
|
17
42
|
MiscHacks.sh %q{
|
18
43
|
diff -u "$1" "$2" | tr a-z A-Z >"$output"
|
19
44
|
}, '/dev/null', '/etc/motd', :output => 'foo'
|
@@ -21,6 +46,42 @@ ever use an untrusted variable as a command.
|
|
21
46
|
unsafe_str = %q{" 'foo' $(bar) `baz` "}
|
22
47
|
MiscHacks.sh 'printf "%s\n" "$1"', unsafe_str
|
23
48
|
|
49
|
+
# fork_and_check
|
50
|
+
|
51
|
+
# These examples raise MiscHacks::ChildError.
|
52
|
+
MiscHacks.fork_and_check do exit! 42 end
|
53
|
+
MiscHacks.fork_and_check do exec 'sh', '-c', 'exit 42' end
|
54
|
+
MiscHacks.fork_and_check do exec 'failure' end
|
55
|
+
|
56
|
+
# Does not raise an error.
|
57
|
+
MiscHacks.fork_and_check do exit! 0 end
|
58
|
+
|
59
|
+
# do_and_exit
|
60
|
+
|
61
|
+
# Prints foo if there are no failures. If anything fails, raises an
|
62
|
+
# exception.
|
63
|
+
MiscHacks.fork_and_check do
|
64
|
+
MiscHacks.do_and_exit! do
|
65
|
+
exec 'sh', '-c', 'echo foo'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# overwrite
|
70
|
+
|
71
|
+
MiscHacks.overwrite 'myconfig' do |io|
|
72
|
+
io << config.to_yaml
|
73
|
+
end
|
74
|
+
|
75
|
+
# tempname_for
|
76
|
+
|
77
|
+
MiscHacks.tempname_for '/foo/bar/baz' # => '/foo/bar/.baz.klyce3f517qkh9l'
|
78
|
+
|
79
|
+
# try_n_times
|
80
|
+
|
81
|
+
io = MiscHacks.try_n_times do
|
82
|
+
File.open path, File::RDWR|File::CREAT|File::EXCL
|
83
|
+
end
|
84
|
+
|
24
85
|
== REQUIREMENTS:
|
25
86
|
|
26
87
|
* POSIX sh
|
data/lib/mischacks.rb
CHANGED
@@ -14,7 +14,7 @@
|
|
14
14
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
15
15
|
|
16
16
|
module MiscHacks
|
17
|
-
VERSION = '0.0.
|
17
|
+
VERSION = '0.0.4'
|
18
18
|
|
19
19
|
class ChildError < RuntimeError
|
20
20
|
attr_reader :status
|
@@ -25,6 +25,19 @@ module MiscHacks
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.sh cmd, *args
|
29
|
+
env = if args.last.is_a? Hash then args.pop else {} end
|
30
|
+
|
31
|
+
fork_and_check do
|
32
|
+
do_and_exit! do
|
33
|
+
env.each_pair do |k, v| ENV[k.to_s] = v.to_s end
|
34
|
+
exec *(%W{sh -e -c #{cmd} sh} + args.map {|a| a.to_s })
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
28
41
|
def self.fork_and_check
|
29
42
|
fork do
|
30
43
|
yield
|
@@ -43,6 +56,8 @@ module MiscHacks
|
|
43
56
|
yield
|
44
57
|
rescue SystemExit => e
|
45
58
|
status = e.status
|
59
|
+
rescue Exception => e
|
60
|
+
warn e.to_formatted_string
|
46
61
|
ensure
|
47
62
|
final_proc.call status
|
48
63
|
end
|
@@ -58,21 +73,66 @@ module MiscHacks
|
|
58
73
|
catching_exit method(:exit!), status, &block
|
59
74
|
end
|
60
75
|
|
61
|
-
def self.
|
62
|
-
|
76
|
+
def self.overwrite path, mode=nil
|
77
|
+
begin
|
78
|
+
stat = File.stat path
|
63
79
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
warn e.to_formatted_string
|
71
|
-
end
|
72
|
-
end
|
80
|
+
raise ArgumentError, "Not a file: #{path}", caller unless stat.file?
|
81
|
+
raise ArgumentError, "Not writable: #{path}", caller unless stat.writable?
|
82
|
+
|
83
|
+
mode ||= stat.mode & 0777
|
84
|
+
rescue Errno::ENOENT
|
85
|
+
stat = nil
|
73
86
|
end
|
74
87
|
|
75
|
-
|
88
|
+
temppath, io = try_n_times do
|
89
|
+
t = tempname_for path
|
90
|
+
io = File.open t, File::RDWR|File::CREAT|File::EXCL
|
91
|
+
[t, io]
|
92
|
+
end
|
93
|
+
|
94
|
+
begin
|
95
|
+
ret = yield io
|
96
|
+
|
97
|
+
File.chmod mode, temppath if mode
|
98
|
+
|
99
|
+
File.rename temppath, path
|
100
|
+
|
101
|
+
rescue
|
102
|
+
File.unlink temppath
|
103
|
+
raise
|
104
|
+
|
105
|
+
ensure
|
106
|
+
io.close
|
107
|
+
end
|
108
|
+
|
109
|
+
ret
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.tempname_for path
|
113
|
+
dirname = File.dirname path
|
114
|
+
basename = File.basename path
|
115
|
+
|
116
|
+
'%s/.%s.%s%s%s' % [
|
117
|
+
dirname,
|
118
|
+
basename,
|
119
|
+
Time.now.to_i.to_s(36),
|
120
|
+
$$.to_s(36),
|
121
|
+
rand(1<<32).to_s(36)
|
122
|
+
]
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.try_n_times n=10
|
126
|
+
i = 0
|
127
|
+
begin
|
128
|
+
ret = yield
|
129
|
+
rescue
|
130
|
+
i += 1
|
131
|
+
retry if i < n
|
132
|
+
raise
|
133
|
+
end
|
134
|
+
|
135
|
+
ret
|
76
136
|
end
|
77
137
|
end
|
78
138
|
|
data/mischacks.gemspec
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{mischacks}
|
5
|
-
s.version = "0.0.
|
5
|
+
s.version = "0.0.4"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Johan Kiviniemi"]
|
9
9
|
s.date = %q{2009-06-28}
|
10
|
-
s.description = %q{Safely pass untrusted parameters to sh scripts}
|
10
|
+
s.description = %q{Miscellaneous methods that may or may not be useful. sh:: Safely pass untrusted parameters to sh scripts. fork_and_check:: Run a block in a forked process and raise an exception if the process returns a non-zero value. do_and_exit, do_and_exit!:: Run a block. If the block does not run exit!, a successful exec or equivalent, run exit(1) or exit!(1) ourselves. Useful to make sure a forked block either runs a successful exec or dies. Any exceptions from the block are printed to standard error. overwrite:: Safely replace a file. Writes to a temporary file and then moves it over the old file. tempname_for:: Generates an unique temporary path based on a filename. The generated filename resides in the same directory as the original one. try_n_times:: Retries a block of code until it succeeds or a maximum number of attempts (default 10) is exceeded. Exception#to_formatted_string:: Returns a string that looks like how Ruby would dump an uncaught exception.}
|
11
11
|
s.email = ["devel@johan.kiviniemi.name"]
|
12
12
|
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
|
13
13
|
s.files = ["COPYING", "History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/mischacks.rb", "mischacks.gemspec", "spec/mischacks_spec.rb", "spec/spec_helper.rb"]
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.require_paths = ["lib"]
|
18
18
|
s.rubyforge_project = %q{mischacks}
|
19
19
|
s.rubygems_version = %q{1.3.1}
|
20
|
-
s.summary = %q{
|
20
|
+
s.summary = %q{Miscellaneous methods that may or may not be useful}
|
21
21
|
|
22
22
|
if s.respond_to? :specification_version then
|
23
23
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
data/spec/mischacks_spec.rb
CHANGED
@@ -17,11 +17,64 @@ require File.expand_path(File.dirname(__FILE__)+'/spec_helper.rb')
|
|
17
17
|
require 'mischacks'
|
18
18
|
|
19
19
|
require 'digest/sha1'
|
20
|
+
require 'fileutils'
|
21
|
+
require 'set'
|
22
|
+
require 'tmpdir'
|
20
23
|
|
21
24
|
mh = MiscHacks
|
22
25
|
ce = MiscHacks::ChildError
|
23
26
|
|
24
27
|
describe mh do
|
28
|
+
describe 'sh' do
|
29
|
+
unsafe_str = %q{" 'foo' $(bar) `baz` "}
|
30
|
+
checksum = Digest::SHA1.hexdigest unsafe_str
|
31
|
+
|
32
|
+
it "should raise #{ce} on error" do
|
33
|
+
good = ['true', '', 'printf ""']
|
34
|
+
bad = ['false', 'exit 2', 'return 2']
|
35
|
+
|
36
|
+
bad.each do |c|
|
37
|
+
lambda do mh.sh c end.should raise_error ce
|
38
|
+
end
|
39
|
+
|
40
|
+
good.each do |c|
|
41
|
+
lambda do mh.sh c end.should_not raise_error
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should call sh with -e' do
|
46
|
+
lambda do mh.sh 'false; true' end.should raise_error ce
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should pass normal parameters safely' do
|
50
|
+
test = lambda do |str, sha|
|
51
|
+
mh.sh %q{
|
52
|
+
temp="$(mktemp -t mischacks.XXXXXXXXXX)"
|
53
|
+
trap 'rm -f "$temp"' 0 1 2 13 15
|
54
|
+
printf "%s" "$1" >"$temp"
|
55
|
+
printf "%s *%s\n" "$2" "$temp" | sha1sum -c >/dev/null 2>&1
|
56
|
+
}, str, sha
|
57
|
+
end
|
58
|
+
|
59
|
+
lambda do test.call unsafe_str, checksum end.should_not raise_error
|
60
|
+
lambda do test.call 'foo', checksum end.should raise_error ce
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should pass environment variables safely' do
|
64
|
+
test = lambda do |str, sha|
|
65
|
+
mh.sh %q{
|
66
|
+
temp="$(mktemp -t mischacks.XXXXXXXXXX)"
|
67
|
+
trap 'rm -f "$temp"' 0 1 2 13 15
|
68
|
+
printf "%s" "$string" >"$temp"
|
69
|
+
printf "%s *%s\n" "$checksum" "$temp" | sha1sum -c >/dev/null 2>&1
|
70
|
+
}, :string => str, :checksum => sha
|
71
|
+
end
|
72
|
+
|
73
|
+
lambda do test.call unsafe_str, checksum end.should_not raise_error
|
74
|
+
lambda do test.call 'foo', checksum end.should raise_error ce
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
25
78
|
describe 'fork_and_check' do
|
26
79
|
it 'should raise an error when child fails' do
|
27
80
|
lambda do mh.fork_and_check do exit 1 end end.should raise_error ce
|
@@ -127,53 +180,90 @@ describe mh do
|
|
127
180
|
end
|
128
181
|
end
|
129
182
|
|
130
|
-
describe '
|
131
|
-
|
132
|
-
|
183
|
+
describe 'overwrite' do
|
184
|
+
before :all do
|
185
|
+
@dir = Dir.mktmpdir
|
186
|
+
@file = "#{@dir}/foo"
|
187
|
+
@content0 = "bar\n"
|
188
|
+
@content1 = "baz\n"
|
189
|
+
@mode0 = 0714
|
190
|
+
@mode1 = 0755
|
191
|
+
end
|
133
192
|
|
134
|
-
|
135
|
-
|
136
|
-
|
193
|
+
after :all do
|
194
|
+
FileUtils.rm_rf @dir
|
195
|
+
end
|
137
196
|
|
138
|
-
|
139
|
-
|
197
|
+
it 'should create a fresh file' do
|
198
|
+
mh.overwrite @file do |io|
|
199
|
+
io << @content0
|
200
|
+
File.exists?(@file).should == false
|
140
201
|
end
|
202
|
+
File.read(@file).should == @content0
|
203
|
+
end
|
141
204
|
|
142
|
-
|
143
|
-
|
205
|
+
it 'should overwrite a file' do
|
206
|
+
mh.overwrite @file do |io|
|
207
|
+
io << @content1
|
208
|
+
File.read(@file).should == @content0
|
144
209
|
end
|
210
|
+
File.read(@file).should == @content1
|
145
211
|
end
|
146
212
|
|
147
|
-
it 'should
|
148
|
-
|
213
|
+
it 'should retain the mode' do
|
214
|
+
File.chmod @mode0, @file
|
215
|
+
mh.overwrite @file do end
|
216
|
+
(File.stat(@file).mode & 07777).should == @mode0
|
149
217
|
end
|
150
218
|
|
151
|
-
it 'should
|
152
|
-
|
153
|
-
|
154
|
-
temp="$(mktemp -t mischacks.XXXXXXXXXX)"
|
155
|
-
trap 'rm -f "$temp"' 0 1 2 13 15
|
156
|
-
printf "%s" "$1" >"$temp"
|
157
|
-
printf "%s *%s\n" "$2" "$temp" | sha1sum -c >/dev/null 2>&1
|
158
|
-
}, str, sha
|
159
|
-
end
|
160
|
-
|
161
|
-
lambda do test.call unsafe_str, checksum end.should_not raise_error
|
162
|
-
lambda do test.call 'foo', checksum end.should raise_error ce
|
219
|
+
it 'should set the mode' do
|
220
|
+
mh.overwrite @file, @mode1 do end
|
221
|
+
(File.stat(@file).mode & 07777).should == @mode1
|
163
222
|
end
|
223
|
+
end
|
164
224
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
225
|
+
describe 'tempname_for' do
|
226
|
+
it 'should generate an unique temporary path' do
|
227
|
+
path = '/foo/bar/baz'
|
228
|
+
tempnames = (0...10).map { mh.tempname_for(path) }.to_set
|
229
|
+
|
230
|
+
# It is very, very unlikely there are duplicates.
|
231
|
+
tempnames.length.should > 8
|
232
|
+
|
233
|
+
tempnames.each do |n|
|
234
|
+
n.should =~ %r{\A/foo/bar/.baz.[a-z0-9]+\z}
|
173
235
|
end
|
236
|
+
end
|
237
|
+
end
|
174
238
|
|
175
|
-
|
176
|
-
|
239
|
+
describe 'try_n_times' do
|
240
|
+
it 'should try 10 times' do
|
241
|
+
e = Class.new RuntimeError
|
242
|
+
|
243
|
+
i = 0
|
244
|
+
mh.try_n_times do
|
245
|
+
i += 1
|
246
|
+
42
|
247
|
+
end.should == 42
|
248
|
+
i.should == 1
|
249
|
+
|
250
|
+
i = 0
|
251
|
+
mh.try_n_times do
|
252
|
+
i += 1
|
253
|
+
raise e if i < 10
|
254
|
+
42
|
255
|
+
end.should == 42
|
256
|
+
i.should == 10
|
257
|
+
|
258
|
+
i = 0
|
259
|
+
lambda do
|
260
|
+
mh.try_n_times do
|
261
|
+
i += 1
|
262
|
+
raise e if i < 11
|
263
|
+
42
|
264
|
+
end
|
265
|
+
end.should raise_error e
|
266
|
+
i.should == 10
|
177
267
|
end
|
178
268
|
end
|
179
269
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ion1-mischacks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Johan Kiviniemi
|
@@ -22,7 +22,7 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 2.3.1
|
24
24
|
version:
|
25
|
-
description: Safely pass untrusted parameters to sh scripts
|
25
|
+
description: "Miscellaneous methods that may or may not be useful. sh:: Safely pass untrusted parameters to sh scripts. fork_and_check:: Run a block in a forked process and raise an exception if the process returns a non-zero value. do_and_exit, do_and_exit!:: Run a block. If the block does not run exit!, a successful exec or equivalent, run exit(1) or exit!(1) ourselves. Useful to make sure a forked block either runs a successful exec or dies. Any exceptions from the block are printed to standard error. overwrite:: Safely replace a file. Writes to a temporary file and then moves it over the old file. tempname_for:: Generates an unique temporary path based on a filename. The generated filename resides in the same directory as the original one. try_n_times:: Retries a block of code until it succeeds or a maximum number of attempts (default 10) is exceeded. Exception#to_formatted_string:: Returns a string that looks like how Ruby would dump an uncaught exception."
|
26
26
|
email:
|
27
27
|
- devel@johan.kiviniemi.name
|
28
28
|
executables: []
|
@@ -69,6 +69,6 @@ rubyforge_project: mischacks
|
|
69
69
|
rubygems_version: 1.2.0
|
70
70
|
signing_key:
|
71
71
|
specification_version: 2
|
72
|
-
summary:
|
72
|
+
summary: Miscellaneous methods that may or may not be useful
|
73
73
|
test_files: []
|
74
74
|
|