spandx 0.10.1 → 0.11.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 +4 -4
- data/CHANGELOG.md +7 -1
- data/lib/spandx/cli/commands/index/build.rb +2 -1
- data/lib/spandx/cli/commands/scan.rb +1 -1
- data/lib/spandx/cli.rb +1 -0
- data/lib/spandx/{rubygems/offline_index.rb → core/cache.rb} +21 -10
- data/lib/spandx/{gateways → core}/http.rb +1 -1
- data/lib/spandx/core/report.rb +17 -4
- data/lib/spandx/dotnet/nuget_gateway.rb +7 -0
- data/lib/spandx/java/index.rb +13 -0
- data/lib/spandx/python/parsers/pipfile_lock.rb +51 -0
- data/lib/spandx/python/pypi.rb +19 -0
- data/lib/spandx/python/source.rb +45 -0
- data/lib/spandx/rubygems/gateway.rb +3 -4
- data/lib/spandx/version.rb +1 -1
- data/lib/spandx.rb +7 -5
- metadata +8 -6
- data/lib/spandx/gateways/pypi.rb +0 -59
- data/lib/spandx/parsers/pipfile_lock.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da243ee84b54d710a15eeb5182f7b70084cbc69cb86beaa93c25c13dc650fe2a
|
4
|
+
data.tar.gz: 904eff3844f540311828386dde54ad70054f5c4fe90e456bb0af702efc9f7cc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67135d0cff701f454ce5d127a7f4ec7fd061d63bc832d5eb7df20bb26efa6e45bc90e28bc22eceaa2c135437858aa63337db8ff898245d870fe7acfcae14413d
|
7
|
+
data.tar.gz: a4086a92383a8c334c3488004b6be02a0ce80a79b9c7386eea5948d3d52d031d46e742d9e290afa45e306c4d3d1e4bbb7c2849cdd1d2d3dc98b5aba74fd13ed8
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Version 0.
|
1
|
+
Version 0.11.0
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
@@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
8
8
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
9
9
|
|
10
10
|
## [Unreleased]
|
11
|
+
|
12
|
+
## [0.11.0] - 2020-03-20
|
13
|
+
### Added
|
14
|
+
- Add `--format` option to scan command.
|
15
|
+
- Read from offline `nuget` cache.
|
16
|
+
|
11
17
|
## [0.10.1] - 2020-03-16
|
12
18
|
### Fixed
|
13
19
|
- Update location of `rubygems` index data
|
data/lib/spandx/cli.rb
CHANGED
@@ -23,6 +23,7 @@ module Spandx
|
|
23
23
|
method_option :recursive, aliases: '-r', type: :boolean, desc: 'Perform recursive scan', default: false
|
24
24
|
method_option :airgap, aliases: '-a', type: :boolean, desc: 'Disable network connections', default: false
|
25
25
|
method_option :logfile, aliases: '-l', type: :string, desc: 'Path to a logfile', default: '/dev/null'
|
26
|
+
method_option :format, aliases: '-f', type: :string, desc: 'Format of report', default: 'json'
|
26
27
|
def scan(lockfile)
|
27
28
|
Spandx.airgap = options[:airgap]
|
28
29
|
Spandx.logger = Logger.new(options[:logfile])
|
@@ -1,16 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Spandx
|
4
|
-
module
|
5
|
-
class
|
6
|
-
attr_reader :db
|
4
|
+
module Core
|
5
|
+
class Cache
|
6
|
+
attr_reader :db, :package_manager
|
7
7
|
|
8
|
-
def initialize(package_manager)
|
9
|
-
@
|
8
|
+
def initialize(package_manager, url:)
|
9
|
+
@package_manager = package_manager
|
10
|
+
@db = ::Spandx::Core::Database.new(url: url)
|
11
|
+
@cache = {}
|
12
|
+
@lines = {}
|
10
13
|
end
|
11
14
|
|
12
15
|
def licenses_for(name:, version:)
|
13
16
|
found = search(name: name, version: version)
|
17
|
+
Spandx.logger.debug("Cache miss: #{name}-#{version}") if found.nil?
|
14
18
|
found ? found[2].split('-|-') : []
|
15
19
|
end
|
16
20
|
|
@@ -25,8 +29,9 @@ module Spandx
|
|
25
29
|
end
|
26
30
|
|
27
31
|
def search(name:, version:)
|
28
|
-
|
29
|
-
|
32
|
+
datafile = datafile_for(name)
|
33
|
+
db.open(datafile) do |io|
|
34
|
+
search_for("#{name}-#{version}", io, @lines.fetch(datafile) { |key| @lines[key] = lines_in(io) })
|
30
35
|
end
|
31
36
|
rescue Errno::ENOENT => error
|
32
37
|
Spandx.logger.error(error)
|
@@ -34,7 +39,7 @@ module Spandx
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def datafile_for(name)
|
37
|
-
".index/#{key_for(name)}
|
42
|
+
".index/#{key_for(name)}/#{package_manager}"
|
38
43
|
end
|
39
44
|
|
40
45
|
def lines_in(io)
|
@@ -49,10 +54,14 @@ module Spandx
|
|
49
54
|
|
50
55
|
def search_for(term, io, lines)
|
51
56
|
return if lines.empty?
|
57
|
+
return @cache[term] if @cache.key?(term)
|
52
58
|
|
53
59
|
mid = lines.size == 1 ? 0 : lines.size / 2
|
54
60
|
io.seek(lines[mid])
|
55
|
-
comparison = matches?(term, parse_row(io))
|
61
|
+
comparison = matches?(term, parse_row(io)) do |row|
|
62
|
+
return row
|
63
|
+
end
|
64
|
+
|
56
65
|
search_for(term, io, partition(comparison, mid, lines))
|
57
66
|
end
|
58
67
|
|
@@ -68,7 +77,9 @@ module Spandx
|
|
68
77
|
end
|
69
78
|
|
70
79
|
def parse_row(io)
|
71
|
-
CSV.parse(io.readline)[0]
|
80
|
+
row = CSV.parse(io.readline)[0]
|
81
|
+
@cache["#{row[0]}-#{row[1]}"] = row
|
82
|
+
row
|
72
83
|
end
|
73
84
|
end
|
74
85
|
end
|
data/lib/spandx/core/report.rb
CHANGED
@@ -3,16 +3,29 @@
|
|
3
3
|
module Spandx
|
4
4
|
module Core
|
5
5
|
class Report
|
6
|
-
|
7
|
-
|
6
|
+
FORMATS = {
|
7
|
+
json: :to_json,
|
8
|
+
hash: :to_h,
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@dependencies = []
|
8
13
|
end
|
9
14
|
|
10
15
|
def add(dependency)
|
11
|
-
@
|
16
|
+
@dependencies.push(dependency)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to(format)
|
20
|
+
public_send(FORMATS.fetch(format&.to_sym, :to_json))
|
12
21
|
end
|
13
22
|
|
14
23
|
def to_h
|
15
|
-
|
24
|
+
{ version: '1.0', dependencies: [] }.tap do |report|
|
25
|
+
@dependencies.each do |dependency|
|
26
|
+
report[:dependencies].push(dependency.to_h)
|
27
|
+
end
|
28
|
+
end
|
16
29
|
end
|
17
30
|
|
18
31
|
def to_json(*_args)
|
@@ -15,6 +15,9 @@ module Spandx
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def licenses_for(name, version)
|
18
|
+
found = cache.licenses_for(name: name, version: version)
|
19
|
+
return found if found.any?
|
20
|
+
|
18
21
|
document = nuspec_for(name, version)
|
19
22
|
|
20
23
|
extract_licenses_from(document) ||
|
@@ -33,6 +36,10 @@ module Spandx
|
|
33
36
|
|
34
37
|
attr_reader :http, :guess
|
35
38
|
|
39
|
+
def cache
|
40
|
+
@cache ||= ::Spandx::Core::Cache.new(:nuget, url: 'https://github.com/mokhan/spandx-index.git')
|
41
|
+
end
|
42
|
+
|
36
43
|
def each_page(start_page:)
|
37
44
|
url = "https://#{host}/v3/catalog0/index.json"
|
38
45
|
items_from(fetch_json(url))
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Python
|
5
|
+
module Parsers
|
6
|
+
class PipfileLock < ::Spandx::Core::Parser
|
7
|
+
def self.matches?(filename)
|
8
|
+
filename.match?(/Pipfile.*\.lock/)
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(lockfile)
|
12
|
+
results = []
|
13
|
+
dependencies_from(lockfile) do |x|
|
14
|
+
results << ::Spandx::Core::Dependency.new(
|
15
|
+
name: x[:name],
|
16
|
+
version: x[:version],
|
17
|
+
licenses: x[:licenses]
|
18
|
+
)
|
19
|
+
end
|
20
|
+
results
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def dependencies_from(lockfile)
|
26
|
+
json = JSON.parse(IO.read(lockfile))
|
27
|
+
each_dependency(pypi_for(json), json) do |name, version, definition|
|
28
|
+
yield({ name: name, version: version, licenses: [catalogue[definition['license']]] })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def each_dependency(pypi, json, groups: %w[default develop])
|
33
|
+
groups.each do |group|
|
34
|
+
json[group].each do |name, value|
|
35
|
+
version = canonicalize(value['version'])
|
36
|
+
yield name, version, pypi.definition_for(name, version)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def canonicalize(version)
|
42
|
+
version.gsub(/==/, '')
|
43
|
+
end
|
44
|
+
|
45
|
+
def pypi_for(json)
|
46
|
+
PyPI.new(sources: Source.sources_from(json))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Python
|
5
|
+
class PyPI
|
6
|
+
def initialize(sources: [Source.default])
|
7
|
+
@sources = sources
|
8
|
+
end
|
9
|
+
|
10
|
+
def definition_for(name, version)
|
11
|
+
@sources.each do |source|
|
12
|
+
response = source.lookup(name, version)
|
13
|
+
return JSON.parse(response.body).fetch('info', {}) if response
|
14
|
+
end
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Python
|
5
|
+
class Source
|
6
|
+
attr_reader :name, :uri, :verify_ssl
|
7
|
+
|
8
|
+
def initialize(source)
|
9
|
+
@name = source['name']
|
10
|
+
@uri = URI.parse(source['url'])
|
11
|
+
@verify_ssl = source['verify_ssl']
|
12
|
+
end
|
13
|
+
|
14
|
+
def host
|
15
|
+
@uri.host
|
16
|
+
end
|
17
|
+
|
18
|
+
def uri_for(name, version)
|
19
|
+
"https://#{host}/pypi/#{name}/#{version}/json"
|
20
|
+
end
|
21
|
+
|
22
|
+
def lookup(name, version, http: Spandx.http)
|
23
|
+
response = http.get(uri_for(name, version))
|
24
|
+
response if http.ok?(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def sources_from(json)
|
29
|
+
meta = json['_meta']
|
30
|
+
meta['sources'].map do |hash|
|
31
|
+
new(hash)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def default
|
36
|
+
new(
|
37
|
+
'name' => 'pypi',
|
38
|
+
'url' => 'https://pypi.org/simple',
|
39
|
+
'verify_ssl' => true
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -9,7 +9,7 @@ module Spandx
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def licenses_for(name, version)
|
12
|
-
found =
|
12
|
+
found = cache.licenses_for(name: name, version: version)
|
13
13
|
found.any? ? found : details_on(name, version)['licenses'] || []
|
14
14
|
end
|
15
15
|
|
@@ -17,12 +17,11 @@ module Spandx
|
|
17
17
|
|
18
18
|
attr_reader :http
|
19
19
|
|
20
|
-
def
|
21
|
-
@
|
20
|
+
def cache
|
21
|
+
@cache ||= ::Spandx::Core::Cache.new(:rubygems, url: 'https://github.com/mokhan/spandx-rubygems.git')
|
22
22
|
end
|
23
23
|
|
24
24
|
def details_on(name, version)
|
25
|
-
Spandx.logger.debug("Cache miss: #{name}-#{version}")
|
26
25
|
url = "https://rubygems.org/api/v2/rubygems/#{name}/versions/#{version}.json"
|
27
26
|
response = http.get(url, default: {})
|
28
27
|
http.ok?(response) ? parse(response.body) : {}
|
data/lib/spandx/version.rb
CHANGED
data/lib/spandx.rb
CHANGED
@@ -10,10 +10,12 @@ require 'net/hippie'
|
|
10
10
|
require 'nokogiri'
|
11
11
|
require 'pathname'
|
12
12
|
|
13
|
+
require 'spandx/core/cache'
|
13
14
|
require 'spandx/core/content'
|
14
15
|
require 'spandx/core/database'
|
15
16
|
require 'spandx/core/dependency'
|
16
17
|
require 'spandx/core/guess'
|
18
|
+
require 'spandx/core/http'
|
17
19
|
require 'spandx/core/parser'
|
18
20
|
require 'spandx/core/report'
|
19
21
|
require 'spandx/core/score'
|
@@ -24,13 +26,13 @@ require 'spandx/dotnet/parsers/csproj'
|
|
24
26
|
require 'spandx/dotnet/parsers/packages_config'
|
25
27
|
require 'spandx/dotnet/parsers/sln'
|
26
28
|
require 'spandx/dotnet/project_file'
|
27
|
-
require 'spandx/
|
28
|
-
require 'spandx/gateways/pypi'
|
29
|
+
require 'spandx/java/index'
|
29
30
|
require 'spandx/java/metadata'
|
30
31
|
require 'spandx/java/parsers/maven'
|
31
|
-
require 'spandx/parsers/pipfile_lock'
|
32
|
+
require 'spandx/python/parsers/pipfile_lock'
|
33
|
+
require 'spandx/python/pypi'
|
34
|
+
require 'spandx/python/source'
|
32
35
|
require 'spandx/rubygems/gateway'
|
33
|
-
require 'spandx/rubygems/offline_index'
|
34
36
|
require 'spandx/rubygems/parsers/gemfile_lock'
|
35
37
|
require 'spandx/spdx/catalogue'
|
36
38
|
require 'spandx/spdx/gateway'
|
@@ -52,7 +54,7 @@ module Spandx
|
|
52
54
|
end
|
53
55
|
|
54
56
|
def http
|
55
|
-
@http ||= Spandx::
|
57
|
+
@http ||= Spandx::Core::Http.new
|
56
58
|
end
|
57
59
|
|
58
60
|
def logger
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spandx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mo khan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-03-
|
11
|
+
date: 2020-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -260,10 +260,12 @@ files:
|
|
260
260
|
- lib/spandx/cli/commands/index/build.rb
|
261
261
|
- lib/spandx/cli/commands/index/update.rb
|
262
262
|
- lib/spandx/cli/commands/scan.rb
|
263
|
+
- lib/spandx/core/cache.rb
|
263
264
|
- lib/spandx/core/content.rb
|
264
265
|
- lib/spandx/core/database.rb
|
265
266
|
- lib/spandx/core/dependency.rb
|
266
267
|
- lib/spandx/core/guess.rb
|
268
|
+
- lib/spandx/core/http.rb
|
267
269
|
- lib/spandx/core/parser.rb
|
268
270
|
- lib/spandx/core/report.rb
|
269
271
|
- lib/spandx/core/score.rb
|
@@ -274,13 +276,13 @@ files:
|
|
274
276
|
- lib/spandx/dotnet/parsers/packages_config.rb
|
275
277
|
- lib/spandx/dotnet/parsers/sln.rb
|
276
278
|
- lib/spandx/dotnet/project_file.rb
|
277
|
-
- lib/spandx/
|
278
|
-
- lib/spandx/gateways/pypi.rb
|
279
|
+
- lib/spandx/java/index.rb
|
279
280
|
- lib/spandx/java/metadata.rb
|
280
281
|
- lib/spandx/java/parsers/maven.rb
|
281
|
-
- lib/spandx/parsers/pipfile_lock.rb
|
282
|
+
- lib/spandx/python/parsers/pipfile_lock.rb
|
283
|
+
- lib/spandx/python/pypi.rb
|
284
|
+
- lib/spandx/python/source.rb
|
282
285
|
- lib/spandx/rubygems/gateway.rb
|
283
|
-
- lib/spandx/rubygems/offline_index.rb
|
284
286
|
- lib/spandx/rubygems/parsers/gemfile_lock.rb
|
285
287
|
- lib/spandx/spdx/catalogue.rb
|
286
288
|
- lib/spandx/spdx/gateway.rb
|
data/lib/spandx/gateways/pypi.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Spandx
|
4
|
-
module Gateways
|
5
|
-
class PyPI
|
6
|
-
class Source
|
7
|
-
attr_reader :name, :uri, :verify_ssl
|
8
|
-
|
9
|
-
def initialize(source)
|
10
|
-
@name = source['name']
|
11
|
-
@uri = URI.parse(source['url'])
|
12
|
-
@verify_ssl = source['verify_ssl']
|
13
|
-
end
|
14
|
-
|
15
|
-
def host
|
16
|
-
@uri.host
|
17
|
-
end
|
18
|
-
|
19
|
-
def uri_for(name, version)
|
20
|
-
"https://#{host}/pypi/#{name}/#{version}/json"
|
21
|
-
end
|
22
|
-
|
23
|
-
def lookup(name, version, http: Spandx.http)
|
24
|
-
response = http.get(uri_for(name, version))
|
25
|
-
response if http.ok?(response)
|
26
|
-
end
|
27
|
-
|
28
|
-
class << self
|
29
|
-
def sources_from(json)
|
30
|
-
meta = json['_meta']
|
31
|
-
meta['sources'].map do |hash|
|
32
|
-
Gateways::PyPI::Source.new(hash)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def default
|
37
|
-
new(
|
38
|
-
'name' => 'pypi',
|
39
|
-
'url' => 'https://pypi.org/simple',
|
40
|
-
'verify_ssl' => true
|
41
|
-
)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def initialize(sources: [Source.default])
|
47
|
-
@sources = sources
|
48
|
-
end
|
49
|
-
|
50
|
-
def definition_for(name, version)
|
51
|
-
@sources.each do |source|
|
52
|
-
response = source.lookup(name, version)
|
53
|
-
return JSON.parse(response.body).fetch('info', {}) if response
|
54
|
-
end
|
55
|
-
{}
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Spandx
|
4
|
-
module Parsers
|
5
|
-
class PipfileLock < ::Spandx::Core::Parser
|
6
|
-
def self.matches?(filename)
|
7
|
-
filename.match?(/Pipfile.*\.lock/)
|
8
|
-
end
|
9
|
-
|
10
|
-
def parse(lockfile)
|
11
|
-
results = []
|
12
|
-
dependencies_from(lockfile) do |x|
|
13
|
-
results << ::Spandx::Core::Dependency.new(
|
14
|
-
name: x[:name],
|
15
|
-
version: x[:version],
|
16
|
-
licenses: x[:licenses]
|
17
|
-
)
|
18
|
-
end
|
19
|
-
results
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def dependencies_from(lockfile)
|
25
|
-
json = JSON.parse(IO.read(lockfile))
|
26
|
-
each_dependency(pypi_for(json), json) do |name, version, definition|
|
27
|
-
yield({ name: name, version: version, licenses: [catalogue[definition['license']]] })
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def each_dependency(pypi, json, groups: %w[default develop])
|
32
|
-
groups.each do |group|
|
33
|
-
json[group].each do |name, value|
|
34
|
-
version = canonicalize(value['version'])
|
35
|
-
yield name, version, pypi.definition_for(name, version)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def canonicalize(version)
|
41
|
-
version.gsub(/==/, '')
|
42
|
-
end
|
43
|
-
|
44
|
-
def pypi_for(json)
|
45
|
-
Gateways::PyPI.new(
|
46
|
-
sources: Gateways::PyPI::Source.sources_from(json)
|
47
|
-
)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|