ruby/lib/rubygems/package/tar_reader.rb
Aaron Patterson 12aa9e7457 [rubygems/rubygems] Use IO.copy_stream with IO object directly
Before this patch we would use `IO.copy_stream` with the tar entry
object rather than just straight to the IO.  That means every time
copy_stream wanted data, we would have to proxy the call.

The reason we did this is because every tar entry object _shares_ the
same IO object, and previous to https://github.com/rubygems/rubygems/commit/8927533b0a47
we would call `IO.copy_stream` _without_ a size.  Without passing a
size, copy_stream will just read until there is nothing left to read, so
these proxy object emulate finding "the end of the file" (where "end of
file" means "end of tar chunk").  Without emulating this "end of file"
behavior, copy_stream would just keep reading past the end of the tar
chunk.

However, now that we're passing the size to copy_stream, we can bypass
the proxy object overhead and just use the IO object directly because
copy_stream knows exactly the number of bytes it needs to read and will
stop when it reaches the goal.

https://github.com/rubygems/rubygems/commit/857002c135
2025-09-16 17:17:32 +09:00

104 lines
1.9 KiB
Ruby

# frozen_string_literal: true
# rubocop:disable Style/AsciiComments
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
# rubocop:enable Style/AsciiComments
##
# TarReader reads tar files and allows iteration over their items
class Gem::Package::TarReader
include Enumerable
##
# Creates a new TarReader on +io+ and yields it to the block, if given.
def self.new(io)
reader = super
return reader unless block_given?
begin
yield reader
ensure
reader.close
end
nil
end
attr_reader :io # :nodoc:
##
# Creates a new tar file reader on +io+ which needs to respond to #pos,
# #eof?, #read, #getc and #pos=
def initialize(io)
@io = io
@init_pos = io.pos
end
##
# Close the tar file
def close
end
##
# Iterates over files in the tarball yielding each entry
def each
return enum_for __method__ unless block_given?
until @io.eof? do
begin
header = Gem::Package::TarHeader.from @io
rescue ArgumentError => e
# Specialize only exceptions from Gem::Package::TarHeader.strict_oct
raise e unless e.message.match?(/ is not an octal string$/)
raise Gem::Package::TarInvalidError, e.message
end
return if header.empty?
entry = Gem::Package::TarReader::Entry.new header, @io
yield entry
entry.close
end
end
alias_method :each_entry, :each
##
# NOTE: Do not call #rewind during #each
def rewind
if @init_pos == 0
@io.rewind
else
@io.pos = @init_pos
end
end
##
# Seeks through the tar file until it finds the +entry+ with +name+ and
# yields it. Rewinds the tar file to the beginning when the block
# terminates.
def seek(name) # :yields: entry
found = find do |entry|
entry.full_name == name
end
return unless found
yield found
ensure
rewind
end
end
require_relative "tar_reader/entry"