123456789_123456789_123456789_123456789_123456789_

Class: Gem::Package::TarWriter

Relationships & Source Files
Namespace Children
Classes:
Exceptions:
Inherits: Object
Defined in: lib/rubygems/package/tar_writer.rb

Overview

Allows writing of tar files

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(io) ⇒ TarWriter

Creates a new TarWriter, yielding it if a block is given

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 75

def self.new(io)
  writer = super

  return writer unless block_given?

  begin
    yield writer
  ensure
    writer.close
  end

  nil
end

#initialize(io) ⇒ TarWriter

Creates a new TarWriter that will write to io

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 92

def initialize(io)
  @io = io
  @closed = false
end

Instance Attribute Details

#closed?Boolean (readonly)

Is the TarWriter closed?

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 271

def closed?
  @closed
end

Instance Method Details

#add_file(name, mode) {|RestrictedStream.new(@io)| ... }

Adds file name with permissions mode, and yields an IO for writing the file to

Yields:

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 101

def add_file(name, mode) # :yields: io
  check_closed

  name, prefix = split_name name

  init_pos = @io.pos
  @io.write Gem::Package::TarHeader::EMPTY_HEADER # 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: Gem.source_date_epoch

  @io.write header
  @io.pos = final_pos

  self
end

#add_file_digest(name, mode, digest_algorithms)

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.

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 137

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
        digest.name
      else
        digest_algorithm.class.name[/::([^:]+)\z/, 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

#add_file_signed(name, mode, signer)

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.

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 169

def add_file_signed(name, mode, signer)
  digest_algorithms = [
    signer.digest_algorithm,
    Gem::Security.create_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
        digest.name
      else
        digest.class.name[/::([^:]+)\z/, 1]
      end

    digest_name == signer.digest_name
  end

  raise "no #{signer.digest_name} in #{digests.values.compact}" unless signature_digest

  if signer.key
    signature = signer.sign signature_digest.digest

    add_file_simple "#{name}.sig", 0o444, signature.length do |io|
      io.write signature
    end
  end

  digests
end

#add_file_simple(name, mode, size) {|os| ... }

Add file name with permissions mode size bytes long. Yields an IO to write the file to.

Yields:

  • (os)
[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 207

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: Gem.source_date_epoch).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

#check_closed

Raises IOError if the TarWriter is closed

Raises:

  • (IOError)
[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 252

def check_closed
  raise IOError, "closed #{self.class}" if closed?
end

#close

Closes the TarWriter

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 259

def close
  check_closed

  @io.write "\0" * 1024
  flush

  @closed = true
end

#flush

Flushes the TarWriter’s IO

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 278

def flush
  check_closed

  @io.flush if @io.respond_to? :flush
end

#mkdir(name, mode)

Creates a new directory in the tar file name with mode

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 287

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: Gem.source_date_epoch

  @io.write header

  self
end

#split_name(name)

This method is for internal use only.

Splits name into a name and prefix that can fit in the TarHeader

[ GitHub ]

  
# File 'lib/rubygems/package/tar_writer.rb', line 305

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

  prefix = ""
  if name.bytesize > 100
    parts = name.split("/", -1) # parts are never empty here
    name = parts.pop            # initially empty for names with a trailing slash ("foo/.../bar/")
    prefix = parts.join("/")    # if empty, then it's impossible to split (parts is empty too)
    while !parts.empty? && (prefix.bytesize > 155 || name.empty?)
      name = parts.pop + "/" + name
      prefix = parts.join("/")
    end

    if name.bytesize > 100 || prefix.empty?
      raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)")
    end

    if prefix.bytesize > 155
      raise Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)")
    end
  end

  [name, prefix]
end