dependabot-nuget 0.289.0 → 0.290.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +7 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +26 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +0 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +3 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +24 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +0 -13
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +17 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Advisory.cs +13 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/AllowedUpdate.cs +18 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CommitOptions.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Condition.cs +19 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyGroup.cs +8 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/GroupPullRequest.cs +9 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +13 -10
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PullRequest.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/RequirementsUpdateStrategy.cs +15 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +24 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/VersionConverter.cs +19 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +13 -12
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +5 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +45 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +35 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +0 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +85 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +7 -31
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +340 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +18 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +24 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +0 -12
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +84 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +66 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +55 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +0 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +557 -713
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +2 -2
- data/lib/dependabot/nuget/analysis/analysis_json_reader.rb +1 -1
- data/lib/dependabot/nuget/analysis/dependency_analysis.rb +3 -3
- data/lib/dependabot/nuget/discovery/dependency_details.rb +10 -3
- data/lib/dependabot/nuget/discovery/dependency_file_discovery.rb +8 -12
- data/lib/dependabot/nuget/discovery/discovery_json_reader.rb +214 -29
- data/lib/dependabot/nuget/discovery/project_discovery.rb +41 -8
- data/lib/dependabot/nuget/discovery/workspace_discovery.rb +14 -19
- data/lib/dependabot/nuget/file_fetcher.rb +2 -3
- data/lib/dependabot/nuget/file_parser.rb +2 -3
- data/lib/dependabot/nuget/file_updater.rb +13 -13
- data/lib/dependabot/nuget/native_helpers.rb +14 -5
- data/lib/dependabot/nuget/update_checker/requirements_updater.rb +23 -27
- data/lib/dependabot/nuget/update_checker.rb +116 -190
- metadata +18 -29
- data/lib/dependabot/nuget/discovery/directory_packages_props_discovery.rb +0 -43
- data/lib/dependabot/nuget/http_response_helpers.rb +0 -19
- data/lib/dependabot/nuget/native_discovery/native_dependency_details.rb +0 -102
- data/lib/dependabot/nuget/native_discovery/native_dependency_file_discovery.rb +0 -122
- data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +0 -277
- data/lib/dependabot/nuget/native_discovery/native_evaluation_details.rb +0 -63
- data/lib/dependabot/nuget/native_discovery/native_project_discovery.rb +0 -104
- data/lib/dependabot/nuget/native_discovery/native_property_details.rb +0 -43
- data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +0 -61
- data/lib/dependabot/nuget/native_update_checker/native_requirements_updater.rb +0 -105
- data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +0 -214
- data/lib/dependabot/nuget/nuget_client.rb +0 -223
- data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +0 -116
- data/lib/dependabot/nuget/update_checker/dependency_finder.rb +0 -297
- data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +0 -221
- data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +0 -110
- data/lib/dependabot/nuget/update_checker/property_updater.rb +0 -196
- data/lib/dependabot/nuget/update_checker/repository_finder.rb +0 -466
- data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +0 -34
- data/lib/dependabot/nuget/update_checker/tfm_finder.rb +0 -30
- data/lib/dependabot/nuget/update_checker/version_finder.rb +0 -449
@@ -1,297 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "nokogiri"
|
5
|
-
require "sorbet-runtime"
|
6
|
-
require "stringio"
|
7
|
-
require "zip"
|
8
|
-
|
9
|
-
require "dependabot/update_checkers/base"
|
10
|
-
require "dependabot/nuget/version"
|
11
|
-
|
12
|
-
module Dependabot
|
13
|
-
module Nuget
|
14
|
-
class UpdateChecker < Dependabot::UpdateCheckers::Base
|
15
|
-
class DependencyFinder
|
16
|
-
extend T::Sig
|
17
|
-
|
18
|
-
require_relative "requirements_updater"
|
19
|
-
require_relative "nuspec_fetcher"
|
20
|
-
|
21
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
22
|
-
def self.transitive_dependencies_cache
|
23
|
-
CacheManager.cache("dependency_finder_transitive_dependencies")
|
24
|
-
end
|
25
|
-
|
26
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
27
|
-
def self.updated_peer_dependencies_cache
|
28
|
-
CacheManager.cache("dependency_finder_updated_peer_dependencies")
|
29
|
-
end
|
30
|
-
|
31
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
32
|
-
def self.fetch_dependencies_cache
|
33
|
-
CacheManager.cache("dependency_finder_fetch_dependencies")
|
34
|
-
end
|
35
|
-
|
36
|
-
sig do
|
37
|
-
params(
|
38
|
-
dependency: Dependabot::Dependency,
|
39
|
-
dependency_files: T::Array[Dependabot::DependencyFile],
|
40
|
-
ignored_versions: T::Array[String],
|
41
|
-
credentials: T::Array[Dependabot::Credential],
|
42
|
-
repo_contents_path: T.nilable(String)
|
43
|
-
).void
|
44
|
-
end
|
45
|
-
def initialize(dependency:, dependency_files:, ignored_versions:, credentials:, repo_contents_path:)
|
46
|
-
@dependency = dependency
|
47
|
-
@dependency_files = dependency_files
|
48
|
-
@ignored_versions = ignored_versions
|
49
|
-
@credentials = credentials
|
50
|
-
@repo_contents_path = repo_contents_path
|
51
|
-
end
|
52
|
-
|
53
|
-
sig { returns(T::Array[Dependabot::Dependency]) }
|
54
|
-
def transitive_dependencies
|
55
|
-
key = "#{dependency.name.downcase}::#{dependency.version}"
|
56
|
-
cache = DependencyFinder.transitive_dependencies_cache
|
57
|
-
|
58
|
-
unless cache[key]
|
59
|
-
begin
|
60
|
-
# first do a quick sanity check on the version string; if it can't be parsed, an exception will be raised
|
61
|
-
_ = Version.new(dependency.version)
|
62
|
-
|
63
|
-
cache[key] = fetch_transitive_dependencies(
|
64
|
-
@dependency.name,
|
65
|
-
T.must(@dependency.version)
|
66
|
-
).map do |dependency_info|
|
67
|
-
package_name = dependency_info["packageName"]
|
68
|
-
target_version = dependency_info["version"]
|
69
|
-
|
70
|
-
Dependency.new(
|
71
|
-
name: package_name,
|
72
|
-
version: target_version.to_s,
|
73
|
-
requirements: [], # Empty requirements for transitive dependencies
|
74
|
-
package_manager: @dependency.package_manager
|
75
|
-
)
|
76
|
-
end
|
77
|
-
rescue StandardError
|
78
|
-
# if anything happened above, there are no meaningful dependencies that can be derived
|
79
|
-
cache[key] = []
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
cache[key]
|
84
|
-
end
|
85
|
-
|
86
|
-
sig { returns(T::Array[Dependabot::Dependency]) }
|
87
|
-
def updated_peer_dependencies
|
88
|
-
key = "#{dependency.name.downcase}::#{dependency.version}"
|
89
|
-
cache = DependencyFinder.updated_peer_dependencies_cache
|
90
|
-
|
91
|
-
cache[key] ||= fetch_transitive_dependencies(
|
92
|
-
@dependency.name,
|
93
|
-
T.must(@dependency.version)
|
94
|
-
).filter_map do |dependency_info|
|
95
|
-
package_name = dependency_info["packageName"]
|
96
|
-
target_version = dependency_info["version"]
|
97
|
-
|
98
|
-
# Find the Dependency object for the peer dependency. We will not return
|
99
|
-
# dependencies that are not referenced from dependency files.
|
100
|
-
peer_dependency = top_level_dependencies.find { |d| d.name == package_name }
|
101
|
-
next unless peer_dependency
|
102
|
-
next unless target_version > peer_dependency.numeric_version
|
103
|
-
|
104
|
-
# Use version finder to determine the source details for the peer dependency.
|
105
|
-
target_version_details = version_finder(peer_dependency).versions.find do |v|
|
106
|
-
v.fetch(:version) == target_version
|
107
|
-
end
|
108
|
-
next unless target_version_details
|
109
|
-
|
110
|
-
Dependency.new(
|
111
|
-
name: peer_dependency.name,
|
112
|
-
version: target_version_details.fetch(:version).to_s,
|
113
|
-
requirements: updated_requirements(peer_dependency, target_version_details),
|
114
|
-
previous_version: peer_dependency.version,
|
115
|
-
previous_requirements: peer_dependency.requirements,
|
116
|
-
package_manager: peer_dependency.package_manager,
|
117
|
-
metadata: { information_only: true } # Instruct updater to not directly update this dependency
|
118
|
-
)
|
119
|
-
end
|
120
|
-
|
121
|
-
cache[key]
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
|
126
|
-
sig { returns(Dependabot::Dependency) }
|
127
|
-
attr_reader :dependency
|
128
|
-
|
129
|
-
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
130
|
-
attr_reader :dependency_files
|
131
|
-
|
132
|
-
sig { returns(T::Array[String]) }
|
133
|
-
attr_reader :ignored_versions
|
134
|
-
|
135
|
-
sig { returns(T::Array[Dependabot::Credential]) }
|
136
|
-
attr_reader :credentials
|
137
|
-
|
138
|
-
sig { returns(T.nilable(String)) }
|
139
|
-
attr_reader :repo_contents_path
|
140
|
-
|
141
|
-
sig do
|
142
|
-
params(
|
143
|
-
dep: Dependabot::Dependency,
|
144
|
-
target_version_details: T::Hash[Symbol, T.untyped]
|
145
|
-
)
|
146
|
-
.returns(T::Array[T::Hash[String, T.untyped]])
|
147
|
-
end
|
148
|
-
def updated_requirements(dep, target_version_details)
|
149
|
-
@updated_requirements ||= T.let({}, T.nilable(T::Hash[String, T.untyped]))
|
150
|
-
@updated_requirements[dep.name] ||=
|
151
|
-
RequirementsUpdater.new(
|
152
|
-
requirements: dep.requirements,
|
153
|
-
latest_version: target_version_details.fetch(:version).to_s,
|
154
|
-
source_details: target_version_details.slice(:nuspec_url, :repo_url, :source_url)
|
155
|
-
).updated_requirements
|
156
|
-
end
|
157
|
-
|
158
|
-
sig { returns(T::Array[Dependabot::Dependency]) }
|
159
|
-
def top_level_dependencies
|
160
|
-
@top_level_dependencies ||=
|
161
|
-
T.let(
|
162
|
-
Nuget::FileParser.new(
|
163
|
-
dependency_files: dependency_files,
|
164
|
-
repo_contents_path: repo_contents_path,
|
165
|
-
source: nil
|
166
|
-
).parse.select(&:top_level?),
|
167
|
-
T.nilable(T::Array[Dependabot::Dependency])
|
168
|
-
)
|
169
|
-
end
|
170
|
-
|
171
|
-
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
172
|
-
def nuget_configs
|
173
|
-
@nuget_configs ||=
|
174
|
-
T.let(
|
175
|
-
@dependency_files.select { |f| f.name.match?(/nuget\.config$/i) },
|
176
|
-
T.nilable(T::Array[Dependabot::DependencyFile])
|
177
|
-
)
|
178
|
-
end
|
179
|
-
|
180
|
-
sig { returns(T::Array[T::Hash[Symbol, String]]) }
|
181
|
-
def dependency_urls
|
182
|
-
@dependency_urls ||=
|
183
|
-
T.let(
|
184
|
-
RepositoryFinder.new(
|
185
|
-
dependency: @dependency,
|
186
|
-
credentials: @credentials,
|
187
|
-
config_files: nuget_configs
|
188
|
-
)
|
189
|
-
.dependency_urls
|
190
|
-
.select { |url| url.fetch(:repository_type) == "v3" },
|
191
|
-
T.nilable(T::Array[T::Hash[Symbol, String]])
|
192
|
-
)
|
193
|
-
end
|
194
|
-
|
195
|
-
sig { params(package_id: String, package_version: String).returns(T::Array[T::Hash[String, T.untyped]]) }
|
196
|
-
def fetch_transitive_dependencies(package_id, package_version)
|
197
|
-
all_dependencies = {}
|
198
|
-
fetch_transitive_dependencies_impl(package_id, package_version, all_dependencies)
|
199
|
-
all_dependencies.map { |_, dependency_info| dependency_info }
|
200
|
-
end
|
201
|
-
|
202
|
-
sig { params(package_id: String, package_version: String, all_dependencies: T::Hash[String, T.untyped]).void }
|
203
|
-
def fetch_transitive_dependencies_impl(package_id, package_version, all_dependencies)
|
204
|
-
dependencies = fetch_dependencies(package_id, package_version)
|
205
|
-
return unless dependencies.any?
|
206
|
-
|
207
|
-
dependencies.each do |dependency|
|
208
|
-
next if dependency.nil?
|
209
|
-
|
210
|
-
dependency_id = dependency["packageName"]
|
211
|
-
dependency_version_range = dependency["versionRange"]
|
212
|
-
|
213
|
-
nuget_version_range_regex = /[\[(](\d+(\.\d+)*(-\w+(\.\d+)*)?)/
|
214
|
-
nuget_version_range_match_data = nuget_version_range_regex.match(dependency_version_range)
|
215
|
-
|
216
|
-
dependency_version = if nuget_version_range_match_data.nil?
|
217
|
-
dependency_version_range
|
218
|
-
else
|
219
|
-
nuget_version_range_match_data[1]
|
220
|
-
end
|
221
|
-
|
222
|
-
dependency["version"] = Version.new(dependency_version)
|
223
|
-
|
224
|
-
current_dependency = all_dependencies[dependency_id.downcase]
|
225
|
-
next unless current_dependency.nil? || current_dependency["version"] < dependency["version"]
|
226
|
-
|
227
|
-
all_dependencies[dependency_id.downcase] = dependency
|
228
|
-
fetch_transitive_dependencies_impl(dependency_id, dependency_version, all_dependencies)
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
sig { params(package_id: String, package_version: String).returns(T::Array[T::Hash[String, T.untyped]]) }
|
233
|
-
def fetch_dependencies(package_id, package_version)
|
234
|
-
key = "#{package_id.downcase}::#{package_version}"
|
235
|
-
cache = DependencyFinder.fetch_dependencies_cache
|
236
|
-
|
237
|
-
cache[key] ||= begin
|
238
|
-
nuspec_xml = NuspecFetcher.fetch_nuspec(dependency_urls, package_id, package_version)
|
239
|
-
if nuspec_xml.nil?
|
240
|
-
[]
|
241
|
-
else
|
242
|
-
read_dependencies_from_nuspec(nuspec_xml)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
cache[key]
|
247
|
-
end
|
248
|
-
|
249
|
-
sig { params(nuspec_xml: Nokogiri::XML::Document).returns(T::Array[T::Hash[String, String]]) }
|
250
|
-
def read_dependencies_from_nuspec(nuspec_xml) # rubocop:disable Metrics/PerceivedComplexity
|
251
|
-
# we want to exclude development dependencies from the lookup
|
252
|
-
allowed_attributes = %w(all compile native runtime)
|
253
|
-
|
254
|
-
nuspec_xml_dependencies = nuspec_xml.xpath("//dependencies/child::node()/dependency").select do |dependency|
|
255
|
-
include_attr = dependency.attribute("include")
|
256
|
-
exclude_attr = dependency.attribute("exclude")
|
257
|
-
|
258
|
-
if include_attr.nil? && exclude_attr.nil?
|
259
|
-
true
|
260
|
-
elsif include_attr
|
261
|
-
include_values = include_attr.value.split(",").map(&:strip)
|
262
|
-
include_values.any? { |element1| allowed_attributes.any? { |element2| element1.casecmp?(element2) } }
|
263
|
-
else
|
264
|
-
exclude_values = exclude_attr.value.split(",").map(&:strip)
|
265
|
-
exclude_values.none? { |element1| allowed_attributes.any? { |element2| element1.casecmp?(element2) } }
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
dependency_list = []
|
270
|
-
nuspec_xml_dependencies.each do |dependency|
|
271
|
-
next unless dependency.attribute("version")
|
272
|
-
|
273
|
-
dependency_list << {
|
274
|
-
"packageName" => dependency.attribute("id").value,
|
275
|
-
"versionRange" => dependency.attribute("version").value
|
276
|
-
}
|
277
|
-
end
|
278
|
-
|
279
|
-
dependency_list
|
280
|
-
end
|
281
|
-
|
282
|
-
sig { params(dep: Dependabot::Dependency).returns(Dependabot::Nuget::UpdateChecker::VersionFinder) }
|
283
|
-
def version_finder(dep)
|
284
|
-
VersionFinder.new(
|
285
|
-
dependency: dep,
|
286
|
-
dependency_files: dependency_files,
|
287
|
-
credentials: credentials,
|
288
|
-
ignored_versions: ignored_versions,
|
289
|
-
raise_on_ignored: false,
|
290
|
-
security_advisories: [],
|
291
|
-
repo_contents_path: repo_contents_path
|
292
|
-
)
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
@@ -1,221 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "nokogiri"
|
5
|
-
require "stringio"
|
6
|
-
require "sorbet-runtime"
|
7
|
-
require "zip"
|
8
|
-
|
9
|
-
require "dependabot/nuget/http_response_helpers"
|
10
|
-
|
11
|
-
module Dependabot
|
12
|
-
module Nuget
|
13
|
-
class NupkgFetcher
|
14
|
-
extend T::Sig
|
15
|
-
|
16
|
-
require_relative "repository_finder"
|
17
|
-
|
18
|
-
sig do
|
19
|
-
params(
|
20
|
-
dependency_urls: T::Array[T::Hash[Symbol, String]],
|
21
|
-
package_id: String,
|
22
|
-
package_version: String
|
23
|
-
)
|
24
|
-
.returns(T.nilable(String))
|
25
|
-
end
|
26
|
-
def self.fetch_nupkg_buffer(dependency_urls, package_id, package_version)
|
27
|
-
# check all repositories for the first one that has the nupkg
|
28
|
-
dependency_urls.reduce(T.let(nil, T.nilable(String))) do |nupkg_buffer, repository_details|
|
29
|
-
nupkg_buffer || fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
sig do
|
34
|
-
params(
|
35
|
-
repository_details: T::Hash[Symbol, T.untyped],
|
36
|
-
package_id: T.nilable(String),
|
37
|
-
package_version: T.nilable(String)
|
38
|
-
)
|
39
|
-
.returns(T.nilable(String))
|
40
|
-
end
|
41
|
-
def self.fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
|
42
|
-
return unless package_id && package_version && !package_version.empty?
|
43
|
-
|
44
|
-
feed_url = repository_details[:repository_url]
|
45
|
-
repository_type = repository_details[:repository_type]
|
46
|
-
|
47
|
-
package_url = if repository_type == "v2"
|
48
|
-
get_nuget_v2_package_url(repository_details, package_id, package_version)
|
49
|
-
elsif repository_type == "v3"
|
50
|
-
get_nuget_v3_package_url(repository_details, package_id, package_version)
|
51
|
-
else
|
52
|
-
raise Dependabot::DependencyFileNotResolvable, "Unexpected NuGet feed format: #{feed_url}"
|
53
|
-
end
|
54
|
-
|
55
|
-
package_url
|
56
|
-
end
|
57
|
-
|
58
|
-
sig do
|
59
|
-
params(
|
60
|
-
repository_details: T::Hash[Symbol, T.untyped],
|
61
|
-
package_id: String,
|
62
|
-
package_version: String
|
63
|
-
)
|
64
|
-
.returns(T.nilable(String))
|
65
|
-
end
|
66
|
-
def self.fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
|
67
|
-
package_url = fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
|
68
|
-
return unless package_url
|
69
|
-
|
70
|
-
auth_header = repository_details[:auth_header]
|
71
|
-
fetch_stream(package_url, auth_header)
|
72
|
-
end
|
73
|
-
|
74
|
-
sig do
|
75
|
-
params(
|
76
|
-
repository_details: T::Hash[Symbol, T.untyped],
|
77
|
-
package_id: String,
|
78
|
-
package_version: String
|
79
|
-
)
|
80
|
-
.returns(T.nilable(String))
|
81
|
-
end
|
82
|
-
def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
|
83
|
-
base_url = repository_details[:base_url]
|
84
|
-
unless base_url
|
85
|
-
return get_nuget_v3_package_url_from_search(repository_details, package_id,
|
86
|
-
package_version)
|
87
|
-
end
|
88
|
-
|
89
|
-
base_url = base_url.delete_suffix("/")
|
90
|
-
package_id_downcased = package_id.downcase
|
91
|
-
"#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.#{package_version}.nupkg"
|
92
|
-
end
|
93
|
-
|
94
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
95
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
96
|
-
sig do
|
97
|
-
params(
|
98
|
-
repository_details: T::Hash[Symbol, T.untyped],
|
99
|
-
package_id: String,
|
100
|
-
package_version: String
|
101
|
-
)
|
102
|
-
.returns(T.nilable(String))
|
103
|
-
end
|
104
|
-
def self.get_nuget_v3_package_url_from_search(repository_details, package_id, package_version)
|
105
|
-
search_url = repository_details[:search_url]
|
106
|
-
return nil unless search_url
|
107
|
-
|
108
|
-
# get search result
|
109
|
-
search_result_response = fetch_url(search_url, repository_details)
|
110
|
-
return nil unless search_result_response&.status == 200
|
111
|
-
|
112
|
-
search_response_body = HttpResponseHelpers.remove_wrapping_zero_width_chars(T.must(search_result_response).body)
|
113
|
-
search_results = JSON.parse(search_response_body)
|
114
|
-
|
115
|
-
# find matching package and version
|
116
|
-
package_search_result = search_results&.[]("data")&.find { |d| package_id.casecmp?(d&.[]("id")) }
|
117
|
-
version_search_result = package_search_result&.[]("versions")&.find do |v|
|
118
|
-
package_version.casecmp?(v&.[]("version"))
|
119
|
-
end
|
120
|
-
registration_leaf_url = version_search_result&.[]("@id")
|
121
|
-
return nil unless registration_leaf_url
|
122
|
-
|
123
|
-
registration_leaf_response = fetch_url(registration_leaf_url, repository_details)
|
124
|
-
return nil unless registration_leaf_response
|
125
|
-
return nil unless registration_leaf_response.status == 200
|
126
|
-
|
127
|
-
registration_leaf_response_body =
|
128
|
-
HttpResponseHelpers.remove_wrapping_zero_width_chars(registration_leaf_response.body)
|
129
|
-
registration_leaf = JSON.parse(registration_leaf_response_body)
|
130
|
-
|
131
|
-
# finally, get the .nupkg url
|
132
|
-
registration_leaf&.[]("packageContent")
|
133
|
-
end
|
134
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
135
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
136
|
-
|
137
|
-
sig do
|
138
|
-
params(
|
139
|
-
repository_details: T::Hash[Symbol, T.untyped],
|
140
|
-
package_id: String,
|
141
|
-
package_version: String
|
142
|
-
)
|
143
|
-
.returns(T.nilable(String))
|
144
|
-
end
|
145
|
-
def self.get_nuget_v2_package_url(repository_details, package_id, package_version)
|
146
|
-
# get package XML
|
147
|
-
base_url = repository_details[:base_url].delete_suffix("/")
|
148
|
-
package_url = "#{base_url}/Packages(Id='#{package_id}',Version='#{package_version}')"
|
149
|
-
response = fetch_url(package_url, repository_details)
|
150
|
-
return nil unless response&.status == 200
|
151
|
-
|
152
|
-
# find relevant element
|
153
|
-
doc = Nokogiri::XML(T.must(response).body)
|
154
|
-
doc.remove_namespaces!
|
155
|
-
|
156
|
-
content_element = doc.xpath("/entry/content")
|
157
|
-
nupkg_url = content_element&.attribute("src")&.value
|
158
|
-
nupkg_url
|
159
|
-
end
|
160
|
-
|
161
|
-
sig do
|
162
|
-
params(
|
163
|
-
stream_url: String,
|
164
|
-
auth_header: T::Hash[String, String],
|
165
|
-
max_redirects: Integer
|
166
|
-
)
|
167
|
-
.returns(T.nilable(String))
|
168
|
-
end
|
169
|
-
def self.fetch_stream(stream_url, auth_header, max_redirects = 5)
|
170
|
-
current_url = stream_url
|
171
|
-
current_redirects = 0
|
172
|
-
|
173
|
-
loop do
|
174
|
-
# Directly download the stream without any additional settings _except_ for `omit_default_port: true` which
|
175
|
-
# is necessary to not break the URL signing that some NuGet feeds use.
|
176
|
-
response = Excon.get(
|
177
|
-
current_url,
|
178
|
-
headers: auth_header,
|
179
|
-
omit_default_port: true
|
180
|
-
)
|
181
|
-
|
182
|
-
# redirect the HTTP response as appropriate based on documentation here:
|
183
|
-
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
184
|
-
case response.status
|
185
|
-
when 200
|
186
|
-
return response.body
|
187
|
-
when 301, 302, 303, 307, 308
|
188
|
-
current_redirects += 1
|
189
|
-
return nil if current_redirects > max_redirects
|
190
|
-
|
191
|
-
current_url = T.must(response.headers["Location"])
|
192
|
-
else
|
193
|
-
return nil
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
sig do
|
199
|
-
params(
|
200
|
-
url: String,
|
201
|
-
repository_details: T::Hash[Symbol, T.untyped]
|
202
|
-
)
|
203
|
-
.returns(T.nilable(Excon::Response))
|
204
|
-
end
|
205
|
-
def self.fetch_url(url, repository_details)
|
206
|
-
fetch_url_with_auth(url, repository_details.fetch(:auth_header))
|
207
|
-
end
|
208
|
-
|
209
|
-
sig { params(url: String, auth_header: T::Hash[T.any(String, Symbol), T.untyped]).returns(Excon::Response) }
|
210
|
-
def self.fetch_url_with_auth(url, auth_header)
|
211
|
-
cache = CacheManager.cache("nupkg_fetcher_cache")
|
212
|
-
cache[url] ||= Dependabot::RegistryClient.get(
|
213
|
-
url: url,
|
214
|
-
headers: auth_header
|
215
|
-
)
|
216
|
-
|
217
|
-
cache[url]
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "nokogiri"
|
5
|
-
require "stringio"
|
6
|
-
require "sorbet-runtime"
|
7
|
-
require "zip"
|
8
|
-
|
9
|
-
module Dependabot
|
10
|
-
module Nuget
|
11
|
-
class NuspecFetcher
|
12
|
-
extend T::Sig
|
13
|
-
|
14
|
-
require_relative "nupkg_fetcher"
|
15
|
-
require_relative "repository_finder"
|
16
|
-
|
17
|
-
sig do
|
18
|
-
params(
|
19
|
-
dependency_urls: T::Array[T::Hash[Symbol, String]],
|
20
|
-
package_id: String,
|
21
|
-
package_version: T.nilable(String)
|
22
|
-
)
|
23
|
-
.returns(T.nilable(Nokogiri::XML::Document))
|
24
|
-
end
|
25
|
-
def self.fetch_nuspec(dependency_urls, package_id, package_version)
|
26
|
-
# check all repositories for the first one that has the nuspec
|
27
|
-
dependency_urls.reduce(T.let(nil, T.nilable(Nokogiri::XML::Document))) do |nuspec_xml, repository_details|
|
28
|
-
nuspec_xml || fetch_nuspec_from_repository(repository_details, package_id, package_version)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
sig do
|
33
|
-
params(
|
34
|
-
repository_details: T::Hash[Symbol, T.untyped],
|
35
|
-
package_id: T.nilable(String),
|
36
|
-
package_version: T.nilable(String)
|
37
|
-
)
|
38
|
-
.returns(T.nilable(Nokogiri::XML::Document))
|
39
|
-
end
|
40
|
-
def self.fetch_nuspec_from_repository(repository_details, package_id, package_version)
|
41
|
-
return unless package_id && package_version && !package_version.empty?
|
42
|
-
|
43
|
-
feed_url = repository_details[:repository_url]
|
44
|
-
auth_header = repository_details[:auth_header]
|
45
|
-
|
46
|
-
nuspec_xml = nil
|
47
|
-
|
48
|
-
if feed_supports_nuspec_download?(feed_url)
|
49
|
-
# we can use the normal nuget apis to get the nuspec and list out the dependencies
|
50
|
-
base_url = repository_details[:base_url].delete_suffix("/")
|
51
|
-
package_id_downcased = package_id.downcase
|
52
|
-
nuspec_url = "#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.nuspec"
|
53
|
-
|
54
|
-
nuspec_response = Dependabot::RegistryClient.get(
|
55
|
-
url: nuspec_url,
|
56
|
-
headers: auth_header
|
57
|
-
)
|
58
|
-
|
59
|
-
return unless nuspec_response.status == 200
|
60
|
-
|
61
|
-
nuspec_response_body = remove_invalid_characters(nuspec_response.body)
|
62
|
-
nuspec_xml = Nokogiri::XML(nuspec_response_body)
|
63
|
-
else
|
64
|
-
# no guarantee we can directly query the .nuspec; fall back to extracting it from the .nupkg
|
65
|
-
package_data = NupkgFetcher.fetch_nupkg_buffer_from_repository(repository_details, package_id,
|
66
|
-
package_version)
|
67
|
-
return if package_data.nil?
|
68
|
-
|
69
|
-
nuspec_string = extract_nuspec(package_data, package_id)
|
70
|
-
nuspec_xml = Nokogiri::XML(nuspec_string)
|
71
|
-
end
|
72
|
-
|
73
|
-
nuspec_xml.remove_namespaces!
|
74
|
-
nuspec_xml
|
75
|
-
end
|
76
|
-
|
77
|
-
sig { params(feed_url: String).returns(T::Boolean) }
|
78
|
-
def self.feed_supports_nuspec_download?(feed_url)
|
79
|
-
feed_regexs = [
|
80
|
-
# nuget
|
81
|
-
%r{https://api\.nuget\.org/v3/index\.json},
|
82
|
-
# azure devops
|
83
|
-
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json},
|
84
|
-
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)},
|
85
|
-
%r{https://(?<organization>[^\.\/]+)\.pkgs\.visualstudio\.com/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)}
|
86
|
-
]
|
87
|
-
feed_regexs.any? { |reg| reg.match(feed_url) }
|
88
|
-
end
|
89
|
-
|
90
|
-
sig { params(zip_stream: String, package_id: String).returns(T.nilable(String)) }
|
91
|
-
def self.extract_nuspec(zip_stream, package_id)
|
92
|
-
Zip::File.open_buffer(zip_stream) do |zip|
|
93
|
-
nuspec_entry = zip.find { |entry| entry.name == "#{package_id}.nuspec" }
|
94
|
-
return nuspec_entry.get_input_stream.read if nuspec_entry
|
95
|
-
end
|
96
|
-
nil
|
97
|
-
end
|
98
|
-
|
99
|
-
sig { params(string: String).returns(String) }
|
100
|
-
def self.remove_invalid_characters(string)
|
101
|
-
string.dup
|
102
|
-
.force_encoding(Encoding::UTF_8)
|
103
|
-
.encode
|
104
|
-
.scrub("")
|
105
|
-
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
|
106
|
-
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|