checksum-tools 1.1.0
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/Gemfile +10 -0
- data/Gemfile.lock +51 -0
- data/README.rdoc +109 -0
- data/Rakefile +38 -0
- data/bin/checksum-tools +172 -0
- data/lib/checksum-tools.rb +180 -0
- data/lib/checksum-tools/local.rb +122 -0
- data/lib/checksum-tools/remote.rb +148 -0
- metadata +170 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
checksum-tools (1.1.0)
|
|
5
|
+
net-sftp
|
|
6
|
+
net-ssh
|
|
7
|
+
progressbar
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
diff-lcs (1.2.4)
|
|
13
|
+
ffi (1.9.0)
|
|
14
|
+
gssapi (1.1.2)
|
|
15
|
+
ffi (>= 1.0.1)
|
|
16
|
+
json (1.8.0)
|
|
17
|
+
net-sftp (2.1.2)
|
|
18
|
+
net-ssh (>= 2.6.5)
|
|
19
|
+
net-ssh (2.7.0)
|
|
20
|
+
net-ssh-kerberos (0.2.7)
|
|
21
|
+
net-ssh (>= 2.0)
|
|
22
|
+
net-ssh-krb (0.3.0)
|
|
23
|
+
gssapi (~> 1.1.2)
|
|
24
|
+
net-ssh (>= 2.0)
|
|
25
|
+
progressbar (0.21.0)
|
|
26
|
+
rake (10.1.0)
|
|
27
|
+
rcov (1.0.0)
|
|
28
|
+
rdoc (4.0.1)
|
|
29
|
+
json (~> 1.4)
|
|
30
|
+
rspec (2.14.1)
|
|
31
|
+
rspec-core (~> 2.14.0)
|
|
32
|
+
rspec-expectations (~> 2.14.0)
|
|
33
|
+
rspec-mocks (~> 2.14.0)
|
|
34
|
+
rspec-core (2.14.5)
|
|
35
|
+
rspec-expectations (2.14.3)
|
|
36
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
|
37
|
+
rspec-mocks (2.14.3)
|
|
38
|
+
yard (0.8.7.2)
|
|
39
|
+
|
|
40
|
+
PLATFORMS
|
|
41
|
+
ruby
|
|
42
|
+
|
|
43
|
+
DEPENDENCIES
|
|
44
|
+
checksum-tools!
|
|
45
|
+
net-ssh-kerberos
|
|
46
|
+
net-ssh-krb
|
|
47
|
+
rake (>= 0.8.7)
|
|
48
|
+
rcov
|
|
49
|
+
rdoc
|
|
50
|
+
rspec
|
|
51
|
+
yard
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
= Name
|
|
2
|
+
|
|
3
|
+
checksum-tools --- Generate or verify checksums for a set of files
|
|
4
|
+
|
|
5
|
+
= Setup
|
|
6
|
+
|
|
7
|
+
== Requirements
|
|
8
|
+
|
|
9
|
+
* Ruby 1.8.7
|
|
10
|
+
* Rubygems 1.3.6 or greater
|
|
11
|
+
|
|
12
|
+
== Installation
|
|
13
|
+
|
|
14
|
+
gem install checksum-tools
|
|
15
|
+
|
|
16
|
+
= Synopsis
|
|
17
|
+
|
|
18
|
+
Usage: checksum-tools [options] [path]
|
|
19
|
+
-a, --action ACTION Specify action to take
|
|
20
|
+
generate|verify
|
|
21
|
+
-c, --config FILE Load configuration from FILE
|
|
22
|
+
-d, --digest DIGEST Generate checksums of type DIGEST
|
|
23
|
+
-e, --extension EXT File extension for digest files
|
|
24
|
+
-f, --filemask MASK Include files matching MASK
|
|
25
|
+
-n, --no-action Dry run; don't execute
|
|
26
|
+
-o, --overwrite Overwrite existing digest files
|
|
27
|
+
-q, --quiet Hide the progress bar
|
|
28
|
+
-r, --recursive Recurse into subdirectories
|
|
29
|
+
-x, --exclude MASK Exclude files matching MASK
|
|
30
|
+
-D, --digest-types Show digest types
|
|
31
|
+
-v, --version Print version and exit
|
|
32
|
+
-h, --help Show this help message
|
|
33
|
+
|
|
34
|
+
= Description
|
|
35
|
+
|
|
36
|
+
checksum-tools is a dual-purpose tool to generate or verify checksums of various types for a set of files in a given directory.
|
|
37
|
+
|
|
38
|
+
= Options
|
|
39
|
+
|
|
40
|
+
<tt>-a, --action ACTION</tt>
|
|
41
|
+
|
|
42
|
+
Valid choices are <tt>generate</tt> and <tt>verify</tt>. In <tt>generate</tt> mode, a digest file is created for each content file processed. In <tt>verify</tt> mode, the digests in digest files are verified against the content files and PASS/FAIL information written to standard output. Default: <tt>verify</tt>.
|
|
43
|
+
|
|
44
|
+
<tt>-c, --config FILE</tt>
|
|
45
|
+
|
|
46
|
+
Load default configuration values from <tt>FILE</tt>. Default: a file called <tt>.checksum-tools</tt> in the user's home directory. Options specified on the command line will override any defaults in the configuration file.
|
|
47
|
+
|
|
48
|
+
<tt>-d, --digest DIGEST</tt>
|
|
49
|
+
|
|
50
|
+
In <tt>generate</tt> mode, specifies the type of digest to generate. Valid options depend on the Ruby libraries available on the system, but usually include <tt>md5</tt>, <tt>sha1</tt>, <tt>sha256</tt>, <tt>sha384</tt>, and <tt>sha512</tt>. Default: <tt>md5</tt>. Including this option multiple times causes multiple digests to be generated for each file.
|
|
51
|
+
|
|
52
|
+
<tt>-D, --digest-types</tt>
|
|
53
|
+
|
|
54
|
+
Display the available digest types for the specified target directory/path/system.
|
|
55
|
+
|
|
56
|
+
<tt>-e, --extension EXT</tt>
|
|
57
|
+
|
|
58
|
+
The file extension to be appended to the names of content files when generating digest files. Default: <tt>.digest</tt>.
|
|
59
|
+
|
|
60
|
+
<tt>-f, --filemask MASK</tt>
|
|
61
|
+
|
|
62
|
+
Generate or verify checksums for files matching <tt>MASK</tt>. Include this option multiple times to match multiple filemasks. Default: <tt>*</tt> (all files)
|
|
63
|
+
|
|
64
|
+
<tt>-n, --no-action</tt>
|
|
65
|
+
|
|
66
|
+
Dry run — display configuration options and exit.
|
|
67
|
+
|
|
68
|
+
<tt>-o, --overwrite</tt>
|
|
69
|
+
|
|
70
|
+
Overwrite existing digest files with newly-generated ones.
|
|
71
|
+
|
|
72
|
+
<tt>-q, --quiet</tt>
|
|
73
|
+
|
|
74
|
+
Do not display a progress bar for each file generated.
|
|
75
|
+
|
|
76
|
+
<tt>-r, --recursive</tt>
|
|
77
|
+
|
|
78
|
+
Recurse into subdirectories.
|
|
79
|
+
|
|
80
|
+
<tt>-v, --version</tt>
|
|
81
|
+
|
|
82
|
+
Print version number and exit.
|
|
83
|
+
|
|
84
|
+
<tt>-x, --exclude MASK</tt>
|
|
85
|
+
|
|
86
|
+
Exclude files matching <tt>MASK</tt> from the operation. Can be used to skip a certain subset of files otherwise included using the <tt>--filemask</tt> option.
|
|
87
|
+
|
|
88
|
+
<tt>-h, --help</tt>
|
|
89
|
+
|
|
90
|
+
Display a simple help message and exit.
|
|
91
|
+
|
|
92
|
+
== Releases
|
|
93
|
+
- <b>0.6.6</b> Initial release
|
|
94
|
+
- <b>0.7.0</b> Added ability to verify against digest files created by other tools
|
|
95
|
+
- <b>0.7.1</b> Added -v / --version switch
|
|
96
|
+
- <b>0.7.2</b> Fixed shift error when evaluating .digest files
|
|
97
|
+
- <b>0.9.0</b> Added remote openssl invocation capability to generate digests on non-Ruby systems
|
|
98
|
+
- <b>0.9.1</b> Slight refactoring of bin/checksum-tools; removed debugging code
|
|
99
|
+
- <b>0.9.2</b> Fix gem deployment problem with bundler
|
|
100
|
+
- <b>0.9.3</b> Hotfix to revert SFTP/SSH object changes in 0.9.2
|
|
101
|
+
- <b>0.9.4</b> [HYDRALABWARE-59] Checksum-tools anomalies
|
|
102
|
+
- <b>0.9.5</b> Minor bug fixes and spec tests
|
|
103
|
+
- <b>1.0.0</b> Add --openssl command line parameter for self-configuration of remotes
|
|
104
|
+
- <b>1.0.1</b> Hotfix for broken spec test
|
|
105
|
+
- <b>1.0.3</b> Hotfix for remote paths with spaces in them
|
|
106
|
+
- <b>1.0.4</b> Hotfix for remote paths with singles quotes in them
|
|
107
|
+
- <b>1.0.5</b> Ensure remote commands are always invoked through bash
|
|
108
|
+
- <b>1.0.6</b> Fix remote digest type introspection bug introduced in 1.0.5
|
|
109
|
+
- <b>1.0.7</b> Compensate for differences between OpenSSL 0.9.8 and OpenSSL 1.0.0
|
data/Rakefile
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'bundler'
|
|
4
|
+
require 'bundler/gem_tasks'
|
|
5
|
+
|
|
6
|
+
Dir.glob('lib/tasks/*.rake').each { |r| import r }
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
Bundler.setup(:default, :development)
|
|
10
|
+
rescue Bundler::BundlerError => e
|
|
11
|
+
$stderr.puts e.message
|
|
12
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
|
13
|
+
exit e.status_code
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require 'rspec/core/rake_task'
|
|
17
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
18
|
+
|
|
19
|
+
RSpec::Core::RakeTask.new(:features) do |spec|
|
|
20
|
+
spec.libs << 'lib' << 'spec'
|
|
21
|
+
spec.spec_files = FileList['spec/features/**/*_spec.rb']
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
|
26
|
+
spec.pattern = 'spec/unit_tests/**/*.rb'
|
|
27
|
+
spec.rcov = true
|
|
28
|
+
spec.rcov_opts = %w{--exclude spec\/*,gems\/*,ruby\/* --aggregate coverage.data}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
task :clean do
|
|
33
|
+
puts 'Cleaning old coverage.data'
|
|
34
|
+
FileUtils.rm('coverage.data') if(File.exists? 'coverage.data')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
task :default => [:spec]
|
|
38
|
+
|
data/bin/checksum-tools
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
begin
|
|
5
|
+
require 'checksum-tools'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
require 'bundler/setup'
|
|
8
|
+
require 'checksum-tools'
|
|
9
|
+
end
|
|
10
|
+
require 'optparse'
|
|
11
|
+
require 'progressbar'
|
|
12
|
+
require 'yaml'
|
|
13
|
+
|
|
14
|
+
DEFAULT_OPTIONS = {
|
|
15
|
+
:action => 'verify',
|
|
16
|
+
:digests => [],
|
|
17
|
+
:filemasks => [],
|
|
18
|
+
:progress => true,
|
|
19
|
+
:exclude => [],
|
|
20
|
+
:extension => '.digest',
|
|
21
|
+
:openssl => nil,
|
|
22
|
+
:overwrite => false,
|
|
23
|
+
:recursive => false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@pbar = nil
|
|
27
|
+
def progress(filename, size, pos)
|
|
28
|
+
unless size.nil?
|
|
29
|
+
if pos == -1
|
|
30
|
+
@pbar.finish unless @pbar.nil?
|
|
31
|
+
@pbar = nil
|
|
32
|
+
else
|
|
33
|
+
if @pbar.nil?
|
|
34
|
+
@pbar = ProgressBar.new(File.basename(filename), size)
|
|
35
|
+
end
|
|
36
|
+
@pbar.set(pos)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def generate(tool, path, options)
|
|
42
|
+
tool.create_digest_files(path,options[:filemasks]) do |filename, size, pos|
|
|
43
|
+
progress(filename, size, pos) if options[:progress]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def verify(tool, path, options)
|
|
48
|
+
tool.verify_digest_files(path,options[:filemasks]) do |filename, size, pos, result|
|
|
49
|
+
if result.nil?
|
|
50
|
+
progress(filename, size, pos) if options[:progress]
|
|
51
|
+
else
|
|
52
|
+
progress(filename, -1, -1) if options[:progress]
|
|
53
|
+
result.delete_if { |k,v| v == true }
|
|
54
|
+
if result.length == 0
|
|
55
|
+
print "PASS"
|
|
56
|
+
else
|
|
57
|
+
print "FAIL [#{result.keys.join(',')}]"
|
|
58
|
+
end
|
|
59
|
+
puts " #{filename}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
VALID_ACTIONS = ['generate','verify','console']
|
|
65
|
+
|
|
66
|
+
config_file = File.join(ENV['HOME'], '.checksum-tools')
|
|
67
|
+
config_file_options = begin
|
|
68
|
+
YAML.load(File.read(config_file))
|
|
69
|
+
rescue
|
|
70
|
+
{}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
cmdline_options = {}
|
|
74
|
+
dry_run = false
|
|
75
|
+
|
|
76
|
+
optparse = OptionParser.new do |opts|
|
|
77
|
+
opts.banner = "Usage: #{File.basename($0)} [options] [path]"
|
|
78
|
+
|
|
79
|
+
opts.on('-a', '--action ACTION', VALID_ACTIONS, "Specify action to take", "#{VALID_ACTIONS.join('|')}") do |action|
|
|
80
|
+
cmdline_options[:action] = action
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
opts.on('-c', '--config FILE', "Load configuration from FILE") do |filename|
|
|
84
|
+
config_file = filename
|
|
85
|
+
config_file_options = YAML.load(File.read(config_file))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
opts.on('-d', '--digest DIGEST', "Generate checksums of type DIGEST") do |digest|
|
|
89
|
+
(cmdline_options[:digests] ||= []) << digest.to_sym
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
opts.on('-e', '--extension EXT', "File extension for digest files") do |ext|
|
|
93
|
+
cmdline_options[:extension] = ext
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
opts.on('-f', '--filemask MASK', "Include files matching MASK") do |mask|
|
|
97
|
+
(cmdline_options[:filemasks] ||= []) << mask
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
opts.on('-n', '--no-action', "Dry run; don't execute") do
|
|
101
|
+
dry_run = true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
opts.on('-o', '--overwrite', "Overwrite existing digest files") do
|
|
105
|
+
cmdline_options[:overwrite] = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
opts.on('-q', '--quiet', "Hide the progress bar") do
|
|
109
|
+
cmdline_options[:progress] = false
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
opts.on('-r', '--recursive', "Recurse into subdirectories") do
|
|
113
|
+
cmdline_options[:recursive] = true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
opts.on('-s', '--openssl PATH', "Specify location of openssl on remote server") do |arg|
|
|
117
|
+
cmdline_options[:openssl] = arg
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
opts.on('-x', '--exclude MASK', "Exclude files matching MASK") do |mask|
|
|
121
|
+
cmdline_options[:exclude] << mask
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
opts.on_tail('-D', '--digest-types', "Show digest types") do
|
|
125
|
+
path_info = Checksum::Tools.parse_path(ARGV.pop || '.')
|
|
126
|
+
tool = Checksum::Tools.new(path_info, cmdline_options)
|
|
127
|
+
puts "Available digest types for #{tool.host || 'local system'}:"
|
|
128
|
+
puts tool.digests.join(' ')
|
|
129
|
+
exit
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
opts.on_tail('-v', '--version', "Print version and exit") do
|
|
133
|
+
puts "checksum-tools #{Checksum::Tools::VERSION}"
|
|
134
|
+
exit
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
opts.on_tail('-h', '--help', "Show this help message") do
|
|
138
|
+
puts opts
|
|
139
|
+
exit
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
optparse.parse!
|
|
144
|
+
|
|
145
|
+
options = DEFAULT_OPTIONS.merge(config_file_options).merge(cmdline_options)
|
|
146
|
+
options[:filemasks] << '*' if options[:filemasks].length == 0
|
|
147
|
+
options[:digests] << :md5 if options[:digests].length == 0
|
|
148
|
+
options[:digests].uniq!
|
|
149
|
+
options[:filemasks].uniq!
|
|
150
|
+
options[:exclude].uniq!
|
|
151
|
+
|
|
152
|
+
if options[:extension] !~ /[A-Za-z0-9]/
|
|
153
|
+
puts optparse
|
|
154
|
+
exit
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
if dry_run
|
|
158
|
+
puts "Defaults loaded from #{config_file}"
|
|
159
|
+
puts YAML.dump(options)
|
|
160
|
+
exit
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
args = options[:digests].dup
|
|
164
|
+
args << options.dup.reject { |k,v| not [:exclude,:extension,:openssl,:overwrite,:recursive].include?(k) }
|
|
165
|
+
path_info = Checksum::Tools.parse_path(ARGV.pop || '.')
|
|
166
|
+
tool = Checksum::Tools.new(path_info, *args)
|
|
167
|
+
|
|
168
|
+
case options[:action]
|
|
169
|
+
when 'generate' then generate(tool, path_info[:dir], options)
|
|
170
|
+
when 'verify' then verify(tool, path_info[:dir], options)
|
|
171
|
+
when 'console' then require 'ripl'; Ripl.start :binding => binding
|
|
172
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
module Checksum
|
|
2
|
+
module Tools
|
|
3
|
+
VERSION = "1.1.0"
|
|
4
|
+
|
|
5
|
+
CHUNK_SIZE = 1048576 # 1M blocks
|
|
6
|
+
DEFAULT_OPTS = { :overwrite => false, :recursive => false, :exclude => [], :extension => '.digest' }
|
|
7
|
+
|
|
8
|
+
autoload :Local, File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), 'local')
|
|
9
|
+
autoload :Remote, File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), 'remote')
|
|
10
|
+
|
|
11
|
+
class Exception < ::Exception; end
|
|
12
|
+
class ConfigurationError < Exception; end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def new(path_info, *args)
|
|
16
|
+
remote = path_info[:remote]
|
|
17
|
+
if remote.nil? or remote[:host].nil?
|
|
18
|
+
Local.new(*args)
|
|
19
|
+
else
|
|
20
|
+
Remote.new(remote[:host],remote[:user],*args)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse_path(path)
|
|
25
|
+
user,host,dir = path.to_s.scan(/^(?:(.+)@)?(?:(.+):)?(?:(.+))?$/).flatten
|
|
26
|
+
dir ||= '.'
|
|
27
|
+
result = { :remote => { :user => user, :host => host }, :dir => dir }
|
|
28
|
+
return result
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Base
|
|
33
|
+
attr_reader :host
|
|
34
|
+
|
|
35
|
+
def initialize(*args)
|
|
36
|
+
@opts = DEFAULT_OPTS
|
|
37
|
+
if args.last.is_a?(Hash)
|
|
38
|
+
@opts.merge!(args.pop)
|
|
39
|
+
end
|
|
40
|
+
@opts[:exclude] << digest_filename("*")
|
|
41
|
+
@opts[:exclude].uniq!
|
|
42
|
+
@digest_types = args
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def digest_filename(filename)
|
|
46
|
+
"#{filename}.#{opts[:extension].sub(/^\.+/,'')}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def create_digest_files(base_dir, file_masks, &block)
|
|
50
|
+
process_files(base_dir, file_masks) do |filename|
|
|
51
|
+
yield(filename, -1, -1) if block_given?
|
|
52
|
+
create_digest_file(filename, &block)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def create_digest_file(filename, &block)
|
|
57
|
+
digest_file = digest_filename(filename)
|
|
58
|
+
if File.expand_path(filename) == File.expand_path(digest_file)
|
|
59
|
+
raise ArgumentError, "Digest file #{digest_file} will clobber content file #{filename}!"
|
|
60
|
+
end
|
|
61
|
+
if opts[:overwrite] or not file_exists?(digest_file)
|
|
62
|
+
result = digest_file(filename, &block)
|
|
63
|
+
file_open(digest_file,'w') do |cksum|
|
|
64
|
+
result.each_pair do |type,hexdigest|
|
|
65
|
+
cksum.puts("#{type.to_s.upcase}(#{File.basename(filename)})= #{hexdigest}")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def digest_files(base_dir, file_masks, &block)
|
|
72
|
+
process_files(base_dir, file_masks) do |filename|
|
|
73
|
+
yield(filename, -1, -1) if block_given?
|
|
74
|
+
digest_file(filename, &block)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def verify_digest_files(base_dir, file_masks, &block)
|
|
79
|
+
process_files(base_dir, file_masks) do |filename|
|
|
80
|
+
yield(filename, -1, -1) if block_given?
|
|
81
|
+
result = verify_digest_file(filename, &block)
|
|
82
|
+
yield(filename, -1, 0, result)
|
|
83
|
+
result
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def verify_digest_file(filename, &block)
|
|
88
|
+
digest_file = digest_filename(filename)
|
|
89
|
+
unless file_exists?(digest_file)
|
|
90
|
+
return { :digest_file => false }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
hashes = {}
|
|
94
|
+
digest_data = file_read(digest_file)
|
|
95
|
+
|
|
96
|
+
ext_type = File.extname(digest_file)[1..-1].downcase.to_sym
|
|
97
|
+
# Check to see if the digest file extension implies a specific digest type (e.g., .md5)
|
|
98
|
+
if digests.include?(ext_type)
|
|
99
|
+
# Find a hex value of the correct length in the digest file
|
|
100
|
+
len = self.digest_length(ext_type)
|
|
101
|
+
digest = digest_data.scan(/\b((?:[0-9a-f]{4})+)\b/im).flatten.find { |d| d.length == len }
|
|
102
|
+
hashes[ext_type] = digest
|
|
103
|
+
else
|
|
104
|
+
digest_data = digest_data.split(/\n/)
|
|
105
|
+
hashes = digest_data.inject({}) do |collector,sum|
|
|
106
|
+
(hash,hashed_fname,digest) = sum.scan(/(.+)\((.+)\)= (.+)/).flatten
|
|
107
|
+
unless File.basename(hashed_fname) == File.basename(filename)
|
|
108
|
+
warn "WARNING: Filename mismatch in #{digest_file}: #{File.basename(hashed_fname)}"
|
|
109
|
+
end
|
|
110
|
+
collector[hash.downcase.to_sym] = digest
|
|
111
|
+
collector
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
verify_file(filename, hashes, &block)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def verify_file(filename, hashes, &block)
|
|
118
|
+
with_types(hashes.keys) do
|
|
119
|
+
actual = digest_file(filename, &block)
|
|
120
|
+
result = {}
|
|
121
|
+
actual.each_pair do |hash,digest|
|
|
122
|
+
result[hash] = hashes[hash].downcase == digest.downcase
|
|
123
|
+
end
|
|
124
|
+
result
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
protected
|
|
129
|
+
|
|
130
|
+
def glob_to_re(glob)
|
|
131
|
+
replacements = [
|
|
132
|
+
[/\./,'##DOT##'],[/\\\*/,'##STAR##'],[/\\\?/,'##QUEST##'],
|
|
133
|
+
[/\*/,'.*'],[/\?/,'.'],
|
|
134
|
+
[/##STAR##/,'\*'],[/##QUEST##/,'\?'],[/##DOT##/,'\.']
|
|
135
|
+
]
|
|
136
|
+
result = glob.dup
|
|
137
|
+
replacements.each { |args| result.gsub!(*args) }
|
|
138
|
+
Regexp.new(result)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def file_read(filename)
|
|
142
|
+
if file_exists?(filename)
|
|
143
|
+
result = ''
|
|
144
|
+
file_open(filename) { |io| result = io.read }
|
|
145
|
+
return result
|
|
146
|
+
else
|
|
147
|
+
raise Errno::ENOENT, filename
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def process_files(base_dir, include_masks = ["*"])
|
|
152
|
+
unless block_given?
|
|
153
|
+
raise ArgumentError, "no block given"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
excludes = Array(opts[:exclude]).collect { |ex| glob_to_re(ex) }
|
|
157
|
+
targets = file_list(base_dir, *include_masks).reject { |f| excludes.any? { |re| f.match(re) } }
|
|
158
|
+
targets.sort!
|
|
159
|
+
targets.uniq!
|
|
160
|
+
|
|
161
|
+
result = {}
|
|
162
|
+
targets.each do |filename|
|
|
163
|
+
result[filename] = yield(filename)
|
|
164
|
+
end
|
|
165
|
+
return result
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def with_types(new_types)
|
|
169
|
+
old_types = @digest_types
|
|
170
|
+
@digest_types = new_types
|
|
171
|
+
begin
|
|
172
|
+
return(yield)
|
|
173
|
+
ensure
|
|
174
|
+
@digest_types = old_types
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module Checksum::Tools
|
|
2
|
+
|
|
3
|
+
class Local < Base
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
@@digests = {}
|
|
7
|
+
|
|
8
|
+
def register_digest(key, file, &block)
|
|
9
|
+
if @@digests.has_key?(key)
|
|
10
|
+
return false
|
|
11
|
+
else
|
|
12
|
+
begin
|
|
13
|
+
require file
|
|
14
|
+
@@digests[key] = block
|
|
15
|
+
return key
|
|
16
|
+
rescue LoadError
|
|
17
|
+
return false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def digest_for(key)
|
|
23
|
+
handler = @@digests[key]
|
|
24
|
+
if handler.nil?
|
|
25
|
+
raise ArgumentError, "undefined digest class: #{key.to_s}"
|
|
26
|
+
end
|
|
27
|
+
return handler.call()
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def digests
|
|
31
|
+
@@digests.keys
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr :digest_types
|
|
36
|
+
attr :opts
|
|
37
|
+
|
|
38
|
+
def initialize(*args)
|
|
39
|
+
super(*args)
|
|
40
|
+
@digest_types.each { |type| self.class.digest_for(type) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def digests
|
|
44
|
+
self.class.digests
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def digest_length(type)
|
|
48
|
+
self.class.digest_for(type).hexdigest.length
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def digest_file(filename)
|
|
52
|
+
if File.exists?(filename)
|
|
53
|
+
size = file_size(filename)
|
|
54
|
+
block = block_given? ? lambda { |pos| yield(filename, size, pos) } : nil
|
|
55
|
+
File.open(filename, 'r') do |io|
|
|
56
|
+
yield(filename, size, 0) if block_given?
|
|
57
|
+
return digest_stream(io, &block)
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
raise Errno::ENOENT, filename
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def digest_stream(io)
|
|
65
|
+
output = digest_types.inject({}) { |collector,key| collector[key] = self.class.digest_for(key); collector }
|
|
66
|
+
io.rewind
|
|
67
|
+
while chunk = io.read(CHUNK_SIZE)
|
|
68
|
+
output.values.each do |digest|
|
|
69
|
+
digest << chunk
|
|
70
|
+
end
|
|
71
|
+
if block_given?
|
|
72
|
+
yield(io.pos)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
output.each_pair do |type,digest|
|
|
76
|
+
output[type] = digest.hexdigest
|
|
77
|
+
end
|
|
78
|
+
return output
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def verify_stream(io, hashes, &block)
|
|
82
|
+
with_types(hashes.keys) do
|
|
83
|
+
actual = digest_stream(io, &block)
|
|
84
|
+
result = {}
|
|
85
|
+
actual.each_pair do |hash,digest|
|
|
86
|
+
result[hash] = hashes[hash] == digest
|
|
87
|
+
end
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
protected
|
|
93
|
+
|
|
94
|
+
def file_list(base_dir, *file_masks)
|
|
95
|
+
path = [base_dir, '*']
|
|
96
|
+
path.insert(1,'**') if opts[:recursive]
|
|
97
|
+
result = Dir[File.join(*path)]
|
|
98
|
+
result.reject! { |f| File.directory?(f) or (not file_masks.any? { |m| File.fnmatch?(m,File.basename(f)) }) }
|
|
99
|
+
result
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def file_open(*args, &block)
|
|
103
|
+
File.open(*args, &block)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def file_exists?(filename)
|
|
107
|
+
File.exists?(filename)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def file_size(filename)
|
|
111
|
+
File.size(filename)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
Checksum::Tools::Local.register_digest(:md5, 'digest/md5') { Digest::MD5.new }
|
|
119
|
+
Checksum::Tools::Local.register_digest(:sha1, 'digest/sha1') { Digest::SHA1.new }
|
|
120
|
+
Checksum::Tools::Local.register_digest(:sha256, 'digest/sha2') { Digest::SHA2.new(256) }
|
|
121
|
+
Checksum::Tools::Local.register_digest(:sha384, 'digest/sha2') { Digest::SHA2.new(384) }
|
|
122
|
+
Checksum::Tools::Local.register_digest(:sha512, 'digest/sha2') { Digest::SHA2.new(512) }
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require 'net/ssh'
|
|
2
|
+
require 'net/sftp'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'net/ssh/kerberos'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
raise LoadError.new "Include 'net-ssh-kerberos' (ruby 1.8) or 'net-ssh-krb' (ruby 1.9) in your Gemfile"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Checksum::Tools
|
|
11
|
+
|
|
12
|
+
class Remote < Base
|
|
13
|
+
|
|
14
|
+
attr :digest_types
|
|
15
|
+
attr :opts
|
|
16
|
+
|
|
17
|
+
def initialize(host, user, *args)
|
|
18
|
+
super(*args)
|
|
19
|
+
@host = host
|
|
20
|
+
@user = user
|
|
21
|
+
@digest_length_cache = {}
|
|
22
|
+
unless (ssl = opts.delete(:openssl)).nil?
|
|
23
|
+
begin
|
|
24
|
+
remote_properties[:openssl] = ssl
|
|
25
|
+
rescue ConfigurationError
|
|
26
|
+
@remote_properties = { :openssl => ssl }
|
|
27
|
+
end
|
|
28
|
+
write_remote_properties
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def openssl
|
|
33
|
+
remote_properties[:openssl]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def sftp
|
|
37
|
+
@sftp ||= begin
|
|
38
|
+
auth_methods = %w(gssapi-with-mic publickey hostbased) if defined? Net::SSH::Kerberos
|
|
39
|
+
auth_methods ||= %w(publickey hostbased)
|
|
40
|
+
Net::SFTP.start(@host, @user, :auth_methods => auth_methods)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ssh
|
|
45
|
+
result = sftp.session
|
|
46
|
+
if block_given?
|
|
47
|
+
channel = result.open_channel do |ch|
|
|
48
|
+
yield(ch)
|
|
49
|
+
end
|
|
50
|
+
channel.wait
|
|
51
|
+
end
|
|
52
|
+
result
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def exec!(cmd)
|
|
56
|
+
result = ''
|
|
57
|
+
ssh do |ch|
|
|
58
|
+
ch.exec("bash -l") do |ch2, success|
|
|
59
|
+
ch2.on_data { |c,data| result += data }
|
|
60
|
+
ch2.send_data "#{cmd}\n"
|
|
61
|
+
ch2.send_data "exit\n"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
result.chomp
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def digests
|
|
68
|
+
resp = ''
|
|
69
|
+
resp = exec! "#{openssl} dgst -h 2>&1"
|
|
70
|
+
resp.scan(/-(.+?)\s+to use the .+ message digest algorithm/).flatten.collect { |d| d.to_sym }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def digest_length(type)
|
|
74
|
+
if @digest_length_cache[type].nil?
|
|
75
|
+
resp = exec! "echo - | #{openssl} dgst -#{type}"
|
|
76
|
+
@digest_length_cache[type] = resp.split(/\s/).last.chomp.length
|
|
77
|
+
end
|
|
78
|
+
@digest_length_cache[type]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def digest_file(filename)
|
|
82
|
+
if file_exists?(filename)
|
|
83
|
+
size = file_size(filename)
|
|
84
|
+
yield(filename, size, 0) if block_given?
|
|
85
|
+
output = {}
|
|
86
|
+
digest_types.each { |key|
|
|
87
|
+
resp = exec! "#{openssl} dgst -#{key} $'#{filename.gsub(/[']/,'\\\\\'')}'"
|
|
88
|
+
output[key] = resp.split(/\= /).last
|
|
89
|
+
}
|
|
90
|
+
yield(filename, size, size) if block_given?
|
|
91
|
+
return output
|
|
92
|
+
else
|
|
93
|
+
raise Errno::ENOENT, filename
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
protected
|
|
98
|
+
|
|
99
|
+
def file_list(base_dir, *file_masks)
|
|
100
|
+
path = ['*']
|
|
101
|
+
path.unshift('**') if opts[:recursive]
|
|
102
|
+
result = sftp.dir[base_dir, File.join(*path)]
|
|
103
|
+
result.reject! { |f| f.directory? or (not file_masks.any? { |m| File.fnmatch?(m,File.basename(f.name)) }) }
|
|
104
|
+
result.collect { |f| File.expand_path(File.join(base_dir, f.name)) }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def file_open(*args, &block)
|
|
108
|
+
sftp.file.open(*args, &block)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def file_exists?(filename)
|
|
112
|
+
sftp.stat!(filename)
|
|
113
|
+
return true
|
|
114
|
+
rescue Net::SFTP::StatusException
|
|
115
|
+
return false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def file_size(filename)
|
|
119
|
+
if file_exists?(filename)
|
|
120
|
+
sftp.stat!(filename).size
|
|
121
|
+
else
|
|
122
|
+
raise Errno::ENOENT, filename
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def remote_properties
|
|
127
|
+
if @remote_properties.nil?
|
|
128
|
+
home_dir = exec!('echo $HOME')
|
|
129
|
+
settings_file = File.join(home_dir,".checksum-tools-system")
|
|
130
|
+
if file_exists?(settings_file)
|
|
131
|
+
@remote_properties = YAML.load(file_read(settings_file))
|
|
132
|
+
else
|
|
133
|
+
raise ConfigurationError, "Checksum Tools not configured for #{@user}@#{host}. Please use the --openssl parameter to specify the location of the remote openssl binary."
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
@remote_properties
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def write_remote_properties
|
|
140
|
+
home_dir = exec!('echo $HOME')
|
|
141
|
+
settings_file = File.join(home_dir,".checksum-tools-system")
|
|
142
|
+
file_open(settings_file, 'w') { |io| YAML.dump(remote_properties, io) }
|
|
143
|
+
remote_properties
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: checksum-tools
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Michael Klein
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-10-15 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: progressbar
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ! '>='
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '0'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: net-ssh
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
type: :runtime
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: net-sftp
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ! '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ! '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: rake
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 0.8.7
|
|
70
|
+
type: :development
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ! '>='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 0.8.7
|
|
78
|
+
- !ruby/object:Gem::Dependency
|
|
79
|
+
name: rdoc
|
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ! '>='
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
type: :development
|
|
87
|
+
prerelease: false
|
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
89
|
+
none: false
|
|
90
|
+
requirements:
|
|
91
|
+
- - ! '>='
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: rspec
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - ! '>='
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
type: :development
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
none: false
|
|
106
|
+
requirements:
|
|
107
|
+
- - ! '>='
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: yard
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
none: false
|
|
114
|
+
requirements:
|
|
115
|
+
- - ! '>='
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
none: false
|
|
122
|
+
requirements:
|
|
123
|
+
- - ! '>='
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: '0'
|
|
126
|
+
description: Contains classes and executable files to generate and verify checksums
|
|
127
|
+
email:
|
|
128
|
+
- mbklein@stanford.edu
|
|
129
|
+
executables:
|
|
130
|
+
- checksum-tools
|
|
131
|
+
extensions: []
|
|
132
|
+
extra_rdoc_files: []
|
|
133
|
+
files:
|
|
134
|
+
- lib/checksum-tools/local.rb
|
|
135
|
+
- lib/checksum-tools/remote.rb
|
|
136
|
+
- lib/checksum-tools.rb
|
|
137
|
+
- bin/checksum-tools
|
|
138
|
+
- Gemfile
|
|
139
|
+
- Gemfile.lock
|
|
140
|
+
- Rakefile
|
|
141
|
+
- README.rdoc
|
|
142
|
+
homepage:
|
|
143
|
+
licenses: []
|
|
144
|
+
post_install_message:
|
|
145
|
+
rdoc_options: []
|
|
146
|
+
require_paths:
|
|
147
|
+
- lib
|
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
|
+
none: false
|
|
150
|
+
requirements:
|
|
151
|
+
- - ! '>='
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: '0'
|
|
154
|
+
segments:
|
|
155
|
+
- 0
|
|
156
|
+
hash: -3039575937498161987
|
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
|
+
none: false
|
|
159
|
+
requirements:
|
|
160
|
+
- - ! '>='
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: 1.3.6
|
|
163
|
+
requirements: []
|
|
164
|
+
rubyforge_project:
|
|
165
|
+
rubygems_version: 1.8.23
|
|
166
|
+
signing_key:
|
|
167
|
+
specification_version: 3
|
|
168
|
+
summary: Checksum creation and verification tools
|
|
169
|
+
test_files: []
|
|
170
|
+
has_rdoc:
|