summaryrefslogtreecommitdiff
path: root/jni/ruby/lib/rubygems/package/tar_writer.rb
diff options
context:
space:
mode:
Diffstat (limited to 'jni/ruby/lib/rubygems/package/tar_writer.rb')
-rw-r--r--jni/ruby/lib/rubygems/package/tar_writer.rb326
1 files changed, 326 insertions, 0 deletions
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
+