Class: Dependabot::Maven::Shared::SharedPackageDetailsFetcher

Inherits:
Object
  • Object
show all
Extended by:
T::Helpers, T::Sig
Defined in:
lib/dependabot/maven/shared/shared_package_details_fetcher.rb

Direct Known Subclasses

Package::PackageDetailsFetcher

Constant Summary collapse

MAVEN_METADATA_XML =
T.let("maven-metadata.xml", String)
REPOSITORY_TYPE =
T.let("maven_repository", String)
URL_KEY =
T.let("url", String)
AUTH_HEADERS_KEY =
T.let("auth_headers", String)
DEFAULT_CENTRAL_REPO_URL =
T.let("https://repo.maven.apache.org/maven2", String)

Instance Method Summary collapse

Instance Method Details

#auth_headers(maven_repo_url) ⇒ Object



354
355
356
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 354

def auth_headers(maven_repo_url)
  auth_headers_finder.auth_headers(maven_repo_url)
end

#auth_headers_finderObject



359
360
361
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 359

def auth_headers_finder
  @auth_headers_finder ||= T.let(Utils::AuthHeadersFinder.new(credentials), T.nilable(Utils::AuthHeadersFinder))
end

#central_repo_urlObject



335
336
337
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 335

def central_repo_url
  DEFAULT_CENTRAL_REPO_URL
end

#central_repo_urlsObject



341
342
343
344
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 341

def central_repo_urls
  central_url_without_protocol = central_repo_url.gsub(%r{^.*://}, "")
  %w(http:// https://).map { |p| p + central_url_without_protocol }
end

#check_response(response, repository_url) ⇒ Object



178
179
180
181
182
183
184
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 178

def check_response(response, repository_url)
  return unless [401, 403].include?(response.status)
  return if forbidden_urls.include?(repository_url)
  return if central_repo_urls.include?(repository_url)

  forbidden_urls << repository_url
end

#credentialsObject



30
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 30

def credentials; end

#credentials_repository_detailsObject



320
321
322
323
324
325
326
327
328
329
330
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 320

def credentials_repository_details
  credentials
    .select { |cred| cred["type"] == REPOSITORY_TYPE && cred[URL_KEY] }
    .map do |cred|
      url_value = cred.fetch(URL_KEY).gsub(%r{/+$}, "")
      {
        URL_KEY => url_value,
        AUTH_HEADERS_KEY => auth_headers(url_value)
      }
    end
end

#dependencyObject



27
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 27

def dependency; end

#dependency_base_url(repository_url) ⇒ Object



58
59
60
61
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 58

def dependency_base_url(repository_url)
  group_path, artifact_id = dependency_parts
  "#{repository_url}/#{group_path}/#{artifact_id}"
end

#dependency_files_url(repository_url, version) ⇒ Object



74
75
76
77
78
79
80
81
82
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 74

def dependency_files_url(repository_url, version)
  _, artifact_id = dependency_parts
  base_url = dependency_base_url(repository_url)
  type = dependency.requirements.first&.dig(:metadata, :packaging_type) || "jar"
  classifier = dependency.requirements.first&.dig(:metadata, :classifier)
  actual_classifier = classifier.nil? ? "" : "-#{classifier}"

  "#{base_url}/#{version}/#{artifact_id}-#{version}#{actual_classifier}.#{type}"
end

#dependency_metadata(repository_details) ⇒ Object



367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 367

def (repository_details)
  @dependency_metadata = T.let(
    @dependency_metadata, T.nilable(T::Hash[T.untyped, Nokogiri::XML::Document])
  )
  @dependency_metadata ||= {}
  repository_key = repository_details.hash
  return @dependency_metadata[repository_key] if @dependency_metadata.key?(repository_key)

  xml_document = (repository_details)
  @dependency_metadata[repository_key] ||= xml_document if xml_document
  @dependency_metadata[repository_key]
end

#dependency_metadata_from_html(repository_details) ⇒ Object



382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 382

def (repository_details)
  @dependency_metadata_from_html = T.let(
    @dependency_metadata_from_html, T.nilable(T::Hash[T.untyped, Nokogiri::HTML::Document])
  )
  @dependency_metadata_from_html ||= {}
  repository_key = repository_details.hash
  return @dependency_metadata_from_html[repository_key] if @dependency_metadata_from_html.key?(repository_key)

  html_document = (repository_details)
  @dependency_metadata_from_html[repository_key] ||= html_document if html_document
  @dependency_metadata_from_html[repository_key]
end

#dependency_metadata_url(repository_url) ⇒ Object



65
66
67
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 65

def (repository_url)
  "#{dependency_base_url(repository_url)}/#{MAVEN_METADATA_XML}"
end

#dependency_partsObject



44
45
46
47
48
49
50
51
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 44

def dependency_parts
  @dependency_parts = T.let(@dependency_parts, T.nilable([String, String]))
  return @dependency_parts if @dependency_parts

  group_id, artifact_id = dependency.name.split(":")
  group_path = T.must(group_id).tr(".", "/")
  @dependency_parts = [group_path, T.must(artifact_id)]
end

#extract_metadata_from_xml(xml, url) ⇒ Object



114
115
116
117
118
119
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 114

def (xml, url)
  xml.css("versions > version")
     .select { |node| version_class.correct?(node.content) }
     .map { |node| version_class.new(node.content) }
     .map { |version| { version: version, source_url: url } }
end

#extract_version_details_from_html(html_doc) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 151

def extract_version_details_from_html(html_doc)
  versions_detail_hash = T.let({}, T::Hash[String, T::Hash[Symbol, T.untyped]])

  html_doc.css("a[title]").each do |link|
    version_string = link["title"]
    version = version_string.gsub(%r{/$}, "")

    raw_date_text = link.next.text.strip.split("\n").last.strip

    release_date = begin
      Time.parse(raw_date_text)
    rescue StandardError
      nil
    end

    next unless version && version_class.correct?(version)

    versions_detail_hash[version] = { release_date: release_date }
  end

  versions_detail_hash
end

#fetch_dependency_metadata(repository_details) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 88

def (repository_details)
  url = repository_details.fetch(URL_KEY)
  headers = repository_details.fetch(AUTH_HEADERS_KEY)
  response = Dependabot::RegistryClient.get(
    url: (url),
    headers: headers
  )
  check_response(response, url)
  return unless response.status < 400

  Nokogiri::XML(response.body)
rescue URI::InvalidURIError
  nil
rescue Excon::Error::Socket, Excon::Error::Timeout,
       Excon::Error::TooManyRedirects => e
  handle_registry_error(url, e, response)
  nil
end

#fetch_dependency_metadata_from_html(repository_details) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 127

def (repository_details)
  url = repository_details.fetch(URL_KEY)
  headers = repository_details.fetch(AUTH_HEADERS_KEY)
  response = Dependabot::RegistryClient.get(
    url: dependency_base_url(url),
    headers: headers
  )
  check_response(response, url)
  return unless response.status < 400

  Nokogiri::HTML(response.body)
rescue URI::InvalidURIError
  nil
rescue Excon::Error::Socket, Excon::Error::Timeout,
       Excon::Error::TooManyRedirects => e
  handle_registry_error(url, e, response)
  nil
end

#forbidden_urlsObject



347
348
349
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 347

def forbidden_urls
  @forbidden_urls ||= T.let([], T.nilable(T::Array[String]))
end

#handle_registry_error(url, error, response) ⇒ Object

Raises:

  • (RegistryError)


194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 194

def handle_registry_error(url, error, response)
  return unless central_repo_urls.include?(url)

  response_status = response&.status || 0
  response_body = if response
                    "RegistryError: #{response.status} response status with body #{response.body}"
                  else
                    "RegistryError: #{error.message}"
                  end

  raise RegistryError.new(response_status, response_body)
end

#released?(version) ⇒ Boolean

Returns:

  • (Boolean)


294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 294

def released?(version)
  @released_check = T.let(@released_check, T.nilable(T::Hash[Dependabot::Version, T::Boolean]))
  @released_check ||= {}
  return T.must(@released_check[version]) if @released_check.key?(version)

  @released_check[version] =
    repositories.any? do |repository_details|
      url = repository_details.fetch(URL_KEY)
      headers = repository_details.fetch(AUTH_HEADERS_KEY)
      response = Dependabot::RegistryClient.head(
        url: dependency_files_url(url, version),
        headers: headers
      )
      response.status < 400
    rescue Excon::Error::Socket, Excon::Error::Timeout,
           Excon::Error::TooManyRedirects
      false
    rescue URI::InvalidURIError => e
      raise DependencyFileNotResolvable, e.message
    end
end

#repositoriesObject



35
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 35

def repositories; end

#version_classObject



398
399
400
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 398

def version_class
  dependency.version_class
end

#versionsObject



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 213

def versions
  @version_details = T.let(@version_details, T.nilable(T::Array[T::Hash[Symbol, T.untyped]]))
  return @version_details if @version_details

  @version_details = versions_details_from_xml

  begin
    versions_details_hash = versions_details_hash_from_html if @version_details.any?

    if versions_details_hash
      @version_details = @version_details.map do |vd|
        html_details = versions_details_hash[vd[:version].to_s]

        next vd unless html_details

        release_date = html_details[:release_date]

        next vd unless release_date

        vd.merge(
          release_date: html_details[:release_date],
          source_url: vd[:source_url]
        )
      end
    end
  rescue StandardError => e
    Dependabot.logger.error(
      "Error fetching version details from HTML: #{e.message}"
    )
  end

  @version_details = @version_details.sort_by { |d| d.fetch(:version) }
  @version_details
end

#versions_details_from_xmlObject

Raises:

  • (PrivateSourceAuthenticationFailure)


250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 250

def versions_details_from_xml
  forbidden_urls.clear
  version_details = repositories.flat_map do |repository_details|
    url = repository_details.fetch(URL_KEY)
    xml = (repository_details)
    next [] if xml.nil?

    (xml, url)
  end

  raise PrivateSourceAuthenticationFailure, forbidden_urls.first if version_details.none? && forbidden_urls.any?

  version_details
end

#versions_details_hash_from_htmlObject



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/dependabot/maven/shared/shared_package_details_fetcher.rb', line 267

def versions_details_hash_from_html
  forbidden_urls.clear

  versions_detail_hash = T.let(
    {}, T::Hash[String, T::Hash[Symbol, T.untyped]]
  )
  repositories.each do |repository_details|
    html = (repository_details)
    next if html.nil?

    versions_detail_hash = extract_version_details_from_html(html)
    break if versions_detail_hash.any?
  end

  if versions_detail_hash.any? && forbidden_urls.any?
    raise PrivateSourceAuthenticationFailure,
          forbidden_urls.first
  end

  versions_detail_hash
end