Wraps a Archive::Tar::Minitar::Reader with convenience methods and wrapped stream management; Input only works with data streams that can be rewound.
Returns the Reader object for direct access.
Iterates over each entry in the provided input. This wraps the common pattern of:
Archive::Tar::Minitar::Input.open(io) do |i| inp.each do |entry| # ... end end
If a block is not provided, an enumerator will be created with the same behaviour.
# File lib/archive/tar/minitar/input.rb, line 47 def self.each_entry(input) return to_enum(__method__, input) unless block_given? open(input) do |stream| stream.each do |entry| yield entry end end end
Creates a new Input object. If input
is a stream object that responds to read, then it will simply be wrapped.
Otherwise, one will be created and opened using Kernel#open. When #close is called, the stream object
wrapped will be closed.
An exception will be raised if the stream that is wrapped does not support rewinding.
# File lib/archive/tar/minitar/input.rb, line 68 def initialize(input) @io = if input.respond_to?(:read) input else ::Kernel.open(input, 'rb') end unless Archive::Tar::Minitar.seekable?(@io, :rewind) raise Archive::Tar::Minitar::NonSeekableStream end @tar = Reader.new(@io) end
With no associated block, Input.open
is a synonym for
Input.new
. If the optional code block is given, it will be
given the new Input as an argument and the Input object will automatically be closed when the
block terminates (this also closes the wrapped stream object). In this
instance, Input.open
returns the value of the block.
# File lib/archive/tar/minitar/input.rb, line 19 def self.open(input) stream = new(input) return stream unless block_given? # This exception context must remain, otherwise the stream closes on open # even if a block is not given. begin yield stream ensure stream.close end end
Closes both the Reader object and the wrapped data stream.
# File lib/archive/tar/minitar/input.rb, line 204 def close @io.close @tar.close end
Returns false if the wrapped data stream is open.
# File lib/archive/tar/minitar/input.rb, line 196 def closed? @io.closed? end
When provided a block, iterates through each entry in the archive. When finished, rewinds to the beginning of the stream.
If not provided a block, creates an enumerator with the same semantics.
# File lib/archive/tar/minitar/input.rb, line 86 def each_entry return to_enum unless block_given? @tar.each do |entry| yield entry end ensure @tar.rewind end
Extracts the current entry
to destdir
. If a block
is provided, it yields an action
Symbol, the full name of the
file being extracted (name
), and a Hash of statistical
information (stats
).
The action
will be one of:
:dir
The entry
is a directory.
:file_start
The entry
is a file; the extract of the file is just
beginning.
:file_progress
Yielded every 4096 bytes during the extract of the entry
.
:file_done
Yielded when the entry
is completed.
The stats
hash contains the following keys:
:current
The current total number of bytes read in the entry
.
:currinc
The current number of bytes read in this read cycle.
:entry
The entry being extracted; this is a Reader::EntryStream, with all methods thereof.
# File lib/archive/tar/minitar/input.rb, line 116 def extract_entry(destdir, entry) # :yields action, name, stats: stats = { :current => 0, :currinc => 0, :entry => entry } # extract_entry is not vulnerable to prefix '/' vulnerabilities, but it # is vulnerable to relative path directories. This code will break this # vulnerability. For this version, we are breaking relative paths HARD by # throwing an exception. # # Future versions may permit relative paths as long as the file does not # leave +destdir+. # # However, squeeze consecutive '/' characters together. full_name = entry.full_name.squeeze('/') if full_name =~ /\.{2}(?:\/|\z)/ raise SecureRelativePathError, %q(Path contains '..') end if entry.directory? dest = File.join(destdir, full_name) yield :dir, full_name, stats if block_given? if Archive::Tar::Minitar.dir?(dest) begin FileUtils.chmod(entry.mode, dest) rescue nil end else File.unlink(dest.chomp('/')) if File.symlink?(dest.chomp('/')) FileUtils.mkdir_p(dest, :mode => entry.mode) FileUtils.chmod(entry.mode, dest) end fsync_dir(dest) fsync_dir(File.join(dest, '..')) return else # it's a file destdir = File.join(destdir, File.dirname(full_name)) FileUtils.mkdir_p(destdir, :mode => 0o755) destfile = File.join(destdir, File.basename(full_name)) File.unlink(destfile) if File.symlink?(destfile) # Errno::ENOENT # rubocop:disable Style/RescueModifier FileUtils.chmod(0o600, destfile) rescue nil # rubocop:enable Style/RescueModifier yield :file_start, full_name, stats if block_given? File.open(destfile, 'wb', entry.mode) do |os| loop do data = entry.read(4096) break unless data stats[:currinc] = os.write(data) stats[:current] += stats[:currinc] yield :file_progress, full_name, stats if block_given? end os.fsync end FileUtils.chmod(entry.mode, destfile) fsync_dir(File.dirname(destfile)) fsync_dir(File.join(File.dirname(destfile), '..')) yield :file_done, full_name, stats if block_given? end end