1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/rubygems/package/tar_writer.rb

298 lines
5.9 KiB
Ruby
Raw Normal View History

# -*- coding: utf-8 -*-
#--
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#++
##
# 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
@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, 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,
OpenSSL::Digest::SHA512,
].uniq
digests = add_file_digest name, mode, digest_algorithms do |io|
yield io
end
signature_digest = digests.values.find do |digest|
digest.name == signer.digest_name
end
signature = signer.sign signature_digest.digest
add_file_simple "#{name}.sig", 0444, signature.length do |io|
io.write signature
end if signature
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).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
@io.write header
self
end
##
# Splits +name+ into a name and prefix that can fit in the TarHeader
def split_name(name) # :nodoc:
raise Gem::Package::TooLongFileName if name.bytesize > 256
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 or prefix.bytesize > 155 then
raise Gem::Package::TooLongFileName
end
end
return name, prefix
end
end