ratonvirus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|