ratonvirus 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +22 -0
- data/README.md +247 -0
- data/app/validators/antivirus_validator.rb +26 -0
- data/config/locales/en.yml +6 -0
- data/config/locales/fi.yml +6 -0
- data/config/locales/sv.yml +6 -0
- data/lib/ratonvirus.rb +109 -0
- data/lib/ratonvirus/engine.rb +6 -0
- data/lib/ratonvirus/error.rb +7 -0
- data/lib/ratonvirus/processable.rb +17 -0
- data/lib/ratonvirus/scanner/addon/remove_infected.rb +18 -0
- data/lib/ratonvirus/scanner/base.rb +154 -0
- data/lib/ratonvirus/scanner/eicar.rb +34 -0
- data/lib/ratonvirus/scanner/support/callbacks.rb +84 -0
- data/lib/ratonvirus/storage/active_storage.rb +96 -0
- data/lib/ratonvirus/storage/base.rb +56 -0
- data/lib/ratonvirus/storage/carrierwave.rb +36 -0
- data/lib/ratonvirus/storage/filepath.rb +46 -0
- data/lib/ratonvirus/storage/multi.rb +78 -0
- data/lib/ratonvirus/support/backend.rb +181 -0
- data/lib/ratonvirus/version.rb +5 -0
- data/lib/tasks/ratonvirus.rake +40 -0
- metadata +179 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ratonvirus
|
4
|
+
module Storage
|
5
|
+
class Carrierwave < Base
|
6
|
+
def changed?(record, attribute)
|
7
|
+
record.public_send :"#{attribute}_changed?"
|
8
|
+
end
|
9
|
+
|
10
|
+
def accept?(resource)
|
11
|
+
if resource.is_a?(Array)
|
12
|
+
resource.all? { |subr| subr.is_a?(::CarrierWave::Uploader::Base) }
|
13
|
+
else
|
14
|
+
resource.is_a?(::CarrierWave::Uploader::Base)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def asset_path(asset)
|
19
|
+
return unless block_given?
|
20
|
+
return if asset.nil?
|
21
|
+
return if asset.file.nil?
|
22
|
+
|
23
|
+
yield asset.file.path
|
24
|
+
end
|
25
|
+
|
26
|
+
def asset_remove(asset)
|
27
|
+
path = asset.file.path
|
28
|
+
result = asset.remove!
|
29
|
+
|
30
|
+
# Remove the temp cache dir if it exists
|
31
|
+
dir = File.dirname(path)
|
32
|
+
FileUtils.remove_dir(dir) if File.directory?(dir)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Ratonvirus
|
2
|
+
module Storage
|
3
|
+
class Filepath < Base
|
4
|
+
def changed?(record, attribute)
|
5
|
+
if record.respond_to? :"#{attribute}_changed?"
|
6
|
+
return record.public_send :"#{attribute}_changed?"
|
7
|
+
end
|
8
|
+
|
9
|
+
# Some backends do not implement the `attribute_changed?` methods for
|
10
|
+
# the file resources. In that case our best guess is to check whether
|
11
|
+
# the whole record has changed.
|
12
|
+
record.changed?
|
13
|
+
end
|
14
|
+
|
15
|
+
def accept?(resource)
|
16
|
+
if resource.is_a?(Array)
|
17
|
+
resource.all? { |r| r.is_a?(String) || r.is_a?(File) }
|
18
|
+
else
|
19
|
+
resource.is_a?(String) || resource.is_a?(File)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def asset_path(asset, &block)
|
24
|
+
return unless block_given?
|
25
|
+
|
26
|
+
return unless asset
|
27
|
+
return if asset.empty?
|
28
|
+
|
29
|
+
if asset.respond_to?(:path)
|
30
|
+
# A file asset that responds to path (e.g. default `File`
|
31
|
+
# object).
|
32
|
+
asset_path(asset.path, &block)
|
33
|
+
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
# Plain file path string provided as resource
|
38
|
+
yield asset
|
39
|
+
end
|
40
|
+
|
41
|
+
def asset_remove(asset)
|
42
|
+
FileUtils.remove_file(asset) if File.file?(asset)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Ratonvirus
|
2
|
+
module Storage
|
3
|
+
# Multi storage allows the developers to configure multiple storage backends
|
4
|
+
# for the application at the same time. For instance, in case the scanner is
|
5
|
+
# used for both: scanning the Active Storage resources as well as scanning
|
6
|
+
# file paths, they are handled with separate storages.
|
7
|
+
#
|
8
|
+
# To configure the Multi-storage with two backends, use the following:
|
9
|
+
# Ratonvirus.storage = :multi, {storages: [:filepath, :active_storage]}
|
10
|
+
class Multi < Base
|
11
|
+
# Setup the @storages array with the initialized storage instances.
|
12
|
+
def setup
|
13
|
+
@storages = []
|
14
|
+
|
15
|
+
if config[:storages].is_a?(Array)
|
16
|
+
config[:storages].each do |storage|
|
17
|
+
if storage.is_a?(Array)
|
18
|
+
type = storage[0]
|
19
|
+
storage_config = storage[1]
|
20
|
+
else
|
21
|
+
type = storage
|
22
|
+
end
|
23
|
+
|
24
|
+
cls = Ratonvirus.backend_class('Storage', type)
|
25
|
+
@storages << cls.new(storage_config || {})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Processing of the resource is handled by the first storage in the list
|
31
|
+
# that returns `true` for `accept?(resource)`. Any consequent storages are
|
32
|
+
# skipped.
|
33
|
+
def process(resource, &block)
|
34
|
+
return unless block_given?
|
35
|
+
|
36
|
+
storage_for(resource) do |storage|
|
37
|
+
storage.process(resource, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Fetch the resource from the record using the attribute and check if any
|
42
|
+
# storages accept that resource. If an accepting storage is found, only
|
43
|
+
# check `changed?` against that storage. Otherwise, call the `changed?`
|
44
|
+
# method passing both given parameters for all storages in order and
|
45
|
+
# return in case one of them reports the resource to be changed.
|
46
|
+
def changed?(record, attribute)
|
47
|
+
resource = record.public_send(attribute)
|
48
|
+
|
49
|
+
storage_for(resource) do |storage|
|
50
|
+
return storage.changed?(record, attribute)
|
51
|
+
end
|
52
|
+
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check if any of the storages accept the resource.
|
57
|
+
def accept?(resource)
|
58
|
+
storage_for(resource) do |storage|
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
# Iterates through the @storages array and returns the first storage
|
67
|
+
# that accepts the resource.
|
68
|
+
def storage_for(resource)
|
69
|
+
@storages.each do |storage|
|
70
|
+
if storage.accept?(resource)
|
71
|
+
yield storage
|
72
|
+
return
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module Ratonvirus
|
2
|
+
module Support
|
3
|
+
# The backend implementation allows us to set different backends on the main
|
4
|
+
# Ratonvirus configuration, e.g. scanner and storage backends. This makes
|
5
|
+
# the library agnostic of the actual implementation of these both and allows
|
6
|
+
# the developer to configure
|
7
|
+
#
|
8
|
+
# The solution is a bit hacky monkey patch type of solution as it adds code
|
9
|
+
# to the underlying implementation through class_eval. The reason for this
|
10
|
+
# is to define arbitrary getter, setter and destroye methods that are nicer
|
11
|
+
# to use for the user. Wrapping this functionality to its own module
|
12
|
+
# makes the resulting code less prone to errors as all of the backends are
|
13
|
+
# defined exactly the same way.
|
14
|
+
#
|
15
|
+
# Modifying this may be tough, so be sure to test properly in case you make
|
16
|
+
# any modifications.
|
17
|
+
module Backend
|
18
|
+
# First argument "backend_cls":
|
19
|
+
# The subclass that refers to the backend's namespace, e.g.
|
20
|
+
# `"Scanner"`.
|
21
|
+
#
|
22
|
+
# Second argument "backend_type":
|
23
|
+
# The backend type in the given namespace, e.g. `:eicar`
|
24
|
+
#
|
25
|
+
# The returned result will be e.g.
|
26
|
+
# Ratonvirus::Scanner::Eicar
|
27
|
+
# Ratonvirus::Storage::ActiveStorage
|
28
|
+
def backend_class(backend_cls, backend_type)
|
29
|
+
return backend_type if backend_type.is_a?(Class)
|
30
|
+
|
31
|
+
subclass = ActiveSupport::Inflector.camelize(backend_type.to_s)
|
32
|
+
ActiveSupport::Inflector.constantize(
|
33
|
+
"#{self.name}::#{backend_cls}::#{subclass}"
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
# Defines the "backend" methods.
|
39
|
+
#
|
40
|
+
# For example, this:
|
41
|
+
# define_backend :foo, 'Foo'
|
42
|
+
#
|
43
|
+
# Would define the following methods:
|
44
|
+
# # Getter for foo
|
45
|
+
# def self.foo
|
46
|
+
# @foo ||= create_foo
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # Setter for foo
|
50
|
+
# def self.foo=(foo_type)
|
51
|
+
# set_backend(
|
52
|
+
# :foo,
|
53
|
+
# 'Foo',
|
54
|
+
# foo_type
|
55
|
+
# )
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# # Destroys the currently active foo.
|
59
|
+
# # The foo is re-initialized when the getter is called.
|
60
|
+
# def self.destroy_foo
|
61
|
+
# @foo = nil
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# private
|
65
|
+
# def self.create_foo
|
66
|
+
# if @foo_defs.nil?
|
67
|
+
# raise NotDefinedError.new("Foo not defined!")
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# @foo_defs[:klass].new(@foo_defs[:config])
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# Usage (getter):
|
74
|
+
# Ratonvirus.foo
|
75
|
+
#
|
76
|
+
# Usage (setter):
|
77
|
+
# Ratonvirus.foo = :bar
|
78
|
+
# Ratonvirus.foo = :bar, {option: 'value'}
|
79
|
+
# Ratonvirus.foo = Ratonvirus::Foo::Bar.new
|
80
|
+
# Ratonvirus.foo = Ratonvirus::Foo::Bar.new({option: 'value'})
|
81
|
+
#
|
82
|
+
# Usage (destroyer):
|
83
|
+
# Ratonvirus.destroy_foo
|
84
|
+
#
|
85
|
+
def define_backend(backend_type, backend_subclass)
|
86
|
+
self.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
87
|
+
# Getter for #{backend_type}
|
88
|
+
def self.#{backend_type}
|
89
|
+
@#{backend_type} ||= create_#{backend_type}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Setter for #{backend_type}
|
93
|
+
def self.#{backend_type}=(#{backend_type}_value)
|
94
|
+
set_backend(
|
95
|
+
:#{backend_type},
|
96
|
+
"#{backend_subclass}",
|
97
|
+
#{backend_type}_value
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Destroys the currently active #{backend_type}.
|
102
|
+
# The #{backend_type} is re-initialized when the getter is called.
|
103
|
+
def self.destroy_#{backend_type}
|
104
|
+
@#{backend_type} = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Creates a new backend instance
|
108
|
+
# private
|
109
|
+
def self.create_#{backend_type}
|
110
|
+
if @#{backend_type}_defs.nil?
|
111
|
+
raise NotDefinedError.new("#{backend_subclass} not defined!")
|
112
|
+
end
|
113
|
+
|
114
|
+
@#{backend_type}_defs[:klass].new(
|
115
|
+
@#{backend_type}_defs[:config]
|
116
|
+
)
|
117
|
+
end
|
118
|
+
private_class_method :create_#{backend_type}
|
119
|
+
CODE
|
120
|
+
end
|
121
|
+
|
122
|
+
# Sets the backend to local variables for the backend initialization.
|
123
|
+
# The goal of this method is to get the following configuration set to
|
124
|
+
# local `@x_defs` variable, where 'x' is the type of backend.
|
125
|
+
#
|
126
|
+
# For example, for a backend with type "scanner", this would be
|
127
|
+
# @scanner_defs.
|
128
|
+
#
|
129
|
+
# The first argument, "backend_type" is the type of backend we are
|
130
|
+
# configuring, e.g. `:scanner`.
|
131
|
+
#
|
132
|
+
# The second argument "backend_cls" is the backend subclass that is
|
133
|
+
# used in the module's namespace, e.g. "Scanner". This would refer to
|
134
|
+
# subclasses `Ratonvirus::Scanner::...`.
|
135
|
+
#
|
136
|
+
# The third argument "backend_value" is the actual value the user
|
137
|
+
# provided for the setter method, e.g. `:eicar` or
|
138
|
+
# `Ratonvirus::Scanner::Eicar.new`. The user may also provide a second
|
139
|
+
# argument to the setter method e.g. like
|
140
|
+
# `Ratonvirus.scanner = :eicar, {conf: 'option'}`, in which case these
|
141
|
+
# both arguments are provided in this argument as an array.
|
142
|
+
def set_backend(backend_type, backend_cls, backend_value)
|
143
|
+
base_class = backend_class(backend_cls, "Base")
|
144
|
+
|
145
|
+
if backend_value.is_a?(base_class)
|
146
|
+
# Set the instance
|
147
|
+
instance_variable_set(:"@#{backend_type}", backend_value)
|
148
|
+
|
149
|
+
# Store the class (type) and config for storing them below to local
|
150
|
+
# variable in case it needs to be re-initialized at some point.
|
151
|
+
subtype = backend_value.class
|
152
|
+
config = backend_value.config
|
153
|
+
else
|
154
|
+
if backend_value.is_a?(Array)
|
155
|
+
subtype = backend_value.shift
|
156
|
+
config = backend_value.shift || {}
|
157
|
+
|
158
|
+
unless subtype.is_a?(Symbol)
|
159
|
+
raise InvalidError.new(
|
160
|
+
"Invalid #{backend_type} type: #{subtype}"
|
161
|
+
)
|
162
|
+
end
|
163
|
+
elsif backend_value.is_a?(Symbol)
|
164
|
+
subtype = backend_value
|
165
|
+
config = {}
|
166
|
+
else
|
167
|
+
raise InvalidError.new("Invalid #{backend_type} provided!")
|
168
|
+
end
|
169
|
+
|
170
|
+
# Destroy the current one
|
171
|
+
send(:"destroy_#{backend_type}")
|
172
|
+
end
|
173
|
+
|
174
|
+
instance_variable_set(:"@#{backend_type}_defs", {
|
175
|
+
klass: backend_class(backend_cls, subtype),
|
176
|
+
config: config,
|
177
|
+
})
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
namespace :ratonvirus do
|
2
|
+
desc "Tests if the antivirus scanner is available and properly configured"
|
3
|
+
task test: :environment do
|
4
|
+
begin
|
5
|
+
if Ratonvirus.scanner.available?
|
6
|
+
puts "Ratonvirus correctly configured."
|
7
|
+
else
|
8
|
+
puts "Ratonvirus scanner is not available!"
|
9
|
+
puts ""
|
10
|
+
puts "Please refer to Ratonvirus documentation for proper configuration."
|
11
|
+
end
|
12
|
+
rescue
|
13
|
+
puts "Ratonvirus scanner is not configured."
|
14
|
+
puts ""
|
15
|
+
puts "Please refer to Ratonvirus documentation for proper configuration."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Scans the given file through the antivirus scanner"
|
20
|
+
task scan: :environment do |t, args|
|
21
|
+
if args.extras.length < 1
|
22
|
+
puts "No files given."
|
23
|
+
puts "Usage:"
|
24
|
+
puts " #{t.name}[/path/to/first/file.pdf,/path/to/second/file.pdf]"
|
25
|
+
next
|
26
|
+
end
|
27
|
+
|
28
|
+
args.extras.each do |path|
|
29
|
+
if File.file?(path)
|
30
|
+
if Ratonvirus.scanner.virus?(path)
|
31
|
+
puts "Detected a virus at: #{path}"
|
32
|
+
else
|
33
|
+
puts "Clean file at: #{path}"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
puts "File does not exist at: #{path}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ratonvirus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Antti Hukkanen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.16.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.16.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activemodel
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activestorage
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '5.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '5.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: carrierwave
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.2'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.2'
|
125
|
+
description: Adds antivirus check capability for Rails applications.
|
126
|
+
email:
|
127
|
+
- antti.hukkanen@mainiotech.fi
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- CHANGELOG.md
|
133
|
+
- LICENSE
|
134
|
+
- README.md
|
135
|
+
- app/validators/antivirus_validator.rb
|
136
|
+
- config/locales/en.yml
|
137
|
+
- config/locales/fi.yml
|
138
|
+
- config/locales/sv.yml
|
139
|
+
- lib/ratonvirus.rb
|
140
|
+
- lib/ratonvirus/engine.rb
|
141
|
+
- lib/ratonvirus/error.rb
|
142
|
+
- lib/ratonvirus/processable.rb
|
143
|
+
- lib/ratonvirus/scanner/addon/remove_infected.rb
|
144
|
+
- lib/ratonvirus/scanner/base.rb
|
145
|
+
- lib/ratonvirus/scanner/eicar.rb
|
146
|
+
- lib/ratonvirus/scanner/support/callbacks.rb
|
147
|
+
- lib/ratonvirus/storage/active_storage.rb
|
148
|
+
- lib/ratonvirus/storage/base.rb
|
149
|
+
- lib/ratonvirus/storage/carrierwave.rb
|
150
|
+
- lib/ratonvirus/storage/filepath.rb
|
151
|
+
- lib/ratonvirus/storage/multi.rb
|
152
|
+
- lib/ratonvirus/support/backend.rb
|
153
|
+
- lib/ratonvirus/version.rb
|
154
|
+
- lib/tasks/ratonvirus.rake
|
155
|
+
homepage: https://github.com/mainio/ratonvirus
|
156
|
+
licenses:
|
157
|
+
- MIT
|
158
|
+
metadata: {}
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
requirements: []
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 2.7.7
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: Provides antivirus checks for Rails.
|
179
|
+
test_files: []
|