safeguard 0.0.3 → 0.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/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
|