class Minitar::Input
Minitar::Input.new.
This notice applies to Minitar::Input.open, Minitar::Input.each_entry, and
* {CWE-088}[https://cwe.mitre.org/data/definitions/88.html]
* {CWE-078}[https://cwe.mitre.org/data/definitions/78.html]
* {CWE-073}[https://cwe.mitre.org/data/definitions/73.html]
that the input value is safe.
user to execute arbitrary system commands. It is the caller’s responsibility to ensure
a readable stream object. Using an untrusted value for input may allow a malicious
Constructing a Minitar::Input will use Kernel.open if the provided input is not
=== Security Notice
only works with data streams that can be rewound.
Wraps a Minitar::Reader with convenience methods and wrapped stream management; Input
def self.each_entry(input)
Minitar::Input.each_entry(io) -> enumerator
:call-seq:
If a block is not provided, an enumerator will be created with the same behaviour.
end
end
# ...
inp.each do |entry|
Minitar::Input.open(io) do |i|
Iterates over each entry in the provided input. This wraps the common pattern of:
def self.each_entry(input) return to_enum(__method__, input) unless block_given? Input.open(input) do |stream| stream.each do |entry| yield entry end end end
def self.open(input)
Minitar::Input.open(io) -> input
:call-seq:
closes the wrapped stream object). The return value will be the value of the block.
the Input object will automatically be closed when the block terminates (this also
If a block is given, the new Input will be yielded to the block as an argument and
With no associated block, +Input.open+ is a synonym for +Input.new+.
def self.open(input) stream = new(input) if 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 else stream end end
def close
def close @io.close @tar.close end
def closed? = @io.closed?
def closed? = @io.closed?
def each_entry
rewinds to the beginning of the stream.
When provided a block, iterates through each entry in the archive. When finished,
def each_entry return to_enum unless block_given? @tar.each do |entry| yield entry end ensure @tar.rewind end
def extract_directory(destdir, full_name, entry, stats, options)
def extract_directory(destdir, full_name, entry, stats, options) dest = File.join(destdir, full_name) yield :dir, full_name, stats if block_given? if 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 if options.fetch(:fsync, true) fsync_dir(dest) fsync_dir(File.join(dest, "..")) end end
def extract_entry(destdir, entry, options = {}, &) # :yields: action, name, stats
all methods thereof.
:entry:: The entry being extracted; this is a Reader::EntryStream, with
:currinc:: The current number of bytes read in this read cycle.
:current:: The current total number of bytes read in the +entry+.
The +stats+ hash contains the following keys:
:file_done:: Yielded when the +entry+ is completed.
+entry+.
:file_progress:: Yielded every 4096 bytes during the extract of the
beginning.
:file_start:: The +entry+ is a file; the extract of the file is just
:dir:: The +entry+ is a directory.
The +action+ will be one of:
statistical information (+stats+).
+action+ Symbol, the full name of the file being extracted (+name+), and a Hash of
Extracts the current +entry+ to +destdir+. If a block is provided, it yields an
def extract_entry(destdir, entry, options = {}, &) # :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 /\.{2}(?:\/|\z)/.match?(full_name) raise SecureRelativePathError, "Path contains '..'" end if entry.directory? extract_directory(destdir, full_name, entry, stats, options, &) else # it's a file extract_file(destdir, full_name, entry, stats, options, &) end end
def extract_file(destdir, full_name, entry, stats, options)
def extract_file(destdir, full_name, entry, stats, options) 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 begin FileUtils.chmod(0o600, destfile) rescue nil end 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 if options.fetch(:fsync, true) yield :file_fsync, full_name, stats if block_given? os.fsync end end FileUtils.chmod(entry.mode, destfile) if options.fetch(:fsync, true) yield :dir_fsync, full_name, stats if block_given? fsync_dir(File.dirname(destfile)) fsync_dir(File.join(File.dirname(destfile), "..")) end yield :file_done, full_name, stats if block_given? end
def fsync_dir(dirname)
def fsync_dir(dirname) # make sure this hits the disc dir = IO.open(dirname, "rb") dir.fsync rescue # ignore IOError if it's an unpatched (old) Ruby nil ensure dir&.close rescue nil # standard:disable Style/RescueModifier end
def initialize(input)
Minitar::Input.new(io) -> input
:call-seq:
rewinding.
An exception will be raised if the stream that is wrapped does not support
Kernel#open. When Input#close is called, the stream object wrapped will be closed.
then it will simply be wrapped. Otherwise, one will be created and opened using
Creates a new Input object. If +input+ is a stream object that responds to #read,
def initialize(input) @io = if input.respond_to?(:read) input else ::Kernel.open(input, "rb") end raise Minitar::NonSeekableStream unless Minitar.seekable?(@io, :rewind) @tar = Reader.new(@io) end