dependabot-nuget 0.80.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +49 -0
- data/lib/dependabot/nuget/file_fetcher/sln_project_paths_finder.rb +53 -0
- data/lib/dependabot/nuget/file_fetcher.rb +216 -0
- data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +63 -0
- data/lib/dependabot/nuget/file_parser/project_file_parser.rb +154 -0
- data/lib/dependabot/nuget/file_parser/property_value_finder.rb +129 -0
- data/lib/dependabot/nuget/file_parser.rb +86 -0
- data/lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb +67 -0
- data/lib/dependabot/nuget/file_updater/project_file_declaration_finder.rb +76 -0
- data/lib/dependabot/nuget/file_updater/property_value_updater.rb +62 -0
- data/lib/dependabot/nuget/file_updater.rb +152 -0
- data/lib/dependabot/nuget/metadata_finder.rb +117 -0
- data/lib/dependabot/nuget/requirement.rb +90 -0
- data/lib/dependabot/nuget/update_checker/property_updater.rb +95 -0
- data/lib/dependabot/nuget/update_checker/repository_finder.rb +230 -0
- data/lib/dependabot/nuget/update_checker/requirements_updater.rb +79 -0
- data/lib/dependabot/nuget/update_checker/version_finder.rb +229 -0
- data/lib/dependabot/nuget/update_checker.rb +128 -0
- data/lib/dependabot/nuget/version.rb +23 -0
- data/lib/dependabot/nuget.rb +10 -0
- metadata +190 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "dependabot/dependency"
|
6
|
+
require "dependabot/file_parsers"
|
7
|
+
require "dependabot/file_parsers/base"
|
8
|
+
|
9
|
+
# For details on how dotnet handles version constraints, see:
|
10
|
+
# https://docs.microsoft.com/en-us/nuget/reference/package-versioning
|
11
|
+
module Dependabot
|
12
|
+
module Nuget
|
13
|
+
class FileParser < Dependabot::FileParsers::Base
|
14
|
+
require "dependabot/file_parsers/base/dependency_set"
|
15
|
+
require_relative "file_parser/project_file_parser"
|
16
|
+
require_relative "file_parser/packages_config_parser"
|
17
|
+
|
18
|
+
PACKAGE_CONF_DEPENDENCY_SELECTOR = "packages > packages"
|
19
|
+
|
20
|
+
def parse
|
21
|
+
dependency_set = DependencySet.new
|
22
|
+
dependency_set += project_file_dependencies
|
23
|
+
dependency_set += packages_config_dependencies
|
24
|
+
dependency_set.dependencies
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def project_file_dependencies
|
30
|
+
dependency_set = DependencySet.new
|
31
|
+
|
32
|
+
(project_files + project_import_files).each do |file|
|
33
|
+
parser = project_file_parser
|
34
|
+
dependency_set += parser.dependency_set(project_file: file)
|
35
|
+
end
|
36
|
+
|
37
|
+
dependency_set
|
38
|
+
end
|
39
|
+
|
40
|
+
def packages_config_dependencies
|
41
|
+
dependency_set = DependencySet.new
|
42
|
+
|
43
|
+
packages_config_files.each do |file|
|
44
|
+
parser = PackagesConfigParser.new(packages_config: file)
|
45
|
+
dependency_set += parser.dependency_set
|
46
|
+
end
|
47
|
+
|
48
|
+
dependency_set
|
49
|
+
end
|
50
|
+
|
51
|
+
def project_file_parser
|
52
|
+
@project_file_parser ||=
|
53
|
+
ProjectFileParser.new(dependency_files: dependency_files)
|
54
|
+
end
|
55
|
+
|
56
|
+
def project_files
|
57
|
+
dependency_files.select { |df| df.name.match?(/\.[a-z]{2}proj$/) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def packages_config_files
|
61
|
+
dependency_files.select do |f|
|
62
|
+
f.name.split("/").last.casecmp("packages.config").zero?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def project_import_files
|
67
|
+
dependency_files -
|
68
|
+
project_files -
|
69
|
+
packages_config_files -
|
70
|
+
[nuget_config]
|
71
|
+
end
|
72
|
+
|
73
|
+
def nuget_config
|
74
|
+
dependency_files.find { |f| f.name.casecmp("nuget.config").zero? }
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_required_files
|
78
|
+
return if project_files.any? || packages_config_files.any?
|
79
|
+
|
80
|
+
raise "No project file or packages.config!"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Dependabot::FileParsers.register("nuget", Dependabot::Nuget::FileParser)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "dependabot/nuget/file_updater"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Nuget
|
8
|
+
class FileUpdater
|
9
|
+
class PackagesConfigDeclarationFinder
|
10
|
+
DECLARATION_REGEX =
|
11
|
+
%r{<package [^>]*?/>|
|
12
|
+
<package [^>]*?[^/]>.*?</package>}mx.freeze
|
13
|
+
|
14
|
+
attr_reader :dependency_name, :declaring_requirement,
|
15
|
+
:packages_config
|
16
|
+
|
17
|
+
def initialize(dependency_name:, packages_config:,
|
18
|
+
declaring_requirement:)
|
19
|
+
@dependency_name = dependency_name
|
20
|
+
@packages_config = packages_config
|
21
|
+
@declaring_requirement = declaring_requirement
|
22
|
+
|
23
|
+
if declaring_requirement[:file].split("/").last.
|
24
|
+
casecmp("packages.config").zero?
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
raise "Requirement not from packages.config!"
|
29
|
+
end
|
30
|
+
|
31
|
+
def declaration_strings
|
32
|
+
@declaration_strings ||= fetch_declaration_strings
|
33
|
+
end
|
34
|
+
|
35
|
+
def declaration_nodes
|
36
|
+
declaration_strings.map do |declaration_string|
|
37
|
+
Nokogiri::XML(declaration_string)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def fetch_declaration_strings
|
44
|
+
deep_find_declarations(packages_config.content).select do |nd|
|
45
|
+
node = Nokogiri::XML(nd)
|
46
|
+
node.remove_namespaces!
|
47
|
+
node = node.at_xpath("/package")
|
48
|
+
|
49
|
+
node_name = node.attribute("id")&.value&.strip ||
|
50
|
+
node.at_xpath("./id")&.content&.strip
|
51
|
+
next false unless node_name == dependency_name
|
52
|
+
|
53
|
+
node_requirement = node.attribute("version")&.value&.strip ||
|
54
|
+
node.at_xpath("./version")&.content&.strip
|
55
|
+
node_requirement == declaring_requirement.fetch(:requirement)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def deep_find_declarations(string)
|
60
|
+
string.scan(DECLARATION_REGEX).flat_map do |matching_node|
|
61
|
+
[matching_node, *deep_find_declarations(matching_node[0..-2])]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "dependabot/nuget/file_updater"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Nuget
|
8
|
+
class FileUpdater
|
9
|
+
class ProjectFileDeclarationFinder
|
10
|
+
DECLARATION_REGEX =
|
11
|
+
%r{
|
12
|
+
<PackageReference [^>]*?/>|
|
13
|
+
<PackageReference [^>]*?[^/]>.*?</PackageReference>|
|
14
|
+
<Dependency [^>]*?/>|
|
15
|
+
<Dependency [^>]*?[^/]>.*?</Dependency>|
|
16
|
+
<DevelopmentDependency [^>]*?/>|
|
17
|
+
<DevelopmentDependency [^>]*?[^/]>.*?</DevelopmentDependency>
|
18
|
+
}mx.freeze
|
19
|
+
|
20
|
+
attr_reader :dependency_name, :declaring_requirement,
|
21
|
+
:dependency_files
|
22
|
+
|
23
|
+
def initialize(dependency_name:, dependency_files:,
|
24
|
+
declaring_requirement:)
|
25
|
+
@dependency_name = dependency_name
|
26
|
+
@dependency_files = dependency_files
|
27
|
+
@declaring_requirement = declaring_requirement
|
28
|
+
end
|
29
|
+
|
30
|
+
def declaration_strings
|
31
|
+
@declaration_strings ||= fetch_declaration_strings
|
32
|
+
end
|
33
|
+
|
34
|
+
def declaration_nodes
|
35
|
+
declaration_strings.map do |declaration_string|
|
36
|
+
Nokogiri::XML(declaration_string)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def fetch_declaration_strings
|
43
|
+
deep_find_declarations(declaring_file.content).select do |nd|
|
44
|
+
node = Nokogiri::XML(nd)
|
45
|
+
node.remove_namespaces!
|
46
|
+
node = node.at_xpath("/PackageReference") ||
|
47
|
+
node.at_xpath("/Dependency") ||
|
48
|
+
node.at_xpath("/DevelopmentDependency")
|
49
|
+
|
50
|
+
node_name = node.attribute("Include")&.value&.strip ||
|
51
|
+
node.at_xpath("./Include")&.content&.strip
|
52
|
+
next false unless node_name == dependency_name
|
53
|
+
|
54
|
+
node_requirement = node.attribute("Version")&.value&.strip ||
|
55
|
+
node.at_xpath("./Version")&.content&.strip
|
56
|
+
node_requirement == declaring_requirement.fetch(:requirement)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def deep_find_declarations(string)
|
61
|
+
string.scan(DECLARATION_REGEX).flat_map do |matching_node|
|
62
|
+
[matching_node, *deep_find_declarations(matching_node[0..-2])]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def declaring_file
|
67
|
+
filename = declaring_requirement.fetch(:file)
|
68
|
+
declaring_file = dependency_files.find { |f| f.name == filename }
|
69
|
+
return declaring_file if declaring_file
|
70
|
+
|
71
|
+
raise "No file found with name #{filename}!"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "dependabot/dependency_file"
|
6
|
+
require "dependabot/nuget/file_updater"
|
7
|
+
require "dependabot/nuget/file_parser/property_value_finder"
|
8
|
+
|
9
|
+
module Dependabot
|
10
|
+
module Nuget
|
11
|
+
class FileUpdater
|
12
|
+
class PropertyValueUpdater
|
13
|
+
def initialize(dependency_files:)
|
14
|
+
@dependency_files = dependency_files
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_files_for_property_change(property_name:, updated_value:,
|
18
|
+
callsite_file:)
|
19
|
+
declaration_details =
|
20
|
+
property_value_finder.
|
21
|
+
property_details(
|
22
|
+
property_name: property_name,
|
23
|
+
callsite_file: callsite_file
|
24
|
+
)
|
25
|
+
|
26
|
+
declaration_file = dependency_files.find do |f|
|
27
|
+
declaration_details.fetch(:file) == f.name
|
28
|
+
end
|
29
|
+
node = declaration_details.fetch(:node)
|
30
|
+
|
31
|
+
updated_content = declaration_file.content.sub(
|
32
|
+
%r{<#{Regexp.quote(node.name)}>
|
33
|
+
\s*#{Regexp.quote(node.content)}\s*
|
34
|
+
</#{Regexp.quote(node.name)}>}xm,
|
35
|
+
"<#{node.name}>#{updated_value}</#{node.name}>"
|
36
|
+
)
|
37
|
+
|
38
|
+
files = dependency_files.dup
|
39
|
+
files[files.index(declaration_file)] =
|
40
|
+
update_file(file: declaration_file, content: updated_content)
|
41
|
+
files
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :dependency_files
|
47
|
+
|
48
|
+
def property_value_finder
|
49
|
+
@property_value_finder ||=
|
50
|
+
Nuget::FileParser::PropertyValueFinder.
|
51
|
+
new(dependency_files: dependency_files)
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_file(file:, content:)
|
55
|
+
updated_file = file.dup
|
56
|
+
updated_file.content = content
|
57
|
+
updated_file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/file_updaters"
|
4
|
+
require "dependabot/file_updaters/base"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Nuget
|
8
|
+
class FileUpdater < Dependabot::FileUpdaters::Base
|
9
|
+
require_relative "file_updater/packages_config_declaration_finder"
|
10
|
+
require_relative "file_updater/project_file_declaration_finder"
|
11
|
+
require_relative "file_updater/property_value_updater"
|
12
|
+
|
13
|
+
def self.updated_files_regex
|
14
|
+
[
|
15
|
+
%r{^[^/]*\.[a-z]{2}proj$},
|
16
|
+
/^packages\.config$/i
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
def updated_dependency_files
|
21
|
+
updated_files = dependency_files.dup
|
22
|
+
|
23
|
+
# Loop through each of the changed requirements, applying changes to
|
24
|
+
# all files for that change. Note that the logic is different here
|
25
|
+
# to other languages because donet has property inheritance across
|
26
|
+
# files
|
27
|
+
dependencies.each do |dependency|
|
28
|
+
updated_files = update_files_for_dependency(
|
29
|
+
files: updated_files,
|
30
|
+
dependency: dependency
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
updated_files.reject! { |f| dependency_files.include?(f) }
|
35
|
+
|
36
|
+
raise "No files changed!" if updated_files.none?
|
37
|
+
|
38
|
+
updated_files
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def project_files
|
44
|
+
dependency_files.select { |df| df.name.match?(/\.[a-z]{2}proj$/) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def packages_config_files
|
48
|
+
dependency_files.select do |f|
|
49
|
+
f.name.split("/").last.casecmp("packages.config").zero?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_required_files
|
54
|
+
return if project_files.any? || packages_config_files.any?
|
55
|
+
|
56
|
+
raise "No project file or packages.config!"
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_files_for_dependency(files:, dependency:)
|
60
|
+
# The UpdateChecker ensures the order of requirements is preserved
|
61
|
+
# when updating, so we can zip them together in new/old pairs.
|
62
|
+
reqs = dependency.requirements.zip(dependency.previous_requirements).
|
63
|
+
reject { |new_req, old_req| new_req == old_req }
|
64
|
+
|
65
|
+
# Loop through each changed requirement and update the files
|
66
|
+
reqs.each do |new_req, old_req|
|
67
|
+
raise "Bad req match" unless new_req[:file] == old_req[:file]
|
68
|
+
next if new_req[:requirement] == old_req[:requirement]
|
69
|
+
|
70
|
+
file = files.find { |f| f.name == new_req.fetch(:file) }
|
71
|
+
|
72
|
+
files =
|
73
|
+
if new_req.dig(:metadata, :property_name)
|
74
|
+
update_property_value(files, file, new_req)
|
75
|
+
else
|
76
|
+
update_declaration(files, dependency, file, old_req, new_req)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
files
|
81
|
+
end
|
82
|
+
|
83
|
+
def update_property_value(files, file, req)
|
84
|
+
files = files.dup
|
85
|
+
property_name = req.fetch(:metadata).fetch(:property_name)
|
86
|
+
|
87
|
+
PropertyValueUpdater.
|
88
|
+
new(dependency_files: files).
|
89
|
+
update_files_for_property_change(
|
90
|
+
property_name: property_name,
|
91
|
+
updated_value: req.fetch(:requirement),
|
92
|
+
callsite_file: file
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_declaration(files, dependency, file, old_req, new_req)
|
97
|
+
files = files.dup
|
98
|
+
|
99
|
+
updated_content = file.content
|
100
|
+
|
101
|
+
original_declarations(dependency, old_req).each do |old_dec|
|
102
|
+
updated_content = updated_content.gsub(
|
103
|
+
old_dec,
|
104
|
+
updated_declaration(old_dec, old_req, new_req)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
raise "Expected content to change!" if updated_content == file.content
|
109
|
+
|
110
|
+
files[files.index(file)] =
|
111
|
+
updated_file(file: file, content: updated_content)
|
112
|
+
files
|
113
|
+
end
|
114
|
+
|
115
|
+
def original_declarations(dependency, requirement)
|
116
|
+
declaration_finder(dependency, requirement).declaration_strings
|
117
|
+
end
|
118
|
+
|
119
|
+
def declaration_finder(dependency, requirement)
|
120
|
+
@declaration_finders ||= {}
|
121
|
+
|
122
|
+
requirement_fn = requirement.fetch(:file)
|
123
|
+
@declaration_finders[dependency.hash + requirement.hash] ||=
|
124
|
+
if requirement_fn.split("/").last.casecmp("packages.config").zero?
|
125
|
+
PackagesConfigDeclarationFinder.new(
|
126
|
+
dependency_name: dependency.name,
|
127
|
+
declaring_requirement: requirement,
|
128
|
+
packages_config:
|
129
|
+
packages_config_files.find { |f| f.name == requirement_fn }
|
130
|
+
)
|
131
|
+
else
|
132
|
+
ProjectFileDeclarationFinder.new(
|
133
|
+
dependency_name: dependency.name,
|
134
|
+
declaring_requirement: requirement,
|
135
|
+
dependency_files: dependency_files
|
136
|
+
)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def updated_declaration(old_declaration, previous_req, requirement)
|
141
|
+
original_req_string = previous_req.fetch(:requirement)
|
142
|
+
|
143
|
+
old_declaration.gsub(
|
144
|
+
original_req_string,
|
145
|
+
requirement.fetch(:requirement)
|
146
|
+
)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Dependabot::FileUpdaters.register("nuget", Dependabot::Nuget::FileUpdater)
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "dependabot/metadata_finders"
|
5
|
+
require "dependabot/metadata_finders/base"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module Nuget
|
9
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
10
|
+
private
|
11
|
+
|
12
|
+
def look_up_source
|
13
|
+
return Source.from_url(dependency_source_url) if dependency_source_url
|
14
|
+
|
15
|
+
look_up_source_in_nuspec(dependency_nuspec_file)
|
16
|
+
end
|
17
|
+
|
18
|
+
def look_up_source_in_nuspec(nuspec)
|
19
|
+
potential_source_urls = [
|
20
|
+
nuspec.at_css("package > metadata > repository")&.
|
21
|
+
attribute("url")&.value,
|
22
|
+
nuspec.at_css("package > metadata > repository > url")&.content,
|
23
|
+
nuspec.at_css("package > metadata > projectUrl")&.content,
|
24
|
+
nuspec.at_css("package > metadata > licenseUrl")&.content
|
25
|
+
].compact
|
26
|
+
|
27
|
+
source_url = potential_source_urls.find { |url| Source.from_url(url) }
|
28
|
+
source_url ||= source_from_anywhere_in_nuspec(nuspec)
|
29
|
+
|
30
|
+
Source.from_url(source_url)
|
31
|
+
end
|
32
|
+
|
33
|
+
def source_from_anywhere_in_nuspec(nuspec)
|
34
|
+
github_urls = []
|
35
|
+
nuspec.to_s.scan(Source::SOURCE_REGEX) do
|
36
|
+
github_urls << Regexp.last_match.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
github_urls.find do |url|
|
40
|
+
repo = Source.from_url(url).repo
|
41
|
+
repo.downcase.end_with?(dependency.name.downcase)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def dependency_nuspec_file
|
46
|
+
return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
|
47
|
+
|
48
|
+
response = Excon.get(
|
49
|
+
dependency_nuspec_url,
|
50
|
+
headers: auth_header,
|
51
|
+
idempotent: true,
|
52
|
+
**SharedHelpers.excon_defaults
|
53
|
+
)
|
54
|
+
|
55
|
+
@dependency_nuspec_file = Nokogiri::XML(response.body)
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable Metrics/AbcSize
|
59
|
+
def dependency_nuspec_url
|
60
|
+
source = dependency.requirements.
|
61
|
+
find { |r| r&.fetch(:source) }&.fetch(:source)
|
62
|
+
|
63
|
+
if source&.key?(:nuspec_url)
|
64
|
+
source.fetch(:nuspec_url) ||
|
65
|
+
"https://api.nuget.org/v3-flatcontainer/"\
|
66
|
+
"#{dependency.name.downcase}/#{dependency.version}/"\
|
67
|
+
"#{dependency.name.downcase}.nuspec"
|
68
|
+
elsif source&.key?(:nuspec_url)
|
69
|
+
source.fetch("nuspec_url") ||
|
70
|
+
"https://api.nuget.org/v3-flatcontainer/"\
|
71
|
+
"#{dependency.name.downcase}/#{dependency.version}/"\
|
72
|
+
"#{dependency.name.downcase}.nuspec"
|
73
|
+
else
|
74
|
+
"https://api.nuget.org/v3-flatcontainer/"\
|
75
|
+
"#{dependency.name.downcase}/#{dependency.version}/"\
|
76
|
+
"#{dependency.name.downcase}.nuspec"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
# rubocop:enable Metrics/AbcSize
|
80
|
+
|
81
|
+
def dependency_source_url
|
82
|
+
source = dependency.requirements.
|
83
|
+
find { |r| r&.fetch(:source) }&.fetch(:source)
|
84
|
+
|
85
|
+
return unless source
|
86
|
+
return source.fetch(:source_url) if source.key?(:source_url)
|
87
|
+
|
88
|
+
source.fetch("source_url")
|
89
|
+
end
|
90
|
+
|
91
|
+
def auth_header
|
92
|
+
source = dependency.requirements.
|
93
|
+
find { |r| r&.fetch(:source) }&.fetch(:source)
|
94
|
+
url = source&.fetch(:url, nil) || source&.fetch("url")
|
95
|
+
|
96
|
+
token = credentials.
|
97
|
+
select { |cred| cred["type"] == "nuget_feed" }.
|
98
|
+
find { |cred| cred["url"] == url }&.
|
99
|
+
fetch("token", nil)
|
100
|
+
|
101
|
+
return {} unless token
|
102
|
+
|
103
|
+
if token.include?(":")
|
104
|
+
encoded_token = Base64.encode64(token).delete("\n")
|
105
|
+
{ "Authorization" => "Basic #{encoded_token}" }
|
106
|
+
elsif Base64.decode64(token).ascii_only? &&
|
107
|
+
Base64.decode64(token).include?(":")
|
108
|
+
{ "Authorization" => "Basic #{token.delete("\n")}" }
|
109
|
+
else
|
110
|
+
{ "Authorization" => "Bearer #{token}" }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
Dependabot::MetadataFinders.register("nuget", Dependabot::Nuget::MetadataFinder)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/utils"
|
4
|
+
require "dependabot/nuget/version"
|
5
|
+
|
6
|
+
# For details on .NET version constraints see:
|
7
|
+
# https://docs.microsoft.com/en-us/nuget/reference/package-versioning
|
8
|
+
module Dependabot
|
9
|
+
module Nuget
|
10
|
+
class Requirement < Gem::Requirement
|
11
|
+
def self.parse(obj)
|
12
|
+
return ["=", Nuget::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
13
|
+
|
14
|
+
unless (matches = PATTERN.match(obj.to_s))
|
15
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
16
|
+
raise BadRequirementError, msg
|
17
|
+
end
|
18
|
+
|
19
|
+
return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
|
20
|
+
|
21
|
+
[matches[1] || "=", Nuget::Version.new(matches[2])]
|
22
|
+
end
|
23
|
+
|
24
|
+
# For consistency with other langauges, we define a requirements array.
|
25
|
+
# Dotnet doesn't have an `OR` separator for requirements, so it always
|
26
|
+
# contains a single element.
|
27
|
+
def self.requirements_array(requirement_string)
|
28
|
+
[new(requirement_string)]
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(*requirements)
|
32
|
+
requirements = requirements.flatten.flat_map do |req_string|
|
33
|
+
convert_dotnet_constraint_to_ruby_constraint(req_string)
|
34
|
+
end
|
35
|
+
|
36
|
+
super(requirements)
|
37
|
+
end
|
38
|
+
|
39
|
+
def satisfied_by?(version)
|
40
|
+
version = Nuget::Version.new(version.to_s)
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def convert_dotnet_constraint_to_ruby_constraint(req_string)
|
47
|
+
return unless req_string
|
48
|
+
|
49
|
+
if req_string&.start_with?("(", "[")
|
50
|
+
return convert_dotnet_range_to_ruby_range(req_string)
|
51
|
+
end
|
52
|
+
|
53
|
+
return req_string.split(",").map(&:strip) if req_string.include?(",")
|
54
|
+
return req_string unless req_string.include?("*")
|
55
|
+
|
56
|
+
convert_wildcard_req(req_string)
|
57
|
+
end
|
58
|
+
|
59
|
+
def convert_dotnet_range_to_ruby_range(req_string)
|
60
|
+
lower_b, upper_b = req_string.split(",").map(&:strip)
|
61
|
+
|
62
|
+
lower_b =
|
63
|
+
if ["(", "["].include?(lower_b) then nil
|
64
|
+
elsif lower_b.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}"
|
65
|
+
else ">= #{lower_b.sub(/\[\s*/, '').strip}"
|
66
|
+
end
|
67
|
+
|
68
|
+
upper_b =
|
69
|
+
if [")", "]"].include?(upper_b) then nil
|
70
|
+
elsif upper_b.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
|
71
|
+
else "<= #{upper_b.sub(/\s*\]/, '').strip}"
|
72
|
+
end
|
73
|
+
|
74
|
+
[lower_b, upper_b].compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def convert_wildcard_req(req_string)
|
78
|
+
return ">= 0" if req_string.start_with?("*")
|
79
|
+
|
80
|
+
defined_part = req_string.split("*").first
|
81
|
+
suffix = defined_part.end_with?(".") ? "0" : "a"
|
82
|
+
version = defined_part + suffix
|
83
|
+
"~> #{version}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Dependabot::Utils.
|
90
|
+
register_requirement_class("nuget", Dependabot::Nuget::Requirement)
|