spandx 0.13.2 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -2
  3. data/exe/spandx +0 -1
  4. data/ext/spandx/spandx.c +5 -3
  5. data/lib/spandx.rb +1 -1
  6. data/lib/spandx/cli.rb +2 -1
  7. data/lib/spandx/cli/commands/scan.rb +15 -32
  8. data/lib/spandx/cli/main.rb +3 -3
  9. data/lib/spandx/cli/printer.rb +27 -0
  10. data/lib/spandx/cli/printers/csv.rb +17 -0
  11. data/lib/spandx/cli/printers/json.rb +17 -0
  12. data/lib/spandx/cli/printers/table.rb +41 -0
  13. data/lib/spandx/core/dependency.rb +48 -13
  14. data/lib/spandx/core/git.rb +6 -6
  15. data/lib/spandx/core/http.rb +7 -7
  16. data/lib/spandx/core/index_file.rb +2 -0
  17. data/lib/spandx/core/license_plugin.rb +15 -4
  18. data/lib/spandx/core/parser.rb +9 -4
  19. data/lib/spandx/core/path_traversal.rb +4 -13
  20. data/lib/spandx/core/plugin.rb +6 -0
  21. data/lib/spandx/core/thread_pool.rb +11 -11
  22. data/lib/spandx/dotnet/nuget_gateway.rb +1 -1
  23. data/lib/spandx/dotnet/parsers/csproj.rb +7 -7
  24. data/lib/spandx/dotnet/parsers/packages_config.rb +7 -7
  25. data/lib/spandx/dotnet/parsers/sln.rb +10 -13
  26. data/lib/spandx/dotnet/project_file.rb +3 -3
  27. data/lib/spandx/java/parsers/maven.rb +7 -7
  28. data/lib/spandx/js/parsers/npm.rb +8 -8
  29. data/lib/spandx/js/parsers/yarn.rb +7 -7
  30. data/lib/spandx/js/yarn_pkg.rb +1 -1
  31. data/lib/spandx/os/parsers/apk.rb +51 -0
  32. data/lib/spandx/os/parsers/dpkg.rb +69 -0
  33. data/lib/spandx/php/packagist_gateway.rb +1 -1
  34. data/lib/spandx/php/parsers/composer.rb +7 -7
  35. data/lib/spandx/python/parsers/pipfile_lock.rb +4 -4
  36. data/lib/spandx/python/pypi.rb +19 -7
  37. data/lib/spandx/python/source.rb +13 -1
  38. data/lib/spandx/ruby/gateway.rb +1 -1
  39. data/lib/spandx/ruby/parsers/gemfile_lock.rb +10 -9
  40. data/lib/spandx/spdx/catalogue.rb +1 -1
  41. data/lib/spandx/version.rb +1 -1
  42. data/spandx.gemspec +5 -3
  43. metadata +44 -14
  44. data/lib/spandx/core/concurrent.rb +0 -40
  45. data/lib/spandx/core/line_io.rb +0 -23
  46. data/lib/spandx/core/report.rb +0 -60
  47. data/lib/spandx/core/table.rb +0 -29
@@ -27,7 +27,7 @@ module Spandx
27
27
  client.get(escape ? Addressable::URI.escape(uri) : uri)
28
28
  end
29
29
  end
30
- rescue *Net::Hippie::CONNECTION_ERRORS
30
+ rescue *Net::Hippie::CONNECTION_ERRORS, URI::InvalidURIError
31
31
  default
32
32
  end
33
33
 
@@ -36,12 +36,12 @@ module Spandx
36
36
  end
37
37
 
38
38
  def self.default_driver
39
- @default_driver ||= Net::Hippie::Client.new.tap do |client|
40
- client.logger = Spandx.logger
41
- client.open_timeout = 1
42
- client.read_timeout = 5
43
- client.follow_redirects = 3
44
- end
39
+ @default_driver ||= Net::Hippie::Client.new(
40
+ follow_redirects: 3,
41
+ logger: Spandx.logger,
42
+ open_timeout: 1,
43
+ read_timeout: 5
44
+ )
45
45
  end
46
46
 
47
47
  private
@@ -26,6 +26,8 @@ module Spandx
26
26
  until min >= max
27
27
  mid = mid_for(min, max)
28
28
  row = reader.row(mid)
29
+ return unless row
30
+
29
31
  comparison = yield row
30
32
  return row if comparison.zero?
31
33
 
@@ -8,7 +8,8 @@ module Spandx
8
8
  end
9
9
 
10
10
  def enhance(dependency)
11
- return dependency unless known?(dependency.package_manager)
11
+ package_manager = package_manager_for(dependency)
12
+ return dependency unless known?(package_manager)
12
13
  return enhance_from_metadata(dependency) if available_in?(dependency.meta)
13
14
 
14
15
  licenses_for(dependency).each do |text|
@@ -25,12 +26,14 @@ module Spandx
25
26
  end
26
27
 
27
28
  def cache_for(dependency, git: Spandx.git)
28
- git = git[dependency.package_manager.to_sym] || git[:cache]
29
- Spandx::Core::Cache.new(dependency.package_manager, root: "#{git.root}/.index")
29
+ package_manager = package_manager_for(dependency)
30
+ git = git[package_manager.to_sym] || git[:cache]
31
+ key = key_for(package_manager)
32
+ Spandx::Core::Cache.new(key, root: "#{git.root}/.index")
30
33
  end
31
34
 
32
35
  def known?(package_manager)
33
- %i[nuget maven rubygems npm yarn pypi composer].include?(package_manager)
36
+ %i[nuget maven rubygems npm yarn pypi composer apk].include?(package_manager)
34
37
  end
35
38
 
36
39
  def gateway_for(dependency)
@@ -49,6 +52,14 @@ module Spandx
49
52
  end
50
53
  dependency
51
54
  end
55
+
56
+ def key_for(package_manager)
57
+ package_manager == :yarn ? :npm : package_manager
58
+ end
59
+
60
+ def package_manager_for(dependency)
61
+ dependency.package_manager
62
+ end
52
63
  end
53
64
  end
54
65
  end
@@ -9,8 +9,8 @@ module Spandx
9
9
  end
10
10
  end
11
11
 
12
- def matches?(_filename)
13
- raise ::Spandx::Error, :matches?
12
+ def match?(_path)
13
+ raise ::Spandx::Error, :match?
14
14
  end
15
15
 
16
16
  def parse(_dependency)
@@ -20,10 +20,15 @@ module Spandx
20
20
  class << self
21
21
  include Registerable
22
22
 
23
+ def parse(path)
24
+ self.for(path).parse(path)
25
+ end
26
+
23
27
  def for(path)
24
- return UNKNOWN if !File.exist?(path) || File.size(path).zero?
28
+ path = Pathname.new(path)
29
+ return UNKNOWN if !path.exist? || path.zero?
25
30
 
26
- find { |x| x.matches?(File.basename(path)) } || UNKNOWN
31
+ find { |x| x.match?(path) } || UNKNOWN
27
32
  end
28
33
  end
29
34
  end
@@ -6,7 +6,7 @@ module Spandx
6
6
  attr_reader :root
7
7
 
8
8
  def initialize(root, recursive: true)
9
- @root = root
9
+ @root = Pathname.new(root)
10
10
  @recursive = recursive
11
11
  end
12
12
 
@@ -14,27 +14,18 @@ module Spandx
14
14
  each_file_in(root, &block)
15
15
  end
16
16
 
17
- def to_enum
18
- Enumerator.new do |yielder|
19
- each do |item|
20
- yielder.yield item
21
- end
22
- end
23
- end
24
-
25
17
  private
26
18
 
27
19
  def recursive?
28
20
  @recursive
29
21
  end
30
22
 
31
- def each_file_in(dir, &block)
32
- files = File.directory?(dir) ? Dir.glob(File.join(dir, '*')) : [dir]
23
+ def each_file_in(path, &block)
24
+ files = path.directory? ? path.children : [path]
33
25
  files.each do |file|
34
- if File.directory?(file)
26
+ if file.directory?
35
27
  each_file_in(file, &block) if recursive?
36
28
  else
37
- Spandx.logger.debug(file)
38
29
  block.call(file)
39
30
  end
40
31
  end
@@ -9,6 +9,12 @@ module Spandx
9
9
 
10
10
  class << self
11
11
  include Registerable
12
+
13
+ def enhance(dependency)
14
+ Plugin.all.inject(dependency) do |memo, plugin|
15
+ plugin.enhance(memo)
16
+ end
17
+ end
12
18
  end
13
19
  end
14
20
  end
@@ -3,14 +3,14 @@
3
3
  module Spandx
4
4
  module Core
5
5
  class ThreadPool
6
- def initialize(size: Etc.nprocessors)
6
+ def initialize(size: 1)
7
7
  @size = size
8
8
  @queue = Queue.new
9
- @pool = size.times.map { start_worker_thread }
9
+ @pool = size.times.map { start_worker_thread(@queue) }
10
10
  end
11
11
 
12
- def schedule(*args, &block)
13
- @queue.enq([block, args])
12
+ def run(*args, &job)
13
+ @queue.enq([job, args])
14
14
  end
15
15
 
16
16
  def done?
@@ -19,14 +19,14 @@ module Spandx
19
19
 
20
20
  def shutdown
21
21
  @size.times do
22
- schedule { throw :exit }
22
+ run { throw :exit }
23
23
  end
24
24
 
25
25
  @pool.map(&:join)
26
26
  end
27
27
 
28
- def self.open
29
- pool = new
28
+ def self.open(**args)
29
+ pool = new(**args)
30
30
  yield pool
31
31
  ensure
32
32
  pool.shutdown
@@ -34,12 +34,12 @@ module Spandx
34
34
 
35
35
  private
36
36
 
37
- def start_worker_thread
38
- Thread.new do
37
+ def start_worker_thread(queue)
38
+ Thread.new(queue) do |q|
39
39
  catch(:exit) do
40
40
  loop do
41
- job, args = @queue.deq
42
- job.call(*args)
41
+ job, args = q.deq
42
+ job.call(args)
43
43
  end
44
44
  end
45
45
  end
@@ -69,7 +69,7 @@ module Spandx
69
69
 
70
70
  def fetch_json(url)
71
71
  response = http.get(url)
72
- http.ok?(response) ? JSON.parse(response.body) : {}
72
+ http.ok?(response) ? Oj.load(response.body) : {}
73
73
  end
74
74
 
75
75
  def fetch_xml(url)
@@ -4,22 +4,22 @@ module Spandx
4
4
  module Dotnet
5
5
  module Parsers
6
6
  class Csproj < ::Spandx::Core::Parser
7
- def matches?(filename)
8
- ['.csproj', '.props'].include?(File.extname(filename))
7
+ def match?(path)
8
+ ['.csproj', '.props'].include?(path.extname)
9
9
  end
10
10
 
11
- def parse(lockfile)
11
+ def parse(path)
12
12
  ProjectFile
13
- .new(lockfile)
13
+ .new(path)
14
14
  .package_references
15
- .map { |x| map_from(x) }
15
+ .map { |x| map_from(path, x) }
16
16
  end
17
17
 
18
18
  private
19
19
 
20
- def map_from(package_reference)
20
+ def map_from(path, package_reference)
21
21
  ::Spandx::Core::Dependency.new(
22
- package_manager: :nuget,
22
+ path: path,
23
23
  name: package_reference.name,
24
24
  version: package_reference.version,
25
25
  meta: package_reference
@@ -4,22 +4,22 @@ module Spandx
4
4
  module Dotnet
5
5
  module Parsers
6
6
  class PackagesConfig < ::Spandx::Core::Parser
7
- def matches?(filename)
8
- filename.match?(/packages\.config/)
7
+ def match?(path)
8
+ path.basename.fnmatch?('packages.config')
9
9
  end
10
10
 
11
- def parse(lockfile)
12
- Nokogiri::XML(IO.read(lockfile))
11
+ def parse(path)
12
+ Nokogiri::XML(path.read)
13
13
  .search('//package')
14
- .map { |node| map_from(node) }
14
+ .map { |node| map_from(path, node) }
15
15
  end
16
16
 
17
17
  private
18
18
 
19
- def map_from(node)
19
+ def map_from(path, node)
20
20
  name = attribute_for('id', node)
21
21
  version = attribute_for('version', node)
22
- ::Spandx::Core::Dependency.new(package_manager: :nuget, name: name, version: version)
22
+ ::Spandx::Core::Dependency.new(name: name, version: version, path: path)
23
23
  end
24
24
 
25
25
  def attribute_for(key, node)
@@ -4,29 +4,26 @@ module Spandx
4
4
  module Dotnet
5
5
  module Parsers
6
6
  class Sln < ::Spandx::Core::Parser
7
- def matches?(filename)
8
- filename.match?(/.*\.sln/)
7
+ def match?(path)
8
+ path.extname == '.sln'
9
9
  end
10
10
 
11
- def parse(file_path)
12
- project_paths_from(file_path).map do |path|
13
- ::Spandx::Core::Parser
14
- .for(path)
15
- .parse(path)
11
+ def parse(path)
12
+ project_paths_from(path).map do |project_path|
13
+ ::Spandx::Core::Parser.parse(project_path)
16
14
  end.flatten
17
15
  end
18
16
 
19
17
  private
20
18
 
21
- def project_paths_from(file_path)
22
- IO.readlines(file_path).map do |line|
19
+ def project_paths_from(path)
20
+ path.each_line.map do |line|
23
21
  next unless project_line?(line)
24
22
 
25
- path = project_path_from(line)
26
- next unless path
23
+ project_path = project_path_from(line)
24
+ next unless project_path
27
25
 
28
- path = File.join(File.dirname(file_path), path)
29
- Pathname.new(path).cleanpath.to_path
26
+ path.dirname.join(project_path).cleanpath.to_path
30
27
  end.compact
31
28
  end
32
29
 
@@ -6,9 +6,9 @@ module Spandx
6
6
  attr_reader :catalogue, :document, :nuget
7
7
 
8
8
  def initialize(path)
9
- @path = path
10
- @dir = File.dirname(path)
11
- @document = Nokogiri::XML(IO.read(path)).tap(&:remove_namespaces!)
9
+ @path = Pathname(path)
10
+ @dir = @path.dirname
11
+ @document = Nokogiri::XML(@path.read).tap(&:remove_namespaces!)
12
12
  end
13
13
 
14
14
  def package_references
@@ -4,26 +4,26 @@ module Spandx
4
4
  module Java
5
5
  module Parsers
6
6
  class Maven < ::Spandx::Core::Parser
7
- def matches?(filename)
8
- File.basename(filename) == 'pom.xml'
7
+ def match?(path)
8
+ path.basename.fnmatch?('pom.xml')
9
9
  end
10
10
 
11
- def parse(filename)
12
- document = Nokogiri.XML(IO.read(filename)).tap(&:remove_namespaces!)
11
+ def parse(path)
12
+ document = Nokogiri.XML(path.read).tap(&:remove_namespaces!)
13
13
  document.search('//project/dependencies/dependency').map do |node|
14
- map_from(node)
14
+ map_from(path, node)
15
15
  end
16
16
  end
17
17
 
18
18
  private
19
19
 
20
- def map_from(node)
20
+ def map_from(path, node)
21
21
  artifact_id = node.at_xpath('./artifactId').text
22
22
  group_id = node.at_xpath('./groupId').text
23
23
  version = node.at_xpath('./version').text
24
24
 
25
25
  ::Spandx::Core::Dependency.new(
26
- package_manager: :maven,
26
+ path: path,
27
27
  name: "#{group_id}:#{artifact_id}",
28
28
  version: version
29
29
  )
@@ -4,30 +4,30 @@ module Spandx
4
4
  module Js
5
5
  module Parsers
6
6
  class Npm < ::Spandx::Core::Parser
7
- def matches?(filename)
7
+ def match?(filename)
8
8
  File.basename(filename) == 'package-lock.json'
9
9
  end
10
10
 
11
- def parse(file_path)
11
+ def parse(path)
12
12
  items = Set.new
13
- each_metadata(file_path) do |metadata|
14
- items.add(map_from(metadata))
13
+ each_metadata(path) do |metadata|
14
+ items.add(map_from(path, metadata))
15
15
  end
16
16
  items
17
17
  end
18
18
 
19
19
  private
20
20
 
21
- def each_metadata(file_path)
22
- package_lock = JSON.parse(IO.read(file_path))
21
+ def each_metadata(path)
22
+ package_lock = Oj.load(path.read)
23
23
  package_lock['dependencies'].each do |name, metadata|
24
24
  yield metadata.merge('name' => name)
25
25
  end
26
26
  end
27
27
 
28
- def map_from(metadata)
28
+ def map_from(path, metadata)
29
29
  Spandx::Core::Dependency.new(
30
- package_manager: :npm,
30
+ path: path,
31
31
  name: metadata['name'],
32
32
  version: metadata['version'],
33
33
  meta: metadata
@@ -4,21 +4,21 @@ module Spandx
4
4
  module Js
5
5
  module Parsers
6
6
  class Yarn < ::Spandx::Core::Parser
7
- def matches?(filename)
8
- File.basename(filename) == 'yarn.lock'
7
+ def match?(filename)
8
+ filename.basename.fnmatch?('yarn.lock')
9
9
  end
10
10
 
11
- def parse(file_path)
12
- YarnLock.new(file_path).each_with_object(Set.new) do |metadata, memo|
13
- memo << map_from(metadata)
11
+ def parse(path)
12
+ YarnLock.new(path).each_with_object(Set.new) do |metadata, memo|
13
+ memo << map_from(path, metadata)
14
14
  end
15
15
  end
16
16
 
17
17
  private
18
18
 
19
- def map_from(metadata)
19
+ def map_from(path, metadata)
20
20
  ::Spandx::Core::Dependency.new(
21
- package_manager: :yarn,
21
+ path: path,
22
22
  name: metadata['name'],
23
23
  version: metadata['version'],
24
24
  meta: metadata
@@ -27,7 +27,7 @@ module Spandx
27
27
  response = http.get(uri, escape: false)
28
28
 
29
29
  if http.ok?(response)
30
- json = JSON.parse(response.body)
30
+ json = Oj.load(response.body)
31
31
  json['versions'] ? json['versions'][dependency.version] : json
32
32
  else
33
33
  {}