spandx 0.12.3 → 0.13.4
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 +74 -25
- data/README.md +11 -7
- data/exe/spandx +1 -2
- data/ext/spandx/extconf.rb +5 -0
- data/ext/spandx/spandx.c +55 -0
- data/ext/spandx/spandx.h +6 -0
- data/lib/spandx.rb +6 -3
- data/lib/spandx/cli.rb +2 -0
- data/lib/spandx/cli/commands/build.rb +13 -2
- data/lib/spandx/cli/commands/scan.rb +11 -20
- data/lib/spandx/cli/main.rb +3 -2
- data/lib/spandx/core/cache.rb +38 -51
- data/lib/spandx/core/content.rb +5 -23
- data/lib/spandx/core/data_file.rb +66 -0
- data/lib/spandx/core/dependency.rb +47 -13
- data/lib/spandx/core/git.rb +8 -32
- data/lib/spandx/core/guess.rb +48 -40
- data/lib/spandx/core/http.rb +7 -2
- data/lib/spandx/core/index_file.rb +103 -0
- data/lib/spandx/core/license_plugin.rb +15 -4
- data/lib/spandx/core/parser.rb +10 -3
- data/lib/spandx/core/path_traversal.rb +35 -0
- data/lib/spandx/core/relation.rb +38 -0
- data/lib/spandx/core/report.rb +6 -12
- data/lib/spandx/core/spinner.rb +51 -0
- data/lib/spandx/dotnet/index.rb +21 -79
- data/lib/spandx/dotnet/parsers/csproj.rb +7 -7
- data/lib/spandx/dotnet/parsers/packages_config.rb +7 -7
- data/lib/spandx/dotnet/parsers/sln.rb +10 -13
- data/lib/spandx/dotnet/project_file.rb +3 -3
- data/lib/spandx/java/index.rb +5 -2
- data/lib/spandx/java/parsers/maven.rb +7 -7
- data/lib/spandx/js/parsers/npm.rb +6 -6
- data/lib/spandx/js/parsers/yarn.rb +7 -7
- data/lib/spandx/php/parsers/composer.rb +7 -7
- data/lib/spandx/python/index.rb +4 -33
- data/lib/spandx/python/parsers/pipfile_lock.rb +4 -4
- data/lib/spandx/python/pypi.rb +0 -2
- data/lib/spandx/python/source.rb +12 -0
- data/lib/spandx/ruby/parsers/gemfile_lock.rb +10 -9
- data/lib/spandx/spdx/catalogue.rb +5 -1
- data/lib/spandx/spdx/composite_license.rb +60 -0
- data/lib/spandx/spdx/expression.rb +114 -0
- data/lib/spandx/spdx/license.rb +4 -14
- data/lib/spandx/version.rb +1 -1
- data/spandx.gemspec +16 -10
- metadata +100 -30
- data/lib/spandx/core/null_gateway.rb +0 -11
- data/lib/spandx/core/table.rb +0 -29
- data/lib/spandx/core/thread_pool.rb +0 -38
@@ -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 =
|
11
|
-
@document = Nokogiri::XML(
|
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
|
data/lib/spandx/java/index.rb
CHANGED
@@ -13,13 +13,16 @@ module Spandx
|
|
13
13
|
@directory = directory
|
14
14
|
@source = source
|
15
15
|
@name = 'maven'
|
16
|
+
@cache = ::Spandx::Core::Cache.new(@name, root: directory)
|
16
17
|
end
|
17
18
|
|
18
19
|
def update!(catalogue:, output:)
|
19
20
|
each do |metadata|
|
20
|
-
name = "#{metadata.group_id}:#{metadata.artifact_id}
|
21
|
-
output.puts [name, metadata.licenses_from(catalogue)].inspect
|
21
|
+
name = "#{metadata.group_id}:#{metadata.artifact_id}"
|
22
|
+
output.puts [name, metadata.version, metadata.licenses_from(catalogue)].inspect
|
23
|
+
@cache.insert(name, metadata.version, metadata.licenses_from(catalogue))
|
22
24
|
end
|
25
|
+
@cache.rebuild_index
|
23
26
|
end
|
24
27
|
|
25
28
|
def each
|
@@ -4,26 +4,26 @@ module Spandx
|
|
4
4
|
module Java
|
5
5
|
module Parsers
|
6
6
|
class Maven < ::Spandx::Core::Parser
|
7
|
-
def
|
8
|
-
|
7
|
+
def match?(path)
|
8
|
+
path.basename.fnmatch?('pom.xml')
|
9
9
|
end
|
10
10
|
|
11
|
-
def parse(
|
12
|
-
document = Nokogiri.XML(
|
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
|
-
|
26
|
+
path: path,
|
27
27
|
name: "#{group_id}:#{artifact_id}",
|
28
28
|
version: version
|
29
29
|
)
|
@@ -4,14 +4,14 @@ module Spandx
|
|
4
4
|
module Js
|
5
5
|
module Parsers
|
6
6
|
class Npm < ::Spandx::Core::Parser
|
7
|
-
def
|
7
|
+
def match?(filename)
|
8
8
|
File.basename(filename) == 'package-lock.json'
|
9
9
|
end
|
10
10
|
|
11
|
-
def parse(
|
11
|
+
def parse(path)
|
12
12
|
items = Set.new
|
13
|
-
each_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
|
@@ -25,9 +25,9 @@ module Spandx
|
|
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
|
-
|
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
|
8
|
-
|
7
|
+
def match?(filename)
|
8
|
+
filename.basename.fnmatch?('yarn.lock')
|
9
9
|
end
|
10
10
|
|
11
|
-
def parse(
|
12
|
-
YarnLock.new(
|
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
|
-
|
21
|
+
path: path,
|
22
22
|
name: metadata['name'],
|
23
23
|
version: metadata['version'],
|
24
24
|
meta: metadata
|
@@ -4,24 +4,24 @@ module Spandx
|
|
4
4
|
module Php
|
5
5
|
module Parsers
|
6
6
|
class Composer < ::Spandx::Core::Parser
|
7
|
-
def
|
8
|
-
|
7
|
+
def match?(path)
|
8
|
+
path.basename.fnmatch? 'composer.lock'
|
9
9
|
end
|
10
10
|
|
11
|
-
def parse(
|
11
|
+
def parse(path)
|
12
12
|
items = Set.new
|
13
|
-
composer_lock = JSON.parse(
|
13
|
+
composer_lock = JSON.parse(path.read)
|
14
14
|
composer_lock['packages'].concat(composer_lock['packages-dev']).each do |dependency|
|
15
|
-
items.add(map_from(dependency))
|
15
|
+
items.add(map_from(path, dependency))
|
16
16
|
end
|
17
17
|
items
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
-
def map_from(dependency)
|
22
|
+
def map_from(path, dependency)
|
23
23
|
Spandx::Core::Dependency.new(
|
24
|
-
|
24
|
+
path: path,
|
25
25
|
name: dependency['name'],
|
26
26
|
version: dependency['version'],
|
27
27
|
meta: dependency
|
data/lib/spandx/python/index.rb
CHANGED
@@ -12,28 +12,18 @@ module Spandx
|
|
12
12
|
@name = 'pypi'
|
13
13
|
@source = 'https://pypi.org'
|
14
14
|
@pypi = Pypi.new
|
15
|
-
|
15
|
+
@cache = ::Spandx::Core::Cache.new(@name, root: directory)
|
16
16
|
end
|
17
17
|
|
18
18
|
def update!(*)
|
19
19
|
queue = Queue.new
|
20
20
|
[fetch(queue), save(queue)].each(&:join)
|
21
|
+
cache.rebuild_index
|
21
22
|
end
|
22
23
|
|
23
24
|
private
|
24
25
|
|
25
|
-
|
26
|
-
Dir.glob(pattern, base: directory).sort.each do |file|
|
27
|
-
fullpath = File.join(directory, file)
|
28
|
-
yield fullpath unless File.directory?(fullpath)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def sort_index!
|
33
|
-
files('**/pypi') do |path|
|
34
|
-
IO.write(path, IO.readlines(path).sort.join)
|
35
|
-
end
|
36
|
-
end
|
26
|
+
attr_reader :cache
|
37
27
|
|
38
28
|
def fetch(queue)
|
39
29
|
Thread.new do
|
@@ -50,29 +40,10 @@ module Spandx
|
|
50
40
|
item = queue.deq
|
51
41
|
break if item == :stop
|
52
42
|
|
53
|
-
insert
|
43
|
+
cache.insert(item[:name], item[:version], [item[:license]])
|
54
44
|
end
|
55
45
|
end
|
56
46
|
end
|
57
|
-
|
58
|
-
def digest_for(components)
|
59
|
-
Digest::SHA1.hexdigest(Array(components).join('/'))
|
60
|
-
end
|
61
|
-
|
62
|
-
def data_dir_for(name)
|
63
|
-
File.join(directory, digest_for(name)[0...2].downcase)
|
64
|
-
end
|
65
|
-
|
66
|
-
def data_file_for(name)
|
67
|
-
File.join(data_dir_for(name), 'pypi')
|
68
|
-
end
|
69
|
-
|
70
|
-
def insert!(name, version, license)
|
71
|
-
return if license.nil? || license.empty?
|
72
|
-
|
73
|
-
csv = CSV.generate_line([name, version, license], force_quotes: true)
|
74
|
-
IO.write(data_file_for(name), csv, mode: 'a')
|
75
|
-
end
|
76
47
|
end
|
77
48
|
end
|
78
49
|
end
|
@@ -4,8 +4,8 @@ module Spandx
|
|
4
4
|
module Python
|
5
5
|
module Parsers
|
6
6
|
class PipfileLock < ::Spandx::Core::Parser
|
7
|
-
def
|
8
|
-
|
7
|
+
def match?(path)
|
8
|
+
path.basename.fnmatch?('Pipfile*.lock')
|
9
9
|
end
|
10
10
|
|
11
11
|
def parse(lockfile)
|
@@ -19,10 +19,10 @@ module Spandx
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def dependencies_from(lockfile)
|
22
|
-
json = JSON.parse(
|
22
|
+
json = JSON.parse(lockfile.read)
|
23
23
|
each_dependency(json) do |name, version|
|
24
24
|
yield ::Spandx::Core::Dependency.new(
|
25
|
-
|
25
|
+
path: lockfile,
|
26
26
|
name: name,
|
27
27
|
version: version,
|
28
28
|
meta: json
|
data/lib/spandx/python/pypi.rb
CHANGED
data/lib/spandx/python/source.rb
CHANGED
@@ -28,11 +28,23 @@ module Spandx
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def ==(other)
|
32
|
+
name == other.name &&
|
33
|
+
uri.to_s == other.uri.to_s &&
|
34
|
+
verify_ssl == other.verify_ssl
|
35
|
+
end
|
36
|
+
|
37
|
+
def eql(other)
|
38
|
+
self == other
|
39
|
+
end
|
40
|
+
|
31
41
|
class << self
|
32
42
|
def sources_from(json)
|
33
43
|
meta = json['_meta']
|
34
44
|
meta['sources'].map do |hash|
|
35
45
|
new(hash)
|
46
|
+
rescue URI::InvalidURIError
|
47
|
+
default
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
@@ -6,31 +6,32 @@ module Spandx
|
|
6
6
|
class GemfileLock < ::Spandx::Core::Parser
|
7
7
|
STRIP_BUNDLED_WITH = /^BUNDLED WITH$(\r?\n) (?<major>\d+)\.\d+\.\d+/m.freeze
|
8
8
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
9
|
+
def match?(pathname)
|
10
|
+
basename = pathname.basename
|
11
|
+
basename.fnmatch?('Gemfile*.lock') ||
|
12
|
+
basename.fnmatch?('gems*.lock')
|
12
13
|
end
|
13
14
|
|
14
15
|
def parse(lockfile)
|
15
16
|
dependencies_from(lockfile).map do |specification|
|
16
|
-
map_from(specification)
|
17
|
+
map_from(lockfile, specification)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
private
|
21
22
|
|
22
23
|
def dependencies_from(filepath)
|
23
|
-
content =
|
24
|
-
Dir.chdir(
|
24
|
+
content = filepath.read.sub(STRIP_BUNDLED_WITH, '')
|
25
|
+
Dir.chdir(filepath.dirname) do
|
25
26
|
::Bundler::LockfileParser
|
26
|
-
.new(content
|
27
|
+
.new(content)
|
27
28
|
.specs
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
|
-
def map_from(specification)
|
32
|
+
def map_from(lockfile, specification)
|
32
33
|
::Spandx::Core::Dependency.new(
|
33
|
-
|
34
|
+
path: lockfile,
|
34
35
|
name: specification.name,
|
35
36
|
version: specification.version.to_s,
|
36
37
|
meta: {
|
@@ -33,13 +33,17 @@ module Spandx
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def from_file(path)
|
36
|
-
from_json(
|
36
|
+
from_json(Pathname.new(path).read)
|
37
37
|
end
|
38
38
|
|
39
39
|
def from_git
|
40
40
|
from_json(Spandx.git[:spdx].read('json/licenses.json'))
|
41
41
|
end
|
42
42
|
|
43
|
+
def default
|
44
|
+
from_git
|
45
|
+
end
|
46
|
+
|
43
47
|
def empty
|
44
48
|
@empty ||= new(licenses: [])
|
45
49
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Spdx
|
5
|
+
class CompositeLicense < License
|
6
|
+
def self.from_expression(expression, catalogue)
|
7
|
+
tree = Spdx::Expression.new.parse(expression)
|
8
|
+
new(tree[0], catalogue)
|
9
|
+
rescue Parslet::ParseFailed
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(tree, catalogue)
|
14
|
+
@catalogue = catalogue
|
15
|
+
@tree = tree
|
16
|
+
super({})
|
17
|
+
end
|
18
|
+
|
19
|
+
def id
|
20
|
+
if right
|
21
|
+
[left.id, operator, right.id].compact.join(' ').squeeze(' ').strip
|
22
|
+
else
|
23
|
+
left.id.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
if right
|
29
|
+
[left.name, operator, right.name].compact.join(' ').squeeze(' ').strip
|
30
|
+
else
|
31
|
+
left.name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def left
|
38
|
+
node_for(@tree[:left])
|
39
|
+
end
|
40
|
+
|
41
|
+
def operator
|
42
|
+
@tree[:op].to_s.upcase
|
43
|
+
end
|
44
|
+
|
45
|
+
def right
|
46
|
+
node_for(@tree[:right])
|
47
|
+
end
|
48
|
+
|
49
|
+
def node_for(item)
|
50
|
+
return if item.nil?
|
51
|
+
|
52
|
+
if item.is_a?(Hash)
|
53
|
+
self.class.new(item, @catalogue)
|
54
|
+
else
|
55
|
+
@catalogue[item.to_s] || License.unknown(item.to_s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Spdx
|
5
|
+
class Expression < Parslet::Parser
|
6
|
+
# https://spdx.org/spdx-specification-21-web-version
|
7
|
+
#
|
8
|
+
# idstring = 1*(ALPHA / DIGIT / "-" / "." )
|
9
|
+
# license-id = <short form license identifier in Appendix I.1>
|
10
|
+
# license-exception-id = <short form license exception identifier in Appendix I.2>
|
11
|
+
# license-ref = ["DocumentRef-"1*(idstring)":"]"LicenseRef-"1*(idstring)
|
12
|
+
# simple-expression = license-id / license-id"+" / license-ref
|
13
|
+
# compound-expression = 1*1(simple-expression /
|
14
|
+
# simple-expression "WITH" license-exception-id /
|
15
|
+
# compound-expression "AND" compound-expression /
|
16
|
+
# compound-expression "OR" compound-expression ) /
|
17
|
+
# "(" compound-expression ")" )
|
18
|
+
#
|
19
|
+
# license-expression = 1*1(simple-expression / compound-expression)
|
20
|
+
rule(:lparen) { str('(') }
|
21
|
+
rule(:rparen) { str(')') }
|
22
|
+
rule(:digit) { match('\d') }
|
23
|
+
rule(:space) { match('\s') }
|
24
|
+
rule(:space?) { space.maybe }
|
25
|
+
rule(:alpha) { match['a-zA-Z'] }
|
26
|
+
rule(:colon) { str(':') }
|
27
|
+
rule(:dot) { str('.') }
|
28
|
+
rule(:plus) { str('+') }
|
29
|
+
rule(:plus?) { plus.maybe }
|
30
|
+
rule(:hyphen) { str('-') }
|
31
|
+
rule(:hyphen?) { hyphen.maybe }
|
32
|
+
rule(:with_op) { str('with') | str('WITH') }
|
33
|
+
rule(:and_op) { str('AND') | str('and') }
|
34
|
+
rule(:or_op) { str('OR') | str('or') }
|
35
|
+
|
36
|
+
# idstring = 1*(ALPHA / DIGIT / "-" / "." )
|
37
|
+
rule(:id_character) { alpha | digit | hyphen | dot }
|
38
|
+
rule(:id_string) { id_character.repeat(1) }
|
39
|
+
|
40
|
+
# license-id = <short form license identifier in Appendix I.1>
|
41
|
+
rule(:license_id) do
|
42
|
+
id_string
|
43
|
+
end
|
44
|
+
|
45
|
+
# license-ref = ["DocumentRef-"1*(idstring)":"]"LicenseRef-"1*(idstring)
|
46
|
+
rule(:license_ref) do
|
47
|
+
(str('DocumentRef-') >> id_string >> colon).repeat(0, 1) >> str('LicenseRef-') >> id_string
|
48
|
+
end
|
49
|
+
|
50
|
+
# simple-expression = license-id / license-id"+" / license-ref
|
51
|
+
rule(:simple_expression) do
|
52
|
+
license_id >> plus? | license_ref
|
53
|
+
end
|
54
|
+
|
55
|
+
rule(:exception) do
|
56
|
+
match['eE'] >> str('xception')
|
57
|
+
end
|
58
|
+
|
59
|
+
rule(:version) do
|
60
|
+
digit >> dot >> digit
|
61
|
+
end
|
62
|
+
|
63
|
+
# license-exception-id = <short form license exception identifier in Appendix I.2>
|
64
|
+
rule(:license_exception_id) do
|
65
|
+
# alpha.repeat(1) >> hyphen >> exception >> (hyphen? >> version)
|
66
|
+
id_string
|
67
|
+
end
|
68
|
+
|
69
|
+
# simple-expression "WITH" license-exception-id
|
70
|
+
rule(:with_expression) do
|
71
|
+
simple_expression.as(:left) >> space >> with_op.as(:op) >> space >> license_exception_id.as(:right)
|
72
|
+
end
|
73
|
+
|
74
|
+
rule(:binary_operator) do
|
75
|
+
(or_op | and_op).as(:op)
|
76
|
+
end
|
77
|
+
|
78
|
+
rule(:binary_right) do
|
79
|
+
space >> binary_operator >> space >> (binary_expression | simple_expression).as(:right)
|
80
|
+
end
|
81
|
+
|
82
|
+
# compound-expression "AND" compound-expression
|
83
|
+
# compound-expression "OR" compound-expression
|
84
|
+
rule(:binary_expression) do
|
85
|
+
simple_expression.as(:left) >> binary_right
|
86
|
+
end
|
87
|
+
|
88
|
+
# (BSD-2-Clause OR MIT OR Apache-2.0)
|
89
|
+
#
|
90
|
+
#
|
91
|
+
# compound-expression = 1*1(
|
92
|
+
# simple-expression /
|
93
|
+
# simple-expression "WITH" license-exception-id /
|
94
|
+
# compound-expression "AND" compound-expression /
|
95
|
+
# compound-expression "OR" compound-expression
|
96
|
+
# ) / "(" compound-expression ")")
|
97
|
+
rule(:compound_expression) do
|
98
|
+
lparen >> compound_expression >> space? >> rparen |
|
99
|
+
(
|
100
|
+
binary_expression |
|
101
|
+
with_expression |
|
102
|
+
simple_expression.as(:left)
|
103
|
+
).repeat(1, 1)
|
104
|
+
end
|
105
|
+
|
106
|
+
# license-expression = 1*1(simple-expression / compound-expression)
|
107
|
+
rule(:license_expression) do
|
108
|
+
(compound_expression | simple_expression).repeat(1, 1)
|
109
|
+
end
|
110
|
+
|
111
|
+
root(:license_expression)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|