diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/rubygems/package/tar_writer.rb |
Fresh start
Diffstat (limited to 'jni/ruby/lib/rubygems/package/tar_writer.rb')
-rw-r--r-- | jni/ruby/lib/rubygems/package/tar_writer.rb | 326 |
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 + |