dependabot-nuget 0.245.0 → 0.246.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +42 -7
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +164 -90
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +38 -2
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +57 -17
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +115 -14
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/{UpdateWorker.DirsProj.cs → UpdateWorkerTests.DirsProj.cs} +22 -24
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +66 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +355 -83
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +7 -4
  12. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +0 -4
  13. data/lib/dependabot/nuget/file_parser.rb +15 -1
  14. data/lib/dependabot/nuget/http_response_helpers.rb +14 -0
  15. data/lib/dependabot/nuget/metadata_finder.rb +6 -2
  16. data/lib/dependabot/nuget/nuget_client.rb +8 -13
  17. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +23 -13
  18. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +75 -11
  19. data/lib/dependabot/nuget/update_checker/repository_finder.rb +4 -10
  20. data/lib/dependabot/nuget/update_checker.rb +2 -3
  21. metadata +7 -7
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +0 -317
@@ -36,7 +36,9 @@ public class MSBuildHelperTests
36
36
  };
37
37
 
38
38
  // Act
39
- var rootValue = MSBuildHelper.GetRootedValue(projectContents, propertyInfo);
39
+ var (resultType, evaluatedValue, _) = MSBuildHelper.GetEvaluatedValue(projectContents, propertyInfo);
40
+
41
+ Assert.Equal(MSBuildHelper.EvaluationResultType.Success, resultType);
40
42
 
41
43
  // Assert
42
44
  Assert.Equal("""
@@ -48,7 +50,7 @@ public class MSBuildHelperTests
48
50
  <PackageReference Include="Newtonsoft.Json" Version="1.1.1" />
49
51
  </ItemGroup>
50
52
  </Project>
51
- """, rootValue);
53
+ """, evaluatedValue);
52
54
  }
53
55
 
54
56
  [Fact(Timeout = 1000)]
@@ -74,10 +76,11 @@ public class MSBuildHelperTests
74
76
  await Task.Delay(1);
75
77
 
76
78
  // Act
77
- var ex = Assert.Throws<InvalidDataException>(() => MSBuildHelper.GetRootedValue(projectContents, propertyInfo));
79
+ var (resultType, _, errorMessage) = MSBuildHelper.GetEvaluatedValue(projectContents, propertyInfo);
78
80
 
79
81
  // Assert
80
- Assert.Equal("Property 'PackageVersion1' has a circular reference.", ex.Message);
82
+ Assert.Equal(MSBuildHelper.EvaluationResultType.CircularReference, resultType);
83
+ Assert.Equal("Property 'PackageVersion1' has a circular reference.", errorMessage);
81
84
  }
82
85
 
83
86
  [Theory]
@@ -408,10 +408,6 @@ module Dependabot
408
408
  version = dependency_version(package_node, file)
409
409
  next unless name && version
410
410
 
411
- version = Version.new(version)
412
- existing_version = package_versions[name]
413
- next if existing_version && existing_version > version
414
-
415
411
  package_versions[name] = version
416
412
  end
417
413
  end
@@ -30,7 +30,21 @@ module Dependabot
30
30
  dependency_set += packages_config_dependencies
31
31
  dependency_set += global_json_dependencies if global_json
32
32
  dependency_set += dotnet_tools_json_dependencies if dotnet_tools_json
33
- dependency_set.dependencies
33
+
34
+ (dependencies, deps_with_unresolved_versions) = dependency_set.dependencies.partition do |d|
35
+ # try to parse the version; don't care about result, just that it succeeded
36
+ _ = Version.new(d.version)
37
+ true
38
+ rescue ArgumentError
39
+ # version could not be parsed
40
+ false
41
+ end
42
+
43
+ deps_with_unresolved_versions.each do |d|
44
+ Dependabot.logger.warn "Dependency '#{d.name}' excluded due to unparsable version: #{d.version}"
45
+ end
46
+
47
+ dependencies
34
48
  end
35
49
 
36
50
  private
@@ -0,0 +1,14 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Nuget
6
+ module HttpResponseHelpers
7
+ def self.remove_wrapping_zero_width_chars(string)
8
+ string.force_encoding("UTF-8").encode
9
+ .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
10
+ .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -17,8 +17,10 @@ module Dependabot
17
17
  def look_up_source
18
18
  return Source.from_url(dependency_source_url) if dependency_source_url
19
19
 
20
- src_repo = look_up_source_in_nuspec(dependency_nuspec_file)
21
- return src_repo if src_repo
20
+ if dependency_nuspec_file
21
+ src_repo = look_up_source_in_nuspec(dependency_nuspec_file)
22
+ return src_repo if src_repo
23
+ end
22
24
 
23
25
  # Fallback to getting source from the search result's projectUrl or licenseUrl.
24
26
  # GitHub Packages doesn't support getting the `.nuspec`, switch to getting
@@ -113,6 +115,8 @@ module Dependabot
113
115
  def dependency_nuspec_file
114
116
  return @dependency_nuspec_file unless @dependency_nuspec_file.nil?
115
117
 
118
+ return if dependency_nuspec_url.nil?
119
+
116
120
  response = Dependabot::RegistryClient.get(
117
121
  url: dependency_nuspec_url,
118
122
  headers: auth_header
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/nuget/cache_manager"
5
+ require "dependabot/nuget/http_response_helpers"
5
6
  require "dependabot/nuget/update_checker/repository_finder"
6
7
  require "sorbet-runtime"
7
8
 
@@ -52,14 +53,15 @@ module Dependabot
52
53
  doc = execute_xml_nuget_request(repository_details.fetch(:versions_url), repository_details)
53
54
  return unless doc
54
55
 
55
- id_nodes = doc.xpath("/feed/entry/properties/Id")
56
+ # v2 APIs can differ, but all tested have this title value set to the name of the package
57
+ title_nodes = doc.xpath("/feed/entry/title")
56
58
  matching_versions = Set.new
57
- id_nodes.each do |id_node|
58
- return nil unless id_node.text
59
+ title_nodes.each do |title_node|
60
+ return nil unless title_node.text
59
61
 
60
- next unless id_node.text.casecmp?(dependency_name)
62
+ next unless title_node.text.casecmp?(dependency_name)
61
63
 
62
- version_node = id_node.parent.xpath("Version")
64
+ version_node = title_node.parent.xpath("properties/Version")
63
65
  matching_versions << version_node.text if version_node && version_node.text
64
66
  end
65
67
 
@@ -162,7 +164,7 @@ module Dependabot
162
164
  )
163
165
  return unless response.status == 200
164
166
 
165
- body = remove_wrapping_zero_width_chars(response.body)
167
+ body = HttpResponseHelpers.remove_wrapping_zero_width_chars(response.body)
166
168
  JSON.parse(body)
167
169
  end
168
170
 
@@ -193,13 +195,6 @@ module Dependabot
193
195
 
194
196
  raise PrivateSourceTimedOut, repo_url
195
197
  end
196
-
197
- sig { params(string: String).returns(String) }
198
- private_class_method def self.remove_wrapping_zero_width_chars(string)
199
- string.force_encoding("UTF-8").encode
200
- .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
201
- .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
202
- end
203
198
  end
204
199
  end
205
200
  end
@@ -37,19 +37,29 @@ module Dependabot
37
37
  key = "#{dependency.name.downcase}::#{dependency.version}"
38
38
  cache = DependencyFinder.transitive_dependencies_cache
39
39
 
40
- cache[key] ||= fetch_transitive_dependencies(
41
- @dependency.name,
42
- @dependency.version
43
- ).map do |dependency_info|
44
- package_name = dependency_info["packageName"]
45
- target_version = dependency_info["version"]
46
-
47
- Dependency.new(
48
- name: package_name,
49
- version: target_version.to_s,
50
- requirements: [], # Empty requirements for transitive dependencies
51
- package_manager: @dependency.package_manager
52
- )
40
+ unless cache[key]
41
+ begin
42
+ # first do a quick sanity check on the version string; if it can't be parsed, an exception will be raised
43
+ _ = Version.new(dependency.version)
44
+
45
+ cache[key] = fetch_transitive_dependencies(
46
+ @dependency.name,
47
+ @dependency.version
48
+ ).map do |dependency_info|
49
+ package_name = dependency_info["packageName"]
50
+ target_version = dependency_info["version"]
51
+
52
+ Dependency.new(
53
+ name: package_name,
54
+ version: target_version.to_s,
55
+ requirements: [], # Empty requirements for transitive dependencies
56
+ package_manager: @dependency.package_manager
57
+ )
58
+ end
59
+ rescue StandardError
60
+ # if anything happened above, there are no meaningful dependencies that can be derived
61
+ cache[key] = []
62
+ end
53
63
  end
54
64
 
55
65
  cache[key]
@@ -4,6 +4,7 @@
4
4
  require "nokogiri"
5
5
  require "zip"
6
6
  require "stringio"
7
+ require "dependabot/nuget/http_response_helpers"
7
8
 
8
9
  module Dependabot
9
10
  module Nuget
@@ -24,7 +25,7 @@ module Dependabot
24
25
  repository_type = repository_details[:repository_type]
25
26
 
26
27
  package_url = if repository_type == "v2"
27
- get_nuget_v2_package_url(feed_url, package_id, package_version)
28
+ get_nuget_v2_package_url(repository_details, package_id, package_version)
28
29
  elsif repository_type == "v3"
29
30
  get_nuget_v3_package_url(repository_details, package_id, package_version)
30
31
  else
@@ -43,16 +44,66 @@ module Dependabot
43
44
  end
44
45
 
45
46
  def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
46
- base_url = repository_details[:base_url].delete_suffix("/")
47
+ base_url = repository_details[:base_url]
48
+ unless base_url
49
+ return get_nuget_v3_package_url_from_search(repository_details, package_id,
50
+ package_version)
51
+ end
52
+
53
+ base_url = base_url.delete_suffix("/")
47
54
  package_id_downcased = package_id.downcase
48
55
  "#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.#{package_version}.nupkg"
49
56
  end
50
57
 
51
- def self.get_nuget_v2_package_url(feed_url, package_id, package_version)
52
- base_url = feed_url
53
- base_url += "/" unless base_url.end_with?("/")
54
- package_id_downcased = package_id.downcase
55
- "#{base_url}/package/#{package_id_downcased}/#{package_version}"
58
+ # rubocop:disable Metrics/CyclomaticComplexity
59
+ # rubocop:disable Metrics/PerceivedComplexity
60
+ def self.get_nuget_v3_package_url_from_search(repository_details, package_id, package_version)
61
+ search_url = repository_details[:search_url]
62
+ return nil unless search_url
63
+
64
+ # get search result
65
+ search_result_response = fetch_url(search_url, repository_details)
66
+ return nil unless search_result_response.status == 200
67
+
68
+ search_response_body = HttpResponseHelpers.remove_wrapping_zero_width_chars(search_result_response.body)
69
+ search_results = JSON.parse(search_response_body)
70
+
71
+ # find matching package and version
72
+ package_search_result = search_results&.[]("data")&.find { |d| package_id.casecmp?(d&.[]("id")) }
73
+ version_search_result = package_search_result&.[]("versions")&.find do |v|
74
+ package_version.casecmp?(v&.[]("version"))
75
+ end
76
+ registration_leaf_url = version_search_result&.[]("@id")
77
+ return nil unless registration_leaf_url
78
+
79
+ registration_leaf_response = fetch_url(registration_leaf_url, repository_details)
80
+ return nil unless registration_leaf_response
81
+ return nil unless registration_leaf_response.status == 200
82
+
83
+ registration_leaf_response_body =
84
+ HttpResponseHelpers.remove_wrapping_zero_width_chars(registration_leaf_response.body)
85
+ registration_leaf = JSON.parse(registration_leaf_response_body)
86
+
87
+ # finally, get the .nupkg url
88
+ registration_leaf&.[]("packageContent")
89
+ end
90
+ # rubocop:enable Metrics/PerceivedComplexity
91
+ # rubocop:enable Metrics/CyclomaticComplexity
92
+
93
+ def self.get_nuget_v2_package_url(repository_details, package_id, package_version)
94
+ # get package XML
95
+ base_url = repository_details[:base_url].delete_suffix("/")
96
+ package_url = "#{base_url}/Packages(Id='#{package_id}',Version='#{package_version}')"
97
+ response = fetch_url(package_url, repository_details)
98
+ return nil unless response.status == 200
99
+
100
+ # find relevant element
101
+ doc = Nokogiri::XML(response.body)
102
+ doc.remove_namespaces!
103
+
104
+ content_element = doc.xpath("/entry/content")
105
+ nupkg_url = content_element&.attribute("src")&.value
106
+ nupkg_url
56
107
  end
57
108
 
58
109
  def self.fetch_stream(stream_url, auth_header, max_redirects = 5)
@@ -73,19 +124,32 @@ module Dependabot
73
124
  response_block: response_block
74
125
  )
75
126
 
76
- if response.status == 303 || response.status == 307
127
+ # redirect the HTTP response as appropriate based on documentation here:
128
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
129
+ case response.status
130
+ when 200
131
+ package_data.rewind
132
+ return package_data
133
+ when 301, 302, 303, 307, 308
77
134
  current_redirects += 1
78
135
  return nil if current_redirects > max_redirects
79
136
 
80
137
  current_url = response.headers["Location"]
81
- elsif response.status == 200
82
- package_data.rewind
83
- return package_data
84
138
  else
85
139
  return nil
86
140
  end
87
141
  end
88
142
  end
143
+
144
+ def self.fetch_url(url, repository_details)
145
+ cache = CacheManager.cache("nupkg_fetcher_cache")
146
+ cache[url] ||= Dependabot::RegistryClient.get(
147
+ url: url,
148
+ headers: repository_details.fetch(:auth_header)
149
+ )
150
+
151
+ cache[url]
152
+ end
89
153
  end
90
154
  end
91
155
  end
@@ -7,6 +7,7 @@ require "dependabot/errors"
7
7
  require "dependabot/update_checkers/base"
8
8
  require "dependabot/registry_client"
9
9
  require "dependabot/nuget/cache_manager"
10
+ require "dependabot/nuget/http_response_helpers"
10
11
 
11
12
  module Dependabot
12
13
  module Nuget
@@ -75,15 +76,14 @@ module Dependabot
75
76
  check_repo_response(response, repo_details)
76
77
  return unless response.status == 200
77
78
 
78
- body = remove_wrapping_zero_width_chars(response.body)
79
+ body = HttpResponseHelpers.remove_wrapping_zero_width_chars(response.body)
79
80
  parsed_json = JSON.parse(body)
80
81
  base_url = base_url_from_v3_metadata(parsed_json)
81
- resolved_base_url = base_url || repo_details.fetch(:url).gsub("/index.json", "-flatcontainer")
82
82
  search_url = search_url_from_v3_metadata(parsed_json)
83
83
  registration_url = registration_url_from_v3_metadata(parsed_json)
84
84
 
85
85
  details = {
86
- base_url: resolved_base_url,
86
+ base_url: base_url,
87
87
  repository_url: repo_details.fetch(:url),
88
88
  auth_header: auth_header_for_token(repo_details.fetch(:token)),
89
89
  repository_type: "v3"
@@ -171,7 +171,7 @@ module Dependabot
171
171
  base_url: base_url,
172
172
  repository_url: base_url,
173
173
  versions_url: File.join(
174
- base_url,
174
+ base_url.delete_suffix("/"),
175
175
  "FindPackagesById()?id='#{dependency.name}'"
176
176
  ),
177
177
  auth_header: auth_header_for_token(repo_details.fetch(:token)),
@@ -330,12 +330,6 @@ module Dependabot
330
330
  end
331
331
  end
332
332
 
333
- def remove_wrapping_zero_width_chars(string)
334
- string.force_encoding("UTF-8").encode
335
- .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
336
- .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
337
- end
338
-
339
333
  def auth_header_for_token(token)
340
334
  return {} unless token
341
335
 
@@ -1,4 +1,4 @@
1
- # typed: false
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/nuget/file_parser"
@@ -66,9 +66,8 @@ module Dependabot
66
66
  # If any requirements have an uninterpolated property in them then
67
67
  # that property couldn't be found, and the requirement therefore
68
68
  # cannot be unlocked (since we can't update that property)
69
- namespace = Nuget::FileParser::PropertyValueFinder
70
69
  dependency.requirements.none? do |req|
71
- req.fetch(:requirement)&.match?(namespace::PROPERTY_REGEX)
70
+ req.fetch(:requirement)&.match?(Nuget::FileParser::PropertyValueFinder::PROPERTY_REGEX)
72
71
  end
73
72
  end
74
73
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.245.0
4
+ version: 0.246.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-22 00:00:00.000000000 Z
11
+ date: 2024-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.245.0
19
+ version: 0.246.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.245.0
26
+ version: 0.246.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubyzip
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -292,8 +292,8 @@ files:
292
292
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs
293
293
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestExtensions.cs
294
294
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs
295
- - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs
296
295
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs
296
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DirsProj.cs
297
297
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs
298
298
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs
299
299
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs
@@ -302,7 +302,6 @@ files:
302
302
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs
303
303
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs
304
304
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterHelperTests.cs
305
- - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs
306
305
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs
307
306
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyType.cs
308
307
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/BuildFile.cs
@@ -349,6 +348,7 @@ files:
349
348
  - lib/dependabot/nuget/file_parser/property_value_finder.rb
350
349
  - lib/dependabot/nuget/file_updater.rb
351
350
  - lib/dependabot/nuget/file_updater/property_value_updater.rb
351
+ - lib/dependabot/nuget/http_response_helpers.rb
352
352
  - lib/dependabot/nuget/metadata_finder.rb
353
353
  - lib/dependabot/nuget/native_helpers.rb
354
354
  - lib/dependabot/nuget/nuget_client.rb
@@ -371,7 +371,7 @@ licenses:
371
371
  - Nonstandard
372
372
  metadata:
373
373
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
374
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.245.0
374
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.246.0
375
375
  post_install_message:
376
376
  rdoc_options: []
377
377
  require_paths: