summaryrefslogtreecommitdiff
path: root/jni/ruby/lib/rubygems/package
diff options
context:
space:
mode:
Diffstat (limited to 'jni/ruby/lib/rubygems/package')
-rw-r--r--jni/ruby/lib/rubygems/package/digest_io.rb64
-rw-r--r--jni/ruby/lib/rubygems/package/file_source.rb33
-rw-r--r--jni/ruby/lib/rubygems/package/io_source.rb45
-rw-r--r--jni/ruby/lib/rubygems/package/old.rb178
-rw-r--r--jni/ruby/lib/rubygems/package/source.rb3
-rw-r--r--jni/ruby/lib/rubygems/package/tar_header.rb229
-rw-r--r--jni/ruby/lib/rubygems/package/tar_reader.rb123
-rw-r--r--jni/ruby/lib/rubygems/package/tar_reader/entry.rb147
-rw-r--r--jni/ruby/lib/rubygems/package/tar_test_case.rb137
-rw-r--r--jni/ruby/lib/rubygems/package/tar_writer.rb326
10 files changed, 1285 insertions, 0 deletions
diff --git a/jni/ruby/lib/rubygems/package/digest_io.rb b/jni/ruby/lib/rubygems/package/digest_io.rb
new file mode 100644
index 0000000..f8bde0f
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/digest_io.rb
@@ -0,0 +1,64 @@
+##
+# IO wrapper that creates digests of contents written to the IO it wraps.
+
+class Gem::Package::DigestIO
+
+ ##
+ # Collected digests for wrapped writes.
+ #
+ # {
+ # 'SHA1' => #<OpenSSL::Digest: [...]>,
+ # 'SHA512' => #<OpenSSL::Digest: [...]>,
+ # }
+
+ attr_reader :digests
+
+ ##
+ # Wraps +io+ and updates digest for each of the digest algorithms in
+ # the +digests+ Hash. Returns the digests hash. Example:
+ #
+ # io = StringIO.new
+ # digests = {
+ # 'SHA1' => OpenSSL::Digest.new('SHA1'),
+ # 'SHA512' => OpenSSL::Digest.new('SHA512'),
+ # }
+ #
+ # Gem::Package::DigestIO.wrap io, digests do |digest_io|
+ # digest_io.write "hello"
+ # end
+ #
+ # digests['SHA1'].hexdigest #=> "aaf4c61d[...]"
+ # digests['SHA512'].hexdigest #=> "9b71d224[...]"
+
+ def self.wrap io, digests
+ digest_io = new io, digests
+
+ yield digest_io
+
+ return digests
+ end
+
+ ##
+ # Creates a new DigestIO instance. Using ::wrap is recommended, see the
+ # ::wrap documentation for documentation of +io+ and +digests+.
+
+ def initialize io, digests
+ @io = io
+ @digests = digests
+ end
+
+ ##
+ # Writes +data+ to the underlying IO and updates the digests
+
+ def write data
+ result = @io.write data
+
+ @digests.each do |_, digest|
+ digest << data
+ end
+
+ result
+ end
+
+end
+
diff --git a/jni/ruby/lib/rubygems/package/file_source.rb b/jni/ruby/lib/rubygems/package/file_source.rb
new file mode 100644
index 0000000..85316f6
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/file_source.rb
@@ -0,0 +1,33 @@
+##
+# The primary source of gems is a file on disk, including all usages
+# internal to rubygems.
+#
+# This is a private class, do not depend on it directly. Instead, pass a path
+# object to `Gem::Package.new`.
+
+class Gem::Package::FileSource < Gem::Package::Source # :nodoc: all
+
+ attr_reader :path
+
+ def initialize path
+ @path = path
+ end
+
+ def start
+ @start ||= File.read path, 20
+ end
+
+ def present?
+ File.exist? path
+ end
+
+ def with_write_io &block
+ open path, 'wb', &block
+ end
+
+ def with_read_io &block
+ open path, 'rb', &block
+ end
+
+end
+
diff --git a/jni/ruby/lib/rubygems/package/io_source.rb b/jni/ruby/lib/rubygems/package/io_source.rb
new file mode 100644
index 0000000..f89593d
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/io_source.rb
@@ -0,0 +1,45 @@
+##
+# Supports reading and writing gems from/to a generic IO object. This is
+# useful for other applications built on top of rubygems, such as
+# rubygems.org.
+#
+# This is a private class, do not depend on it directly. Instead, pass an IO
+# object to `Gem::Package.new`.
+
+class Gem::Package::IOSource < Gem::Package::Source # :nodoc: all
+
+ attr_reader :io
+
+ def initialize io
+ @io = io
+ end
+
+ def start
+ @start ||= begin
+ if io.pos > 0
+ raise Gem::Package::Error, "Cannot read start unless IO is at start"
+ end
+
+ value = io.read 20
+ io.rewind
+ value
+ end
+ end
+
+ def present?
+ true
+ end
+
+ def with_read_io
+ yield io
+ end
+
+ def with_write_io
+ yield io
+ end
+
+ def path
+ end
+
+end
+
diff --git a/jni/ruby/lib/rubygems/package/old.rb b/jni/ruby/lib/rubygems/package/old.rb
new file mode 100644
index 0000000..d7b228d
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/old.rb
@@ -0,0 +1,178 @@
+#--
+# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
+# All rights reserved.
+# See LICENSE.txt for permissions.
+#++
+
+##
+# The format class knows the guts of the ancient .gem file format and provides
+# the capability to read such ancient gems.
+#
+# Please pretend this doesn't exist.
+
+class Gem::Package::Old < Gem::Package
+
+ undef_method :spec=
+
+ ##
+ # Creates a new old-format package reader for +gem+. Old-format packages
+ # cannot be written.
+
+ def initialize gem
+ require 'fileutils'
+ require 'zlib'
+ Gem.load_yaml
+
+ @contents = nil
+ @gem = gem
+ @security_policy = nil
+ @spec = nil
+ end
+
+ ##
+ # A list of file names contained in this gem
+
+ def contents
+ verify
+
+ return @contents if @contents
+
+ @gem.with_read_io do |io|
+ read_until_dashes io # spec
+ header = file_list io
+
+ @contents = header.map { |file| file['path'] }
+ end
+ end
+
+ ##
+ # Extracts the files in this package into +destination_dir+
+
+ def extract_files destination_dir
+ verify
+
+ errstr = "Error reading files from gem"
+
+ @gem.with_read_io do |io|
+ read_until_dashes io # spec
+ header = file_list io
+ raise Gem::Exception, errstr unless header
+
+ header.each do |entry|
+ full_name = entry['path']
+
+ destination = install_location full_name, destination_dir
+
+ file_data = ''
+
+ read_until_dashes io do |line|
+ file_data << line
+ end
+
+ file_data = file_data.strip.unpack("m")[0]
+ file_data = Zlib::Inflate.inflate file_data
+
+ raise Gem::Package::FormatError, "#{full_name} in #{@gem} is corrupt" if
+ file_data.length != entry['size'].to_i
+
+ FileUtils.rm_rf destination
+
+ FileUtils.mkdir_p File.dirname destination
+
+ open destination, 'wb', entry['mode'] do |out|
+ out.write file_data
+ end
+
+ verbose destination
+ end
+ end
+ rescue Zlib::DataError
+ raise Gem::Exception, errstr
+ end
+
+ ##
+ # Reads the file list section from the old-format gem +io+
+
+ def file_list io # :nodoc:
+ header = ''
+
+ read_until_dashes io do |line|
+ header << line
+ end
+
+ YAML.load header
+ end
+
+ ##
+ # Reads lines until a "---" separator is found
+
+ def read_until_dashes io # :nodoc:
+ while (line = io.gets) && line.chomp.strip != "---" do
+ yield line if block_given?
+ end
+ end
+
+ ##
+ # Skips the Ruby self-install header in +io+.
+
+ def skip_ruby io # :nodoc:
+ loop do
+ line = io.gets
+
+ return if line.chomp == '__END__'
+ break unless line
+ end
+
+ raise Gem::Exception, "Failed to find end of ruby script while reading gem"
+ end
+
+ ##
+ # The specification for this gem
+
+ def spec
+ verify
+
+ return @spec if @spec
+
+ yaml = ''
+
+ @gem.with_read_io do |io|
+ skip_ruby io
+ read_until_dashes io do |line|
+ yaml << line
+ end
+ end
+
+ yaml_error = if RUBY_VERSION < '1.9' then
+ YAML::ParseError
+ elsif YAML.const_defined?(:ENGINE) && YAML::ENGINE.yamler == 'syck' then
+ YAML::ParseError
+ else
+ YAML::SyntaxError
+ end
+
+ begin
+ @spec = Gem::Specification.from_yaml yaml
+ rescue yaml_error
+ raise Gem::Exception, "Failed to parse gem specification out of gem file"
+ end
+ rescue ArgumentError
+ raise Gem::Exception, "Failed to parse gem specification out of gem file"
+ end
+
+ ##
+ # Raises an exception if a security policy that verifies data is active.
+ # Old format gems cannot be verified as signed.
+
+ def verify
+ return true unless @security_policy
+
+ raise Gem::Security::Exception,
+ 'old format gems do not contain signatures and cannot be verified' if
+ @security_policy.verify_data
+
+ true
+ end
+
+end
+
diff --git a/jni/ruby/lib/rubygems/package/source.rb b/jni/ruby/lib/rubygems/package/source.rb
new file mode 100644
index 0000000..1f18d47
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/source.rb
@@ -0,0 +1,3 @@
+class Gem::Package::Source # :nodoc:
+end
+
diff --git a/jni/ruby/lib/rubygems/package/tar_header.rb b/jni/ruby/lib/rubygems/package/tar_header.rb
new file mode 100644
index 0000000..f9ab13a
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/tar_header.rb
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+#--
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#++
+
+##
+#--
+# struct tarfile_entry_posix {
+# char name[100]; # ASCII + (Z unless filled)
+# char mode[8]; # 0 padded, octal, null
+# char uid[8]; # ditto
+# char gid[8]; # ditto
+# char size[12]; # 0 padded, octal, null
+# char mtime[12]; # 0 padded, octal, null
+# char checksum[8]; # 0 padded, octal, null, space
+# char typeflag[1]; # file: "0" dir: "5"
+# char linkname[100]; # ASCII + (Z unless filled)
+# char magic[6]; # "ustar\0"
+# char version[2]; # "00"
+# char uname[32]; # ASCIIZ
+# char gname[32]; # ASCIIZ
+# char devmajor[8]; # 0 padded, octal, null
+# char devminor[8]; # o padded, octal, null
+# char prefix[155]; # ASCII + (Z unless filled)
+# };
+#++
+# A header for a tar file
+
+class Gem::Package::TarHeader
+
+ ##
+ # Fields in the tar header
+
+ FIELDS = [
+ :checksum,
+ :devmajor,
+ :devminor,
+ :gid,
+ :gname,
+ :linkname,
+ :magic,
+ :mode,
+ :mtime,
+ :name,
+ :prefix,
+ :size,
+ :typeflag,
+ :uid,
+ :uname,
+ :version,
+ ]
+
+ ##
+ # Pack format for a tar header
+
+ PACK_FORMAT = 'a100' + # name
+ 'a8' + # mode
+ 'a8' + # uid
+ 'a8' + # gid
+ 'a12' + # size
+ 'a12' + # mtime
+ 'a7a' + # chksum
+ 'a' + # typeflag
+ 'a100' + # linkname
+ 'a6' + # magic
+ 'a2' + # version
+ 'a32' + # uname
+ 'a32' + # gname
+ 'a8' + # devmajor
+ 'a8' + # devminor
+ 'a155' # prefix
+
+ ##
+ # Unpack format for a tar header
+
+ UNPACK_FORMAT = 'A100' + # name
+ 'A8' + # mode
+ 'A8' + # uid
+ 'A8' + # gid
+ 'A12' + # size
+ 'A12' + # mtime
+ 'A8' + # checksum
+ 'A' + # typeflag
+ 'A100' + # linkname
+ 'A6' + # magic
+ 'A2' + # version
+ 'A32' + # uname
+ 'A32' + # gname
+ 'A8' + # devmajor
+ 'A8' + # devminor
+ 'A155' # prefix
+
+ attr_reader(*FIELDS)
+
+ ##
+ # Creates a tar header from IO +stream+
+
+ def self.from(stream)
+ header = stream.read 512
+ empty = (header == "\0" * 512)
+
+ fields = header.unpack UNPACK_FORMAT
+
+ new :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 => empty
+ end
+
+ ##
+ # Creates a new TarHeader using +vals+
+
+ def initialize(vals)
+ unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then
+ raise ArgumentError, ":name, :size, :prefix and :mode required"
+ end
+
+ vals[:uid] ||= 0
+ vals[:gid] ||= 0
+ vals[:mtime] ||= 0
+ vals[:checksum] ||= ""
+ vals[:typeflag] = "0" if vals[:typeflag].nil? || vals[:typeflag].empty?
+ vals[:magic] ||= "ustar"
+ vals[:version] ||= "00"
+ vals[:uname] ||= "wheel"
+ vals[:gname] ||= "wheel"
+ vals[:devmajor] ||= 0
+ vals[:devminor] ||= 0
+
+ FIELDS.each do |name|
+ instance_variable_set "@#{name}", vals[name]
+ end
+
+ @empty = vals[:empty]
+ end
+
+ ##
+ # Is the tar entry empty?
+
+ def empty?
+ @empty
+ end
+
+ def ==(other) # :nodoc:
+ self.class === other and
+ @checksum == other.checksum and
+ @devmajor == other.devmajor and
+ @devminor == other.devminor and
+ @gid == other.gid and
+ @gname == other.gname and
+ @linkname == other.linkname and
+ @magic == other.magic and
+ @mode == other.mode and
+ @mtime == other.mtime and
+ @name == other.name and
+ @prefix == other.prefix and
+ @size == other.size and
+ @typeflag == other.typeflag and
+ @uid == other.uid and
+ @uname == other.uname and
+ @version == other.version
+ end
+
+ def to_s # :nodoc:
+ update_checksum
+ header
+ end
+
+ ##
+ # Updates the TarHeader's checksum
+
+ def update_checksum
+ header = header " " * 8
+ @checksum = oct calculate_checksum(header), 6
+ end
+
+ private
+
+ def calculate_checksum(header)
+ header.unpack("C*").inject { |a, b| a + b }
+ end
+
+ def header(checksum = @checksum)
+ header = [
+ name,
+ oct(mode, 7),
+ oct(uid, 7),
+ oct(gid, 7),
+ oct(size, 11),
+ oct(mtime, 11),
+ checksum,
+ " ",
+ typeflag,
+ linkname,
+ magic,
+ oct(version, 2),
+ uname,
+ gname,
+ oct(devmajor, 7),
+ oct(devminor, 7),
+ prefix
+ ]
+
+ header = header.pack PACK_FORMAT
+
+ header << ("\0" * ((512 - header.size) % 512))
+ end
+
+ def oct(num, len)
+ "%0#{len}o" % num
+ end
+
+end
+
diff --git a/jni/ruby/lib/rubygems/package/tar_reader.rb b/jni/ruby/lib/rubygems/package/tar_reader.rb
new file mode 100644
index 0000000..e257fdd
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/tar_reader.rb
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+#--
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#++
+
+##
+# TarReader reads tar files and allows iteration over their items
+
+class Gem::Package::TarReader
+
+ include Enumerable
+
+ ##
+ # Raised if the tar IO is not seekable
+
+ class UnexpectedEOF < StandardError; end
+
+ ##
+ # Creates a new TarReader on +io+ and yields it to the block, if given.
+
+ def self.new(io)
+ reader = super
+
+ return reader unless block_given?
+
+ begin
+ yield reader
+ ensure
+ reader.close
+ end
+
+ nil
+ end
+
+ ##
+ # Creates a new tar file reader on +io+ which needs to respond to #pos,
+ # #eof?, #read, #getc and #pos=
+
+ def initialize(io)
+ @io = io
+ @init_pos = io.pos
+ end
+
+ ##
+ # Close the tar file
+
+ def close
+ end
+
+ ##
+ # Iterates over files in the tarball yielding each entry
+
+ def each
+ return enum_for __method__ unless block_given?
+
+ until @io.eof? do
+ header = Gem::Package::TarHeader.from @io
+ return if header.empty?
+
+ entry = Gem::Package::TarReader::Entry.new header, @io
+ size = entry.header.size
+
+ yield entry
+
+ skip = (512 - (size % 512)) % 512
+ pending = size - entry.bytes_read
+
+ begin
+ # avoid reading...
+ @io.seek pending, IO::SEEK_CUR
+ pending = 0
+ rescue Errno::EINVAL, NameError
+ while pending > 0 do
+ bytes_read = @io.read([pending, 4096].min).size
+ raise UnexpectedEOF if @io.eof?
+ pending -= bytes_read
+ end
+ end
+
+ @io.read skip # discard trailing zeros
+
+ # make sure nobody can use #read, #getc or #rewind anymore
+ entry.close
+ end
+ end
+
+ alias each_entry each
+
+ ##
+ # NOTE: Do not call #rewind during #each
+
+ def rewind
+ if @init_pos == 0 then
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :rewind
+ @io.rewind
+ else
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
+ @io.pos = @init_pos
+ end
+ end
+
+ ##
+ # Seeks through the tar file until it finds the +entry+ with +name+ and
+ # yields it. Rewinds the tar file to the beginning when the block
+ # terminates.
+
+ def seek name # :yields: entry
+ found = find do |entry|
+ entry.full_name == name
+ end
+
+ return unless found
+
+ return yield found
+ ensure
+ rewind
+ end
+
+end
+
+require 'rubygems/package/tar_reader/entry'
+
diff --git a/jni/ruby/lib/rubygems/package/tar_reader/entry.rb b/jni/ruby/lib/rubygems/package/tar_reader/entry.rb
new file mode 100644
index 0000000..737c763
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/tar_reader/entry.rb
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+#++
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#--
+
+##
+# Class for reading entries out of a tar file
+
+class Gem::Package::TarReader::Entry
+
+ ##
+ # Header for this tar entry
+
+ attr_reader :header
+
+ ##
+ # Creates a new tar entry for +header+ that will be read from +io+
+
+ def initialize(header, io)
+ @closed = false
+ @header = header
+ @io = io
+ @orig_pos = @io.pos
+ @read = 0
+ end
+
+ def check_closed # :nodoc:
+ raise IOError, "closed #{self.class}" if closed?
+ end
+
+ ##
+ # Number of bytes read out of the tar entry
+
+ def bytes_read
+ @read
+ end
+
+ ##
+ # Closes the tar entry
+
+ def close
+ @closed = true
+ end
+
+ ##
+ # Is the tar entry closed?
+
+ def closed?
+ @closed
+ end
+
+ ##
+ # Are we at the end of the tar entry?
+
+ def eof?
+ check_closed
+
+ @read >= @header.size
+ end
+
+ ##
+ # Full name of the tar entry
+
+ def full_name
+ if @header.prefix != "" then
+ File.join @header.prefix, @header.name
+ else
+ @header.name
+ end
+ rescue ArgumentError => e
+ raise unless e.message == 'string contains null byte'
+ raise Gem::Package::TarInvalidError,
+ 'tar is corrupt, name contains null byte'
+ end
+
+ ##
+ # Read one byte from the tar entry
+
+ def getc
+ check_closed
+
+ return nil if @read >= @header.size
+
+ ret = @io.getc
+ @read += 1 if ret
+
+ ret
+ end
+
+ ##
+ # Is this tar entry a directory?
+
+ def directory?
+ @header.typeflag == "5"
+ end
+
+ ##
+ # Is this tar entry a file?
+
+ def file?
+ @header.typeflag == "0"
+ end
+
+ ##
+ # The position in the tar entry
+
+ def pos
+ check_closed
+
+ bytes_read
+ end
+
+ ##
+ # Reads +len+ bytes from the tar file entry, or the rest of the entry if
+ # nil
+
+ def read(len = nil)
+ check_closed
+
+ return nil if @read >= @header.size
+
+ len ||= @header.size - @read
+ max_read = [len, @header.size - @read].min
+
+ ret = @io.read max_read
+ @read += ret.size
+
+ ret
+ end
+
+ alias readpartial read # :nodoc:
+
+ ##
+ # Rewinds to the beginning of the tar file entry
+
+ def rewind
+ check_closed
+
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
+
+ @io.pos = @orig_pos
+ @read = 0
+ end
+
+end
+
diff --git a/jni/ruby/lib/rubygems/package/tar_test_case.rb b/jni/ruby/lib/rubygems/package/tar_test_case.rb
new file mode 100644
index 0000000..5253e32
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/tar_test_case.rb
@@ -0,0 +1,137 @@
+require 'rubygems/test_case'
+require 'rubygems/package'
+
+##
+# A test case for Gem::Package::Tar* classes
+
+class Gem::Package::TarTestCase < Gem::TestCase
+
+ def ASCIIZ(str, length)
+ str + "\0" * (length - str.length)
+ end
+
+ def SP(s)
+ s + " "
+ end
+
+ def SP_Z(s)
+ s + " \0"
+ end
+
+ def Z(s)
+ s + "\0"
+ end
+
+ def assert_headers_equal(expected, actual)
+ expected = expected.to_s unless String === expected
+ actual = actual.to_s unless String === actual
+
+ fields = %w[
+ name 100
+ mode 8
+ uid 8
+ gid 8
+ size 12
+ mtime 12
+ checksum 8
+ typeflag 1
+ linkname 100
+ magic 6
+ version 2
+ uname 32
+ gname 32
+ devmajor 8
+ devminor 8
+ prefix 155
+ ]
+
+ offset = 0
+
+ until fields.empty? do
+ name = fields.shift
+ length = fields.shift.to_i
+
+ if name == "checksum" then
+ chksum_off = offset
+ offset += length
+ next
+ end
+
+ assert_equal expected[offset, length], actual[offset, length],
+ "Field #{name} of the tar header differs."
+
+ offset += length
+ end
+
+ assert_equal expected[chksum_off, 8], actual[chksum_off, 8]
+ end
+
+ def calc_checksum(header)
+ sum = header.unpack("C*").inject{|s,a| s + a}
+ SP(Z(to_oct(sum, 6)))
+ end
+
+ def header(type, fname, dname, length, mode, mtime, checksum = nil)
+ checksum ||= " " * 8
+
+ arr = [ # struct tarfile_entry_posix
+ ASCIIZ(fname, 100), # char name[100]; ASCII + (Z unless filled)
+ Z(to_oct(mode, 7)), # char mode[8]; 0 padded, octal null
+ Z(to_oct(0, 7)), # char uid[8]; ditto
+ Z(to_oct(0, 7)), # char gid[8]; ditto
+ Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null
+ Z(to_oct(mtime, 11)), # char mtime[12]; 0 padded, octal, null
+ checksum, # char checksum[8]; 0 padded, octal, null, space
+ type, # char typeflag[1]; file: "0" dir: "5"
+ "\0" * 100, # char linkname[100]; ASCII + (Z unless filled)
+ "ustar\0", # char magic[6]; "ustar\0"
+ "00", # char version[2]; "00"
+ ASCIIZ("wheel", 32), # char uname[32]; ASCIIZ
+ ASCIIZ("wheel", 32), # char gname[32]; ASCIIZ
+ Z(to_oct(0, 7)), # char devmajor[8]; 0 padded, octal, null
+ Z(to_oct(0, 7)), # char devminor[8]; 0 padded, octal, null
+ ASCIIZ(dname, 155) # char prefix[155]; ASCII + (Z unless filled)
+ ]
+
+ format = "C100C8C8C8C12C12C8CC100C6C2C32C32C8C8C155"
+ h = if RUBY_VERSION >= "1.9" then
+ arr.join
+ else
+ arr = arr.join("").split(//).map{|x| x[0]}
+ arr.pack format
+ end
+ ret = h + "\0" * (512 - h.size)
+ assert_equal(512, ret.size)
+ ret
+ end
+
+ def tar_dir_header(name, prefix, mode, mtime)
+ h = header("5", name, prefix, 0, mode, mtime)
+ checksum = calc_checksum(h)
+ header("5", name, prefix, 0, mode, mtime, checksum)
+ end
+
+ def tar_file_header(fname, dname, mode, length, mtime)
+ h = header("0", fname, dname, length, mode, mtime)
+ checksum = calc_checksum(h)
+ header("0", fname, dname, length, mode, mtime, checksum)
+ end
+
+ def to_oct(n, pad_size)
+ "%0#{pad_size}o" % n
+ end
+
+ def util_entry(tar)
+ io = TempIO.new tar
+
+ header = Gem::Package::TarHeader.from io
+
+ Gem::Package::TarReader::Entry.new header, io
+ end
+
+ def util_dir_entry
+ util_entry tar_dir_header("foo", "bar", 0, Time.now)
+ end
+
+end
+
diff --git a/jni/ruby/lib/rubygems/package/tar_writer.rb b/jni/ruby/lib/rubygems/package/tar_writer.rb
new file mode 100644
index 0000000..dfd6357
--- /dev/null
+++ b/jni/ruby/lib/rubygems/package/tar_writer.rb
@@ -0,0 +1,326 @@
+# -*- coding: utf-8 -*-
+#--
+# Copyright (C) 2004 Mauricio Julio Fernández Pradier
+# See LICENSE.txt for additional licensing information.
+#++
+
+require 'digest'
+
+##
+# Allows writing of tar files
+
+class Gem::Package::TarWriter
+
+ class FileOverflow < StandardError; end
+
+ ##
+ # IO wrapper that allows writing a limited amount of data
+
+ class BoundedStream
+
+ ##
+ # Maximum number of bytes that can be written
+
+ attr_reader :limit
+
+ ##
+ # Number of bytes written
+
+ attr_reader :written
+
+ ##
+ # Wraps +io+ and allows up to +limit+ bytes to be written
+
+ def initialize(io, limit)
+ @io = io
+ @limit = limit
+ @written = 0
+ end
+
+ ##
+ # Writes +data+ onto the IO, raising a FileOverflow exception if the
+ # number of bytes will be more than #limit
+
+ def write(data)
+ if data.bytesize + @written > @limit
+ raise FileOverflow, "You tried to feed more data than fits in the file."
+ end
+ @io.write data
+ @written += data.bytesize
+ data.bytesize
+ end
+
+ end
+
+ ##
+ # IO wrapper that provides only #write
+
+ class RestrictedStream
+
+ ##
+ # Creates a new RestrictedStream wrapping +io+
+
+ def initialize(io)
+ @io = io
+ end
+
+ ##
+ # Writes +data+ onto the IO
+
+ def write(data)
+ @io.write data
+ end
+
+ end
+
+ ##
+ # Creates a new TarWriter, yielding it if a block is given
+
+ def self.new(io)
+ writer = super
+
+ return writer unless block_given?
+
+ begin
+ yield writer
+ ensure
+ writer.close
+ end
+
+ nil
+ end
+
+ ##
+ # Creates a new TarWriter that will write to +io+
+
+ def initialize(io)
+ @io = io
+ @closed = false
+ end
+
+ ##
+ # Adds file +name+ with permissions +mode+, and yields an IO for writing the
+ # file to
+
+ def add_file(name, mode) # :yields: io
+ check_closed
+
+ raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=
+
+ name, prefix = split_name name
+
+ init_pos = @io.pos
+ @io.write "\0" * 512 # placeholder for the header
+
+ yield RestrictedStream.new(@io) if block_given?
+
+ size = @io.pos - init_pos - 512
+
+ remainder = (512 - (size % 512)) % 512
+ @io.write "\0" * remainder
+
+ final_pos = @io.pos
+ @io.pos = init_pos
+
+ header = Gem::Package::TarHeader.new :name => name, :mode => mode,
+ :size => size, :prefix => prefix,
+ :mtime => Time.now
+
+ @io.write header
+ @io.pos = final_pos
+
+ self
+ end
+
+ ##
+ # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing
+ # the file. The +digest_algorithm+ is written to a read-only +name+.sum
+ # file following the given file contents containing the digest name and
+ # hexdigest separated by a tab.
+ #
+ # The created digest object is returned.
+
+ def add_file_digest name, mode, digest_algorithms # :yields: io
+ digests = digest_algorithms.map do |digest_algorithm|
+ digest = digest_algorithm.new
+ digest_name =
+ if digest.respond_to? :name then
+ digest.name
+ else
+ /::([^:]+)$/ =~ digest_algorithm.name
+ $1
+ end
+
+ [digest_name, digest]
+ end
+
+ digests = Hash[*digests.flatten]
+
+ add_file name, mode do |io|
+ Gem::Package::DigestIO.wrap io, digests do |digest_io|
+ yield digest_io
+ end
+ end
+
+ digests
+ end
+
+ ##
+ # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing
+ # the file. The +signer+ is used to add a digest file using its
+ # digest_algorithm per add_file_digest and a cryptographic signature in
+ # +name+.sig. If the signer has no key only the checksum file is added.
+ #
+ # Returns the digest.
+
+ def add_file_signed name, mode, signer
+ digest_algorithms = [
+ signer.digest_algorithm,
+ Digest::SHA512,
+ ].compact.uniq
+
+ digests = add_file_digest name, mode, digest_algorithms do |io|
+ yield io
+ end
+
+ signature_digest = digests.values.compact.find do |digest|
+ digest_name =
+ if digest.respond_to? :name then
+ digest.name
+ else
+ /::([^:]+)$/ =~ digest.class.name
+ $1
+ end
+
+ digest_name == signer.digest_name
+ end
+
+ if signer.key then
+ signature = signer.sign signature_digest.digest
+
+ add_file_simple "#{name}.sig", 0444, signature.length do |io|
+ io.write signature
+ end
+ end
+
+ digests
+ end
+
+ ##
+ # Add file +name+ with permissions +mode+ +size+ bytes long. Yields an IO
+ # to write the file to.
+
+ def add_file_simple(name, mode, size) # :yields: io
+ check_closed
+
+ name, prefix = split_name name
+
+ header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
+ :size => size, :prefix => prefix,
+ :mtime => Time.now).to_s
+
+ @io.write header
+ os = BoundedStream.new @io, size
+
+ yield os if block_given?
+
+ min_padding = size - os.written
+ @io.write("\0" * min_padding)
+
+ remainder = (512 - (size % 512)) % 512
+ @io.write("\0" * remainder)
+
+ self
+ end
+
+ ##
+ # Raises IOError if the TarWriter is closed
+
+ def check_closed
+ raise IOError, "closed #{self.class}" if closed?
+ end
+
+ ##
+ # Closes the TarWriter
+
+ def close
+ check_closed
+
+ @io.write "\0" * 1024
+ flush
+
+ @closed = true
+ end
+
+ ##
+ # Is the TarWriter closed?
+
+ def closed?
+ @closed
+ end
+
+ ##
+ # Flushes the TarWriter's IO
+
+ def flush
+ check_closed
+
+ @io.flush if @io.respond_to? :flush
+ end
+
+ ##
+ # Creates a new directory in the tar file +name+ with +mode+
+
+ def mkdir(name, mode)
+ check_closed
+
+ name, prefix = split_name(name)
+
+ header = Gem::Package::TarHeader.new :name => name, :mode => mode,
+ :typeflag => "5", :size => 0,
+ :prefix => prefix,
+ :mtime => Time.now
+
+ @io.write header
+
+ self
+ end
+
+ ##
+ # Splits +name+ into a name and prefix that can fit in the TarHeader
+
+ def split_name(name) # :nodoc:
+ if name.bytesize > 256
+ raise Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)")
+ end
+
+ if name.bytesize <= 100 then
+ prefix = ""
+ else
+ parts = name.split(/\//)
+ newname = parts.pop
+ nxt = ""
+
+ loop do
+ nxt = parts.pop
+ break if newname.bytesize + 1 + nxt.bytesize > 100
+ newname = nxt + "/" + newname
+ end
+
+ prefix = (parts + [nxt]).join "/"
+ name = newname
+
+ if name.bytesize > 100
+ raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)")
+ end
+
+ if prefix.bytesize > 155 then
+ raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)")
+ end
+ end
+
+ return name, prefix
+ end
+
+end
+