spandx 0.13.3 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -2
  3. data/exe/spandx +0 -1
  4. data/ext/spandx/spandx.c +7 -3
  5. data/lib/spandx.rb +1 -1
  6. data/lib/spandx/cli.rb +2 -2
  7. data/lib/spandx/cli/commands/pull.rb +33 -4
  8. data/lib/spandx/cli/commands/scan.rb +19 -22
  9. data/lib/spandx/cli/main.rb +3 -3
  10. data/lib/spandx/cli/printer.rb +27 -0
  11. data/lib/spandx/cli/printers/csv.rb +17 -0
  12. data/lib/spandx/cli/printers/json.rb +17 -0
  13. data/lib/spandx/cli/printers/table.rb +42 -0
  14. data/lib/spandx/core/dependency.rb +48 -13
  15. data/lib/spandx/core/git.rb +6 -6
  16. data/lib/spandx/core/http.rb +6 -6
  17. data/lib/spandx/core/license_plugin.rb +10 -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 +49 -0
  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 +1 -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 -4
  43. metadata +38 -20
  44. data/lib/spandx/core/report.rb +0 -60
  45. data/lib/spandx/core/spinner.rb +0 -51
  46. data/lib/spandx/core/table.rb +0 -29
@@ -11,9 +11,8 @@ module Spandx
11
11
  end
12
12
 
13
13
  def read(path)
14
- full_path = File.join(root, path)
15
-
16
- IO.read(full_path) if File.exist?(full_path)
14
+ full_path = root.join(path)
15
+ full_path.read if full_path.exist?
17
16
  end
18
17
 
19
18
  def update!
@@ -25,15 +24,16 @@ module Spandx
25
24
  def path_for(url)
26
25
  uri = URI.parse(url)
27
26
  name = uri.path.gsub(/\.git$/, '')
28
- File.expand_path(File.join(Dir.home, '.local', 'share', name))
27
+ Pathname(File.expand_path(File.join(Dir.home, '.local', 'share', name)))
29
28
  end
30
29
 
31
30
  def dotgit?
32
- File.directory?(File.join(root, '.git'))
31
+ root.join('.git').directory?
33
32
  end
34
33
 
35
34
  def clone!
36
- system('git', 'clone', '--quiet', '--depth=1', '--single-branch', '--branch', 'master', url, root)
35
+ system('rm', '-rf', root.to_s) if root.exist?
36
+ system('git', 'clone', '--quiet', '--depth=1', '--single-branch', '--branch', 'master', url, root.to_s)
37
37
  end
38
38
 
39
39
  def pull!
@@ -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
@@ -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,13 +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
- key = key_for(dependency.package_manager)
29
+ package_manager = package_manager_for(dependency)
30
+ git = git[package_manager.to_sym] || git[:cache]
31
+ key = key_for(package_manager)
30
32
  Spandx::Core::Cache.new(key, root: "#{git.root}/.index")
31
33
  end
32
34
 
33
35
  def known?(package_manager)
34
- %i[nuget maven rubygems npm yarn pypi composer].include?(package_manager)
36
+ %i[nuget maven rubygems npm yarn pypi composer apk].include?(package_manager)
35
37
  end
36
38
 
37
39
  def gateway_for(dependency)
@@ -54,6 +56,10 @@ module Spandx
54
56
  def key_for(package_manager)
55
57
  package_manager == :yarn ? :npm : package_manager
56
58
  end
59
+
60
+ def package_manager_for(dependency)
61
+ dependency.package_manager
62
+ end
57
63
  end
58
64
  end
59
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
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Core
5
+ class ThreadPool
6
+ def initialize(size: 1)
7
+ @size = size
8
+ @queue = Queue.new
9
+ @pool = size.times.map { start_worker_thread(@queue) }
10
+ end
11
+
12
+ def run(*args, &job)
13
+ @queue.enq([job, args])
14
+ end
15
+
16
+ def done?
17
+ @queue.empty?
18
+ end
19
+
20
+ def shutdown
21
+ @size.times do
22
+ run { throw :exit }
23
+ end
24
+
25
+ @pool.map(&:join)
26
+ end
27
+
28
+ def self.open(**args)
29
+ pool = new(**args)
30
+ yield pool
31
+ ensure
32
+ pool.shutdown
33
+ end
34
+
35
+ private
36
+
37
+ def start_worker_thread(queue)
38
+ Thread.new(queue) do |q|
39
+ catch(:exit) do
40
+ loop do
41
+ job, args = q.deq
42
+ job.call(args)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ 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