summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiovanni Tirloni <gtirloni@tirloni.net>2026-01-27 15:04:30 -0300
committergit <svn-admin@ruby-lang.org>2026-02-04 09:50:27 +0000
commit1258992bdff8818d3ce868e98ccc3496069d996d (patch)
tree7614b7a4bd2d2e129ef2f3dea6d7cf4f585230c6
parent9cc2242ee269fcb4d1fd10887a8270c59eb161fc (diff)
[ruby/rubygems] Fix gzip cache corruption when recovering from HTTP 416 responses
When a Range request returns 416 (Range Not Satisfiable), the recovery path manually added an "Accept-Encoding: gzip" header before retrying. This bypasses Ruby's automatic gzip decompression mechanism. Ruby's Net::HTTP only sets decode_content=true (enabling automatic decompression) when Accept-Encoding is NOT present in the request headers. By manually setting this header, the decode_content flag remained false, causing gzip-compressed response bodies to be written directly to the compact index cache without decompression. This resulted in "ArgumentError: invalid byte sequence in UTF-8" errors when the corrupted cache was later read and parsed as text. The fix removes the explicit Accept-Encoding header, allowing Ruby's Net::HTTP to handle gzip compression transparently (as it does for all other requests). Ruby will still add Accept-Encoding: gzip automatically AND properly set decode_content=true for automatic decompression. Fixes https://github.com/ruby/rubygems/pull/9271 https://github.com/ruby/rubygems/commit/b48b090e38
-rw-r--r--lib/bundler/fetcher/downloader.rb1
-rw-r--r--spec/bundler/bundler/fetcher/downloader_spec.rb32
2 files changed, 32 insertions, 1 deletions
diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb
index 2eac6e7975..179eed8340 100644
--- a/lib/bundler/fetcher/downloader.rb
+++ b/lib/bundler/fetcher/downloader.rb
@@ -54,7 +54,6 @@ module Bundler
when Gem::Net::HTTPRequestedRangeNotSatisfiable
new_headers = headers.dup
new_headers.delete("Range")
- new_headers["Accept-Encoding"] = "gzip"
fetch(uri, new_headers)
when Gem::Net::HTTPRequestEntityTooLarge
raise FallbackError, response.body
diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb
index 36b9b94990..edf426328a 100644
--- a/spec/bundler/bundler/fetcher/downloader_spec.rb
+++ b/spec/bundler/bundler/fetcher/downloader_spec.rb
@@ -126,6 +126,38 @@ RSpec.describe Bundler::Fetcher::Downloader do
end
end
+ context "when the request response is a Gem::Net::HTTPRequestedRangeNotSatisfiable" do
+ let(:http_response) { Gem::Net::HTTPRequestedRangeNotSatisfiable.new("1.1", 416, "Range Not Satisfiable") }
+ let(:success_response) { Gem::Net::HTTPSuccess.new("1.1", 200, "Success") }
+ let(:options) { { "Range" => "bytes=1000-", "If-None-Match" => "some-etag" } }
+
+ before do
+ # First request returns 416, retry request returns success
+ allow(subject).to receive(:request).with(uri, options).and_return(http_response)
+ allow(subject).to receive(:request).with(uri, { "If-None-Match" => "some-etag" }).and_return(success_response)
+ end
+
+ # The 416 handler removes the Range header and retries without incrementing the counter.
+ # Importantly, it does NOT add Accept-Encoding header, which would break Ruby's
+ # automatic gzip decompression (see issue #9271 for details on that bug).
+ it "should retry the request without the Range header" do
+ expect(subject).to receive(:request).with(uri, options).ordered
+ expect(subject).to receive(:request).with(uri, hash_excluding("Range", "Accept-Encoding")).ordered
+ subject.fetch(uri, options, counter)
+ end
+
+ it "should preserve other headers on retry" do
+ expect(subject).to receive(:request).with(uri, options).ordered
+ expect(subject).to receive(:request).with(uri, hash_including("If-None-Match" => "some-etag")).ordered
+ subject.fetch(uri, options, counter)
+ end
+
+ it "should return the successful response" do
+ result = subject.fetch(uri, options, counter)
+ expect(result).to eq(success_response)
+ end
+ end
+
context "when the request response is some other type" do
let(:http_response) { Gem::Net::HTTPBadGateway.new("1.1", 500, "Fatal Error") }