Skip to content

Commit

Permalink
find .nupkg URL without PackageBaseAddress
Browse files Browse the repository at this point in the history
  • Loading branch information
brettfo committed Feb 23, 2024
1 parent 4229759 commit 2340e62
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 19 deletions.
14 changes: 14 additions & 0 deletions nuget/lib/dependabot/nuget/http_response_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# typed: true
# frozen_string_literal: true

module Dependabot
module Nuget
module HttpResponseHelpers
def self.remove_wrapping_zero_width_chars(string)
string.force_encoding("UTF-8").encode
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
end
end
end
end
10 changes: 2 additions & 8 deletions nuget/lib/dependabot/nuget/nuget_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# frozen_string_literal: true

require "dependabot/nuget/cache_manager"
require "dependabot/nuget/http_response_helpers"
require "dependabot/nuget/update_checker/repository_finder"
require "sorbet-runtime"

Expand Down Expand Up @@ -162,7 +163,7 @@ def self.get_package_versions(dependency_name, repository_details)
)
return unless response.status == 200

body = remove_wrapping_zero_width_chars(response.body)
body = HttpResponseHelpers.remove_wrapping_zero_width_chars(response.body)
JSON.parse(body)
end

Expand Down Expand Up @@ -193,13 +194,6 @@ def self.get_package_versions(dependency_name, repository_details)

raise PrivateSourceTimedOut, repo_url
end

sig { params(string: String).returns(String) }
private_class_method def self.remove_wrapping_zero_width_chars(string)
string.force_encoding("UTF-8").encode
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
end
end
end
end
50 changes: 49 additions & 1 deletion nuget/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "nokogiri"
require "zip"
require "stringio"
require "dependabot/nuget/http_response_helpers"

module Dependabot
module Nuget
Expand Down Expand Up @@ -43,11 +44,48 @@ def self.fetch_nupkg_buffer_from_repository(repository_details, package_id, pack
end

def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
base_url = repository_details[:base_url].delete_suffix("/")
base_url = repository_details[:base_url]
unless base_url
return get_nuget_v3_package_url_from_search(repository_details, package_id,
package_version)
end

base_url = base_url.delete_suffix("/")
package_id_downcased = package_id.downcase
"#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.#{package_version}.nupkg"
end

# rubocop:disable Metrics/PerceivedComplexity
def self.get_nuget_v3_package_url_from_search(repository_details, package_id, package_version)
search_url = repository_details[:search_url]
return nil unless search_url

# get search result
search_result_response = fetch_url(search_url, repository_details)
return nil unless search_result_response.status == 200

search_response_body = HttpResponseHelpers.remove_wrapping_zero_width_chars(search_result_response.body)
search_results = JSON.parse(search_response_body)

# find matching package and version
package_search_result = search_results&.[]("data")&.find { |d| package_id.casecmp?(d&.[]("id")) }
version_search_result = package_search_result&.[]("versions")&.find do |v|
package_version.casecmp?(v&.[]("version"))
end
registration_leaf_url = version_search_result&.[]("@id")

registration_leaf_response = fetch_url(registration_leaf_url, repository_details)
return nil unless registration_leaf_response.status == 200

registration_leaf_response_body =
HttpResponseHelpers.remove_wrapping_zero_width_chars(registration_leaf_response.body)
registration_leaf = JSON.parse(registration_leaf_response_body)

# finally, get the .nupkg url
registration_leaf&.[]("packageContent")
end
# rubocop:enable Metrics/PerceivedComplexity

def self.get_nuget_v2_package_url(feed_url, package_id, package_version)
base_url = feed_url
base_url += "/" unless base_url.end_with?("/")
Expand Down Expand Up @@ -86,6 +124,16 @@ def self.fetch_stream(stream_url, auth_header, max_redirects = 5)
end
end
end

def self.fetch_url(url, repository_details)
cache = CacheManager.cache("nupkg_fetcher_cache")
cache[url] ||= Dependabot::RegistryClient.get(
url: url,
headers: repository_details.fetch(:auth_header)
)

cache[url]
end
end
end
end
12 changes: 3 additions & 9 deletions nuget/lib/dependabot/nuget/update_checker/repository_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require "dependabot/update_checkers/base"
require "dependabot/registry_client"
require "dependabot/nuget/cache_manager"
require "dependabot/nuget/http_response_helpers"

module Dependabot
module Nuget
Expand Down Expand Up @@ -75,15 +76,14 @@ def build_url_for_details(repo_details)
check_repo_response(response, repo_details)
return unless response.status == 200

body = remove_wrapping_zero_width_chars(response.body)
body = HttpResponseHelpers.remove_wrapping_zero_width_chars(response.body)
parsed_json = JSON.parse(body)
base_url = base_url_from_v3_metadata(parsed_json)
resolved_base_url = base_url || repo_details.fetch(:url).gsub("/index.json", "-flatcontainer")
search_url = search_url_from_v3_metadata(parsed_json)
registration_url = registration_url_from_v3_metadata(parsed_json)

details = {
base_url: resolved_base_url,
base_url: base_url,
repository_url: repo_details.fetch(:url),
auth_header: auth_header_for_token(repo_details.fetch(:token)),
repository_type: "v3"
Expand Down Expand Up @@ -330,12 +330,6 @@ def expand_windows_style_environment_variables(string)
end
end

def remove_wrapping_zero_width_chars(string)
string.force_encoding("UTF-8").encode
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
end

def auth_header_for_token(token)
return {} unless token

Expand Down
76 changes: 76 additions & 0 deletions nuget/spec/dependabot/nuget/update_checker/nupkg_fetcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,82 @@

it { is_expected.to eq("https://nuget.pkg.github.com/some-namespace/download/newtonsoft.json/13.0.1/newtonsoft.json.13.0.1.nupkg") }
end

context "from a v3 feed that doesn't specify `PackageBaseAddress`" do
let(:feed_url) { "https://nuget.example.com/v3-without-package-base/index.json" }

before do
# initial `index.json` response; only provides `SearchQueryService` and not `PackageBaseAddress`
stub_request(:get, feed_url)
.to_return(
status: 200,
body: {
version: "3.0.0",
resources: [
{
"@id" => "https://nuget.example.com/query",
"@type" => "SearchQueryService"
}
]
}.to_json
)
# SearchQueryService
stub_request(:get, "https://nuget.example.com/query?q=newtonsoft.json&prerelease=true&semVerLevel=2.0.0")
.to_return(
status: 200,
body: {
totalHits: 2,
data: [
# this is a false match
{
registration: "not-used",
version: "42.42.42",
versions: [
{
version: "1.0.0",
"@id" => "not-used"
},
{
version: "42.42.42",
"@id" => "not-used"
}
],
id: "Newtonsoft.Json.False.Match"
},
# this is the real one
{
registration: "not-used",
version: "13.0.1",
versions: [
{
version: "12.0.1",
"@id" => "https://nuget.example.com/registration/newtonsoft.json/12.0.1.json"
},
{
version: "13.0.1",
"@id" => "https://nuget.example.com/registration/newtonsoft.json/13.0.1.json"
}
],
id: "Newtonsoft.Json"
}
]
}.to_json
)
# registration content
stub_request(:get, "https://nuget.example.com/registration/newtonsoft.json/13.0.1.json")
.to_return(
status: 200,
body: {
listed: true,
packageContent: "https://nuget.example.com/nuget-local/Download/newtonsoft.json.13.0.1.nupkg",
registration: "not-used",
"@id" => "not-used"
}.to_json
)
end

it { is_expected.to eq("https://nuget.example.com/nuget-local/Download/newtonsoft.json.13.0.1.nupkg") }
end
end

describe "#fetch_nupkg_buffer" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
it "gets the right URL" do
expect(dependency_urls).to eq(
[{
base_url: "http://localhost:8082/artifactory/api/nuget/v3/nuget-local",
base_url: nil,
registration_url: "http://localhost:8081/artifactory/api/nuget/v3/" \
"dependabot-nuget-local/registration/microsoft.extensions.dependencymodel/index.json",
repository_url: custom_repo_url,
Expand Down

0 comments on commit 2340e62

Please sign in to comment.