123456789_123456789_123456789_123456789_123456789_

Class: Bundler::CompactIndexClient::Updater

Relationships & Source Files
Namespace Children
Exceptions:
Inherits: Object
Defined in: lib/bundler/compact_index_client/updater.rb

Class Method Summary

Instance Method Summary

Constructor Details

.new(fetcher) ⇒ Updater

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 12

def initialize(fetcher)
  @fetcher = fetcher
end

Instance Method Details

#append(remote_path, local_path, etag_path) (private)

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 26

def append(remote_path, local_path, etag_path)
  return false unless local_path.file? && local_path.size.nonzero?

  CacheFile.copy(local_path) do |file|
    etag = etag_path.read.tap(&:chomp!) if etag_path.file?

    # Subtract a byte to ensure the range won't be empty.
    # Avoids 416 (Range Not Satisfiable) responses.
    response = @fetcher.call(remote_path, request_headers(etag, file.size - 1))
    break true if response.is_a?(Gem::Net::HTTPNotModified)

    file.digests = parse_digests(response)
    # server may ignore Range and return the full response
    if response.is_a?(Gem::Net::HTTPPartialContent)
      break false unless file.append(response.body.byteslice(1..-1))
    else
      file.write(response.body)
    end
    CacheFile.write(etag_path, etag_from_response(response))
    true
  end
end

#byte_sequence(value) (private)

Unwrap surrounding colons (byte sequence) The wrapping characters must be matched or we return nil. Also handles quotes because right now rubygems.org sends them.

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 97

def byte_sequence(value)
  return if value.delete_prefix!(":") && !value.delete_suffix!(":")
  return if value.delete_prefix!('"') && !value.delete_suffix!('"')
  value
end

#etag_for_request(etag_path) (private)

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 65

def etag_for_request(etag_path)
  etag_path.read.tap(&:chomp!) if etag_path.file?
end

#etag_from_response(response) (private)

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 69

def etag_from_response(response)
  return unless response["ETag"]
  etag = response["ETag"].delete_prefix("W/")
  return if etag.delete_prefix!('"') && !etag.delete_suffix!('"')
  etag
end

#parse_digests(response) (private)

Unwraps and returns a Hash of digest algorithms and base64 values according to RFC 8941 Structured Field Values for HTTP. www.rfc-editor.org/rfc/rfc8941#name-parsing-a-byte-sequence Ignores unsupported algorithms.

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 80

def parse_digests(response)
  return unless header = response["Repr-Digest"] || response["Digest"]
  digests = {}
  header.split(",") do |param|
    algorithm, value = param.split("=", 2)
    algorithm.strip!
    algorithm.downcase!
    next unless SUPPORTED_DIGESTS.key?(algorithm)
    next unless value = byte_sequence(value)
    digests[algorithm] = value
  end
  digests.empty? ? nil : digests
end

#replace(remote_path, local_path, etag_path) (private)

request without range header to get the full file or a 304 Not Modified

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 50

def replace(remote_path, local_path, etag_path)
  etag = etag_path.read.tap(&:chomp!) if etag_path.file?
  response = @fetcher.call(remote_path, request_headers(etag))
  return true if response.is_a?(Gem::Net::HTTPNotModified)
  CacheFile.write(local_path, response.body, parse_digests(response))
  CacheFile.write(etag_path, etag_from_response(response))
end

#request_headers(etag, range_start = nil) (private)

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 58

def request_headers(etag, range_start = nil)
  headers = {}
  headers["Range"] = "bytes=#{range_start}-" if range_start
  headers["If-None-Match"] = %("#{etag}") if etag
  headers
end

#update(remote_path, local_path, etag_path)

[ GitHub ]

  
# File 'lib/bundler/compact_index_client/updater.rb', line 16

def update(remote_path, local_path, etag_path)
  append(remote_path, local_path, etag_path) || replace(remote_path, local_path, etag_path)
rescue CacheFile::DigestMismatchError => e
  raise MismatchedChecksumError.new(remote_path, e.message)
rescue Zlib::GzipFile::Error
  raise Bundler::HTTPError
end