spandx 0.10.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|