ddr-antivirus 1.3.3 → 2.0.0.rc1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45edd3ab36b7e24c365064108e8f5ed6566fb458
4
- data.tar.gz: b4ec40b9b605251d6a33b7e14f8b6d81c0086ae1
3
+ metadata.gz: e16210de6035eef0c49f80ce61c7f2dee7b3fc88
4
+ data.tar.gz: bb79eb5e1a8aa063773e160855589f28e54f4dfd
5
5
  SHA512:
6
- metadata.gz: ed04f9f8030a1990b446a924f420084a1376448261f13c89e1bca713480cdb635865791347b0e05536589a9ba574f8eae6866909efe4c7127561a69c88da068d
7
- data.tar.gz: 06fcf4d60add02d1b24fae522ffb2b5eb0d913ce8edd0dbe35ff016df0342e1fc39fbbd255466f5e6b63ff01c24c29cbb5f39c36c63661681fa4e556dbfd7da7
6
+ metadata.gz: 6d7e101d5f4d85ff8067c2ccc8a98e88cb763cc96df60f719ca6831048058aed7c3b36fa18a68fa4acb65099b78b393ff6fc1b09da5992bbd00e9a9c5d2ff1d5
7
+ data.tar.gz: 7c48dd3ef7c397e26ac8fa3a0b7860336b6af332ade18327e5be016b1b98b79136f4aa8d445edeb5cdc50a139a3185ae274d9a22e2512baecf751aa283fb0e59
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
- --warnings
3
2
  --require spec_helper
data/.travis.yml CHANGED
@@ -1,15 +1,8 @@
1
+ sudo: false
1
2
  language: ruby
2
- before_install:
3
- - sudo apt-get update -qq
4
- - sudo apt-get install -y libclamav-dev clamav clamav-daemon clamav-freshclam
5
- before_script:
6
- - sudo freshclam
7
- - sudo /etc/init.d/clamav-daemon start
8
3
  rvm:
9
4
  - 2.1
5
+ - 2.2
10
6
  cache:
11
7
  - bundler
12
- - apt
13
- notifications:
14
- email:
15
- - lib-drs@duke.edu
8
+
data/Gemfile CHANGED
@@ -1,5 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
-
5
- gem "coveralls", require: false
data/README.md CHANGED
@@ -4,8 +4,6 @@ Pluggable antivirus service for Ruby applications.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/ddr-antivirus.svg)](http://badge.fury.io/rb/ddr-antivirus)
6
6
  [![Build Status](https://travis-ci.org/duke-libraries/ddr-antivirus.svg?branch=develop)](https://travis-ci.org/duke-libraries/ddr-antivirus)
7
- [![Coverage Status](https://coveralls.io/repos/duke-libraries/ddr-antivirus/badge.png?branch=develop)](https://coveralls.io/r/duke-libraries/ddr-antivirus?branch=develop)
8
- [![Code Climate](https://codeclimate.com/github/duke-libraries/ddr-antivirus/badges/gpa.svg)](https://codeclimate.com/github/duke-libraries/ddr-antivirus)
9
7
 
10
8
  ## Installation
11
9
 
@@ -21,74 +19,57 @@ Or install it yourself as:
21
19
 
22
20
  $ gem install ddr-antivirus
23
21
 
24
- ## How It Works
25
-
26
- Ddr::Antivirus does *not* provide a virus scanning engine as a runtime dependency. Instead, it will select a scanner adapter class for the software it finds in your environment following this procedure:
27
-
28
- - If the [clamav](https://github.com/eagleas/clamav) gem is available, it will select the `ClamavScannerAdapter`.
29
- - If the ClamAV Daemon client `clamdscan` is on the user's path, it will select the `ClamdScannerAdapter`. Ddr::Antivirus *does not* manage clamd -- i.e., checking its status, starting or reloading the database. These tasks must be managed externally.
30
- - Otherwise, it will select the [`NullScannerAdapter`](#the-nullscanneradapter).
31
-
32
- The auto-selection process may be overridden by configuration:
33
-
34
- ```ruby
35
- Ddr::Antivirus.configure do |config|
36
- config.scanner_adapter = :clamd # or :clamav, or :null
37
- end
38
- ```
39
-
40
- ## Usage
41
-
42
22
  ### Scanning ###
43
23
 
44
- Class: `Ddr::Antivirus::Scanner`
45
-
46
24
  ```ruby
47
25
  require "ddr-antivirus"
48
26
 
49
- # Using the class method .scan
50
- result = Ddr::Antivirus::Scanner.scan(path)
27
+ result = Ddr::Antivirus.scan(path)
51
28
 
52
- # Using the instance method #scan with a block
53
- Ddr::Antivirus::Scanner.new do |scanner|
29
+ Ddr::Antivirus.scanner do |scanner|
54
30
  result = scanner.scan(path)
55
31
  end
56
32
  ```
57
33
 
58
- The scanner raises a `Ddr::Antivirus::VirusFoundError` exception if a virus is found.
34
+ ### Exceptions
59
35
 
60
- ### Results
36
+ All exceptions under the `Ddr::Antivirus` namespace.
61
37
 
62
- Class: `Ddr::Antivirus::Adapters::ScanResult`
38
+ `Error` - Parent exception class.
63
39
 
64
- A scanner adapter may subclass the base class to parse the raw result properly.
40
+ `VirusFoundError` - A virus was found. The message includes the original output from the scanner.
65
41
 
66
- ```ruby
67
- >> require "ddr-antivirus"
68
- => true
42
+ `ScannerError` - The scanner encountered an error (e.g., error exit status).
69
43
 
70
- >> result = Ddr::Antivirus::Scanner.scan("/path/to/blue-devil.png")
71
- => #<Ddr::Antivirus::Adapters::ClamavScanResult:0x007f98fb169cc0 ...
44
+ ### Example
72
45
 
73
- # Was there a virus?
74
- >> result.has_virus?
75
- => false
46
+ ```
47
+ > require 'ddr/antivirus'
48
+ => true
49
+
50
+ > Ddr::Antivirus.scanner_adapter = :clamd
51
+ => :clamd
52
+
53
+ > result = Ddr::Antivirus.scan "/path/to/image.jpg"
54
+ => #<Ddr::Antivirus::ScanResult:0x007f98f8b95670 @file_path="/path/to/image.jpg", @output="/path/to/image.jpg: OK\n\n----------- SCAN SUMMARY -----------\nInfected files: 0\nTime: 0.001 sec (0 m 0 s)\n", @scanned_at=2015-09-11 20:41:17 UTC, @version="ClamAV 0.98.7/20903/Fri Sep 11 08:42:07 2015">
55
+
56
+ > result.version
57
+ => "ClamAV 0.98.7/20903/Fri Sep 11 08:42:07 2015"
76
58
 
77
- # Was there an error?
78
- >> result.error?
79
- => false
59
+ > result.scanned_at
60
+ => 2015-09-11 20:41:17 UTC
80
61
 
81
- # Success? (no virus or error)
82
- >> result.ok?
83
- => true
62
+ > result.output
63
+ => "/path/to/image.jpg: OK\n\n----------- SCAN SUMMARY -----------\nInfected files: 0\nTime: 0.001 sec (0 m 0 s)\n"
84
64
 
85
- # What did the scanner adapter return?
86
- >> result.raw
87
- => 0 # ClamAV example
65
+ > puts result.to_s
66
+ /path/to/image.jpg: OK
88
67
 
89
- # String representation (example)
90
- >> result.to_s
91
- => "/path/to/blue-devil.png: OK (ClamAV 0.98.3/19595/Thu Nov 6 11:32:29 2014)"
68
+ ----------- SCAN SUMMARY -----------
69
+ Infected files: 0
70
+ Time: 0.001 sec (0 m 0 s)
71
+
72
+ [ClamAV 0.98.7/20903/Fri Sep 11 08:42:07 2015]
92
73
  ```
93
74
 
94
75
  ### Logging
@@ -104,12 +85,19 @@ Ddr::Antivirus.logger = Logger.new("/path/to/custom.log")
104
85
 
105
86
  In order to avoid the overhead of ClamAV in test and/or development environments, the package provides a no-op adapter:
106
87
 
107
- ```ruby
88
+ ```
108
89
  >> Ddr::Antivirus.scanner_adapter = :null
109
90
  => :null
110
91
  >> Ddr::Antivirus::Scanner.scan("/path/to/blue-devil.png")
111
- I, [2014-11-07T15:58:17.706866 #82651] INFO -- : /path/to/blue-devil.png: NOT SCANNED - using :null scanner adapter. (ddr-antivirus 1.2.0)
112
- => #<Ddr::Antivirus::Adapters::NullScanResult:0x007f9e2ba1af38 @raw="/path/to/blue-devil.png: NOT SCANNED - using :null scanner adapter.", @file_path="/path/to/blue-devil.png", @scanned_at=2014-11-07 20:58:17 UTC, @version="ddr-antivirus 1.2.0">
92
+ => #<Ddr::Antivirus::NullScanResult:0x007f9e2ba1af38 @output="/path/to/blue-devil.png: NOT SCANNED - using :null scanner adapter.", @file_path="/path/to/blue-devil.png", @scanned_at=2014-11-07 20:58:17 UTC, @version="ddr-antivirus 1.2.0">
93
+ ```
94
+
95
+ ### Test Mode
96
+
97
+ To easily configure `Ddr::Antivirus` to use the `NullScannerAdapter` and log to the null device, turn on test mode:
98
+
99
+ ```ruby
100
+ Ddr::Antivirus.test_mode!
113
101
  ```
114
102
 
115
103
  ## Contributing
@@ -18,9 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "activesupport", "~> 4.0"
22
21
  spec.add_development_dependency "bundler", "~> 1.6"
23
22
  spec.add_development_dependency "rake"
24
- spec.add_development_dependency "clamav"
25
23
  spec.add_development_dependency "rspec", "~> 3.0"
26
24
  end
data/lib/ddr/antivirus.rb CHANGED
@@ -2,50 +2,56 @@ require "logger"
2
2
 
3
3
  require_relative "antivirus/version"
4
4
  require_relative "antivirus/scanner"
5
- require_relative "antivirus/adapters"
6
-
7
- require "active_support/core_ext/module/attribute_accessors"
5
+ require_relative "antivirus/scan_result"
6
+ require_relative "antivirus/scanner_adapter"
7
+ require_relative "antivirus/adapters/null_scanner_adapter"
8
8
 
9
9
  module Ddr
10
10
  module Antivirus
11
11
 
12
- class VirusFoundError < ::StandardError; end
12
+ class Error < ::StandardError; end
13
+ class VirusFoundError < Error; end
14
+ class ScannerError < Error; end
13
15
 
14
- def self.configure
15
- yield self
16
- end
16
+ class << self
17
+ attr_accessor :logger, :scanner_adapter
17
18
 
18
- #
19
- # Custom logger
20
- #
21
- # Defaults to Rails logger if Rails is loaded; otherwise logs to STDERR.
22
- #
23
- mattr_accessor :logger do
24
- if defined?(Rails) && Rails.logger
25
- Rails.logger
26
- else
27
- Logger.new(STDERR)
19
+ def configure
20
+ yield self
21
+ end
22
+
23
+ def scan(path)
24
+ Scanner.scan(path)
28
25
  end
29
- end
30
26
 
31
- #
32
- # Scanner adapter
33
- #
34
- # Defaults to:
35
- # - :clamav adapter if the 'clamav' gem is installed;
36
- # - :clamd adapter if the 'clamdscan' executable is available;
37
- # - otherwise, the :null adapter.
38
- #
39
- mattr_accessor :scanner_adapter do
40
- begin
41
- require "clamav"
42
- :clamav
43
- rescue LoadError
44
- require "open3"
45
- out, status = Open3.capture2e("which -a clamdscan")
46
- status.success? ? :clamd : :null
27
+ def scanner
28
+ s = Scanner.new
29
+ block_given? ? yield(s) : s
30
+ end
31
+
32
+ def test_mode!
33
+ configure do |config|
34
+ config.logger = Logger.new(File::NULL)
35
+ config.scanner_adapter = :null
36
+ end
37
+ end
38
+
39
+ # @return [Class] the scanner adapter class
40
+ def get_adapter
41
+ if scanner_adapter.nil?
42
+ raise Error, "`Ddr::Antivirus.scanner_adapter` is not configured."
43
+ end
44
+ require_relative "antivirus/adapters/#{scanner_adapter}_scanner_adapter"
45
+ adapter_name = scanner_adapter.to_s.capitalize + "ScannerAdapter"
46
+ self.const_get(adapter_name, false)
47
47
  end
48
48
  end
49
49
 
50
+ self.logger = if defined?(Rails) && Rails.logger
51
+ Rails.logger
52
+ else
53
+ Logger.new(STDERR)
54
+ end
55
+
50
56
  end
51
57
  end
@@ -1,69 +1,84 @@
1
+ require "open3"
1
2
  require "fileutils"
2
- require_relative "scanner_adapter"
3
- require_relative "scan_result"
3
+ require "shellwords"
4
4
 
5
- module Ddr
6
- module Antivirus
7
- module Adapters
8
- #
9
- # Adapter for clamd client (clamdscan)
10
- #
11
- class ClamdScannerAdapter < ScannerAdapter
5
+ module Ddr::Antivirus
6
+ #
7
+ # Adapter for clamd client (clamdscan)
8
+ #
9
+ class ClamdScannerAdapter < ScannerAdapter
12
10
 
13
- def scan(path)
14
- raw = clamdscan(path)
15
- ClamdScanResult.new(raw, path)
16
- end
17
-
18
- def clamdscan(path)
19
- original_mode = File.stat(path).mode
20
- FileUtils.chmod("a+r", path) unless File.world_readable?(path)
21
- result = command(path)
22
- FileUtils.chmod(original_mode, path) if File.stat(path).mode != original_mode
23
- result
24
- end
25
-
26
- private
27
-
28
- def command(path)
29
- `clamdscan --no-summary "#{path}"`.strip
30
- end
11
+ SCANNER = "clamdscan".freeze
31
12
 
13
+ def scan(path)
14
+ output, status = clamdscan(path)
15
+ result = ScanResult.new(path, output, version: version, scanned_at: Time.now.utc)
16
+ case status.exitstatus
17
+ when 0
18
+ result
19
+ when 1
20
+ raise VirusFoundError, result.to_s
21
+ when 2
22
+ raise ScannerError, result.to_s
32
23
  end
24
+ end
33
25
 
34
- #
35
- # Result of a scan with the ClamdScannerAdapter
36
- #
37
- class ClamdScanResult < ScanResult
26
+ def clamdscan(path)
27
+ make_readable(path) do
28
+ command(path)
29
+ end
30
+ end
38
31
 
39
- def virus_found
40
- if m = /: ([^\s]+) FOUND$/.match(raw)
41
- m[1]
42
- end
43
- end
32
+ def version
33
+ out, err, status = Open3.capture3(SCANNER, "-V")
34
+ out.strip
35
+ end
44
36
 
45
- def has_virus?
46
- raw =~ / FOUND$/
47
- end
37
+ private
48
38
 
49
- def error?
50
- raw =~ / ERROR$/
51
- end
39
+ def command(path)
40
+ safe_path = Shellwords.shellescape(path)
41
+ Open3.capture2e(SCANNER, safe_path)
42
+ end
52
43
 
53
- def ok?
54
- raw =~ / OK$/
55
- end
44
+ def make_readable(path)
45
+ changed = false
46
+ original = File.stat(path).mode # raises Errno::ENOENT
47
+ if !File.world_readable?(path)
48
+ changed = FileUtils.chmod("a+r", path)
49
+ logger.info "File #{path} made world-readable for virus scanning."
50
+ end
51
+ result = yield
52
+ if changed
53
+ FileUtils.chmod(original, path)
54
+ logger.info "Mode reset to original #{original} on file #{path}."
55
+ end
56
+ result
57
+ end
56
58
 
57
- def to_s
58
- "#{raw} (#{version})"
59
- end
59
+ end
60
60
 
61
- def default_version
62
- `sigtool --version`.strip
63
- end
61
+ # Result of a scan with the ClamdScannerAdapter
62
+ # @api private
63
+ class ClamdScanResult < ScanResult
64
64
 
65
+ def virus_found
66
+ if m = /: ([^\s]+) FOUND$/.match(output)
67
+ m[1]
65
68
  end
69
+ end
66
70
 
71
+ def ok?
72
+ status.exitstatus == 0
67
73
  end
74
+
75
+ def has_virus?
76
+ status.exitstatus == 1
77
+ end
78
+
79
+ def error?
80
+ status.exitstatus == 2
81
+ end
82
+
68
83
  end
69
84
  end
@@ -1,25 +1,13 @@
1
- require_relative "scanner_adapter"
2
- require_relative "scan_result"
3
-
4
- module Ddr
5
- module Antivirus
6
- module Adapters
7
- #
8
- # A no-op adapter, primarily for testing and development.
9
- #
10
- class NullScannerAdapter < ScannerAdapter
11
-
12
- def scan(path)
13
- NullScanResult.new("#{path}: NOT SCANNED - using :null scanner adapter.", path)
14
- end
15
-
16
- end
17
-
18
- #
19
- # The result of the scan with the NullScannerAdapter.
20
- #
21
- class NullScanResult < ScanResult; end
22
-
1
+ module Ddr::Antivirus
2
+ #
3
+ # A no-op adapter, primarily for testing and development.
4
+ #
5
+ class NullScannerAdapter < ScannerAdapter
6
+
7
+ def scan(path)
8
+ ScanResult.new(path, "#{path}: NOT SCANNED - using :null scanner adapter.")
23
9
  end
10
+
24
11
  end
12
+
25
13
  end
@@ -0,0 +1,35 @@
1
+ module Ddr::Antivirus
2
+ #
3
+ # The result of a virus scan.
4
+ #
5
+ class ScanResult
6
+
7
+ attr_reader :file_path, :output, :scanned_at, :version
8
+
9
+ def initialize(file_path, output, scanned_at: nil, version: nil)
10
+ @file_path = file_path
11
+ @output = output
12
+ @scanned_at = scanned_at || default_time
13
+ @version = version || default_version
14
+ end
15
+
16
+ # Default time of virus scan - i.e., now.
17
+ # @return [Time] the time.
18
+ def default_time
19
+ Time.now.utc
20
+ end
21
+
22
+ # Default anti-virus software version information.
23
+ # @return [String] the version.
24
+ def default_version
25
+ "ddr-antivirus #{Ddr::Antivirus::VERSION}"
26
+ end
27
+
28
+ # String representation of the result
29
+ # @return [String] the representation.
30
+ def to_s
31
+ "#{output}\n[#{version}]"
32
+ end
33
+
34
+ end
35
+ end
@@ -1,36 +1,16 @@
1
- require "active_support/core_ext/class/attribute"
1
+ require "delegate"
2
2
 
3
- module Ddr
4
- module Antivirus
5
- class Scanner
3
+ module Ddr::Antivirus
4
+ class Scanner < SimpleDelegator
6
5
 
7
- # Instance of scanner adapter
8
- attr_reader :adapter
9
-
10
- def self.scan(path)
11
- new { |scanner| return scanner.scan(path) }
12
- end
13
-
14
- def initialize
15
- @adapter = Ddr::Antivirus::Adapters.get_adapter
16
- yield self if block_given?
17
- end
18
-
19
- def scan(path)
20
- result = adapter.scan(path)
21
- raise Ddr::Antivirus::VirusFoundError, result if result.has_virus?
22
- logger.error("Antivirus scanner error (#{result.version})") if result.error?
23
- logger.info(result.to_s)
24
- result
25
- end
26
-
27
- private
28
-
29
- def logger
30
- Ddr::Antivirus.logger
31
- end
6
+ def self.scan(path)
7
+ new.scan(path)
8
+ end
32
9
 
10
+ def initialize
11
+ super Ddr::Antivirus.get_adapter.new
33
12
  end
13
+
34
14
  end
35
15
  end
36
16
 
@@ -0,0 +1,29 @@
1
+ require_relative "scan_result"
2
+
3
+ module Ddr::Antivirus
4
+ #
5
+ # @abstract Subclass and override {#scan} to implement a scanner adapter.
6
+ #
7
+ class ScannerAdapter
8
+
9
+ # Scan a file path for viruses.
10
+ #
11
+ # @param path [String] file path to scan.
12
+ # @return [Ddr::Antivirus::Adapters::ScanResult] the result of the scan.
13
+ def scan(path)
14
+ raise NotImplementedError, "Adapters must implement the `scan' method."
15
+ end
16
+
17
+ # Return the adapter configuration options
18
+ def config
19
+ Ddr::Antivirus.adapter_config
20
+ end
21
+
22
+ private
23
+
24
+ def logger
25
+ Ddr::Antivirus.logger
26
+ end
27
+
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  module Ddr
2
2
  module Antivirus
3
- VERSION = "1.3.3"
3
+ VERSION = "2.0.0.rc1"
4
4
  end
5
5
  end
data/spec/spec_helper.rb CHANGED
@@ -1,25 +1,7 @@
1
- require "coveralls"
2
- Coveralls.wear!
3
-
4
- # This file was generated by the `rspec --init` command. Conventionally, all
5
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
- # The generated `.rspec` file contains `--require spec_helper` which will cause this
7
- # file to always be loaded, without a need to explicitly require it in any files.
8
- #
9
- # Given that it is always loaded, you are encouraged to keep this file as
10
- # light-weight as possible. Requiring heavyweight dependencies from this file
11
- # will add to the boot time of your test suite on EVERY test run, even for an
12
- # individual file that may not need all of that loaded. Instead, make a
13
- # separate helper file that requires this one and then use it only in the specs
14
- # that actually need it.
15
- #
16
- # The `.rspec` file also contains a few flags that are not defaults but that
17
- # users commonly want.
18
- #
19
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
20
-
21
1
  require 'ddr/antivirus'
22
2
 
3
+ Ddr::Antivirus.test_mode!
4
+
23
5
  RSpec.configure do |config|
24
6
  # The settings below are suggested to provide a good initial experience
25
7
  # with RSpec, but feel free to customize to your heart's content.
@@ -1,66 +1,72 @@
1
- require "shared_examples_for_scan_results"
1
+ require "tempfile"
2
2
  require "ddr/antivirus/adapters/clamd_scanner_adapter"
3
3
 
4
- module Ddr
5
- module Antivirus
6
- module Adapters
7
- RSpec.describe ClamdScannerAdapter do
4
+ module Ddr::Antivirus
5
+ RSpec.describe ClamdScannerAdapter do
8
6
 
9
- let(:path) { File.expand_path(File.join("..", "..", "fixtures", "blue-devil.png"), __FILE__) }
10
- let(:adapter) { described_class.new }
7
+ let(:path) { File.expand_path(File.join("..", "..", "fixtures", "blue-devil.png"), __FILE__) }
11
8
 
12
- describe "permissions" do
13
- before do
14
- @file = Tempfile.new("test")
15
- @file.write("Scan me!")
16
- @file.close
17
- allow(adapter).to receive(:command).with(@file.path) { "#{@file.path}: OK" }
18
- end
19
- after { @file.unlink }
20
- describe "when the file is not world readable" do
21
- it "should temporarily change the permissions" do
22
- original_mode = File.stat(@file.path).mode
23
- expect(FileUtils).to receive(:chmod).with("a+r", @file.path)
24
- adapter.scan(@file.path)
25
- expect(File.stat(@file.path).mode).to eq(original_mode)
26
- end
27
- end
28
- describe "when the file is world readable" do
29
- before { FileUtils.chmod("a+r", @file.path) }
30
- it "should not change the permissions" do
31
- original_mode = File.stat(@file.path).mode
32
- expect(FileUtils).not_to receive(:chmod)
33
- adapter.scan(@file.path)
34
- expect(File.stat(@file.path).mode).to eq(original_mode)
35
- end
36
- end
37
- end
9
+ before do
10
+ allow(subject).to receive(:version) { "version" }
11
+ end
38
12
 
39
- describe "result" do
40
- subject { adapter.scan(path) }
13
+ describe "permissions" do
14
+ before do
15
+ @file = Tempfile.new("test")
16
+ @file.write("Scan me!")
17
+ @file.close
18
+ allow(subject).to receive(:command).with(@file.path) { ["#{@file.path}: OK", double(exitstatus: 0)] }
19
+ end
20
+ after { @file.unlink }
21
+ describe "when the file is not world readable" do
22
+ it "should temporarily change the permissions" do
23
+ FileUtils.chmod("a-r", @file.path)
24
+ original_mode = File.stat(@file.path).mode
25
+ expect(FileUtils).to receive(:chmod).with("a+r", @file.path)
26
+ subject.scan(@file.path)
27
+ expect(File.stat(@file.path).mode).to eq(original_mode)
28
+ end
29
+ end
30
+ describe "when the file is world readable" do
31
+ before { FileUtils.chmod("a+r", @file.path) }
32
+ it "should not change the permissions" do
33
+ original_mode = File.stat(@file.path).mode
34
+ expect(FileUtils).not_to receive(:chmod)
35
+ subject.scan(@file.path)
36
+ expect(File.stat(@file.path).mode).to eq(original_mode)
37
+ end
38
+ end
39
+ end
41
40
 
42
- it "should be a scan result" do
43
- expect(subject).to be_a(ClamdScanResult)
44
- end
45
- it_should_behave_like "a scan result"
46
- context "when a virus is found" do
47
- before { allow(adapter).to receive(:clamdscan).with(path) { "#{path}: Bad-boy-35 FOUND" } }
48
- it "should have a virus_found" do
49
- expect(subject.virus_found).to eq "Bad-boy-35"
50
- end
51
- it_should_behave_like "a virus scan result"
52
- end
53
- context "when there is an error" do
54
- before { allow(adapter).to receive(:clamdscan).with(path) { "#{path}: ERROR" } }
55
- it "should not have a virus" do
56
- expect(subject).not_to have_virus
57
- end
58
- it_should_behave_like "an error scan result"
59
- end
60
- context "success" do
61
- before { allow(adapter).to receive(:clamdscan).with(path) { "#{path}: OK" } }
62
- it_should_behave_like "a successful scan result"
63
- end
41
+ describe "result" do
42
+ before do
43
+ allow(subject).to receive(:command).with(path) { ["output", status] }
44
+ end
45
+ describe "when a virus is found" do
46
+ let(:status) { double(exitstatus: 1) }
47
+ it "should raise a VirusFoundError" do
48
+ expect { subject.scan(path) }.to raise_error(VirusFoundError)
49
+ end
50
+ end
51
+ describe "when there is an error" do
52
+ let(:status) { double(exitstatus: 2) }
53
+ it "should raise a ScannerError" do
54
+ expect { subject.scan(path) }.to raise_error(ScannerError)
55
+ end
56
+ end
57
+ describe "success" do
58
+ let(:status) { double(exitstatus: 0) }
59
+ it "should have output" do
60
+ expect(subject.scan(path).output).to eq("output")
61
+ end
62
+ it "should have a scanned_at time" do
63
+ expect(subject.scan(path).scanned_at).to be_a(Time)
64
+ end
65
+ it "should have a version" do
66
+ expect(subject.scan(path).version).to eq("version")
67
+ end
68
+ it "should have the file_path" do
69
+ expect(subject.scan(path).file_path).to eq(path)
64
70
  end
65
71
  end
66
72
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ddr-antivirus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
4
+ version: 2.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Chandek-Stark
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-21 00:00:00.000000000 Z
11
+ date: 2015-09-11 00:00:00.000000000 Z
12
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: '4.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '4.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -52,20 +38,6 @@ dependencies:
52
38
  - - ">="
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: clamav
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
41
  - !ruby/object:Gem::Dependency
70
42
  name: rspec
71
43
  requirement: !ruby/object:Gem::Requirement
@@ -97,22 +69,15 @@ files:
97
69
  - ddr-antivirus.gemspec
98
70
  - lib/ddr-antivirus.rb
99
71
  - lib/ddr/antivirus.rb
100
- - lib/ddr/antivirus/adapters.rb
101
- - lib/ddr/antivirus/adapters/clamav_scanner_adapter.rb
102
72
  - lib/ddr/antivirus/adapters/clamd_scanner_adapter.rb
103
73
  - lib/ddr/antivirus/adapters/null_scanner_adapter.rb
104
- - lib/ddr/antivirus/adapters/scan_result.rb
105
- - lib/ddr/antivirus/adapters/scanner_adapter.rb
74
+ - lib/ddr/antivirus/scan_result.rb
106
75
  - lib/ddr/antivirus/scanner.rb
76
+ - lib/ddr/antivirus/scanner_adapter.rb
107
77
  - lib/ddr/antivirus/version.rb
108
78
  - spec/fixtures/blue-devil.png
109
- - spec/shared_examples_for_scan_results.rb
110
79
  - spec/spec_helper.rb
111
- - spec/unit/clamav_scanner_adapter_spec.rb
112
80
  - spec/unit/clamd_scanner_adapter_spec.rb
113
- - spec/unit/null_scanner_adapter_spec.rb
114
- - spec/unit/scan_result_spec.rb
115
- - spec/unit/scanner_spec.rb
116
81
  homepage: https://github.com/duke-libraries/ddr-antivirus
117
82
  licenses:
118
83
  - BSD-3-Clause
@@ -128,9 +93,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
93
  version: '0'
129
94
  required_rubygems_version: !ruby/object:Gem::Requirement
130
95
  requirements:
131
- - - ">="
96
+ - - ">"
132
97
  - !ruby/object:Gem::Version
133
- version: '0'
98
+ version: 1.3.1
134
99
  requirements: []
135
100
  rubyforge_project:
136
101
  rubygems_version: 2.4.6
@@ -139,10 +104,5 @@ specification_version: 4
139
104
  summary: Pluggable antivirus scanning service.
140
105
  test_files:
141
106
  - spec/fixtures/blue-devil.png
142
- - spec/shared_examples_for_scan_results.rb
143
107
  - spec/spec_helper.rb
144
- - spec/unit/clamav_scanner_adapter_spec.rb
145
108
  - spec/unit/clamd_scanner_adapter_spec.rb
146
- - spec/unit/null_scanner_adapter_spec.rb
147
- - spec/unit/scan_result_spec.rb
148
- - spec/unit/scanner_spec.rb
@@ -1,14 +0,0 @@
1
- module Ddr
2
- module Antivirus
3
- module Adapters
4
-
5
- def self.get_adapter
6
- require_relative "adapters/#{Ddr::Antivirus.scanner_adapter}_scanner_adapter"
7
- adapter_name = "#{Ddr::Antivirus.scanner_adapter.to_s.capitalize}ScannerAdapter"
8
- klass = self.const_get(adapter_name.to_sym, false)
9
- klass.new
10
- end
11
-
12
- end
13
- end
14
- end
@@ -1,82 +0,0 @@
1
- require "clamav"
2
-
3
- require_relative "scanner_adapter"
4
- require_relative "scan_result"
5
-
6
- module Ddr
7
- module Antivirus
8
- module Adapters
9
- #
10
- # Scanner adapter for the 'clamav' gem (Ruby libclamav bindings).
11
- #
12
- class ClamavScannerAdapter < ScannerAdapter
13
-
14
- def scan(path)
15
- reload!
16
- raw = engine.scanfile(path)
17
- ClamavScanResult.new(raw, path)
18
- end
19
-
20
- # Load or reload the database of virus signatures.
21
- def reload!
22
- #
23
- # ClamAV.instance.reload is supposed to reload the database if changed and return:
24
- #
25
- # 0 => unnecessary
26
- # 1 => successful
27
- # 2 => error (undocumented)
28
- #
29
- # However, reload raises a RuntimeError when the db needs to be reloaded,
30
- # in which case, loaddb must be called.
31
- #
32
- engine.loaddb unless [0, 1].include?(engine.reload)
33
- rescue RuntimeError
34
- engine.loaddb
35
- end
36
-
37
- def engine
38
- ClamAV.instance
39
- end
40
-
41
- end
42
-
43
- #
44
- # Result of a scan with the ClamavScannerAdapter.
45
- #
46
- class ClamavScanResult < ScanResult
47
-
48
- def virus_found
49
- raw if has_virus?
50
- end
51
-
52
- def has_virus?
53
- ![0, 1].include?(raw)
54
- end
55
-
56
- def error?
57
- raw == 1
58
- end
59
-
60
- # A formatted status message (for consistency with clamdscan output).
61
- # @return [String] the result status.
62
- def status
63
- return "FOUND #{virus_found}" if has_virus?
64
- return "ERROR" if error?
65
- "OK"
66
- end
67
-
68
- def to_s
69
- "#{file_path}: #{status} (#{version})"
70
- end
71
-
72
- def default_version
73
- # Engine and database versions
74
- # E.g., ClamAV 0.98.3/19010/Tue May 20 21:46:01 2014
75
- `sigtool --version`.strip
76
- end
77
-
78
- end
79
-
80
- end
81
- end
82
- end
@@ -1,61 +0,0 @@
1
- module Ddr
2
- module Antivirus
3
- module Adapters
4
- #
5
- # The result of a virus scan.
6
- #
7
- class ScanResult
8
-
9
- attr_reader :raw, :file_path, :scanned_at, :version
10
-
11
- def initialize(raw, file_path, opts={})
12
- @raw = raw
13
- @file_path = file_path
14
- @scanned_at = opts.fetch(:scanned_at, default_time)
15
- @version = opts.fetch(:version, default_version)
16
- end
17
-
18
- # Default time of virus scan - i.e., now.
19
- # @return [Time] the time.
20
- def default_time
21
- Time.now.utc
22
- end
23
-
24
- # Default anti-virus software version information.
25
- # @return [String] the version.
26
- def default_version
27
- "ddr-antivirus #{Ddr::Antivirus::VERSION}"
28
- end
29
-
30
- # the name of virus found.
31
- # @return [String] the virus name.
32
- def virus_found; end
33
-
34
- # Was a virus found?
35
- # @return [true, false] whether a virus was found.
36
- def has_virus?
37
- !virus_found.nil?
38
- end
39
-
40
- # Was there an error (reported by the scanner, not necessarily an exception)?
41
- # @return [true, false] whether there was an error.
42
- def error?
43
- false
44
- end
45
-
46
- # Was the result OK - i.e., not an error and virus not found.
47
- # @return [true, false] whether the result was OK.
48
- def ok?
49
- !(has_virus? || error?)
50
- end
51
-
52
- # String representation of the result
53
- # @return [String] the representation.
54
- def to_s
55
- "#{raw} (#{version})"
56
- end
57
-
58
- end
59
- end
60
- end
61
- end
@@ -1,25 +0,0 @@
1
- module Ddr
2
- module Antivirus
3
- module Adapters
4
- #
5
- # @abstract Subclass and override {#scan} to implement a scanner adapter.
6
- #
7
- class ScannerAdapter
8
-
9
- # Scan a file path for viruses.
10
- #
11
- # @param path [String] file path to scan.
12
- # @return [Ddr::Antivirus::Adapters::ScanResult] the result of the scan.
13
- def scan(path)
14
- raise NotImplementedError, "Adapters must implement the `scan' method."
15
- end
16
-
17
- # Return the adapter configuration options
18
- def config
19
- Ddr::Antivirus.adapter_config
20
- end
21
-
22
- end
23
- end
24
- end
25
- end
@@ -1,53 +0,0 @@
1
- shared_examples "a scan result" do
2
- it "should have a raw result" do
3
- expect(subject.raw).not_to be_nil
4
- end
5
- it "should have a version" do
6
- expect(subject.version).not_to be_nil
7
- end
8
- it "should have a scanned_at time" do
9
- expect(subject.scanned_at).to be_a(Time)
10
- end
11
- it "should have a string representation" do
12
- expect(subject.to_s).not_to be_nil
13
- end
14
- end
15
-
16
- shared_examples "a successful scan result" do
17
- it "should not have a virus" do
18
- expect(subject.virus_found).to be_nil
19
- expect(subject).not_to have_virus
20
- end
21
- it "should not have an error" do
22
- expect(subject).not_to be_error
23
- end
24
- it "should be ok" do
25
- expect(subject).to be_ok
26
- end
27
- end
28
-
29
- shared_examples "an error scan result" do
30
- it "should have an error" do
31
- expect(subject).to be_error
32
- end
33
- it "shoud not have a virus" do
34
- expect(subject.virus_found).to be_nil
35
- expect(subject).not_to have_virus
36
- end
37
- it "should not be ok" do
38
- expect(subject).not_to be_ok
39
- end
40
- end
41
-
42
- shared_examples "a virus scan result" do
43
- it "shoud have a virus" do
44
- expect(subject.virus_found).not_to be_nil
45
- expect(subject).to have_virus
46
- end
47
- it "should not have an error" do
48
- expect(subject).not_to be_error
49
- end
50
- it "should not be ok" do
51
- expect(subject).not_to be_ok
52
- end
53
- end
@@ -1,66 +0,0 @@
1
- require "shared_examples_for_scan_results"
2
- require "ddr/antivirus/adapters/clamav_scanner_adapter"
3
-
4
- module Ddr
5
- module Antivirus
6
- module Adapters
7
- RSpec.describe ClamavScannerAdapter do
8
-
9
- let(:path) { File.expand_path(File.join("..", "..", "fixtures", "blue-devil.png"), __FILE__) }
10
-
11
- describe "#scan" do
12
- context "when the db is already loaded" do
13
- before { subject.engine.loaddb }
14
- it "should reload the db" do
15
- expect(subject.engine).to receive(:reload).and_call_original
16
- expect(subject.engine).not_to receive(:loaddb)
17
- subject.scan(path)
18
- end
19
- end
20
- context "when the db is not already loaded" do
21
- before { allow(subject.engine).to receive(:reload).and_raise(RuntimeError) }
22
- it "should load the db" do
23
- expect(subject.engine).to receive(:loaddb).and_call_original
24
- subject.scan(path)
25
- end
26
- end
27
- context "when the db is not reloaded successfully" do
28
- before { allow(subject.engine).to receive(:reload) { 2 } }
29
- it "should load the db" do
30
- expect(subject.engine).to receive(:loaddb).and_call_original
31
- subject.scan(path)
32
- end
33
- end
34
-
35
- describe "result" do
36
- subject { adapter.scan(path) }
37
- let(:adapter) { described_class.new }
38
- it "should be a ClamavScanResult" do
39
- expect(subject).to be_a(ClamavScanResult)
40
- end
41
- it_should_behave_like "a scan result"
42
- context "when a virus is found" do
43
- before { allow(adapter.engine).to receive(:scanfile).with(path) { "Bad boy 35" } }
44
- it "the raw result should be the virus description" do
45
- expect(subject.raw).to eq "Bad boy 35"
46
- expect(subject.virus_found).to eq "Bad boy 35"
47
- end
48
- it_should_behave_like "a virus scan result"
49
- end
50
- context "when there is an error" do
51
- before { allow(adapter.engine).to receive(:scanfile).with(path) { 1 } }
52
- it "should not have a virus" do
53
- expect(subject).not_to have_virus
54
- end
55
- it_should_behave_like "an error scan result"
56
- end
57
- context "success" do
58
- before { allow(adapter.engine).to receive(:scanfile).with(path) { 0 } }
59
- it_should_behave_like "a successful scan result"
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,24 +0,0 @@
1
- require "shared_examples_for_scan_results"
2
- require "ddr/antivirus/adapters/null_scanner_adapter"
3
-
4
- module Ddr
5
- module Antivirus
6
- module Adapters
7
- RSpec.describe NullScannerAdapter do
8
-
9
- let(:path) { File.expand_path(File.join("..", "..", "fixtures", "blue-devil.png"), __FILE__) }
10
-
11
- describe "result" do
12
- subject { adapter.scan(path) }
13
- let(:adapter) { described_class.new }
14
- it "should be a NullScanResult" do
15
- expect(subject).to be_a(NullScanResult)
16
- end
17
- it_should_behave_like "a scan result"
18
- it_should_behave_like "a successful scan result"
19
- end
20
-
21
- end
22
- end
23
- end
24
- end
@@ -1,27 +0,0 @@
1
- require 'shared_examples_for_scan_results'
2
-
3
- module Ddr
4
- module Antivirus
5
- module Adapters
6
- RSpec.describe ScanResult do
7
- subject { described_class.new("Raw result", "/tmp/foo") }
8
-
9
- it_should_behave_like "a scan result"
10
-
11
- describe "success" do
12
- it_should_behave_like "a successful scan result"
13
- end
14
-
15
- describe "error" do
16
- before { allow(subject).to receive(:error?) { true } }
17
- it_should_behave_like "an error scan result"
18
- end
19
-
20
- describe "virus found" do
21
- before { allow(subject).to receive(:virus_found) { "Bad boy 35" } }
22
- it_should_behave_like "a virus scan result"
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,57 +0,0 @@
1
- require "ddr/antivirus/adapters/scan_result"
2
-
3
- module Ddr
4
- module Antivirus
5
- RSpec.describe Scanner do
6
-
7
- shared_examples "a scanner" do
8
- let(:path) { "/tmp/foo" }
9
- describe "logging" do
10
- it "should log the result" do
11
- expect(Ddr::Antivirus.logger).to receive(:info)
12
- subject.scan(path)
13
- end
14
- end
15
- describe "when a virus is found" do
16
- before { allow_any_instance_of(Ddr::Antivirus::Adapters::ScanResult).to receive(:has_virus?) { true } }
17
- it "should raise an execption" do
18
- expect { subject.scan(path) }.to raise_error
19
- end
20
- end
21
- describe "when a virus is not found" do
22
- before { allow_any_instance_of(Ddr::Antivirus::Adapters::ScanResult).to receive(:has_virus?) { false } }
23
- it "should return the scan result" do
24
- expect(subject.scan(path)).to be_a(Ddr::Antivirus::Adapters::ScanResult)
25
- end
26
- end
27
- describe "when an error occurs in the scanner" do
28
- before { allow_any_instance_of(Ddr::Antivirus::Adapters::ScanResult).to receive(:error?) { true } }
29
- it "should log an error" do
30
- expect(Ddr::Antivirus.logger).to receive(:error)
31
- subject.scan(path)
32
- end
33
- end
34
- end
35
-
36
- before do
37
- @original_adapter = Ddr::Antivirus.scanner_adapter
38
- Ddr::Antivirus.scanner_adapter = :null
39
- end
40
-
41
- after do
42
- Ddr::Antivirus.scanner_adapter = @original_adapter
43
- end
44
-
45
- describe ".scan" do
46
- subject { described_class }
47
- it_behaves_like "a scanner"
48
- end
49
-
50
- describe "#scan" do
51
- subject { described_class.new }
52
- it_behaves_like "a scanner"
53
- end
54
-
55
- end
56
- end
57
- end