dependabot-nuget 0.242.0 → 0.243.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/.editorconfig +37 -28
- data/helpers/lib/NuGetUpdater/.gitignore +1 -0
- data/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +178 -176
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +5 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +10 -5
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +16 -12
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +18 -17
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +7 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +13 -20
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +9 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +32 -16
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +42 -22
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +32 -13
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +47 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +55 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +12 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +50 -42
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +16 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +6 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +18 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +7 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +9 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +81 -80
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +22 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +140 -104
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +25 -25
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +8 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +198 -22
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +401 -399
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +17 -15
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +111 -42
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +161 -82
- data/lib/dependabot/nuget/file_fetcher.rb +3 -23
- data/lib/dependabot/nuget/file_parser/project_file_parser.rb +47 -60
- data/lib/dependabot/nuget/file_parser.rb +24 -6
- data/lib/dependabot/nuget/file_updater.rb +42 -6
- data/lib/dependabot/nuget/native_helpers.rb +27 -8
- data/lib/dependabot/nuget/nuget_client.rb +130 -24
- data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +7 -3
- data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +63 -59
- data/lib/dependabot/nuget/update_checker/dependency_finder.rb +2 -2
- data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +1 -1
- data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +22 -17
- data/lib/dependabot/nuget/update_checker/repository_finder.rb +292 -270
- data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +11 -13
- data/lib/dependabot/nuget/update_checker/tfm_finder.rb +80 -82
- data/lib/dependabot/nuget/update_checker/version_finder.rb +4 -7
- data/lib/dependabot/nuget/version.rb +18 -7
- data/lib/dependabot/nuget.rb +0 -2
- metadata +7 -5
@@ -5,10 +5,13 @@ require "dependabot/dependency_file"
|
|
5
5
|
require "dependabot/file_updaters"
|
6
6
|
require "dependabot/file_updaters/base"
|
7
7
|
require "dependabot/nuget/native_helpers"
|
8
|
+
require "sorbet-runtime"
|
8
9
|
|
9
10
|
module Dependabot
|
10
11
|
module Nuget
|
11
12
|
class FileUpdater < Dependabot::FileUpdaters::Base
|
13
|
+
extend T::Sig
|
14
|
+
|
12
15
|
require_relative "file_updater/property_value_updater"
|
13
16
|
require_relative "file_parser/project_file_parser"
|
14
17
|
require_relative "file_parser/dotnet_tools_json_parser"
|
@@ -54,6 +57,7 @@ module Dependabot
|
|
54
57
|
|
55
58
|
def try_update_projects(dependency)
|
56
59
|
update_ran = T.let(false, T::Boolean)
|
60
|
+
checked_files = Set.new
|
57
61
|
|
58
62
|
# run update for each project file
|
59
63
|
project_files.each do |project_file|
|
@@ -62,9 +66,17 @@ module Dependabot
|
|
62
66
|
|
63
67
|
next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? }
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
|
69
|
+
next unless repo_contents_path
|
70
|
+
|
71
|
+
checked_key = "#{project_file.name}-#{dependency.name}#{dependency.version}"
|
72
|
+
call_nuget_updater_tool(dependency, proj_path) unless checked_files.include?(checked_key)
|
73
|
+
|
74
|
+
checked_files.add(checked_key)
|
75
|
+
# We need to check the downstream references even though we're already evaluated the file
|
76
|
+
downstream_files = project_file_parser.downstream_file_references(project_file: project_file)
|
77
|
+
downstream_files.each do |downstream_file|
|
78
|
+
checked_files.add("#{downstream_file}-#{dependency.name}#{dependency.version}")
|
79
|
+
end
|
68
80
|
update_ran = true
|
69
81
|
end
|
70
82
|
|
@@ -79,15 +91,39 @@ module Dependabot
|
|
79
91
|
project_file = project_files.first
|
80
92
|
proj_path = dependency_file_path(project_file)
|
81
93
|
|
82
|
-
|
83
|
-
|
84
|
-
|
94
|
+
return false unless repo_contents_path
|
95
|
+
|
96
|
+
call_nuget_updater_tool(dependency, proj_path)
|
85
97
|
return true
|
86
98
|
end
|
87
99
|
|
88
100
|
false
|
89
101
|
end
|
90
102
|
|
103
|
+
sig { params(dependency: Dependency, proj_path: String).void }
|
104
|
+
def call_nuget_updater_tool(dependency, proj_path)
|
105
|
+
NativeHelpers.run_nuget_updater_tool(repo_root: T.must(repo_contents_path), proj_path: proj_path,
|
106
|
+
dependency: dependency, is_transitive: !dependency.top_level?,
|
107
|
+
credentials: credentials)
|
108
|
+
|
109
|
+
# Tests need to track how many times we call the tooling updater to ensure we don't recurse needlessly
|
110
|
+
# Ideally we should find a way to not run this code in prod
|
111
|
+
# (or a better way to track calls made to NativeHelpers)
|
112
|
+
@update_tooling_calls ||= {}
|
113
|
+
key = proj_path + dependency.name
|
114
|
+
if @update_tooling_calls[key]
|
115
|
+
@update_tooling_calls[key] += 1
|
116
|
+
else
|
117
|
+
@update_tooling_calls[key] = 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Don't call this from outside tests, we're only checking that we aren't recursing needlessly
|
122
|
+
sig { returns(T.nilable(T::Hash[String, Integer])) }
|
123
|
+
def testonly_update_tooling_calls
|
124
|
+
@update_tooling_calls
|
125
|
+
end
|
126
|
+
|
91
127
|
def project_dependencies(project_file)
|
92
128
|
# Collect all dependencies from the project file and associated packages.config
|
93
129
|
dependencies = project_file_parser.dependency_set(project_file: project_file).dependencies
|
@@ -1,11 +1,17 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strong
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "shellwords"
|
5
|
+
require "sorbet-runtime"
|
6
|
+
|
4
7
|
require_relative "nuget_config_credential_helpers"
|
5
8
|
|
6
9
|
module Dependabot
|
7
10
|
module Nuget
|
8
11
|
module NativeHelpers
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { returns(String) }
|
9
15
|
def self.native_helpers_root
|
10
16
|
helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil)
|
11
17
|
return File.join(helpers_root, "nuget") unless helpers_root.nil?
|
@@ -13,9 +19,10 @@ module Dependabot
|
|
13
19
|
File.expand_path("../../../helpers", __dir__)
|
14
20
|
end
|
15
21
|
|
22
|
+
sig { params(project_tfms: T::Array[String], package_tfms: T::Array[String]).returns(T::Boolean) }
|
16
23
|
def self.run_nuget_framework_check(project_tfms, package_tfms)
|
17
24
|
exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
|
18
|
-
|
25
|
+
command_parts = [
|
19
26
|
exe_path,
|
20
27
|
"framework-check",
|
21
28
|
"--project-tfms",
|
@@ -23,7 +30,8 @@ module Dependabot
|
|
23
30
|
"--package-tfms",
|
24
31
|
*package_tfms,
|
25
32
|
"--verbose"
|
26
|
-
]
|
33
|
+
]
|
34
|
+
command = Shellwords.join(command_parts)
|
27
35
|
|
28
36
|
fingerprint = [
|
29
37
|
exe_path,
|
@@ -48,9 +56,18 @@ module Dependabot
|
|
48
56
|
end
|
49
57
|
|
50
58
|
# rubocop:disable Metrics/MethodLength
|
59
|
+
sig do
|
60
|
+
params(
|
61
|
+
repo_root: String,
|
62
|
+
proj_path: String,
|
63
|
+
dependency: Dependency,
|
64
|
+
is_transitive: T::Boolean,
|
65
|
+
credentials: T::Array[T.untyped]
|
66
|
+
).void
|
67
|
+
end
|
51
68
|
def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency:, is_transitive:, credentials:)
|
52
69
|
exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
|
53
|
-
|
70
|
+
command_parts = [
|
54
71
|
exe_path,
|
55
72
|
"update",
|
56
73
|
"--repo-root",
|
@@ -63,9 +80,11 @@ module Dependabot
|
|
63
80
|
dependency.version,
|
64
81
|
"--previous-version",
|
65
82
|
dependency.previous_version,
|
66
|
-
is_transitive ? "--transitive" :
|
83
|
+
is_transitive ? "--transitive" : nil,
|
67
84
|
"--verbose"
|
68
|
-
].
|
85
|
+
].compact
|
86
|
+
|
87
|
+
command = Shellwords.join(command_parts)
|
69
88
|
|
70
89
|
fingerprint = [
|
71
90
|
exe_path,
|
@@ -80,9 +99,9 @@ module Dependabot
|
|
80
99
|
"<new-version>",
|
81
100
|
"--previous-version",
|
82
101
|
"<previous-version>",
|
83
|
-
is_transitive ? "--transitive" :
|
102
|
+
is_transitive ? "--transitive" : nil,
|
84
103
|
"--verbose"
|
85
|
-
].join(" ")
|
104
|
+
].compact.join(" ")
|
86
105
|
|
87
106
|
puts "running NuGet updater:\n" + command
|
88
107
|
|
@@ -1,13 +1,35 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "dependabot/nuget/cache_manager"
|
5
5
|
require "dependabot/nuget/update_checker/repository_finder"
|
6
|
+
require "sorbet-runtime"
|
6
7
|
|
7
8
|
module Dependabot
|
8
9
|
module Nuget
|
9
10
|
class NugetClient
|
10
|
-
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig do
|
14
|
+
params(dependency_name: String, repository_details: T::Hash[Symbol, String])
|
15
|
+
.returns(T.nilable(T::Set[String]))
|
16
|
+
end
|
17
|
+
def self.get_package_versions(dependency_name, repository_details)
|
18
|
+
repository_type = repository_details.fetch(:repository_type)
|
19
|
+
if repository_type == "v3"
|
20
|
+
get_package_versions_v3(dependency_name, repository_details)
|
21
|
+
elsif repository_type == "v2"
|
22
|
+
get_package_versions_v2(dependency_name, repository_details)
|
23
|
+
else
|
24
|
+
raise "Unknown repository type: #{repository_type}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
sig do
|
29
|
+
params(dependency_name: String, repository_details: T::Hash[Symbol, String])
|
30
|
+
.returns(T.nilable(T::Set[String]))
|
31
|
+
end
|
32
|
+
private_class_method def self.get_package_versions_v3(dependency_name, repository_details)
|
11
33
|
# Use the registration URL if possible because it is fast and correct
|
12
34
|
if repository_details[:registration_url]
|
13
35
|
get_versions_from_registration_v3(repository_details)
|
@@ -17,37 +39,60 @@ module Dependabot
|
|
17
39
|
# Otherwise, use the versions URL (fast but wrong because it includes unlisted versions)
|
18
40
|
elsif repository_details[:versions_url]
|
19
41
|
get_versions_from_versions_url_v3(repository_details)
|
42
|
+
else
|
43
|
+
raise "No version sources were available for #{dependency_name} in #{repository_details}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig do
|
48
|
+
params(dependency_name: String, repository_details: T::Hash[Symbol, String])
|
49
|
+
.returns(T.nilable(T::Set[String]))
|
50
|
+
end
|
51
|
+
private_class_method def self.get_package_versions_v2(dependency_name, repository_details)
|
52
|
+
doc = execute_xml_nuget_request(repository_details.fetch(:versions_url), repository_details)
|
53
|
+
return unless doc
|
54
|
+
|
55
|
+
id_nodes = doc.xpath("/feed/entry/properties/Id")
|
56
|
+
matching_versions = Set.new
|
57
|
+
id_nodes.each do |id_node|
|
58
|
+
return nil unless id_node.text
|
59
|
+
|
60
|
+
next unless id_node.text.casecmp?(dependency_name)
|
61
|
+
|
62
|
+
version_node = id_node.parent.xpath("Version")
|
63
|
+
matching_versions << version_node.text if version_node && version_node.text
|
20
64
|
end
|
65
|
+
|
66
|
+
matching_versions
|
21
67
|
end
|
22
68
|
|
69
|
+
sig { params(repository_details: T::Hash[Symbol, String]).returns(T.nilable(T::Set[String])) }
|
23
70
|
private_class_method def self.get_versions_from_versions_url_v3(repository_details)
|
24
|
-
body =
|
25
|
-
body&.fetch("versions")
|
71
|
+
body = execute_json_nuget_request(repository_details.fetch(:versions_url), repository_details)
|
72
|
+
ver_array = T.let(body&.fetch("versions"), T.nilable(T::Array[String]))
|
73
|
+
ver_array&.to_set
|
26
74
|
end
|
27
75
|
|
76
|
+
sig { params(repository_details: T::Hash[Symbol, String]).returns(T.nilable(T::Set[String])) }
|
28
77
|
private_class_method def self.get_versions_from_registration_v3(repository_details)
|
29
|
-
url = repository_details
|
30
|
-
body =
|
78
|
+
url = repository_details.fetch(:registration_url)
|
79
|
+
body = execute_json_nuget_request(url, repository_details)
|
31
80
|
|
32
81
|
return unless body
|
33
82
|
|
34
83
|
pages = body.fetch("items")
|
35
|
-
versions = Set.new
|
84
|
+
versions = T.let(Set.new, T::Set[String])
|
36
85
|
pages.each do |page|
|
37
86
|
items = page["items"]
|
38
87
|
if items
|
39
88
|
# inlined entries
|
40
|
-
items
|
41
|
-
catalog_entry = item["catalogEntry"]
|
42
|
-
if catalog_entry["listed"] == true
|
43
|
-
vers = catalog_entry["version"]
|
44
|
-
versions << vers
|
45
|
-
end
|
46
|
-
end
|
89
|
+
get_versions_from_inline_page(items, versions)
|
47
90
|
else
|
48
91
|
# paged entries
|
49
92
|
page_url = page["@id"]
|
50
|
-
page_body =
|
93
|
+
page_body = execute_json_nuget_request(page_url, repository_details)
|
94
|
+
next unless page_body
|
95
|
+
|
51
96
|
items = page_body.fetch("items")
|
52
97
|
items.each do |item|
|
53
98
|
catalog_entry = item.fetch("catalogEntry")
|
@@ -59,9 +104,28 @@ module Dependabot
|
|
59
104
|
versions
|
60
105
|
end
|
61
106
|
|
107
|
+
sig { params(items: T::Array[T::Hash[String, T.untyped]], versions: T::Set[String]).void }
|
108
|
+
private_class_method def self.get_versions_from_inline_page(items, versions)
|
109
|
+
items.each do |item|
|
110
|
+
catalog_entry = item["catalogEntry"]
|
111
|
+
|
112
|
+
# a package is considered listed if the `listed` property is either `true` or missing
|
113
|
+
listed_property = catalog_entry["listed"]
|
114
|
+
is_listed = listed_property.nil? || listed_property == true
|
115
|
+
if is_listed
|
116
|
+
vers = catalog_entry["version"]
|
117
|
+
versions << vers
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
sig do
|
123
|
+
params(repository_details: T::Hash[Symbol, String], dependency_name: String)
|
124
|
+
.returns(T.nilable(T::Set[String]))
|
125
|
+
end
|
62
126
|
private_class_method def self.get_versions_from_search_url_v3(repository_details, dependency_name)
|
63
|
-
search_url = repository_details
|
64
|
-
body =
|
127
|
+
search_url = repository_details.fetch(:search_url)
|
128
|
+
body = execute_json_nuget_request(search_url, repository_details)
|
65
129
|
|
66
130
|
body&.fetch("data")
|
67
131
|
&.find { |d| d.fetch("id").casecmp(dependency_name.downcase).zero? }
|
@@ -69,26 +133,68 @@ module Dependabot
|
|
69
133
|
&.map { |d| d.fetch("version") }
|
70
134
|
end
|
71
135
|
|
72
|
-
|
73
|
-
|
74
|
-
|
136
|
+
sig do
|
137
|
+
params(url: String, repository_details: T::Hash[Symbol, T.untyped]).returns(T.nilable(Nokogiri::XML::Document))
|
138
|
+
end
|
139
|
+
private_class_method def self.execute_xml_nuget_request(url, repository_details)
|
140
|
+
response = execute_nuget_request_internal(
|
75
141
|
url: url,
|
76
|
-
|
142
|
+
auth_header: repository_details.fetch(:auth_header),
|
143
|
+
repository_url: repository_details.fetch(:repository_url)
|
77
144
|
)
|
145
|
+
return unless response.status == 200
|
78
146
|
|
79
|
-
|
147
|
+
doc = Nokogiri::XML(response.body)
|
148
|
+
doc.remove_namespaces!
|
149
|
+
doc
|
150
|
+
end
|
80
151
|
|
152
|
+
sig do
|
153
|
+
params(url: String,
|
154
|
+
repository_details: T::Hash[Symbol, T.untyped])
|
155
|
+
.returns(T.nilable(T::Hash[T.untyped, T.untyped]))
|
156
|
+
end
|
157
|
+
private_class_method def self.execute_json_nuget_request(url, repository_details)
|
158
|
+
response = execute_nuget_request_internal(
|
159
|
+
url: url,
|
160
|
+
auth_header: repository_details.fetch(:auth_header),
|
161
|
+
repository_url: repository_details.fetch(:repository_url)
|
162
|
+
)
|
81
163
|
return unless response.status == 200
|
82
164
|
|
83
165
|
body = remove_wrapping_zero_width_chars(response.body)
|
84
166
|
JSON.parse(body)
|
167
|
+
end
|
168
|
+
|
169
|
+
sig do
|
170
|
+
params(url: String, auth_header: T::Hash[Symbol, T.untyped], repository_url: String).returns(Excon::Response)
|
171
|
+
end
|
172
|
+
private_class_method def self.execute_nuget_request_internal(url:, auth_header:, repository_url:)
|
173
|
+
cache = CacheManager.cache("dependency_url_search_cache")
|
174
|
+
if cache[url].nil?
|
175
|
+
response = Dependabot::RegistryClient.get(
|
176
|
+
url: url,
|
177
|
+
headers: auth_header
|
178
|
+
)
|
179
|
+
|
180
|
+
if [401, 402, 403].include?(response.status)
|
181
|
+
raise Dependabot::PrivateSourceAuthenticationFailure, repository_url
|
182
|
+
end
|
183
|
+
|
184
|
+
cache[url] = response if !CacheManager.caching_disabled? && response.status == 200
|
185
|
+
else
|
186
|
+
response = cache[url]
|
187
|
+
end
|
188
|
+
|
189
|
+
response
|
85
190
|
rescue Excon::Error::Timeout, Excon::Error::Socket
|
86
|
-
repo_url =
|
87
|
-
raise if repo_url == Dependabot::Nuget::
|
191
|
+
repo_url = repository_url
|
192
|
+
raise if repo_url == Dependabot::Nuget::RepositoryFinder::DEFAULT_REPOSITORY_URL
|
88
193
|
|
89
194
|
raise PrivateSourceTimedOut, repo_url
|
90
195
|
end
|
91
196
|
|
197
|
+
sig { params(string: String).returns(String) }
|
92
198
|
private_class_method def self.remove_wrapping_zero_width_chars(string)
|
93
199
|
string.force_encoding("UTF-8").encode
|
94
200
|
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
|
@@ -55,17 +55,21 @@ module Dependabot
|
|
55
55
|
File.rename(temporary_nuget_config_path, user_nuget_config_path)
|
56
56
|
end
|
57
57
|
|
58
|
-
# rubocop:disable Lint/SuppressedException
|
59
58
|
def self.patch_nuget_config_for_action(credentials, &_block)
|
60
59
|
add_credentials_to_nuget_config(credentials)
|
61
60
|
begin
|
62
61
|
yield
|
63
|
-
rescue StandardError
|
62
|
+
rescue StandardError => e
|
63
|
+
Dependabot.logger.error(
|
64
|
+
<<~LOG_MESSAGE
|
65
|
+
Block argument of NuGetConfigCredentialHelpers::patch_nuget_config_for_action causes an exception #{e}:
|
66
|
+
#{e.message}
|
67
|
+
LOG_MESSAGE
|
68
|
+
)
|
64
69
|
ensure
|
65
70
|
restore_user_nuget_config
|
66
71
|
end
|
67
72
|
end
|
68
|
-
# rubocop:enable Lint/SuppressedException
|
69
73
|
end
|
70
74
|
end
|
71
75
|
end
|
@@ -5,80 +5,84 @@ require "dependabot/update_checkers/base"
|
|
5
5
|
|
6
6
|
module Dependabot
|
7
7
|
module Nuget
|
8
|
-
class
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
8
|
+
class CompatibilityChecker
|
9
|
+
require_relative "nuspec_fetcher"
|
10
|
+
require_relative "nupkg_fetcher"
|
11
|
+
require_relative "tfm_finder"
|
12
|
+
require_relative "tfm_comparer"
|
13
|
+
|
14
|
+
def initialize(dependency_urls:, dependency:, tfm_finder:)
|
15
|
+
@dependency_urls = dependency_urls
|
16
|
+
@dependency = dependency
|
17
|
+
@tfm_finder = tfm_finder
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
def compatible?(version)
|
21
|
+
nuspec_xml = NuspecFetcher.fetch_nuspec(dependency_urls, dependency.name, version)
|
22
|
+
return false unless nuspec_xml
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
# development dependencies are packages such as analyzers which need to be compatible with the compiler not the
|
25
|
+
# project itself, but some packages that report themselves as development dependencies still contain target
|
26
|
+
# framework dependencies and should be checked for compatibility through the regular means
|
27
|
+
return true if pure_development_dependency?(nuspec_xml)
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
package_tfms = parse_package_tfms(nuspec_xml)
|
30
|
+
package_tfms = fetch_package_tfms(version) if package_tfms.empty?
|
31
|
+
# nil is a special return value that indicates that the package is likely a development dependency
|
32
|
+
return true if package_tfms.nil?
|
33
|
+
return false if package_tfms.empty?
|
34
34
|
|
35
|
-
|
35
|
+
return false if project_tfms.nil? || project_tfms.empty?
|
36
36
|
|
37
|
-
|
38
|
-
|
37
|
+
TfmComparer.are_frameworks_compatible?(project_tfms, package_tfms)
|
38
|
+
end
|
39
39
|
|
40
|
-
|
40
|
+
private
|
41
41
|
|
42
|
-
|
42
|
+
attr_reader :dependency_urls, :dependency, :tfm_finder
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
def pure_development_dependency?(nuspec_xml)
|
45
|
+
contents = nuspec_xml.at_xpath("package/metadata/developmentDependency")&.content&.strip
|
46
|
+
return false unless contents # no `developmentDependency` element
|
47
47
|
|
48
|
-
|
49
|
-
|
48
|
+
self_reports_as_development_dependency = contents.casecmp?("true")
|
49
|
+
return false unless self_reports_as_development_dependency
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
# even though a package self-reports as a development dependency, it might not be if it has dependency groups
|
52
|
+
# with a target framework
|
53
|
+
dependency_groups_with_target_framework =
|
54
|
+
nuspec_xml.at_xpath("/package/metadata/dependencies/group[@targetFramework]")
|
55
|
+
dependency_groups_with_target_framework.to_a.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_package_tfms(nuspec_xml)
|
59
|
+
nuspec_xml.xpath("//dependencies/group").filter_map { |group| group.attribute("targetFramework") }
|
60
|
+
end
|
56
61
|
|
57
|
-
|
58
|
-
|
62
|
+
def project_tfms
|
63
|
+
return @project_tfms if defined?(@project_tfms)
|
59
64
|
|
60
|
-
|
61
|
-
|
65
|
+
@project_tfms = tfm_finder.frameworks(dependency)
|
66
|
+
end
|
62
67
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
68
|
+
def fetch_package_tfms(dependency_version)
|
69
|
+
nupkg_buffer = NupkgFetcher.fetch_nupkg_buffer(dependency_urls, dependency.name, dependency_version)
|
70
|
+
return [] unless nupkg_buffer
|
71
|
+
|
72
|
+
# Parse tfms from the folders beneath the lib folder
|
73
|
+
folder_name = "lib/"
|
74
|
+
tfms = Set.new
|
75
|
+
Zip::File.open_buffer(nupkg_buffer) do |zip|
|
76
|
+
lib_file_entries = zip.select { |entry| entry.name.start_with?(folder_name) }
|
77
|
+
# If there is no lib folder in this package, assume it is a development dependency
|
78
|
+
return nil if lib_file_entries.empty?
|
79
|
+
|
80
|
+
lib_file_entries.each do |entry|
|
81
|
+
_, tfm = entry.name.split("/").first(2)
|
82
|
+
tfms << tfm
|
79
83
|
end
|
80
|
-
tfms.to_a
|
81
84
|
end
|
85
|
+
tfms.to_a
|
82
86
|
end
|
83
87
|
end
|
84
88
|
end
|
@@ -121,12 +121,12 @@ module Dependabot
|
|
121
121
|
|
122
122
|
def dependency_urls
|
123
123
|
@dependency_urls ||=
|
124
|
-
|
124
|
+
RepositoryFinder.new(
|
125
125
|
dependency: @dependency,
|
126
126
|
credentials: @credentials,
|
127
127
|
config_files: nuget_configs
|
128
128
|
).dependency_urls
|
129
|
-
|
129
|
+
.select { |url| url.fetch(:repository_type) == "v3" }
|
130
130
|
end
|
131
131
|
|
132
132
|
def fetch_transitive_dependencies(package_id, package_version)
|
@@ -26,17 +26,9 @@ module Dependabot
|
|
26
26
|
|
27
27
|
nuspec_xml = nil
|
28
28
|
|
29
|
-
if
|
30
|
-
# this is an azure devops url we can extract the nuspec from the nupkg
|
31
|
-
package_data = NupkgFetcher.fetch_nupkg_buffer_from_repository(repository_details, package_id,
|
32
|
-
package_version)
|
33
|
-
return if package_data.nil?
|
34
|
-
|
35
|
-
nuspec_string = extract_nuspec(package_data, package_id)
|
36
|
-
nuspec_xml = Nokogiri::XML(nuspec_string)
|
37
|
-
else
|
29
|
+
if feed_supports_nuspec_download?(feed_url)
|
38
30
|
# we can use the normal nuget apis to get the nuspec and list out the dependencies
|
39
|
-
base_url =
|
31
|
+
base_url = repository_details[:base_url].delete_suffix("/")
|
40
32
|
package_id_downcased = package_id.downcase
|
41
33
|
nuspec_url = "#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.nuspec"
|
42
34
|
|
@@ -47,22 +39,32 @@ module Dependabot
|
|
47
39
|
|
48
40
|
return unless nuspec_response.status == 200
|
49
41
|
|
50
|
-
nuspec_response_body =
|
42
|
+
nuspec_response_body = remove_invalid_characters(nuspec_response.body)
|
51
43
|
nuspec_xml = Nokogiri::XML(nuspec_response_body)
|
44
|
+
else
|
45
|
+
# no guarantee we can directly query the .nuspec; fall back to extracting it from the .nupkg
|
46
|
+
package_data = NupkgFetcher.fetch_nupkg_buffer_from_repository(repository_details, package_id,
|
47
|
+
package_version)
|
48
|
+
return if package_data.nil?
|
49
|
+
|
50
|
+
nuspec_string = extract_nuspec(package_data, package_id)
|
51
|
+
nuspec_xml = Nokogiri::XML(nuspec_string)
|
52
52
|
end
|
53
53
|
|
54
54
|
nuspec_xml.remove_namespaces!
|
55
55
|
nuspec_xml
|
56
56
|
end
|
57
57
|
|
58
|
-
def self.
|
59
|
-
|
60
|
-
|
58
|
+
def self.feed_supports_nuspec_download?(feed_url)
|
59
|
+
feed_regexs = [
|
60
|
+
# nuget
|
61
|
+
%r{https://api\.nuget\.org/v3/index\.json},
|
62
|
+
# azure devops
|
61
63
|
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json},
|
62
64
|
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)},
|
63
65
|
%r{https://(?<organization>[^\.\/]+)\.pkgs\.visualstudio\.com/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)}
|
64
66
|
]
|
65
|
-
|
67
|
+
feed_regexs.any? { |reg| reg.match(feed_url) }
|
66
68
|
end
|
67
69
|
|
68
70
|
def self.extract_nuspec(zip_stream, package_id)
|
@@ -73,8 +75,11 @@ module Dependabot
|
|
73
75
|
nil
|
74
76
|
end
|
75
77
|
|
76
|
-
def self.
|
77
|
-
string.
|
78
|
+
def self.remove_invalid_characters(string)
|
79
|
+
string.dup
|
80
|
+
.force_encoding(Encoding::UTF_8)
|
81
|
+
.encode
|
82
|
+
.scrub("")
|
78
83
|
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
|
79
84
|
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
|
80
85
|
end
|