minitar-0.6.1/0000755000004100000410000000000013053507500013207 5ustar www-datawww-dataminitar-0.6.1/Rakefile0000644000004100000410000000262313053507500014657 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- require 'rubygems' require 'hoe' require 'rake/clean' $LOAD_PATH.unshift('support') Hoe.plugin :doofus Hoe.plugin :gemspec2 Hoe.plugin :git Hoe.plugin :minitest Hoe.plugin :travis Hoe.plugin :deprecated_gem Hoe.plugin :email unless ENV['CI'] or ENV['TRAVIS'] spec = Hoe.spec 'minitar' do developer('Austin Ziegler', 'halostatue@gmail.com') require_ruby_version '>= 1.8' self.history_file = 'History.md' self.readme_file = 'README.rdoc' self.licenses = ['Ruby', 'BSD-2-Clause'] self.post_install_message = <<-EOS The `minitar` executable is no longer bundled with `minitar`. If you are expecting this executable, make sure you also install `minitar-cli`. EOS extra_dev_deps << ['hoe-doofus', '~> 1.0'] extra_dev_deps << ['hoe-gemspec2', '~> 1.1'] extra_dev_deps << ['hoe-git', '~> 1.6'] extra_dev_deps << ['hoe-rubygems', '~> 1.0'] extra_dev_deps << ['hoe-travis', '~> 1.2'] extra_dev_deps << ['minitest', '~> 5.3'] extra_dev_deps << ['minitest-autotest', ['>= 1.0', '<2']] extra_dev_deps << ['rake', '>= 10.0', '< 12'] extra_dev_deps << ['rdoc', '>= 0.0'] end if RUBY_VERSION >= '2.0' && RUBY_ENGINE == 'ruby' namespace :test do desc 'Run test coverage' task :coverage do spec.test_prelude = 'load ".simplecov-prelude.rb"' Rake::Task['test'].execute end end Rake::Task['travis'].prerequisites.replace(%w(test:coverage)) end minitar-0.6.1/Manifest.txt0000644000004100000410000000103313053507500015513 0ustar www-datawww-dataCode-of-Conduct.md Contributing.md History.md Licence.md Manifest.txt README.rdoc Rakefile docs/bsdl.txt docs/ruby.txt lib/archive-tar-minitar.rb lib/archive/tar/minitar.rb lib/archive/tar/minitar/input.rb lib/archive/tar/minitar/output.rb lib/archive/tar/minitar/posix_header.rb lib/archive/tar/minitar/reader.rb lib/archive/tar/minitar/writer.rb lib/minitar.rb test/minitest_helper.rb test/support/tar_test_helpers.rb test/test_tar_header.rb test/test_tar_input.rb test/test_tar_output.rb test/test_tar_reader.rb test/test_tar_writer.rb minitar-0.6.1/README.rdoc0000644000004100000410000000615613053507500015025 0ustar www-datawww-data= minitar home :: https://github.com/halostatue/minitar/ code :: https://github.com/halostatue/minitar/ bugs :: https://github.com/halostatue/minitar/issues rdoc :: http://rdoc.info/gems/minitar/ cli :: https://github.com/halostatue/minitar-cli travis :: {}[https://travis-ci.org/halostatue/minitar] appveyor :: {}[https://ci.appveyor.com/project/halostatue/minitar] coveralls :: {Coverage Status}[https://coveralls.io/r/halostatue/minitar] == Description The minitar library is a pure-Ruby library that provides the ability to deal with POSIX tar(1) archive files. This is release 0.6, providing a number of bug fixes including a directory traversal vulnerability, CVE-2016-10173. This release starts the migration and modernization of the code: * the licence has been changed to match the modern Ruby licensing scheme (Ruby and Simplified BSD instead of Ruby and GNU GPL); * the +minitar+ command-line program has been separated into the +minitar-cli+ gem; and * the +archive-tar-minitar+ gem now points to the +minitar+ and +minitar-cli+ gems and discourages its installation. Some of these changes may break existing programs that depend on the internal structure of the minitar library, but every effort has been made to ensure compatibility; inasmuch as is possible, this compatibility will be maintained through the release of minitar 1.0 (which will have strong breaking changes). minitar (previously called Archive::Tar::Minitar) is based heavily on code originally written by Mauricio Julio Fernández Pradier for the rpa-base project. == Synopsis Using minitar is easy. The simplest case is: require 'minitar' # Packs everything that matches Find.find('tests'). # test.tar will automatically be closed by Minitar.pack. Minitar.pack('tests', File.open('test.tar', 'wb')) # Unpacks 'test.tar' to 'x', creating 'x' if necessary. Minitar.unpack('test.tar', 'x') A gzipped tar can be written with: require 'zlib' # test.tgz will be closed automatically. Minitar.pack('tests', Zlib::GzipWriter.new(File.open('test.tgz', 'wb')) # test.tgz will be closed automatically. Minitar.unpack(Zlib::GzipReader.new(File.open('test.tgz', 'rb')), 'x') As the case above shows, one need not write to a file. However, it will sometimes require that one dive a little deeper into the API, as in the case of StringIO objects. Note that I'm not providing a block with Minitar::Output, as Minitar::Output#close automatically closes both the Output object and the wrapped data stream object. begin sgz = Zlib::GzipWriter.new(StringIO.new(String.new)) tar = Output.new(sgz) Find.find('tests') do |entry| Minitar.pack_file(entry, tar) end ensure # Closes both tar and sgz. tar.close end == minitar Semantic Versioning The minitar library uses a {Semantic Versioning}[http://semver.org/] scheme with one change: * When PATCH is zero (+0+), it will be omitted from version references. minitar-0.6.1/lib/0000755000004100000410000000000013053507500013755 5ustar www-datawww-dataminitar-0.6.1/lib/archive/0000755000004100000410000000000013053507500015376 5ustar www-datawww-dataminitar-0.6.1/lib/archive/tar/0000755000004100000410000000000013053507500016164 5ustar www-datawww-dataminitar-0.6.1/lib/archive/tar/minitar.rb0000644000004100000410000002245313053507500020162 0ustar www-datawww-data# coding: utf-8 ## module Archive; end ## module Archive::Tar; end require 'fileutils' require 'rbconfig' class << Archive::Tar #:nodoc: def const_missing(const) #:nodoc: case const when :PosixHeader warn 'Archive::Tar::PosixHeader has been renamed to ' \ 'Archive::Tar::Minitar::PosixHeader' const_set :PosixHeader, Archive::Tar::Minitar::PosixHeader else super end end private def included(mod) return if modules.include?(mod) warn "Including Minitar via the #{self} namespace is deprecated." modules.add mod end def modules require 'set' @modules ||= Set.new end end # == Synopsis # # Using minitar is easy. The simplest case is: # # require 'zlib' # require 'minitar' # # # Packs everything that matches Find.find('tests'). # # test.tar will automatically be closed by Minitar.pack. # Minitar.pack('tests', File.open('test.tar', 'wb')) # # # Unpacks 'test.tar' to 'x', creating 'x' if necessary. # Minitar.unpack('test.tar', 'x') # # A gzipped tar can be written with: # # # test.tgz will be closed automatically. # Minitar.pack('tests', Zlib::GzipWriter.new(File.open('test.tgz', 'wb')) # # # test.tgz will be closed automatically. # Minitar.unpack(Zlib::GzipReader.new(File.open('test.tgz', 'rb')), 'x') # # As the case above shows, one need not write to a file. However, it will # sometimes require that one dive a little deeper into the API, as in the case # of StringIO objects. Note that I'm not providing a block with # Minitar::Output, as Minitar::Output#close automatically closes both the # Output object and the wrapped data stream object. # # begin # sgz = Zlib::GzipWriter.new(StringIO.new("")) # tar = Output.new(sgz) # Find.find('tests') do |entry| # Minitar.pack_file(entry, tar) # end # ensure # # Closes both tar and sgz. # tar.close # end module Archive::Tar::Minitar VERSION = '0.6.1'.freeze # :nodoc: # The base class for any minitar error. Error = Class.new(::StandardError) # Raised when a wrapped data stream class is not seekable. NonSeekableStream = Class.new(Error) # The exception raised when operations are performed on a stream that has # previously been closed. ClosedStream = Class.new(Error) # The exception raised when a filename exceeds 256 bytes in length, the # maximum supported by the standard Tar format. FileNameTooLong = Class.new(Error) # The exception raised when a data stream ends before the amount of data # expected in the archive's PosixHeader. UnexpectedEOF = Class.new(StandardError) # The exception raised when a file contains a relative path in secure mode # (the default for this version). SecureRelativePathError = Class.new(Error) class << self # Tests if +path+ refers to a directory. Fixes an apparently # corrupted stat() call on Windows. def dir?(path) # rubocop:disable Style/CharacterLiteral File.directory?(path[-1] == ?/ ? path : "#{path}/") # rubocop:enable Style/CharacterLiteral end # A convenience method for wrapping Archive::Tar::Minitar::Input.open # (mode +r+) and Archive::Tar::Minitar::Output.open (mode +w+). No other # modes are currently supported. def open(dest, mode = 'r', &block) case mode when 'r' Input.open(dest, &block) when 'w' Output.open(dest, &block) else raise 'Unknown open mode for Archive::Tar::Minitar.open.' end end def const_missing(c) #:nodoc: case c when :BlockRequired warn 'This constant has been removed.' const_set(:BlockRequired, Class.new(StandardError)) else super end end def windows? #:nodoc: RbConfig::CONFIG['host_os'] =~ /^(mswin|mingw|cygwin)/ end # A convenience method to pack the file provided. +entry+ may either be a # filename (in which case various values for the file (see below) will be # obtained from File#stat(entry) or a Hash with the fields: # # :name:: The filename to be packed into the archive. Required. # :mode:: The mode to be applied. # :uid:: The user owner of the file. (Ignored on Windows.) # :gid:: The group owner of the file. (Ignored on Windows.) # :mtime:: The modification Time of the file. # # During packing, if a block is provided, #pack_file yields an +action+ # Symol, the full name of the file being packed, and a Hash of # statistical information, just as with # Archive::Tar::Minitar::Input#extract_entry. # # 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. # :name:: The filename to be packed into the tarchive. # *REQUIRED*. # :mode:: The mode to be applied. # :uid:: The user owner of the file. (+nil+ on Windows.) # :gid:: The group owner of the file. (+nil+ on Windows.) # :mtime:: The modification Time of the file. def pack_file(entry, outputter) #:yields action, name, stats: if outputter.kind_of?(Archive::Tar::Minitar::Output) outputter = outputter.tar end stats = {} if entry.kind_of?(Hash) name = entry[:name] entry.each { |kk, vv| stats[kk] = vv unless vv.nil? } else name = entry end name = name.sub(%r{\./}, '') stat = File.stat(name) stats[:mode] ||= stat.mode stats[:mtime] ||= stat.mtime stats[:size] = stat.size if windows? stats[:uid] = nil stats[:gid] = nil else stats[:uid] ||= stat.uid stats[:gid] ||= stat.gid end if File.file?(name) outputter.add_file_simple(name, stats) do |os| stats[:current] = 0 yield :file_start, name, stats if block_given? File.open(name, 'rb') do |ff| until ff.eof? stats[:currinc] = os.write(ff.read(4096)) stats[:current] += stats[:currinc] yield :file_progress, name, stats if block_given? end end yield :file_done, name, stats if block_given? end elsif dir?(name) yield :dir, name, stats if block_given? outputter.mkdir(name, stats) else raise %q(Don't yet know how to pack this type of file.) end end # A convenience method to pack files specified by +src+ into +dest+. If # +src+ is an Array, then each file detailed therein will be packed into # the resulting Archive::Tar::Minitar::Output stream; if +recurse_dirs+ is # true, then directories will be recursed. # # If +src+ is an Array, it will be treated as the result of Find.find; all # files matching will be packed. def pack(src, dest, recurse_dirs = true, &block) require 'find' Output.open(dest) do |outp| if src.kind_of?(Array) src.each do |entry| if dir?(entry) and recurse_dirs Find.find(entry) do |ee| pack_file(ee, outp, &block) end else pack_file(entry, outp, &block) end end else Find.find(src) do |entry| pack_file(entry, outp, &block) end end end end # A convenience method to unpack files from +src+ into the directory # specified by +dest+. Only those files named explicitly in +files+ # will be extracted. def unpack(src, dest, files = [], &block) Input.open(src) do |inp| if File.exist?(dest) and !dir?(dest) raise %q(Can't unpack to a non-directory.) end FileUtils.mkdir_p(dest) unless File.exist?(dest) inp.each do |entry| if files.empty? or files.include?(entry.full_name) inp.extract_entry(dest, entry, &block) end end end end # Check whether +io+ can seek without errors. def seekable?(io, methods = nil) # The IO class throws an exception at runtime if we try to change # position on a non-regular file. if io.respond_to?(:stat) io.stat.file? else # Duck-type the rest of this. methods ||= [ :pos, :pos=, :seek, :rewind ] methods = [ methods ] unless methods.kind_of?(Array) methods.all? { |m| io.respond_to?(m) } end end private def included(mod) return if modules.include?(mod) warn "Including #{self} has been deprecated." modules << mod end def modules require 'set' @modules ||= Set.new end end end require 'archive/tar/minitar/posix_header' require 'archive/tar/minitar/input' require 'archive/tar/minitar/output' minitar-0.6.1/lib/archive/tar/minitar/0000755000004100000410000000000013053507500017627 5ustar www-datawww-dataminitar-0.6.1/lib/archive/tar/minitar/output.rb0000644000004100000410000000507413053507500021522 0ustar www-datawww-data# coding: utf-8 require 'archive/tar/minitar/writer' module Archive::Tar::Minitar # Wraps a Archive::Tar::Minitar::Writer with convenience methods and wrapped # stream management. If the stream provided to Output does not support random # access, only Writer#add_file_simple and Writer#mkdir are guaranteed to # work. class Output # With no associated block, +Output.open+ is a synonym for +Output.new+. If # the optional code block is given, it will be given the new Output as an # argument and the Output object will automatically be closed when the # block terminates (this also closes the wrapped stream object). In this # instance, +Output.open+ returns the value of the block. # # call-seq: # Archive::Tar::Minitar::Output.open(io) -> output # Archive::Tar::Minitar::Output.open(io) { |output| block } -> obj def self.open(output) stream = new(output) 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 # Output.tar is a wrapper for Output.open that yields the owned tar object # instead of the Output object. If a block is not provided, an enumerator # will be created with the same behaviour. # # call-seq: # Archive::Tar::Minitar::Output.tar(io) -> enumerator # Archive::Tar::Minitar::Output.tar(io) { |tar| block } -> obj def self.tar(output) return to_enum(__method__, output) unless block_given? open(output) do |stream| yield stream.tar end end # Creates a new Output object. If +output+ is a stream object that responds # to #write, then it will simply be wrapped. Otherwise, one will be created # and opened using Kernel#open. When Output#close is called, the stream # object wrapped will be closed. # # call-seq: # Archive::Tar::Minitar::Output.new(io) -> output # Archive::Tar::Minitar::Output.new(path) -> output def initialize(output) @io = if output.respond_to?(:write) output else ::Kernel.open(output, 'wb') end @tar = Archive::Tar::Minitar::Writer.new(@io) end # Returns the Writer object for direct access. attr_reader :tar # Returns false if the wrapped data stream is open. def closed? @io.closed? end # Closes the Writer object and the wrapped data stream. def close @tar.close @io.close end end end minitar-0.6.1/lib/archive/tar/minitar/posix_header.rb0000644000004100000410000001632413053507500022634 0ustar www-datawww-data# coding: utf-8 ## module Archive; end ## module Archive::Tar; end ## module Archive::Tar::Minitar; end # Implements the POSIX tar header as a Ruby class. The structure of # the POSIX tar header is: # # struct tarfile_entry_posix # { // pack/unpack # char name[100]; // ASCII (+ Z unless filled) a100/Z100 # char mode[8]; // 0 padded, octal, null a8 /A8 # char uid[8]; // 0 padded, octal, null a8 /A8 # char gid[8]; // 0 padded, octal, null a8 /A8 # char size[12]; // 0 padded, octal, null a12 /A12 # char mtime[12]; // 0 padded, octal, null a12 /A12 # char checksum[8]; // 0 padded, octal, null, space a8 /A8 # char typeflag[1]; // see below a /a # char linkname[100]; // ASCII + (Z unless filled) a100/Z100 # char magic[6]; // "ustar\0" a6 /A6 # char version[2]; // "00" a2 /A2 # char uname[32]; // ASCIIZ a32 /Z32 # char gname[32]; // ASCIIZ a32 /Z32 # char devmajor[8]; // 0 padded, octal, null a8 /A8 # char devminor[8]; // 0 padded, octal, null a8 /A8 # char prefix[155]; // ASCII (+ Z unless filled) a155/Z155 # }; # # The #typeflag is one of several known values. # # POSIX indicates that "A POSIX-compliant implementation must treat any # unrecognized typeflag value as a regular file." class Archive::Tar::Minitar::PosixHeader BLOCK_SIZE = 512 # Fields that must be set in a POSIX tar(1) header. REQUIRED_FIELDS = [ :name, :size, :prefix, :mode ].freeze # Fields that may be set in a POSIX tar(1) header. OPTIONAL_FIELDS = [ :uid, :gid, :mtime, :checksum, :typeflag, :linkname, :magic, :version, :uname, :gname, :devmajor, :devminor ].freeze # All fields available in a POSIX tar(1) header. FIELDS = (REQUIRED_FIELDS + OPTIONAL_FIELDS).freeze FIELDS.each do |f| attr_reader f.to_sym unless f.to_sym == :name end # The name of the file. By default, limited to 100 bytes. Required. May be # longer (up to BLOCK_SIZE bytes) if using the GNU long name tar extension. attr_accessor :name # The pack format passed to Array#pack for encoding a header. HEADER_PACK_FORMAT = 'a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155'.freeze # The unpack format passed to String#unpack for decoding a header. HEADER_UNPACK_FORMAT = 'Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155'.freeze class << self # Creates a new PosixHeader from a data stream. def from_stream(stream) from_data(stream.read(BLOCK_SIZE)) end # Creates a new PosixHeader from a data stream. Deprecated; use # PosixHeader.from_stream instead. def new_from_stream(stream) warn "#{__method__} has been deprecated; use from_stream instead." from_stream(stream) end # Creates a new PosixHeader from a BLOCK_SIZE-byte data buffer. def from_data(data) fields = data.unpack(HEADER_UNPACK_FORMAT) name = fields.shift mode = fields.shift.oct uid = fields.shift.oct gid = fields.shift.oct size = fields.shift.oct mtime = fields.shift.oct checksum = fields.shift.oct typeflag = fields.shift linkname = fields.shift magic = fields.shift version = fields.shift.oct uname = fields.shift gname = fields.shift devmajor = fields.shift.oct devminor = fields.shift.oct prefix = fields.shift empty = !data.each_byte.any?(&:nonzero?) new( :name => name, :mode => mode, :uid => uid, :gid => gid, :size => size, :mtime => mtime, :checksum => checksum, :typeflag => typeflag, :magic => magic, :version => version, :uname => uname, :gname => gname, :devmajor => devmajor, :devminor => devminor, :prefix => prefix, :empty => empty, :linkname => linkname ) end end # Creates a new PosixHeader. A PosixHeader cannot be created unless # +name+, +size+, +prefix+, and +mode+ are provided. def initialize(v) REQUIRED_FIELDS.each do |f| raise ArgumentError, "Field #{f} is required." unless v.key?(f) end v[:mtime] = v[:mtime].to_i v[:checksum] ||= '' v[:typeflag] ||= '0' v[:magic] ||= 'ustar' v[:version] ||= '00' FIELDS.each do |f| instance_variable_set("@#{f}", v[f]) end @empty = v[:empty] end # Indicates if the header was an empty header. def empty? @empty end # Returns +true+ if the header is a long name special header which indicates # that the next block of data is the filename. def long_name? typeflag == 'L' && name == '././@LongLink' end # A string representation of the header. def to_s update_checksum header(@checksum) end alias to_str to_s # Update the checksum field. def update_checksum hh = header(' ' * 8) @checksum = oct(calculate_checksum(hh), 6) end private def oct(num, len) if num.nil? "\0" * (len + 1) else "%0#{len}o" % num end end def calculate_checksum(hdr) hdr.unpack('C*').inject { |a, e| a + e } end def header(chksum) arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), oct(mtime, 11), chksum, ' ', typeflag, linkname, magic, version, uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix] str = arr.pack(HEADER_PACK_FORMAT) str + "\0" * ((BLOCK_SIZE - str.size) % BLOCK_SIZE) end ## # :attr_reader: size # The size of the file. Required. ## # :attr_reader: prefix # The prefix of the file; the path before #name. Limited to 155 bytes. # Required. ## # :attr_reader: mode # The Unix file mode of the file. Stored as an octal integer. Required. ## # :attr_reader: uid # The Unix owner user ID of the file. Stored as an octal integer. ## # :attr_reader: uname # The user name of the Unix owner of the file. ## # :attr_reader: gid # The Unix owner group ID of the file. Stored as an octal integer. ## # :attr_reader: gname # The group name of the Unix owner of the file. ## # :attr_reader: mtime # The modification time of the file in epoch seconds. Stored as an # octal integer. ## # :attr_reader: checksum # The checksum of the file. Stored as an octal integer. Calculated # before encoding the header as a string. ## # :attr_reader: typeflag # The type of record in the file. # # +0+:: Regular file. NULL should be treated as a synonym, for compatibility # purposes. # +1+:: Hard link. # +2+:: Symbolic link. # +3+:: Character device node. # +4+:: Block device node. # +5+:: Directory. # +6+:: FIFO node. # +7+:: Reserved. ## # :attr_reader: linkname # The name of the link stored. Not currently used. ## # :attr_reader: magic # Always "ustar\0". ## # :attr_reader: version # Always "00" ## # :attr_reader: devmajor # The major device ID. Not currently used. ## # :attr_reader: devminor # The minor device ID. Not currently used. end minitar-0.6.1/lib/archive/tar/minitar/writer.rb0000644000004100000410000002200113053507500021463 0ustar www-datawww-data# coding: utf-8 module Archive::Tar::Minitar # The class that writes a tar format archive to a data stream. class Writer # The exception raised when the user attempts to write more data to a # BoundedWriteStream than has been allocated. WriteBoundaryOverflow = Class.new(StandardError) # A stream wrapper that can only be written to. Any attempt to read # from this restricted stream will result in a NameError being thrown. class WriteOnlyStream def initialize(io) @io = io end def write(data) @io.write(data) end end private_constant :WriteOnlyStream if respond_to?(:private_constant) # A WriteOnlyStream that also has a size limit. class BoundedWriteStream < WriteOnlyStream def self.const_missing(c) case c when :FileOverflow warn 'Writer::BoundedWriteStream::FileOverflow has been renamed ' \ 'to Writer::WriteBoundaryOverflow' const_set :FileOverflow, Archive::Tar::Minitar::Writer::WriteBoundaryOverflow else super end end # The maximum number of bytes that may be written to this data # stream. attr_reader :limit # The current total number of bytes written to this data stream. attr_reader :written def initialize(io, limit) @io = io @limit = limit @written = 0 end def write(data) raise WriteBoundaryOverflow if (data.size + @written) > @limit @io.write(data) @written += data.size data.size end end private_constant :BoundedWriteStream if respond_to?(:private_constant) def self.const_missing(c) case c when :BoundedStream warn 'BoundedStream has been renamed to BoundedWriteStream' const_set(:BoundedStream, BoundedWriteStream) else super end end # With no associated block, +Writer::open+ is a synonym for +Writer::new+. # If the optional code block is given, it will be passed the new _writer_ # as an argument and the Writer object will automatically be closed when # the block terminates. In this instance, +Writer::open+ returns the value # of the block. # # call-seq: # w = Archive::Tar::Minitar::Writer.open(STDOUT) # w.add_file_simple('foo.txt', :size => 3) # w.close # # Archive::Tar::Minitar::Writer.open(STDOUT) do |w| # w.add_file_simple('foo.txt', :size => 3) # end def self.open(io) # :yields Writer: writer = new(io) return writer unless block_given? # This exception context must remain, otherwise the stream closes on open # even if a block is not given. begin yield writer ensure writer.close end end # Creates and returns a new Writer object. def initialize(io) @io = io @closed = false end # Adds a file to the archive as +name+. The data can be provided in the # opts[:data] or provided to a BoundedWriteStream that is # yielded to the provided block. # # If opts[:data] is provided, all other values to +opts+ are # optional. If the data is provided to the yielded BoundedWriteStream, # opts[:size] must be provided. # # Valid parameters to +opts+ are: # # :data:: Optional. The data to write to the archive. # :mode:: The Unix file permissions mode value. If not # provided, defaults to 0644. # :size:: The size, in bytes. If :data is provided, # this parameter may be ignored (if it is less than # the size of the data provided) or used to add # padding (if it is greater than the size of the data # provided). # :uid:: The Unix file owner user ID number. # :gid:: The Unix file owner group ID number. # :mtime:: File modification time, interpreted as an integer. # # An exception will be raised if the Writer is already closed, or if # more data is written to the BoundedWriteStream than expected. # # call-seq: # writer.add_file_simple('foo.txt', :data => "bar") # writer.add_file_simple('foo.txt', :size => 3) do |w| # w.write("bar") # end def add_file_simple(name, opts = {}) # :yields BoundedWriteStream: raise ClosedStream if @closed name, prefix = split_name(name) header = { :prefix => prefix, :name => name, :mode => opts.fetch(:mode, 0o644), :mtime => opts.fetch(:mtime, nil), :gid => opts.fetch(:gid, nil), :uid => opts.fetch(:uid, nil) } data = opts.fetch(:data, nil) size = opts.fetch(:size, nil) if block_given? if data raise ArgumentError, 'Too much data (opts[:data] and block_given?).' end raise ArgumentError, 'No size provided' unless size else raise ArgumentError, 'No data provided' unless data size = data.size if size < data.size end header[:size] = size @io.write(PosixHeader.new(header)) os = BoundedWriteStream.new(@io, opts[:size]) if block_given? yield os else os.write(data) end min_padding = opts[:size] - os.written @io.write("\0" * min_padding) remainder = (512 - (opts[:size] % 512)) % 512 @io.write("\0" * remainder) end # Adds a file to the archive as +name+. The data can be provided in the # opts[:data] or provided to a yielded +WriteOnlyStream+. The # size of the file will be determined from the amount of data written # to the stream. # # Valid parameters to +opts+ are: # # :mode:: The Unix file permissions mode value. If not # provided, defaults to 0644. # :uid:: The Unix file owner user ID number. # :gid:: The Unix file owner group ID number. # :mtime:: File modification time, interpreted as an integer. # :data:: Optional. The data to write to the archive. # # If opts[:data] is provided, this acts the same as # #add_file_simple. Otherwise, the file's size will be determined from # the amount of data written to the stream. # # For #add_file to be used without opts[:data], the Writer # must be wrapping a stream object that is seekable. Otherwise, # #add_file_simple must be used. # # +opts+ may be modified during the writing of the file to the stream. def add_file(name, opts = {}, &block) # :yields WriteOnlyStream, +opts+: raise ClosedStream if @closed return add_file_simple(name, opts, &block) if opts[:data] unless Archive::Tar::Minitar.seekable?(@io) raise Archive::Tar::Minitar::NonSeekableStream end name, prefix = split_name(name) init_pos = @io.pos @io.write("\0" * 512) # placeholder for the header yield WriteOnlyStream.new(@io), opts size = @io.pos - (init_pos + 512) remainder = (512 - (size % 512)) % 512 @io.write("\0" * remainder) final_pos, @io.pos = @io.pos, init_pos header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime], :size => size, :gid => opts[:gid], :uid => opts[:uid], :prefix => prefix } @io.write(PosixHeader.new(header)) @io.pos = final_pos end # Creates a directory entry in the tar. def mkdir(name, opts = {}) raise ClosedStream if @closed name, prefix = split_name(name) header = { :name => name, :mode => opts[:mode], :typeflag => '5', :size => 0, :gid => opts[:gid], :uid => opts[:uid], :mtime => opts[:mtime], :prefix => prefix } @io.write(PosixHeader.new(header)) nil end # Passes the #flush method to the wrapped stream, used for buffered # streams. def flush raise ClosedStream if @closed @io.flush if @io.respond_to?(:flush) end # Returns false if the writer is open. def closed? @closed end # Closes the Writer. This does not close the underlying wrapped output # stream. def close return if @closed @io.write("\0" * 1024) @closed = true end private def split_name(name) # TODO: Enable long-filename write support. raise FileNameTooLong if name.size > 256 if name.size <= 100 prefix = '' else parts = name.split(/\//) newname = parts.pop nxt = '' loop do nxt = parts.pop || '' break if newname.size + 1 + nxt.size >= 100 newname = "#{nxt}/#{newname}" end prefix = (parts + [nxt]).join('/') name = newname raise FileNameTooLong if name.size > 100 || prefix.size > 155 end [ name, prefix ] end end end minitar-0.6.1/lib/archive/tar/minitar/input.rb0000644000004100000410000001564113053507500021322 0ustar www-datawww-data# coding: utf-8 require 'archive/tar/minitar/reader' module Archive::Tar::Minitar # Wraps a Archive::Tar::Minitar::Reader with convenience methods and wrapped # stream management; Input only works with data streams that can be rewound. class Input include Enumerable # 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. # # call-seq: # Archive::Tar::Minitar::Input.open(io) -> input # Archive::Tar::Minitar::Input.open(io) { |input| block } -> obj 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 # 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. # # call-seq: # Archive::Tar::Minitar::Input.each_entry(io) -> enumerator # Archive::Tar::Minitar::Input.each_entry(io) { |entry| block } -> obj 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 Input#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. # # call-seq: # Archive::Tar::Minitar::Input.new(io) -> input # Archive::Tar::Minitar::Input.new(path) -> input 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 # 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. def each_entry return to_enum unless block_given? @tar.each do |entry| yield entry end ensure @tar.rewind end alias each each_entry # 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. 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 # Returns false if the wrapped data stream is open. def closed? @io.closed? end # Returns the Reader object for direct access. attr_reader :tar # Closes both the Reader object and the wrapped data stream. def close @io.close @tar.close end private def fsync_dir(dirname) # make sure this hits the disc dir = open(dirname, 'rb') dir.fsync rescue # ignore IOError if it's an unpatched (old) Ruby nil ensure dir.close if dir rescue nil # rubocop:disable Style/RescueModifier end end end minitar-0.6.1/lib/archive/tar/minitar/reader.rb0000644000004100000410000001542113053507500021421 0ustar www-datawww-data# coding: utf-8 module Archive::Tar::Minitar # The class that reads a tar format archive from a data stream. The data # stream may be sequential or random access, but certain features only work # with random access data streams. class Reader # This marks the EntryStream closed for reading without closing the # actual data stream. module InvalidEntryStream # rubocop:disable Style/SingleLineMethods # rubocop:disable Style/EmptyLineBetweenDefs def read(*); raise ClosedStream; end def getc; raise ClosedStream; end def rewind; raise ClosedStream; end def closed?; true; end # rubocop:enable Style/EmptyLineBetweenDefs # rubocop:enable Style/SingleLineMethods Style/EmptyLineBetweenDefs end # EntryStreams are pseudo-streams on top of the main data stream. class EntryStream Archive::Tar::Minitar::PosixHeader::FIELDS.each do |field| attr_reader field.to_sym end def initialize(header, io) @io = io @name = header.name @mode = header.mode @uid = header.uid @gid = header.gid @size = header.size @mtime = header.mtime @checksum = header.checksum @typeflag = header.typeflag @linkname = header.linkname @magic = header.magic @version = header.version @uname = header.uname @gname = header.gname @devmajor = header.devmajor @devminor = header.devminor @prefix = header.prefix @read = 0 @orig_pos = if Archive::Tar::Minitar.seekable?(@io) @io.pos else 0 end end # Reads +len+ bytes (or all remaining data) from the entry. Returns # +nil+ if there is no more data to read. def read(len = nil) return nil if @read >= @size len ||= @size - @read max_read = [len, @size - @read].min ret = @io.read(max_read) @read += ret.size ret end # Reads one byte from the entry. Returns +nil+ if there is no more data # to read. def getc return nil if @read >= @size ret = @io.getc @read += 1 if ret ret end # Returns +true+ if the entry represents a directory. def directory? @typeflag == '5' end alias directory directory? # Returns +true+ if the entry represents a plain file. def file? @typeflag == '0' || @typeflag == "\0" end alias file file? # Returns +true+ if the current read pointer is at the end of the # EntryStream data. def eof? @read >= @size end # Returns the current read pointer in the EntryStream. def pos @read end # Sets the current read pointer to the beginning of the EntryStream. def rewind unless Archive::Tar::Minitar.seekable?(@io, :pos=) raise Archive::Tar::Minitar::NonSeekableStream end @io.pos = @orig_pos @read = 0 end def bytes_read @read end # Returns the full and proper name of the entry. def full_name if @prefix != '' File.join(@prefix, @name) else @name end end # Returns false if the entry stream is valid. def closed? false end # Closes the entry. def close invalidate end private def invalidate extend InvalidEntryStream end end # With no associated block, +Reader::open+ is a synonym for # +Reader::new+. If the optional code block is given, it will be passed # the new _writer_ as an argument and the Reader object will # automatically be closed when the block terminates. In this instance, # +Reader::open+ returns the value of the block. def self.open(io) reader = new(io) return reader unless block_given? # This exception context must remain, otherwise the stream closes on open # even if a block is not given. begin yield reader ensure reader.close end end # 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. # # call-seq: # Archive::Tar::Minitar::Reader.each_entry(io) -> enumerator # Archive::Tar::Minitar::Reader.each_entry(io) { |entry| block } -> obj def self.each_entry(io) return to_enum(__method__, io) unless block_given? open(io) do |reader| reader.each_entry do |entry| yield entry end end end # Creates and returns a new Reader object. def initialize(io) @io = io @init_pos = io.pos end # Resets the read pointer to the beginning of data stream. Do not call # this during a #each or #each_entry iteration. This only works with # random access data streams that respond to #rewind and #pos. def rewind if @init_pos.zero? unless Archive::Tar::Minitar.seekable?(@io, :rewind) raise Archive::Tar::Minitar::NonSeekableStream end @io.rewind else unless Archive::Tar::Minitar.seekable?(@io, :pos=) raise Archive::Tar::Minitar::NonSeekableStream end @io.pos = @init_pos end end # Iterates through each entry in the data stream. def each_entry return to_enum unless block_given? loop do return if @io.eof? header = Archive::Tar::Minitar::PosixHeader.from_stream(@io) return if header.empty? if header.long_name? name = @io.read(512).rstrip header = PosixHeader.from_stream(@io) return if header.empty? header.name = name end entry = EntryStream.new(header, @io) size = entry.size yield entry skip = (512 - (size % 512)) % 512 if Archive::Tar::Minitar.seekable?(@io, :seek) # avoid reading... @io.seek(size - entry.bytes_read, IO::SEEK_CUR) else pending = size - entry.bytes_read while pending > 0 bread = @io.read([pending, 4096].min).size raise UnexpectedEOF if @io.eof? pending -= bread end end @io.read(skip) # discard trailing zeros # make sure nobody can use #read, #getc or #rewind anymore entry.close end end alias each each_entry # Returns false if the reader is open (it never closes). def closed? false end def close end end end minitar-0.6.1/lib/archive-tar-minitar.rb0000644000004100000410000000005713053507500020152 0ustar www-datawww-data# coding: utf-8 require 'archive/tar/minitar' minitar-0.6.1/lib/minitar.rb0000644000004100000410000000033113053507500015742 0ustar www-datawww-data# coding: utf-8 require 'archive/tar/minitar' if defined? ::Minitar warn <<-EOS ::Minitar is already defined. This will conflict with future versions of minitar. EOS else ::Minitar = Archive::Tar::Minitar end minitar-0.6.1/test/0000755000004100000410000000000013053507500014166 5ustar www-datawww-dataminitar-0.6.1/test/test_tar_output.rb0000644000004100000410000000254013053507500017761 0ustar www-datawww-data#!/usr/bin/env ruby require 'minitar' require 'minitest_helper' class TestTarOutput < Minitest::Test def setup FileUtils.mkdir_p('data__') %w(a b c).each do |filename| name = File.join('data__', filename) File.open(name, 'wb') { |f| f.puts "#{name}: 123456789012345678901234567890" } end @tarfile = 'data__/bla2.tar' end def teardown FileUtils.rm_rf('data__') end def test_open_no_block output = Minitar::Output.open(@tarfile) refute output.closed? ensure output.close assert output.closed? end def test_file_looks_good Minitar::Output.open(@tarfile) do |os| Dir.chdir('data__') do %w(a b c).each do |name| stat = File.stat(name) opts = { :size => stat.size, :mode => 0o644 } os.tar.add_file_simple(name, opts) do |ss| File.open(name, 'rb') { |ff| ss.write(ff.read(4096)) until ff.eof? } end end end end ff = File.open(@tarfile, 'rb') Minitar::Reader.open(ff) do |is| ii = 0 is.each do |entry| case ii when 0 assert_equal('a', entry.name) when 1 assert_equal('b', entry.name) when 2 assert_equal('c', entry.name) end ii += 1 end assert_equal(3, ii) end ensure ff.close if ff end end minitar-0.6.1/test/test_tar_input.rb0000644000004100000410000001275513053507500017571 0ustar www-datawww-data#!/usr/bin/env ruby require 'minitar' require 'minitest_helper' require 'base64' require 'zlib' class TestTarInput < Minitest::Test TEST_TGZ = Base64.decode64(<<-EOS).freeze H4sIAKJpllQAA0tJLEnUK0ks0kuvYqAVMDAwMDMxUQDR5mbmYNrACMIHA2MjIwUDc3NzEzMz QxMDAwUDQ2NTczMGBQOauQgJlBYDfQ90SiKQkZmHWx1QWVoaHnMgXlGA00MEyHdzMMzOnBbC wPz28n2uJgOR44Xrq7tsHc/utNe/9FdihkmH3pZ7+zOTRFREzkzYJ99iHHDn4n0/Wb3E8Ceq S0uOdSyMMg9Z+WVvX0vJucxs77vrvZf2arWcvHP9wa1Yp9lRnJmC59/P9+43PXum+tj7Ga+8 rtT+u3d941e765Y/bOrnvpv8X6jtz+wKqyk/v3n8P5xlO3l/1dn9q9Zotpy5funw/Of77Y/5 LVltz7ToTl7dXf5ppmf3n9p+PPxz/sz/qjZn9yf9Y4R7I2Ft3tqfPTUMGgMYlEMSpGXmpBrT 2A5Qvjc1xZ3/DTDyv5GJmfFo/qcHCMnILFYAIlA6UDDWU+DlGmgXjYJRMApGwSgYBaNgFIyC UTAKRsEoGAWjYBSMglEwCkbBKBgFo2AUjIJRMApGwSgYBaNgFIwCUgAAGnyo6wAoAAA= EOS FILETIMES = Time.utc(2004).to_i TEST_CONTENTS = { 'data.tar.gz' => { :size => 210, :mode => 0o644 }, 'file3' => { :size => 18, :mode => 0o755 } }.freeze TEST_DATA_CONTENTS = { 'data/' => { :size => 0, :mode => 0o755 }, 'data/__dir__/' => { :size => 0, :mode => 0o755 }, 'data/file1' => { :size => 16, :mode => 0o644 }, 'data/file2' => { :size => 16, :mode => 0o644 } }.freeze def setup FileUtils.mkdir_p('data__') end def teardown FileUtils.rm_rf('data__') end def test_open_no_block reader = Zlib::GzipReader.new(StringIO.new(TEST_TGZ)) input = Minitar::Input.open(reader) refute input.closed? ensure input.close assert input.closed? end def test_each_works reader = Zlib::GzipReader.new(StringIO.new(TEST_TGZ)) Minitar::Input.open(reader) do |stream| outer = 0 stream.each.with_index do |entry, i| assert_kind_of(Minitar::Reader::EntryStream, entry) assert TEST_CONTENTS.key?(entry.name) assert_equal(TEST_CONTENTS[entry.name][:size], entry.size, entry.name) assert_modes_equal(TEST_CONTENTS[entry.name][:mode], entry.mode, entry.name) assert_equal(FILETIMES, entry.mtime, 'entry.mtime') if i.zero? data_reader = Zlib::GzipReader.new(StringIO.new(entry.read)) Minitar::Input.open(data_reader) do |is2| inner = 0 is2.each_with_index do |entry2, _j| assert_kind_of(Minitar::Reader::EntryStream, entry2) assert TEST_DATA_CONTENTS.key?(entry2.name) assert_equal(TEST_DATA_CONTENTS[entry2.name][:size], entry2.size, entry2.name) assert_modes_equal(TEST_DATA_CONTENTS[entry2.name][:mode], entry2.mode, entry2.name) assert_equal(FILETIMES, entry2.mtime, entry2.name) inner += 1 end assert_equal(4, inner) end end outer += 1 end assert_equal(2, outer) end end def test_extract_entry_works reader = Zlib::GzipReader.new(StringIO.new(TEST_TGZ)) Minitar::Input.open(reader) do |stream| outer_count = 0 stream.each_with_index do |entry, i| stream.extract_entry('data__', entry) name = File.join('data__', entry.name) assert TEST_CONTENTS.key?(entry.name) if entry.directory? assert(File.directory?(name)) else assert(File.file?(name)) assert_equal(TEST_CONTENTS[entry.name][:size], File.stat(name).size) end assert_modes_equal(TEST_CONTENTS[entry.name][:mode], File.stat(name).mode, entry.name) if i.zero? begin ff = File.open(name, 'rb') data_reader = Zlib::GzipReader.new(ff) Minitar::Input.open(data_reader) do |is2| is2.each_with_index do |entry2, _j| is2.extract_entry('data__', entry2) name2 = File.join('data__', entry2.name) assert TEST_DATA_CONTENTS.key?(entry2.name) if entry2.directory? assert(File.directory?(name2)) else assert(File.file?(name2)) assert_equal(TEST_DATA_CONTENTS[entry2.name][:size], File.stat(name2).size) end assert_modes_equal(TEST_DATA_CONTENTS[entry2.name][:mode], File.stat(name2).mode, name2) end end ensure ff.close unless ff.closed? end end outer_count += 1 end assert_equal(2, outer_count) end end def test_extract_entry_breaks_symlinks return if Minitar.windows? IO.respond_to?(:write) && IO.write('data__/file4', '') || File.open('data__/file4', 'w') { |f| f.write '' } File.symlink('data__/file4', 'data__/file3') File.symlink('data__/file4', 'data__/data') Minitar.unpack(Zlib::GzipReader.new(StringIO.new(TEST_TGZ)), 'data__') Minitar.unpack(Zlib::GzipReader.new(File.open('data__/data.tar.gz', 'rb')), 'data__') refute File.symlink?('data__/file3') refute File.symlink?('data__/data') end RELATIVE_DIRECTORY_TGZ = Base64.decode64 <<-EOS H4sICIIoKVgCA2JhZC1kaXIudGFyANPT0y8sTy0qqWSgHTAwMDAzMVEA0eZmpmDawAjChwEFQ2MDQyMg MDUzVDAwNDY0N2VQMGCgAygtLkksAjolEcjIzMOtDqgsLQ2/J0H+gNOjYBSMglEwyAEA2LchrwAGAAA= EOS def test_extract_entry_fails_with_relative_directory reader = Zlib::GzipReader.new(StringIO.new(RELATIVE_DIRECTORY_TGZ)) Minitar::Input.open(reader) do |stream| stream.each do |entry| assert_raises Archive::Tar::Minitar::SecureRelativePathError do stream.extract_entry('data__', entry) end end end end end minitar-0.6.1/test/test_tar_header.rb0000644000004100000410000000417613053507500017660 0ustar www-datawww-data# frozen_string_literal: true require 'minitest_helper' class TestTarHeader < Minitest::Test def test_arguments_are_checked ph = Archive::Tar::Minitar::PosixHeader assert_raises(ArgumentError) { ph.new(:name => '', :size => '', :mode => '') } assert_raises(ArgumentError) { ph.new(:name => '', :size => '', :prefix => '') } assert_raises(ArgumentError) { ph.new(:name => '', :prefix => '', :mode => '') } assert_raises(ArgumentError) { ph.new(:prefix => '', :size => '', :mode => '') } end def test_basic_headers header = { :name => 'bla', :mode => 0o12345, :size => 10, :prefix => '', :typeflag => '0' } assert_headers_equal(tar_file_header('bla', '', 0o12345, 10), Archive::Tar::Minitar::PosixHeader.new(header).to_s) header = { :name => 'bla', :mode => 0o12345, :size => 0, :prefix => '', :typeflag => '5' } assert_headers_equal(tar_dir_header('bla', '', 0o12345), Archive::Tar::Minitar::PosixHeader.new(header).to_s) end def test_long_name_works header = { :name => 'a' * 100, :mode => 0o12345, :size => 10, :prefix => '' } assert_headers_equal(tar_file_header('a' * 100, '', 0o12345, 10), Archive::Tar::Minitar::PosixHeader.new(header).to_s) header = { :name => 'a' * 100, :mode => 0o12345, :size => 10, :prefix => 'bb' * 60 } assert_headers_equal(tar_file_header('a' * 100, 'bb' * 60, 0o12345, 10), Archive::Tar::Minitar::PosixHeader.new(header).to_s) end def test_from_stream header = tar_file_header('a' * 100, '', 0o12345, 10) header = StringIO.new(header) h = Archive::Tar::Minitar::PosixHeader.from_stream(header) assert_equal('a' * 100, h.name) assert_equal(0o12345, h.mode) assert_equal(10, h.size) assert_equal('', h.prefix) assert_equal('ustar', h.magic) end def test_from_stream_with_evil_name header = tar_file_header("a \0" + "\0" * 97, '', 0o12345, 10) header = StringIO.new(header) h = Archive::Tar::Minitar::PosixHeader.from_stream header assert_equal('a ', h.name) end end minitar-0.6.1/test/minitest_helper.rb0000644000004100000410000000032613053507500017707 0ustar www-datawww-data# -*- ruby encoding: utf-8 -*- require 'fileutils' require 'minitar' gem 'minitest' require 'minitest/autorun' Dir.glob(File.join(File.dirname(__FILE__), 'support/*.rb')).each do |support| require support end minitar-0.6.1/test/support/0000755000004100000410000000000013053507500015702 5ustar www-datawww-dataminitar-0.6.1/test/support/tar_test_helpers.rb0000644000004100000410000000545713053507500021611 0ustar www-datawww-data# frozen_string_literal: true module TarTestHelpers Field = Struct.new(:name, :offset, :length) def self.Field(name, length) # rubocop:disable Style/MethodName @offset ||= 0 field = Field.new(name, @offset, length) @offset += length FIELDS[name] = field FIELD_ORDER << name field end private FIELDS = {} # rubocop:disable Style/MutableConstant FIELD_ORDER = [] # rubocop:disable Style/MutableConstant Field('name', 100) Field('mode', 8) Field('uid', 8) Field('gid', 8) Field('size', 12) Field('mtime', 12) Field('checksum', 8) Field('typeflag', 1) Field('linkname', 100) Field('magic', 6) Field('version', 2) Field('uname', 32) Field('gname', 32) Field('devmajor', 8) Field('devminor', 8) Field('prefix', 155) BLANK_CHECKSUM = ' ' * 8 NULL_100 = "\0" * 100 USTAR = "ustar\0".freeze DOUBLE_ZERO = '00'.freeze def assert_headers_equal(expected, actual) FIELD_ORDER.each do |field| message = if field == 'checksum' 'Header checksums are expected to match.' else "Header field #{field} is expected to match." end offset = FIELDS[field].offset length = FIELDS[field].length assert_equal(expected[offset, length], actual[offset, length], message) end end def assert_modes_equal(expected, actual, name) return if Minitar.windows? assert_equal( mode_string(expected), mode_string(actual), "Mode for #{name} does not match" ) end def tar_file_header(fname, dname, mode, length) update_checksum(header('0', fname, dname, length, mode)) end def tar_dir_header(name, prefix, mode) update_checksum(header('5', name, prefix, 0, mode)) end def header(type, fname, dname, length, mode) arr = [ asciiz(fname, 100), z(to_oct(mode, 7)), z(to_oct(nil, 7)), z(to_oct(nil, 7)), z(to_oct(length, 11)), z(to_oct(0, 11)), BLANK_CHECKSUM, type, NULL_100, USTAR, DOUBLE_ZERO, asciiz('', 32), asciiz('', 32), z(to_oct(nil, 7)), z(to_oct(nil, 7)), asciiz(dname, 155) ] h = arr.join.bytes.to_a.pack('C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155') ret = h + "\0" * (512 - h.size) assert_equal(512, ret.size) ret end def update_checksum(header) header[FIELDS['checksum'].offset, FIELDS['checksum'].length] = # inject(:+) was introduced in which version? sp(z(to_oct(header.unpack('C*').inject { |a, e| a + e }, 6))) header end def to_oct(n, pad_size) if n.nil? "\0" * pad_size else "%0#{pad_size}o" % n end end def asciiz(str, length) str + "\0" * (length - str.length) end def sp(s) s + ' ' end def z(s) s + "\0" end def mode_string(value) '%04o' % (value & 0o777) end Minitest::Test.send(:include, self) end minitar-0.6.1/test/test_tar_writer.rb0000644000004100000410000001312413053507500017735 0ustar www-datawww-data#!/usr/bin/env ruby require 'minitar' require 'minitest_helper' class TestTarWriter < Minitest::Test class DummyIO attr_reader :data def initialize @data = '' end def write(dat) data << dat dat.size end def reset @data = '' end end def setup @data = 'a' * 10 @dummyos = DummyIO.new @os = Minitar::Writer.new(@dummyos) end def teardown @os.close end def test_open_no_block writer = Minitar::Writer.open(@dummyos) refute writer.closed? ensure writer.close assert writer.closed? end def test_add_file_simple @dummyos.reset Minitar::Writer.open(@dummyos) do |os| os.add_file_simple('lib/foo/bar', :mode => 0o644, :size => 10) do |f| f.write 'a' * 10 end os.add_file_simple('lib/bar/baz', :mode => 0o644, :size => 100) do |f| f.write 'fillme' end end assert_headers_equal(tar_file_header('lib/foo/bar', '', 0o644, 10), @dummyos.data[0, 512]) assert_equal('a' * 10 + "\0" * 502, @dummyos.data[512, 512]) assert_headers_equal(tar_file_header('lib/bar/baz', '', 0o644, 100), @dummyos.data[512 * 2, 512]) assert_equal('fillme' + "\0" * 506, @dummyos.data[512 * 3, 512]) assert_equal("\0" * 512, @dummyos.data[512 * 4, 512]) assert_equal("\0" * 512, @dummyos.data[512 * 5, 512]) end def test_write_operations_fail_after_closed @dummyos.reset @os.add_file_simple('sadd', :mode => 0o644, :size => 20) { |f| } @os.close assert_raises(Minitar::ClosedStream) { @os.flush } assert_raises(Minitar::ClosedStream) { @os.add_file('dfdsf', :mode => 0o644) {} } assert_raises(Minitar::ClosedStream) { @os.mkdir 'sdfdsf', :mode => 0o644 } end def test_file_name_is_split_correctly # test insane file lengths, and: a{100}/b{155}, etc @dummyos.reset names = [ "#{'a' * 155}/#{'b' * 100}", "#{'a' * 151}/#{'qwer/' * 19}bla", "/#{'a' * 49}/#{'b' * 50}", "#{'a' * 49}/#{'b' * 50}x", "#{'a' * 49}x/#{'b' * 50}" ] o_names = [ 'b' * 100, "#{'qwer/' * 19}bla", 'b' * 50, "#{'b' * 50}x", 'b' * 50 ] o_prefixes = [ 'a' * 155, 'a' * 151, "/#{'a' * 49}", 'a' * 49, "#{'a' * 49}x" ] names.each do |name| @os.add_file_simple(name, :mode => 0o644, :size => 10) {} end names.each_index do |i| assert_headers_equal( tar_file_header(o_names[i], o_prefixes[i], 0o644, 10), @dummyos.data[2 * i * 512, 512] ) end assert_raises(Minitar::FileNameTooLong) do @os.add_file_simple(File.join('a' * 152, 'b' * 10, 'a' * 92), :mode => 0o644, :size => 10) {} end assert_raises(Minitar::FileNameTooLong) do @os.add_file_simple(File.join('a' * 162, 'b' * 10), :mode => 0o644, :size => 10) {} end assert_raises(Minitar::FileNameTooLong) do @os.add_file_simple(File.join('a' * 10, 'b' * 110), :mode => 0o644, :size => 10) {} end # Issue #6. assert_raises(Minitar::FileNameTooLong) do @os.add_file_simple('a' * 114, :mode => 0o644, :size => 10) {} end end def test_add_file dummyos = StringIO.new def dummyos.method_missing(meth, *a) # rubocop:disable Style/MethodMissing string.send(meth, *a) end content1 = ('a'..'z').to_a.join('') # 26 content2 = ('aa'..'zz').to_a.join('') # 1352 Minitar::Writer.open(dummyos) do |os| os.add_file('lib/foo/bar', :mode => 0o644) { |f, _opts| f.write 'a' * 10 } os.add_file('lib/bar/baz', :mode => 0o644) { |f, _opts| f.write content1 } os.add_file('lib/bar/baz', :mode => 0o644) { |f, _opts| f.write content2 } os.add_file('lib/bar/baz', :mode => 0o644) { |_f, _opts| } end assert_headers_equal(tar_file_header('lib/foo/bar', '', 0o644, 10), dummyos[0, 512]) assert_equal(%Q(#{'a' * 10}#{"\0" * 502}), dummyos[512, 512]) offset = 512 * 2 [content1, content2, ''].each do |data| assert_headers_equal(tar_file_header('lib/bar/baz', '', 0o644, data.size), dummyos[offset, 512]) offset += 512 until !data || data == '' chunk = data[0, 512] data[0, 512] = '' assert_equal(chunk + "\0" * (512 - chunk.size), dummyos[offset, 512]) offset += 512 end end assert_equal("\0" * 1024, dummyos[offset, 1024]) end def test_add_file_tests_seekability assert_raises(Archive::Tar::Minitar::NonSeekableStream) do @os.add_file('libdfdsfd', :mode => 0o644) { |f| } end end def test_write_header @dummyos.reset @os.add_file_simple('lib/foo/bar', :mode => 0o644, :size => 0) {} @os.flush assert_headers_equal(tar_file_header('lib/foo/bar', '', 0o644, 0), @dummyos.data[0, 512]) @dummyos.reset @os.mkdir('lib/foo', :mode => 0o644) assert_headers_equal(tar_dir_header('lib/foo', '', 0o644), @dummyos.data[0, 512]) @os.mkdir('lib/bar', :mode => 0o644) assert_headers_equal(tar_dir_header('lib/bar', '', 0o644), @dummyos.data[512 * 1, 512]) end def test_write_data @dummyos.reset @os.add_file_simple('lib/foo/bar', :mode => 0o644, :size => 10) do |f| f.write @data end @os.flush assert_equal(@data + ("\0" * (512 - @data.size)), @dummyos.data[512, 512]) end def test_file_size_is_checked @dummyos.reset assert_raises(Minitar::Writer::WriteBoundaryOverflow) do @os.add_file_simple('lib/foo/bar', :mode => 0o644, :size => 10) do |f| f.write '1' * 100 end end @os.add_file_simple('lib/foo/bar', :mode => 0o644, :size => 10) { |f| } end end minitar-0.6.1/test/test_tar_reader.rb0000644000004100000410000001204413053507500017663 0ustar www-datawww-data#!/usr/bin/env ruby require 'minitar' require 'minitest_helper' class TestTarReader < Minitest::Test def test_open_no_block str = tar_file_header('lib/foo', '', 0o10644, 10) + "\0" * 512 str += tar_file_header('bar', 'baz', 0o644, 0) str += tar_dir_header('foo', 'bar', 0o12345) str += "\0" * 1024 reader = Minitar::Reader.open(StringIO.new(str)) refute reader.closed? ensure reader.close refute reader.closed? # Reader doesn't actually close anything end def test_multiple_entries str = tar_file_header('lib/foo', '', 0o10644, 10) + "\0" * 512 str += tar_file_header('bar', 'baz', 0o644, 0) str += tar_dir_header('foo', 'bar', 0o12345) str += "\0" * 1024 names = %w(lib/foo bar foo) prefixes = ['', 'baz', 'bar'] modes = [0o10644, 0o644, 0o12345] sizes = [10, 0, 0] isdir = [false, false, true] isfile = [true, true, false] Minitar::Reader.new(StringIO.new(str)) do |is| i = 0 is.each_entry do |entry| assert_kind_of(Minitar::Reader::EntryStream, entry) assert_equal(names[i], entry.name) assert_equal(prefixes[i], entry.prefix) assert_equal(sizes[i], entry.size) assert_equal(modes[i], entry.mode) assert_equal(isdir[i], entry.directory?) assert_equal(isfile[i], entry.file?) if prefixes[i] != '' assert_equal(File.join(prefixes[i], names[i]), entry.full_name) else assert_equal(names[i], entry.name) end i += 1 end assert_equal(names.size, i) end end def test_rewind_entry_works content = ('a'..'z').to_a.join(' ') str = tar_file_header('lib/foo', '', 0o10644, content.size) + content + "\0" * (512 - content.size) str << "\0" * 1024 Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| 3.times do entry.rewind assert_equal(content, entry.read) assert_equal(content.size, entry.pos) end end end end def test_rewind_works content = ('a'..'z').to_a.join(' ') str = tar_file_header('lib/foo', '', 0o10644, content.size) + content + "\0" * (512 - content.size) str << "\0" * 1024 Minitar::Reader.new(StringIO.new(str)) do |is| 3.times do is.rewind i = 0 is.each_entry do |entry| assert_equal(content, entry.read) i += 1 end assert_equal(1, i) end end end def test_read_works contents = ('a'..'z').inject('') { |a, e| a << e * 100 } str = tar_file_header('lib/foo', '', 0o10644, contents.size) + contents str += "\0" * (512 - (str.size % 512)) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| assert_kind_of(Minitar::Reader::EntryStream, entry) data = entry.read(3000) # bigger than contents.size assert_equal(contents, data) assert_equal(true, entry.eof?) end end Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| assert_kind_of(Minitar::Reader::EntryStream, entry) data = entry.read(100) (entry.size - data.size).times { data << entry.getc.chr } assert_equal(contents, data) assert_equal(nil, entry.read(10)) assert_equal(true, entry.eof?) end end Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| assert_kind_of(Minitar::Reader::EntryStream, entry) data = entry.read assert_equal(contents, data) assert_equal(nil, entry.read(10)) assert_equal(nil, entry.read) assert_equal(nil, entry.getc) assert_equal(true, entry.eof?) end end end def test_eof_works str = tar_file_header('bar', 'baz', 0o644, 0) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| assert_kind_of(Minitar::Reader::EntryStream, entry) data = entry.read assert_equal(nil, data) assert_equal(nil, entry.read(10)) assert_equal(nil, entry.read) assert_equal(nil, entry.getc) assert_equal(true, entry.eof?) end end str = tar_dir_header('foo', 'bar', 0o12345) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| assert_kind_of(Minitar::Reader::EntryStream, entry) data = entry.read assert_equal(nil, data) assert_equal(nil, entry.read(10)) assert_equal(nil, entry.read) assert_equal(nil, entry.getc) assert_equal(true, entry.eof?) end end str = tar_dir_header('foo', 'bar', 0o12345) str += tar_file_header('bar', 'baz', 0o644, 0) str += tar_file_header('bar', 'baz', 0o644, 0) Minitar::Reader.new(StringIO.new(str)) do |is| is.each_entry do |entry| assert_kind_of(Minitar::Reader::EntryStream, entry) data = entry.read assert_equal(nil, data) assert_equal(nil, entry.read(10)) assert_equal(nil, entry.read) assert_equal(nil, entry.getc) assert_equal(true, entry.eof?) end end end end minitar-0.6.1/Code-of-Conduct.md0000644000004100000410000000623613053507500016411 0ustar www-datawww-data# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ minitar-0.6.1/docs/0000755000004100000410000000000013053507500014137 5ustar www-datawww-dataminitar-0.6.1/docs/ruby.txt0000644000004100000410000000470713053507500015671 0ustar www-datawww-dataRuby is copyrighted free software by Yukihiro Matsumoto . You can redistribute it and/or modify it under either the terms of the 2-clause BSDL (see the file BSDL), or the conditions below: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b) use the modified software only within your corporation or organization. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 3. You may distribute the software in object code or binary form, provided that you do at least ONE of the following: a) distribute the binaries and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b) accompany the distribution with the machine-readable source of the software. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). But some files in the distribution are not written by the author, so that they are not under these terms. For the list of those files and their copying conditions, see the file LEGAL. 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.minitar-0.6.1/docs/bsdl.txt0000644000004100000410000000231213053507500015622 0ustar www-datawww-dataRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. minitar-0.6.1/minitar.gemspec0000644000004100000410000001130513053507500016217 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "minitar" s.version = "0.6.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Austin Ziegler"] s.date = "2017-02-08" s.description = "The minitar library is a pure-Ruby library that provides the ability to deal\nwith POSIX tar(1) archive files.\n\nThis is release 0.6, providing a number of bug fixes including a directory\ntraversal vulnerability, CVE-2016-10173. This release starts the migration and\nmodernization of the code:\n\n* the licence has been changed to match the modern Ruby licensing scheme\n (Ruby and Simplified BSD instead of Ruby and GNU GPL);\n* the +minitar+ command-line program has been separated into the\n +minitar-cli+ gem; and\n* the +archive-tar-minitar+ gem now points to the +minitar+ and +minitar-cli+\n gems and discourages its installation.\n\nSome of these changes may break existing programs that depend on the internal\nstructure of the minitar library, but every effort has been made to ensure\ncompatibility; inasmuch as is possible, this compatibility will be maintained\nthrough the release of minitar 1.0 (which will have strong breaking changes).\n\nminitar (previously called Archive::Tar::Minitar) is based heavily on code\noriginally written by Mauricio Julio Fern\u{e1}ndez Pradier for the rpa-base\nproject." s.email = ["halostatue@gmail.com"] s.extra_rdoc_files = ["Code-of-Conduct.md", "Contributing.md", "History.md", "Licence.md", "Manifest.txt", "README.rdoc", "docs/bsdl.txt", "docs/ruby.txt"] s.files = ["Code-of-Conduct.md", "Contributing.md", "History.md", "Licence.md", "Manifest.txt", "README.rdoc", "Rakefile", "docs/bsdl.txt", "docs/ruby.txt", "lib/archive-tar-minitar.rb", "lib/archive/tar/minitar.rb", "lib/archive/tar/minitar/input.rb", "lib/archive/tar/minitar/output.rb", "lib/archive/tar/minitar/posix_header.rb", "lib/archive/tar/minitar/reader.rb", "lib/archive/tar/minitar/writer.rb", "lib/minitar.rb", "test/minitest_helper.rb", "test/support/tar_test_helpers.rb", "test/test_tar_header.rb", "test/test_tar_input.rb", "test/test_tar_output.rb", "test/test_tar_reader.rb", "test/test_tar_writer.rb"] s.homepage = "https://github.com/halostatue/minitar/" s.licenses = ["Ruby", "BSD-2-Clause"] s.post_install_message = "The `minitar` executable is no longer bundled with `minitar`. If you are\nexpecting this executable, make sure you also install `minitar-cli`.\n" s.rdoc_options = ["--main", "README.rdoc"] s.require_paths = ["lib"] s.required_ruby_version = Gem::Requirement.new(">= 1.8") s.rubygems_version = "1.8.23" s.summary = "The minitar library is a pure-Ruby library that provides the ability to deal with POSIX tar(1) archive files" if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 3.16"]) s.add_development_dependency(%q, ["~> 1.0"]) s.add_development_dependency(%q, ["~> 1.1"]) s.add_development_dependency(%q, ["~> 1.6"]) s.add_development_dependency(%q, ["~> 1.0"]) s.add_development_dependency(%q, ["~> 1.2"]) s.add_development_dependency(%q, ["~> 5.10"]) s.add_development_dependency(%q, ["< 2", ">= 1.0"]) s.add_development_dependency(%q, ["< 12", ">= 10.0"]) s.add_development_dependency(%q, [">= 0.0"]) else s.add_dependency(%q, ["~> 3.16"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.1"]) s.add_dependency(%q, ["~> 1.6"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.2"]) s.add_dependency(%q, ["~> 5.10"]) s.add_dependency(%q, ["< 2", ">= 1.0"]) s.add_dependency(%q, ["< 12", ">= 10.0"]) s.add_dependency(%q, [">= 0.0"]) end else s.add_dependency(%q, ["~> 3.16"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.1"]) s.add_dependency(%q, ["~> 1.6"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 1.2"]) s.add_dependency(%q, ["~> 5.10"]) s.add_dependency(%q, ["< 2", ">= 1.0"]) s.add_dependency(%q, ["< 12", ">= 10.0"]) s.add_dependency(%q, [">= 0.0"]) end end minitar-0.6.1/Licence.md0000644000004100000410000000062613053507500015077 0ustar www-datawww-data## Licence minitar is free software that may be redistributed and/or modified under the terms of Ruby’s licence or the Simplified BSD licence. * Copyright 2004–2017 Austin Ziegler. * Portions copyright 2004 Mauricio Julio Fernández Pradier. ### Simplified BSD Licence See the file docs/bsdl.txt in the main distribution. ### Ruby’s Licence See the file docs/ruby.txt in the main distribution. minitar-0.6.1/History.md0000644000004100000410000001263613053507500015202 0ustar www-datawww-data## 0.6.1 / 2017-02-07 * Fixed issue [#24][] where streams were being improperly closed immediately on open unless there was a block provided. * Hopefully fixes issue [#23][] by releasing archive-tar-minitar after minitar-cli is available. ## 0.6 / 2017-02-07 * Breaking Changes: * Extracted `bin/minitar` into a new gem, `minitar-cli`. No, I am *not* going to bump the major version for this. As far as I can tell, few people use the command-line utility anyway. (Installing `archive-tar-minitar` will install both `minitar` and `minitar-cli`, at least until version 1.0.) * Minitar extraction before 0.6 traverses directories if the tarball includes a relative directory reference, as reported in [#16][] by @ecneladis. This has been disallowed entirely and will throw a SecureRelativePathError when found. Additionally, if the final destination of an entry is an already-existing symbolic link, the existing symbolic link will be removed and the file will be written correctly (on platforms that support symblic links). * Enhancements: * Licence change. After speaking with Mauricio Fernández, we have changed the licensing of this library to Ruby and Simplified BSD and have dropped the GNU GPL license. This takes effect from the 0.6 release. * Printing a deprecation warning for including Archive::Tar to put Minitar in the top-level namespace. * Printing a deprecation warning for including Archive::Tar::Minitar into a class (Minitar will be a class for version 1.0). * Moved Archive::Tar::PosixHeader to Archive::Tar::Minitar::PosixHeader with a deprecation warning. Do not depend on Archive::Tar::Minitar::PosixHeader, as it will be moving to ::Minitar::PosixHeader in a future release. * Added an alias, ::Minitar, for Archive::Tar::Minitar, opted in with `require 'minitar'`. In future releases, this alias will be enabled by default, and the Archive::Tar namespace will be removed entirely for version 1.0. * Modified the handling of `mtime` in PosixHeader to do an integer conversion (#to_i) so that a Time object can be used instead of the integer value of the time object. * Writer::RestrictedStream was renamed to Writer::WriteOnlyStream for clarity. No alias or deprecation warning was provided for this as it is an internal implementation detail. * Writer::BoundedStream was renamed to Writer::BoundedWriteStream for clarity. A deprecation warning is provided on first use because a BoundedWriteStream may raise a BoundedWriteStream::FileOverflow exception. * Writer::BoundedWriteStream::FileOverflow has been renamed to Writer::WriteBoundaryOverflow and inherits from StandardError instead of RuntimeError. Note that for Ruby 2.0 or higher, an error will be raised when specifying Writer::BoundedWriteStream::FileOverflow because Writer::BoundedWriteStream has been declared a private constant. * Modified Writer#add_file_simple to accept the data for a file in `opts[:data]`. When `opts[:data]` is provided, a stream block must not be provided. Improved the documentation for this method. * Modified Writer#add_file to accept `opts[:data]` and transparently call Writer#add_file_simple in this case. * Methods that require blocks are no longer required, so the Archive::Tar::Minitar::BlockRequired exception has been removed with a warning (this may not work on Ruby 1.8). * Dramatically reduced the number of strings created when creating a POSIX tarball header. * Added a helper, Input.each_entry that iterates over each entry in an opened entry object. * Bugs: * Fix [#2][] to handle IO streams that are not seekable, such as pipes, STDIN, or STDOUT. * Fix [#3][] to make the test timezone resilient. * Fix [#4][] for supporting the reading of tar files with filenames in the GNU long filename extension format. Ported from @atoulme’s fork, originally provided by Curtis Sampson. * Fix [#6][] by making it raise the correct error for a long filename with no path components. * Fix [#13][] provided by @fetep fixes an off-by-one error on filename splitting. * Fix [#14][] provided by @kzys should fix Windows detection issues. * Fix [#16][] as specified above. * Fix an issue where Minitar.pack would not include Unix hidden files when creating a tarball. * Development: * Modernized minitar tooling around Hoe. * Added travis and coveralls. ## 0.5.2 / 2008-02-26 * Bugs: * Fixed a Ruby 1.9 compatibility error. ## 0.5.1 / 2004-09-27 * Bugs: * Fixed a variable name error. ## 0.5.0 * Initial release. Does files and directories. Command does create, extract, and list. [#2]: https://github.com/halostatue/minitar/issues/2 [#3]: https://github.com/halostatue/minitar/issues/3 [#4]: https://github.com/halostatue/minitar/issues/4 [#6]: https://github.com/halostatue/minitar/issues/6 [#13]: https://github.com/halostatue/minitar/issues/13 [#14]: https://github.com/halostatue/minitar/issues/14 [#16]: https://github.com/halostatue/minitar/issues/16 [#23]: https://github.com/halostatue/minitar/issues/23 [#24]: https://github.com/halostatue/minitar/issues/24 minitar-0.6.1/Contributing.md0000644000004100000410000000524613053507500016207 0ustar www-datawww-data## Contributing I value any contribution to minitar you can provide: a bug report, a feature request, or code contributions. There are a few guidelines for contributing to minitar: * Code changes *will not* be accepted without tests. The test suite is written with [Minitest][]. * Match my coding style. * Use a thoughtfully-named topic branch that contains your change. Rebase your commits into logical chunks as necessary. * Use [quality commit messages][]. * Do not change the version number; when your patch is accepted and a release is made, the version will be updated at that point. * Submit a GitHub pull request with your changes. * New or changed behaviours require appropriate documentation. ### Test Dependencies minitar uses Ryan Davis’s [Hoe][] to manage the release process, and it adds a number of rake tasks. You will mostly be interested in: $ rake which runs the tests the same way that: $ rake test $ rake travis will do. To assist with the installation of the development dependencies for minitar, I have provided the simplest possible Gemfile pointing to the (generated) `minitar.gemspec` file. This will permit you to do: $ bundle install to get the development dependencies. If you aleady have `hoe` installed, you can accomplish the same thing with: $ rake newb This task will install any missing dependencies, run the tests/specs, and generate the RDoc. You can run tests with code coverage analysis by running: $ rake test:coverage ### Workflow Here's the most direct way to get your work merged into the project: * Fork the project. * Clone down your fork (`git clone git://github.com//minitar.git`). * Create a topic branch to contain your change (`git checkout -b my_awesome_feature`). * Hack away, add tests. Not necessarily in that order. * Make sure everything still passes by running `rake`. * If necessary, rebase your commits into logical chunks, without errors. * Push the branch up (`git push origin my_awesome_feature`). * Create a pull request against halostatue/minitar and describe what your change does and the why you think it should be merged. ### Contributors * Austin Ziegler created minitar, based on work originally written by Mauricio Fernández for rpa-base. Thanks to everyone who has contributed to minitar: * Antoine Toulme * Curtis Sampson * Daniel J. Berger * Kazuyoshi Kato * Matthew Kent * Michal Suchanek * Mike Furr * Pete Fritchman * Zach Dennis [Minitest]: https://github.com/seattlerb/minitest [quality commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [Hoe]: https://github.com/seattlerb/hoe