safeguard 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +11 -1
- data/bin/safeguard +1 -1
- data/i18n/en.yml +11 -0
- data/i18n/pt.yml +11 -0
- data/lib/safeguard/command/add/hash.rb +31 -0
- data/lib/safeguard/command/add.rb +7 -14
- data/lib/safeguard/command/hash.rb +10 -19
- data/lib/safeguard/command/init.rb +3 -3
- data/lib/safeguard/command/verify.rb +16 -16
- data/lib/safeguard/command.rb +35 -3
- data/lib/safeguard/core_ext/file.rb +2 -0
- data/lib/safeguard/core_ext/module.rb +16 -0
- data/lib/safeguard/digest.rb +42 -44
- data/lib/safeguard/hasher.rb +11 -4
- data/lib/safeguard/output/terminal.rb +74 -0
- data/lib/safeguard/output.rb +7 -0
- data/lib/safeguard/repository/hash_table.rb +44 -5
- data/lib/safeguard/repository.rb +15 -7
- data/lib/safeguard/verifier.rb +16 -11
- data/lib/safeguard/version.rb +2 -2
- data/lib/safeguard/worker.rb +56 -0
- data/lib/safeguard.rb +42 -9
- data/safeguard.gemspec +1 -0
- metadata +28 -10
data/README.markdown
CHANGED
@@ -1,3 +1,13 @@
|
|
1
1
|
# Safeguard
|
2
2
|
|
3
|
-
|
3
|
+
Hash-based file integrity verification utility
|
4
|
+
|
5
|
+
# Installation
|
6
|
+
|
7
|
+
Latest version:
|
8
|
+
|
9
|
+
gem install safeguard
|
10
|
+
|
11
|
+
From source:
|
12
|
+
|
13
|
+
git clone git://github.com/matheusmoreira/safeguard.git
|
data/bin/safeguard
CHANGED
data/i18n/en.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
en:
|
2
|
+
safeguard:
|
3
|
+
output:
|
4
|
+
terminal:
|
5
|
+
before_verifying: "Verifying '%{file}' with %{function}..."
|
6
|
+
before_hashing: "Hashing '%{file}' with %{function}..."
|
7
|
+
after_hashing: ' %{hash}'
|
8
|
+
after_verifying: ' %{result}'
|
9
|
+
hash_missing: 'Hash not in repository'
|
10
|
+
file_missing: 'File not in repository'
|
11
|
+
initialized_repository: 'Initialized Safeguard repository in %{directory}'
|
data/i18n/pt.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
pt:
|
2
|
+
safeguard:
|
3
|
+
output:
|
4
|
+
terminal:
|
5
|
+
before_verifying: "Verificando o %{function} de '%{file}'..."
|
6
|
+
before_hashing: "Calculando o %{function} de '%{file}'..."
|
7
|
+
after_hashing: ' %{hash}'
|
8
|
+
after_verifying: ' %{result}'
|
9
|
+
hash_missing: 'Hash não está presente no repositório'
|
10
|
+
file_missing: 'Arquivo não está presente no repositório'
|
11
|
+
initialized_repository: 'Repositório do Safeguard inicializado em %{directory}'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'safeguard/command/add'
|
2
|
+
require 'safeguard/output/terminal'
|
3
|
+
require 'safeguard/repository'
|
4
|
+
require 'ribbon'
|
5
|
+
|
6
|
+
module Safeguard
|
7
|
+
class Command
|
8
|
+
class Add
|
9
|
+
|
10
|
+
# Hashes files and stores the result in the repository.
|
11
|
+
#
|
12
|
+
# $ safeguard add hash --sha1 *.mp3
|
13
|
+
class Hash < Add
|
14
|
+
|
15
|
+
add_supported_algorithms_as_options!
|
16
|
+
opt :force, 'Rehash files that are already in the repository'
|
17
|
+
|
18
|
+
when_called do |options, files|
|
19
|
+
Repository.new(options.dir).before_save do |repo|
|
20
|
+
functions = options.functions? []
|
21
|
+
hasher_options = Ribbon.new force: options.force?, functions: functions
|
22
|
+
mappings = Output::Terminal.create_mappings_for :before_hashing, :after_hashing
|
23
|
+
Ribbon.merge! hasher_options, mappings
|
24
|
+
repo.hash_and_add! *files, hasher_options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,26 +1,19 @@
|
|
1
1
|
require 'safeguard/command'
|
2
|
-
require 'safeguard/
|
2
|
+
require 'safeguard/repository'
|
3
3
|
|
4
4
|
module Safeguard
|
5
5
|
class Command
|
6
6
|
|
7
|
-
# Adds files to a Repository.
|
7
|
+
# Adds files to a Repository without hashing them.
|
8
|
+
#
|
9
|
+
# $ safeguard add *.mp3
|
8
10
|
class Add < Command
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
"Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
|
13
|
-
arity: [1,0], on_multiple: :append
|
14
|
-
|
15
|
-
opt :force, '--force', 'Rehash files that are already in the repository'
|
16
|
-
|
17
|
-
# For every argument, try to add it to the Repository in the current
|
18
|
-
# directory.
|
19
|
-
action do |options, args|
|
12
|
+
# For every argument, add it to the Repository in the current directory.
|
13
|
+
action do |options, files|
|
20
14
|
repo = Repository.new options.dir
|
21
15
|
repo.before_save do
|
22
|
-
repo.
|
23
|
-
:force => options.force?
|
16
|
+
repo.add! *files
|
24
17
|
end
|
25
18
|
end
|
26
19
|
|
@@ -1,33 +1,24 @@
|
|
1
1
|
require 'safeguard/command'
|
2
|
-
require 'safeguard/
|
2
|
+
require 'safeguard/output/terminal'
|
3
|
+
require 'safeguard/repository'
|
4
|
+
require 'ribbon'
|
3
5
|
|
4
6
|
module Safeguard
|
5
7
|
class Command
|
6
8
|
|
7
9
|
# Outputs the checksum of files.
|
8
10
|
#
|
9
|
-
# $ safeguard hash *.mp3
|
10
|
-
#
|
11
|
-
# The hash function can be specified with the --hash-function option. The
|
12
|
-
# default is SHA1, but MD5 and CRC32 are also supported.
|
13
|
-
#
|
14
|
-
# $ safeguard --hash-function md5 hash *.mp3
|
11
|
+
# $ safeguard hash --sha1 --md5 --crc32 *.mp3
|
15
12
|
class Hash < Command
|
16
13
|
|
17
|
-
|
18
|
-
"Algorithm to use to calculate the file's checksum. " <<
|
19
|
-
"Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
|
20
|
-
default: :sha1, arity: [1,-1]
|
14
|
+
add_supported_algorithms_as_options!
|
21
15
|
|
22
16
|
# For every argument, outputs its checksum if it exists as a file.
|
23
|
-
action do |options,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
puts "\t#{function} => #{hash_data[function]}"
|
29
|
-
end
|
30
|
-
end
|
17
|
+
action do |options, files|
|
18
|
+
hasher_options = Ribbon.new functions: options.functions?([])
|
19
|
+
mappings = Output::Terminal.create_mappings_for :before_hashing, :after_hashing
|
20
|
+
Ribbon.merge! hasher_options, mappings
|
21
|
+
Hasher.new(*files, hasher_options).hash_files!
|
31
22
|
end
|
32
23
|
|
33
24
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'safeguard/command'
|
2
|
+
require 'safeguard/output/terminal'
|
2
3
|
require 'safeguard/repository'
|
3
|
-
require 'fileutils'
|
4
4
|
|
5
5
|
module Safeguard
|
6
6
|
class Command
|
7
7
|
|
8
8
|
# Initializes a Repository in a given directory.
|
9
9
|
#
|
10
|
-
# $ safeguard init
|
10
|
+
# $ safeguard init
|
11
11
|
class Init < Command
|
12
12
|
|
13
13
|
# Initializes a Safeguard Repository in a directory, which is either the
|
@@ -17,7 +17,7 @@ module Safeguard
|
|
17
17
|
dir = File.expand_path(args.pop || options.dir)
|
18
18
|
repo = Repository.new(dir)
|
19
19
|
repo.create_directory!
|
20
|
-
|
20
|
+
Output::Terminal.initialized_repository repo.dir
|
21
21
|
end
|
22
22
|
|
23
23
|
end
|
@@ -1,31 +1,31 @@
|
|
1
1
|
require 'safeguard/command'
|
2
|
+
require 'safeguard/output/terminal'
|
3
|
+
require 'safeguard/repository'
|
4
|
+
require 'ribbon'
|
2
5
|
|
3
6
|
module Safeguard
|
4
7
|
class Command
|
5
8
|
|
6
9
|
# Verifies the files present in a Repository.
|
10
|
+
#
|
11
|
+
# # Verifies all files in repository using SHA1
|
12
|
+
# $ safeguard verify --sha1
|
13
|
+
#
|
14
|
+
# # Verifies all files ending in '.mp3' using CRC32
|
15
|
+
# $ safeguard verify --crc32 *.mp3
|
7
16
|
class Verify < Command
|
8
17
|
|
9
|
-
|
10
|
-
"Algorithm used to calculate the file's checksum. " <<
|
11
|
-
"Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
|
12
|
-
arity: [1,0], on_multiple: :append
|
18
|
+
add_supported_algorithms_as_options!
|
13
19
|
|
14
20
|
# Verify the files passed as arguments using information from the
|
15
21
|
# Repository in the current directory.
|
16
|
-
action do |options,
|
22
|
+
action do |options, files|
|
17
23
|
repo = Repository.new options.dir
|
18
|
-
functions = options.functions
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
value = results[file][function]
|
24
|
-
value = "File not in repository" if value == :file_missing
|
25
|
-
value = "Hash not in repository" if value == :hash_missing
|
26
|
-
puts "\t#{function} => #{value}"
|
27
|
-
end
|
28
|
-
end
|
24
|
+
functions = options.functions? []
|
25
|
+
verifier_options = Ribbon.new functions: functions
|
26
|
+
mappings = Output::Terminal.create_mappings_for :before_verifying, :after_verifying
|
27
|
+
Ribbon.merge! verifier_options, mappings
|
28
|
+
results = repo.verify_files *files, verifier_options
|
29
29
|
end
|
30
30
|
|
31
31
|
end
|
data/lib/safeguard/command.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'safeguard/digest'
|
2
|
+
require 'safeguard/version'
|
1
3
|
require 'acclaim/command'
|
2
|
-
require '
|
4
|
+
require 'i18n'
|
3
5
|
|
4
6
|
module Safeguard
|
5
7
|
|
@@ -9,20 +11,50 @@ module Safeguard
|
|
9
11
|
class Command < Acclaim::Command
|
10
12
|
|
11
13
|
help
|
12
|
-
version
|
14
|
+
version Safeguard::Version::STRING
|
13
15
|
|
14
|
-
opt :dir, '-D', '--
|
16
|
+
opt :dir, '-D', '--directory', 'Directory in which the repository is.',
|
15
17
|
default: Dir.pwd, arity: [1,0]
|
16
18
|
|
19
|
+
opt :locale, Symbol, 'The locale to use for language & localization.',
|
20
|
+
arity: [1,0] do |options, locale|
|
21
|
+
I18n.locale = locale.shift
|
22
|
+
end
|
23
|
+
|
17
24
|
action do |options, args|
|
18
25
|
Init.execute options, args
|
19
26
|
end
|
20
27
|
|
28
|
+
# The class methods.
|
29
|
+
class << self
|
30
|
+
|
31
|
+
# An option will be made for every element in
|
32
|
+
# Digest::SUPPORTED_ALGORITHMS. They may be used together and always
|
33
|
+
# append to the function array, bound to the <tt>:functions</tt> key.
|
34
|
+
#
|
35
|
+
# However, due to the lack of a formally defined +functions+ option,
|
36
|
+
# the Acclaim option parser will not set that option to anything.
|
37
|
+
# Therefore, calling <tt>options.functions</tt> will return an empty
|
38
|
+
# ribbon if the user doesn't specify any functions on the command line.
|
39
|
+
#
|
40
|
+
# <tt>options.functions?</tt> should ALWAYS be called, preferably while
|
41
|
+
# providing an empty array as fallback.
|
42
|
+
def add_supported_algorithms_as_options!
|
43
|
+
Digest::SUPPORTED_ALGORITHMS.each do |algorithm|
|
44
|
+
option algorithm, "Use #{algorithm.to_s.upcase}." do |options|
|
45
|
+
options.functions = (options.functions? []) << algorithm
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
21
52
|
end
|
22
53
|
|
23
54
|
end
|
24
55
|
|
25
56
|
require 'safeguard/command/add'
|
57
|
+
require 'safeguard/command/add/hash'
|
26
58
|
require 'safeguard/command/hash'
|
27
59
|
require 'safeguard/command/init'
|
28
60
|
require 'safeguard/command/verify'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Safeguard
|
2
|
+
module CoreExt
|
3
|
+
|
4
|
+
# Ruby core extensions for the Module class.
|
5
|
+
module Module
|
6
|
+
|
7
|
+
def translation_key(*args)
|
8
|
+
name.split('::').push(*args).map(&:to_s).map(&:downcase).join '.'
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
::Module.send :include, Safeguard::CoreExt::Module
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/safeguard/digest.rb
CHANGED
@@ -7,56 +7,54 @@ module Safeguard
|
|
7
7
|
# Encapsulates checksum computation for files using a set of hash functions.
|
8
8
|
module Digest
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
# Algorithms are expected include the Digest::Instance module.
|
15
|
-
#
|
16
|
-
# OpenSSL::Digest::SHA1.include? Digest::Instance
|
17
|
-
# => true
|
18
|
-
def self.digest_with(algorithm, filename)
|
19
|
-
digest = algorithm.new
|
20
|
-
File.open(filename, 'rb') do |file|
|
21
|
-
file.each_chunk do |chunk|
|
22
|
-
digest << chunk
|
23
|
-
end
|
24
|
-
end
|
25
|
-
digest.hexdigest!
|
26
|
-
end
|
10
|
+
algorithms = { sha1: OpenSSL::Digest::SHA1,
|
11
|
+
md5: OpenSSL::Digest::MD5,
|
12
|
+
crc32: Safeguard::Digest::CRC32 }.freeze
|
27
13
|
|
28
|
-
|
14
|
+
SUPPORTED_ALGORITHMS = algorithms.keys.freeze
|
29
15
|
|
30
|
-
#
|
31
|
-
|
32
|
-
|
16
|
+
# Define one method for every supported algorithm.
|
17
|
+
algorithms.each do |algorithm, digest_class|
|
18
|
+
define_singleton_method algorithm do |*args|
|
19
|
+
digest_with digest_class, *args
|
20
|
+
end
|
33
21
|
end
|
34
22
|
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
23
|
+
# The class methods.
|
24
|
+
class << self
|
25
|
+
|
26
|
+
# Digests a file using an +algorithm+.
|
27
|
+
#
|
28
|
+
# Algorithms are expected include the Digest::Instance module.
|
29
|
+
#
|
30
|
+
# OpenSSL::Digest::SHA1.include? Digest::Instance
|
31
|
+
# => true
|
32
|
+
def digest_with(algorithm, filename)
|
33
|
+
digest = algorithm.new
|
34
|
+
File.open(filename, 'rb') do |file|
|
35
|
+
file.each_chunk do |chunk|
|
36
|
+
digest << chunk
|
37
|
+
end
|
38
|
+
end
|
39
|
+
digest.hexdigest!
|
40
|
+
end
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
|
46
|
-
|
42
|
+
# Digests a file using a hash function, which can be the symbol of any
|
43
|
+
# Digest module method that takes a file. Uses SHA1 by default.
|
44
|
+
#
|
45
|
+
# Safeguard::Digest.file(file, :md5)
|
46
|
+
#
|
47
|
+
# Is equivalent to:
|
48
|
+
#
|
49
|
+
# Safeguard::Digest.md5(file)
|
50
|
+
def file(file, hash_function = :sha1)
|
51
|
+
hash_function = hash_function.to_sym
|
52
|
+
unless respond_to? hash_function
|
53
|
+
raise ArgumentError, "Unsupported hash function: #{hash_function}"
|
54
|
+
end
|
55
|
+
send hash_function, file
|
56
|
+
end
|
47
57
|
|
48
|
-
# Digests a file using a hash function, which can be the symbol of any
|
49
|
-
# Digest module method that takes a file. Uses SHA1 by default.
|
50
|
-
#
|
51
|
-
# Safeguard::Digest.file(file, :md5)
|
52
|
-
#
|
53
|
-
# Is equivalent to:
|
54
|
-
#
|
55
|
-
# Safeguard::Digest.md5(file)
|
56
|
-
def self.file(file, hash_function = :sha1)
|
57
|
-
f = hash_function.to_sym
|
58
|
-
raise ArgumentError, "Unsupported hash function: #{f}" unless respond_to? f
|
59
|
-
send hash_function, file
|
60
58
|
end
|
61
59
|
|
62
60
|
end
|
data/lib/safeguard/hasher.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'safeguard/digest'
|
2
|
+
require 'safeguard/worker'
|
2
3
|
require 'ribbon'
|
3
4
|
require 'ribbon/core_ext/array'
|
4
5
|
|
5
6
|
module Safeguard
|
6
7
|
|
7
8
|
# Hashes a set of files.
|
8
|
-
class Hasher
|
9
|
+
class Hasher < Worker
|
9
10
|
|
10
11
|
# The files which will be hashed.
|
11
12
|
attr_accessor :files
|
@@ -13,6 +14,9 @@ module Safeguard
|
|
13
14
|
# The hash functions to use.
|
14
15
|
attr_accessor :functions
|
15
16
|
|
17
|
+
# Available callbacks.
|
18
|
+
has_callbacks :before_hashing, :after_hashing
|
19
|
+
|
16
20
|
# Initializes a new hasher with the given files. Last argument can be an
|
17
21
|
# options hash or ribbon which specifies the hash functions to use:
|
18
22
|
#
|
@@ -20,12 +24,13 @@ module Safeguard
|
|
20
24
|
# Hasher.new *files, :functions => :sha1
|
21
25
|
# Hasher.new *files, :functions => [ :sha1, :md5, :crc32 ]
|
22
26
|
def initialize(*args)
|
23
|
-
|
24
|
-
funcs =
|
27
|
+
options = args.extract_ribbon!
|
28
|
+
funcs = options.functions? do
|
25
29
|
raise ArgumentError, 'No hash functions specified'
|
26
30
|
end
|
27
31
|
self.functions = [*funcs]
|
28
32
|
self.files = args
|
33
|
+
initialize_callbacks_from options
|
29
34
|
end
|
30
35
|
|
31
36
|
# Calculates the hash of each file. Updates the cached hash results.
|
@@ -33,7 +38,9 @@ module Safeguard
|
|
33
38
|
results = Ribbon.new
|
34
39
|
files.each do |file|
|
35
40
|
functions.each do |function|
|
36
|
-
|
41
|
+
call_callback before_hashing, file, function
|
42
|
+
results[file][function] = result = Safeguard::Digest.file file, function
|
43
|
+
call_callback after_hashing, file, function, result
|
37
44
|
end
|
38
45
|
end
|
39
46
|
results = Ribbon[results]
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'safeguard/core_ext/module'
|
2
|
+
require 'i18n'
|
3
|
+
|
4
|
+
module Safeguard
|
5
|
+
module Output
|
6
|
+
|
7
|
+
# Encapsulates all terminal output.
|
8
|
+
module Terminal
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Appends the given +key+ to this module's translation key and forwards
|
13
|
+
# all other arguments to <tt>I18n.translate</tt>.
|
14
|
+
def translate(key, *args)
|
15
|
+
I18n.t translation_key(key), *args
|
16
|
+
end
|
17
|
+
|
18
|
+
# Prints a string before a file is verified without a new line.
|
19
|
+
def before_verifying(file, function)
|
20
|
+
options = { file: file, function: function }
|
21
|
+
print translate(:before_verifying, preprocess(options))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Prints a string before a file is hashed without a new line.
|
25
|
+
def before_hashing(file, function)
|
26
|
+
options = { file: file, function: function }
|
27
|
+
print translate(:before_hashing, preprocess(options))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Prints a string after a file is hashed with a new line.
|
31
|
+
def after_hashing(file, function, hash)
|
32
|
+
options = { file: file, function: function, hash: hash }
|
33
|
+
puts translate(:after_hashing, preprocess(options))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Prints a string after a file is verified with a new line.
|
37
|
+
def after_verifying(file, function, result)
|
38
|
+
options = { file: file, function: function, result: result }
|
39
|
+
puts translate(:after_verifying, preprocess(options))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates an options ribbon containing containing mappings to this
|
43
|
+
# class' methods.
|
44
|
+
def create_mappings_for(*keys)
|
45
|
+
options = Ribbon.new
|
46
|
+
keys.each { |key| options[key] = method key }
|
47
|
+
options
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialized_repository(directory)
|
51
|
+
options = { directory: directory }
|
52
|
+
puts translate(:initialized_repository, preprocess(options))
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Applies common preprocessing.
|
58
|
+
def preprocess(options = {})
|
59
|
+
options[:function] &&= options[:function].to_s.upcase
|
60
|
+
options[:result] &&= case result = options[:result]
|
61
|
+
when false, nil then :Mismatch
|
62
|
+
when true then :OK
|
63
|
+
else translate result
|
64
|
+
end
|
65
|
+
options
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -11,6 +11,7 @@ module Safeguard
|
|
11
11
|
|
12
12
|
# Initializes this hash table with the contents of the given Ribbon.
|
13
13
|
def initialize(ribbon = nil, &block)
|
14
|
+
ribbon = ribbon.ribbon if self.class === ribbon
|
14
15
|
merge! ribbon, &block if ribbon
|
15
16
|
end
|
16
17
|
|
@@ -27,14 +28,47 @@ module Safeguard
|
|
27
28
|
end
|
28
29
|
|
29
30
|
# Merges this hash table's data with the other's.
|
30
|
-
def merge!(other)
|
31
|
-
|
32
|
-
|
31
|
+
def merge!(other, &block)
|
32
|
+
ribbon.deep_merge! other, &block
|
33
|
+
end
|
34
|
+
|
35
|
+
# Fetches a value. Same as Hash#fetch.
|
36
|
+
def fetch(*args, &block)
|
37
|
+
ribbon.fetch *args, &block
|
33
38
|
end
|
34
39
|
|
35
40
|
# Looks up the checksum data for the given +filename+.
|
36
41
|
def [](filename)
|
37
|
-
ribbon[filename]
|
42
|
+
ribbon.ribbon[filename]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds a file to the hash table.
|
46
|
+
#
|
47
|
+
# The file will not have any hash information associated with it.
|
48
|
+
def add(*filenames)
|
49
|
+
filenames.each { |filename| self[filename] }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Same as #add, but returns +self+.
|
53
|
+
def <<(filename)
|
54
|
+
add filename
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns whether or not the given file is present in this hash table.
|
59
|
+
def has_file?(filename)
|
60
|
+
ribbon.has_key? filename
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns whether the given file has any hash information associated.
|
64
|
+
def has_hashes?(filename, *functions)
|
65
|
+
has_file?(filename) and not self[filename].empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns whether the given file has a hash associated with the function
|
69
|
+
# stored.
|
70
|
+
def has_hash?(filename, function)
|
71
|
+
has_file?(filename) and self[filename].fetch function, nil
|
38
72
|
end
|
39
73
|
|
40
74
|
# Returns a list of files present in this hash table.
|
@@ -45,7 +79,12 @@ module Safeguard
|
|
45
79
|
# The underlying wrapped ribbon used to store filenames and their
|
46
80
|
# associated hashes.
|
47
81
|
def ribbon
|
48
|
-
@ribbon ||= Ribbon::Wrapper.new
|
82
|
+
(@ribbon ||= Ribbon::Wrapper.new).tap do |ribbon|
|
83
|
+
# TODO: Possible bottleneck here. #wrap_all! is a recursive call.
|
84
|
+
# Keep its usage localized instead of calling it on every access?
|
85
|
+
# Once after every modification, perhaps?
|
86
|
+
ribbon.wrap_all!
|
87
|
+
end
|
49
88
|
end
|
50
89
|
|
51
90
|
end
|
data/lib/safeguard/repository.rb
CHANGED
@@ -53,14 +53,22 @@ module Safeguard
|
|
53
53
|
save_hash_table
|
54
54
|
end
|
55
55
|
|
56
|
+
# Adds the given files to the repository.
|
57
|
+
def add!(*files)
|
58
|
+
hash_table.add *files
|
59
|
+
end
|
60
|
+
|
56
61
|
# Calculates the checksum of the given files and stores the results. +args+
|
57
62
|
# will be used to instantiate a new Hasher.
|
58
|
-
|
59
|
-
|
60
|
-
|
63
|
+
#
|
64
|
+
# By default, the hashes of files already in the repository will not be
|
65
|
+
# recalculated. To force that, call with <tt>:force => true</tt>.
|
66
|
+
def hash_and_add!(*args)
|
67
|
+
options = args.extract_ribbon!
|
68
|
+
hasher = Hasher.new *args, options
|
61
69
|
hasher.files.delete_if do |file|
|
62
|
-
|
63
|
-
end unless
|
70
|
+
hasher.functions.any? { |function| hash_table.has_hash? file, function }
|
71
|
+
end unless options.force?
|
64
72
|
results = hasher.results
|
65
73
|
hash_table.merge! results
|
66
74
|
end
|
@@ -71,7 +79,7 @@ module Safeguard
|
|
71
79
|
end
|
72
80
|
|
73
81
|
# Creates a verifier using this repository's hash table.
|
74
|
-
def
|
82
|
+
def create_verifier_for(*args)
|
75
83
|
ribbon = args.extract_wrapped_ribbon!
|
76
84
|
Verifier.new *args, ribbon.merge!(hash_table: hash_table)
|
77
85
|
end
|
@@ -79,7 +87,7 @@ module Safeguard
|
|
79
87
|
# Recalculates the checksum of the given files and compares them to the
|
80
88
|
# stored values. +args+ will be used to instantiate a new Verifier.
|
81
89
|
def verify_files(*args)
|
82
|
-
|
90
|
+
create_verifier_for(*args).results
|
83
91
|
end
|
84
92
|
|
85
93
|
# Returns the path to the repository relative to the given +dir+.
|
data/lib/safeguard/verifier.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'safeguard/hasher'
|
2
2
|
require 'safeguard/repository/hash_table'
|
3
|
+
require 'safeguard/worker'
|
3
4
|
require 'ribbon'
|
4
5
|
require 'ribbon/core_ext/array'
|
5
6
|
|
6
7
|
module Safeguard
|
7
8
|
|
8
9
|
# Rehashes a set of files and compares the results to the stored hashes.
|
9
|
-
class Verifier
|
10
|
+
class Verifier < Worker
|
10
11
|
|
11
12
|
# The hasher used to calculate the checksum of the files.
|
12
13
|
attr_accessor :hasher
|
@@ -14,6 +15,9 @@ module Safeguard
|
|
14
15
|
# Hash table to compare results against.
|
15
16
|
attr_accessor :hash_table
|
16
17
|
|
18
|
+
# Available callbacks.
|
19
|
+
has_callbacks :before_verifying, :after_verifying
|
20
|
+
|
17
21
|
# Initializes a new verifier with the given files. Last argument can be an
|
18
22
|
# options hash or ribbon which specifies the hash table to verify against
|
19
23
|
# and the Hasher to use. If a hasher isn't specified, a new one will be
|
@@ -28,17 +32,16 @@ module Safeguard
|
|
28
32
|
# Verifier.new :functions => [ :crc32, :md5 ], :hash_table => table
|
29
33
|
# Verifier.new *files, :functions => :crc32, :hash_table => table
|
30
34
|
def initialize(*args)
|
31
|
-
|
32
|
-
table =
|
35
|
+
options = args.extract_ribbon!
|
36
|
+
table = options.hash_table? do
|
33
37
|
raise ArgumentError, 'No hash table to verify against'
|
34
38
|
end
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
self.hasher = ribbon.hasher? do
|
40
|
-
Hasher.new *args, ribbon
|
39
|
+
self.hash_table = Repository::HashTable.new table
|
40
|
+
args = hash_table.files if args.empty?
|
41
|
+
self.hasher = options.hasher? do
|
42
|
+
Hasher.new *args, options
|
41
43
|
end
|
44
|
+
initialize_callbacks_from options
|
42
45
|
end
|
43
46
|
|
44
47
|
# Uses the hasher to recalculate the hashes of the files using the specified
|
@@ -52,8 +55,9 @@ module Safeguard
|
|
52
55
|
results = Ribbon.new
|
53
56
|
hasher.each do |file, hash_data|
|
54
57
|
hasher.functions.each do |function|
|
55
|
-
|
56
|
-
|
58
|
+
call_callback before_verifying, file, function
|
59
|
+
results[file][function] = result = if hash_table.has_file? file
|
60
|
+
if hash_table.has_hash? file, function
|
57
61
|
hash_data[function] == hash_table[file][function]
|
58
62
|
else
|
59
63
|
:hash_missing
|
@@ -61,6 +65,7 @@ module Safeguard
|
|
61
65
|
else
|
62
66
|
:file_missing
|
63
67
|
end
|
68
|
+
call_callback after_verifying, file, function, result
|
64
69
|
end
|
65
70
|
end
|
66
71
|
results = Ribbon[results]
|
data/lib/safeguard/version.rb
CHANGED
@@ -11,12 +11,12 @@ module Safeguard
|
|
11
11
|
# Minor version.
|
12
12
|
#
|
13
13
|
# Increments denote backward-compatible changes and additions.
|
14
|
-
MINOR =
|
14
|
+
MINOR = 1
|
15
15
|
|
16
16
|
# Patch version.
|
17
17
|
#
|
18
18
|
# Increments denote changes in implementation.
|
19
|
-
PATCH =
|
19
|
+
PATCH = 0
|
20
20
|
|
21
21
|
# Build version.
|
22
22
|
#
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'ribbon'
|
2
|
+
|
3
|
+
module Safeguard
|
4
|
+
|
5
|
+
# Something that works behind the scenes, possibly talking to an user
|
6
|
+
# interface in the middle of it through callbacks.
|
7
|
+
class Worker
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# All callbacks defined for this class.
|
12
|
+
def callbacks
|
13
|
+
@callbacks ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Defines callbacks for this class.
|
17
|
+
def has_callback(*names)
|
18
|
+
callbacks.push *names
|
19
|
+
names.map(&:to_sym).each do |callback|
|
20
|
+
iv_name = callback.to_s.prepend('@').to_sym
|
21
|
+
define_method callback do |&block|
|
22
|
+
assign_callback iv_name, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Same as +has_callback+.
|
28
|
+
alias has_callbacks has_callback
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
# Calls the given callback, passing it the rest of the arguments.
|
35
|
+
def call_callback(block, *args)
|
36
|
+
block.call *args if block.respond_to? :call
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize_callbacks_from(options)
|
40
|
+
options = Ribbon.wrap options
|
41
|
+
self.class.callbacks.each do |callback|
|
42
|
+
send callback, &options.fetch(callback, nil)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Sets the callback to the given variable name if the block responds to
|
49
|
+
# +call+.
|
50
|
+
def assign_callback(name, &block)
|
51
|
+
instance_variable_set name, block if block.respond_to? :call
|
52
|
+
instance_variable_get name
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/safeguard.rb
CHANGED
@@ -1,16 +1,49 @@
|
|
1
|
-
require '
|
2
|
-
require 'safeguard/digest'
|
3
|
-
require 'safeguard/hasher'
|
4
|
-
require 'safeguard/repository'
|
5
|
-
require 'safeguard/verifier'
|
6
|
-
require 'safeguard/version'
|
1
|
+
require 'i18n'
|
7
2
|
|
8
3
|
# Safeguard module.
|
9
4
|
module Safeguard
|
10
5
|
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Run a command by name with the given arguments.
|
9
|
+
def run(*args)
|
10
|
+
initialize_i18n
|
11
|
+
Command.run *args
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the version of Safeguard.
|
15
|
+
def version
|
16
|
+
Version::STRING
|
17
|
+
end
|
18
|
+
|
19
|
+
# Where the Safeguard installation is located.
|
20
|
+
def root
|
21
|
+
File.expand_path '../..', __FILE__
|
22
|
+
end
|
23
|
+
|
24
|
+
# Directory where translations are kept.
|
25
|
+
def i18n
|
26
|
+
File.join root, 'i18n'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Array of translation files.
|
30
|
+
def translation_files
|
31
|
+
Dir[File.join(i18n, '*')]
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize_i18n
|
35
|
+
I18n.load_path = translation_files
|
36
|
+
end
|
37
|
+
|
14
38
|
end
|
15
39
|
|
16
40
|
end
|
41
|
+
|
42
|
+
require 'safeguard/command'
|
43
|
+
require 'safeguard/digest'
|
44
|
+
require 'safeguard/hasher'
|
45
|
+
require 'safeguard/output'
|
46
|
+
require 'safeguard/repository'
|
47
|
+
require 'safeguard/verifier'
|
48
|
+
require 'safeguard/version'
|
49
|
+
require 'safeguard/worker'
|
data/safeguard.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safeguard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: acclaim
|
16
|
-
requirement: &
|
16
|
+
requirement: &18215560 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *18215560
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: ribbon
|
27
|
-
requirement: &
|
27
|
+
requirement: &18215080 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,21 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *18215080
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: i18n
|
38
|
+
requirement: &18214540 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *18214540
|
36
47
|
- !ruby/object:Gem::Dependency
|
37
48
|
name: rookie
|
38
|
-
requirement: &
|
49
|
+
requirement: &18214080 !ruby/object:Gem::Requirement
|
39
50
|
none: false
|
40
51
|
requirements:
|
41
52
|
- - ! '>='
|
@@ -43,7 +54,7 @@ dependencies:
|
|
43
54
|
version: '0'
|
44
55
|
type: :development
|
45
56
|
prerelease: false
|
46
|
-
version_requirements: *
|
57
|
+
version_requirements: *18214080
|
47
58
|
description: Hash-based file integrity verification utility
|
48
59
|
email: matheus.a.m.moreira@gmail.com
|
49
60
|
executables:
|
@@ -58,20 +69,27 @@ files:
|
|
58
69
|
- README.markdown
|
59
70
|
- Rakefile
|
60
71
|
- bin/safeguard
|
72
|
+
- i18n/en.yml
|
73
|
+
- i18n/pt.yml
|
61
74
|
- lib/safeguard.rb
|
62
75
|
- lib/safeguard/command.rb
|
63
76
|
- lib/safeguard/command/add.rb
|
77
|
+
- lib/safeguard/command/add/hash.rb
|
64
78
|
- lib/safeguard/command/hash.rb
|
65
79
|
- lib/safeguard/command/init.rb
|
66
80
|
- lib/safeguard/command/verify.rb
|
67
81
|
- lib/safeguard/core_ext/file.rb
|
82
|
+
- lib/safeguard/core_ext/module.rb
|
68
83
|
- lib/safeguard/digest.rb
|
69
84
|
- lib/safeguard/digest/crc32.rb
|
70
85
|
- lib/safeguard/hasher.rb
|
86
|
+
- lib/safeguard/output.rb
|
87
|
+
- lib/safeguard/output/terminal.rb
|
71
88
|
- lib/safeguard/repository.rb
|
72
89
|
- lib/safeguard/repository/hash_table.rb
|
73
90
|
- lib/safeguard/verifier.rb
|
74
91
|
- lib/safeguard/version.rb
|
92
|
+
- lib/safeguard/worker.rb
|
75
93
|
- safeguard.gemspec
|
76
94
|
homepage: https://github.com/matheusmoreira/safeguard
|
77
95
|
licenses: []
|
@@ -87,7 +105,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
105
|
version: '0'
|
88
106
|
segments:
|
89
107
|
- 0
|
90
|
-
hash: -
|
108
|
+
hash: -537913789085051653
|
91
109
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
110
|
none: false
|
93
111
|
requirements:
|
@@ -96,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
114
|
version: '0'
|
97
115
|
segments:
|
98
116
|
- 0
|
99
|
-
hash: -
|
117
|
+
hash: -537913789085051653
|
100
118
|
requirements: []
|
101
119
|
rubyforge_project:
|
102
120
|
rubygems_version: 1.8.10
|