diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-03-31 22:40:06 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-03-31 22:40:06 +0000 |
commit | 8cc45aae947d453acca029e13eb64f3f5f0bf942 (patch) | |
tree | f9485a20c99defe1aae3f32555a41d23c2298ad8 /lib/rubygems/package.rb | |
parent | dc8359969ec71ece10357ba9396430db7f029e45 (diff) | |
download | ruby-8cc45aae947d453acca029e13eb64f3f5f0bf942.tar.gz |
Import RubyGems 1.1.0
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@15873 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rubygems/package.rb')
-rw-r--r-- | lib/rubygems/package.rb | 793 |
1 files changed, 17 insertions, 776 deletions
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index f15e4feecb..9cb393b0c7 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -45,768 +45,15 @@ module Gem::Package class TooLongFileName < Error; end class FormatError < Error; end - module FSyncDir - private - def fsync_dir(dirname) - # make sure this hits the disc - begin - dir = open(dirname, "r") - dir.fsync - rescue # ignore IOError if it's an unpatched (old) Ruby - ensure - dir.close if dir rescue nil - end - end - end - - class TarHeader - FIELDS = [:name, :mode, :uid, :gid, :size, :mtime, :checksum, :typeflag, - :linkname, :magic, :version, :uname, :gname, :devmajor, - :devminor, :prefix] - FIELDS.each {|x| attr_reader x} - - def self.new_from_stream(stream) - data = stream.read(512) - fields = data.unpack("A100" + # record name - "A8A8A8" + # mode, uid, gid - "A12A12" + # size, mtime - "A8A" + # checksum, typeflag - "A100" + # linkname - "A6A2" + # magic, version - "A32" + # uname - "A32" + # gname - "A8A8" + # devmajor, devminor - "A155") # prefix - 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 = (data == "\0" * 512) - - new(:name=>name, :mode=>mode, :uid=>uid, :gid=>gid, :size=>size, - :mtime=>mtime, :checksum=>checksum, :typeflag=>typeflag, - :magic=>magic, :version=>version, :uname=>uname, :gname=>gname, - :devmajor=>devmajor, :devminor=>devminor, :prefix=>prefix, - :empty => empty ) - end - - def initialize(vals) - unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] - raise ArgumentError, ":name, :size, :prefix and :mode required" - end - vals[:uid] ||= 0 - vals[:gid] ||= 0 - vals[:mtime] ||= 0 - vals[:checksum] ||= "" - vals[:typeflag] ||= "0" - vals[:magic] ||= "ustar" - vals[:version] ||= "00" - vals[:uname] ||= "wheel" - vals[:gname] ||= "wheel" - vals[:devmajor] ||= 0 - vals[:devminor] ||= 0 - FIELDS.each {|x| instance_variable_set "@#{x.to_s}", vals[x]} - @empty = vals[:empty] - end - - def empty? - @empty - end - - def to_s - update_checksum - header(checksum) - end - - def update_checksum - h = header(" " * 8) - @checksum = oct(calculate_checksum(h), 6) - end - - private - def oct(num, len) - "%0#{len}o" % num - end - - def calculate_checksum(hdr) - #hdr.split('').map { |c| c[0] }.inject { |a, b| a + b } # HACK rubinius - hdr.unpack("C*").inject{|a,b| a+b} - end - - def header(chksum) - # 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) - # }; - arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11), - oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version, - uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix] - str = arr.pack("a100a8a8a8a12a12" + # name, mode, uid, gid, size, mtime - "a7aaa100a6a2" + # chksum, typeflag, linkname, magic, version - "a32a32a8a8a155") # uname, gname, devmajor, devminor, prefix - str + "\0" * ((512 - str.size) % 512) - end - end - - class TarWriter - class FileOverflow < StandardError; end - class BlockNeeded < StandardError; end - - class BoundedStream - attr_reader :limit, :written - def initialize(io, limit) - @io = io - @limit = limit - @written = 0 - end - - def write(data) - if data.size + @written > @limit - raise FileOverflow, - "You tried to feed more data than fits in the file." - end - @io.write data - @written += data.size - data.size - end - end - - class RestrictedStream - def initialize(anIO) - @io = anIO - end - - def write(data) - @io.write data - end - end - - def self.new(anIO) - writer = super(anIO) - return writer unless block_given? - begin - yield writer - ensure - writer.close - end - nil - end - - def initialize(anIO) - @io = anIO - @closed = false - end - - def add_file_simple(name, mode, size) - raise BlockNeeded unless block_given? - raise ClosedIO if @closed - name, prefix = split_name(name) - header = TarHeader.new(:name => name, :mode => mode, - :size => size, :prefix => prefix).to_s - @io.write header - os = BoundedStream.new(@io, size) - yield os - #FIXME: what if an exception is raised in the block? - min_padding = size - os.written - @io.write("\0" * min_padding) - remainder = (512 - (size % 512)) % 512 - @io.write("\0" * remainder) - end - - def add_file(name, mode) - raise BlockNeeded unless block_given? - raise ClosedIO if @closed - raise 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) - #FIXME: what if an exception is raised in the block? - #FIXME: what if an exception is raised in the block? - size = @io.pos - init_pos - 512 - remainder = (512 - (size % 512)) % 512 - @io.write("\0" * remainder) - final_pos = @io.pos - @io.pos = init_pos - header = TarHeader.new(:name => name, :mode => mode, - :size => size, :prefix => prefix).to_s - @io.write header - @io.pos = final_pos - end - - def mkdir(name, mode) - raise ClosedIO if @closed - name, prefix = split_name(name) - header = TarHeader.new(:name => name, :mode => mode, :typeflag => "5", - :size => 0, :prefix => prefix).to_s - @io.write header - nil - end - - def flush - raise ClosedIO if @closed - @io.flush if @io.respond_to? :flush - end - - def close - #raise ClosedIO if @closed - return if @closed - @io.write "\0" * 1024 - @closed = true - end - - private - def split_name name - raise TooLongFileName if name.size > 256 - if name.size <= 100 - prefix = "" - else - parts = name.split(/\//) - newname = parts.pop - nxt = "" - loop do - nxt = parts.pop - break if newname.size + 1 + nxt.size > 100 - newname = nxt + "/" + newname - end - prefix = (parts + [nxt]).join "/" - name = newname - raise TooLongFileName if name.size > 100 || prefix.size > 155 - end - return name, prefix - end - end - - class TarReader - - include Gem::Package - - class UnexpectedEOF < StandardError; end - - module InvalidEntry - def read(len=nil); raise ClosedIO; end - def getc; raise ClosedIO; end - def rewind; raise ClosedIO; end - end - - class Entry - TarHeader::FIELDS.each{|x| attr_reader x} - - def initialize(header, anIO) - @io = anIO - @name = header.name - @mode = header.mode - @uid = header.uid - @gid = header.gid - @size = header.size - @mtime = header.mtime - @checksum = header.checksum - @typeflag = header.typeflag - @linkname = header.linkname - @magic = header.magic - @version = header.version - @uname = header.uname - @gname = header.gname - @devmajor = header.devmajor - @devminor = header.devminor - @prefix = header.prefix - @read = 0 - @orig_pos = @io.pos - end - - def read(len = nil) - return nil if @read >= @size - len ||= @size - @read - max_read = [len, @size - @read].min - ret = @io.read(max_read) - @read += ret.size - ret - end - - def getc - return nil if @read >= @size - ret = @io.getc - @read += 1 if ret - ret - end - - def is_directory? - @typeflag == "5" - end - - def is_file? - @typeflag == "0" - end - - def eof? - @read >= @size - end - - def pos - @read - end - - def rewind - raise NonSeekableIO unless @io.respond_to? :pos= - @io.pos = @orig_pos - @read = 0 - end - - alias_method :is_directory, :is_directory? - alias_method :is_file, :is_file? - - def bytes_read - @read - end - - def full_name - if @prefix != "" - File.join(@prefix, @name) - else - @name - end - end - - def close - invalidate - end - - private - def invalidate - extend InvalidEntry - end - end - - def self.new(anIO) - reader = super(anIO) - return reader unless block_given? - begin - yield reader - ensure - reader.close - end - nil - end - - def initialize(anIO) - @io = anIO - @init_pos = anIO.pos - end - - def each(&block) - each_entry(&block) - end - - # do not call this during a #each or #each_entry iteration - def rewind - if @init_pos == 0 - raise NonSeekableIO unless @io.respond_to? :rewind - @io.rewind - else - raise NonSeekableIO unless @io.respond_to? :pos= - @io.pos = @init_pos - end - end - - def each_entry - loop do - return if @io.eof? - header = TarHeader.new_from_stream(@io) - return if header.empty? - entry = Entry.new header, @io - size = entry.size - yield entry - skip = (512 - (size % 512)) % 512 - if @io.respond_to? :seek - # avoid reading... - @io.seek(size - entry.bytes_read, IO::SEEK_CUR) - else - pending = size - entry.bytes_read - while pending > 0 - bread = @io.read([pending, 4096].min).size - raise UnexpectedEOF if @io.eof? - pending -= bread - end - end - @io.read(skip) # discard trailing zeros - # make sure nobody can use #read, #getc or #rewind anymore - entry.close - end - end - - def close - end - - end - - class TarInput - - include FSyncDir - include Enumerable - - attr_reader :metadata - - class << self; private :new end - - def initialize(io, security_policy = nil) - @io = io - @tarreader = TarReader.new(@io) - has_meta = false - data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil - dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil - - @tarreader.each do |entry| - case entry.full_name - when "metadata" - @metadata = load_gemspec(entry.read) - has_meta = true - break - when "metadata.gz" - begin - # if we have a security_policy, then pre-read the metadata file - # and calculate it's digest - sio = nil - if security_policy - Gem.ensure_ssl_available - sio = StringIO.new(entry.read) - meta_dgst = dgst_algo.digest(sio.string) - sio.rewind - end - - gzis = Zlib::GzipReader.new(sio || entry) - # YAML wants an instance of IO - @metadata = load_gemspec(gzis) - has_meta = true - ensure - gzis.close unless gzis.nil? - end - when 'metadata.gz.sig' - meta_sig = entry.read - when 'data.tar.gz.sig' - data_sig = entry.read - when 'data.tar.gz' - if security_policy - Gem.ensure_ssl_available - data_dgst = dgst_algo.digest(entry.read) - end - end - end - - if security_policy then - Gem.ensure_ssl_available - - # map trust policy from string to actual class (or a serialized YAML - # file, if that exists) - if String === security_policy then - if Gem::Security::Policy.key? security_policy then - # load one of the pre-defined security policies - security_policy = Gem::Security::Policy[security_policy] - elsif File.exist? security_policy then - # FIXME: this doesn't work yet - security_policy = YAML.load File.read(security_policy) - else - raise Gem::Exception, "Unknown trust policy '#{security_policy}'" - end - end - - if data_sig && data_dgst && meta_sig && meta_dgst then - # the user has a trust policy, and we have a signed gem - # file, so use the trust policy to verify the gem signature - - begin - security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain) - rescue Exception => e - raise "Couldn't verify data signature: #{e}" - end - - begin - security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain) - rescue Exception => e - raise "Couldn't verify metadata signature: #{e}" - end - elsif security_policy.only_signed - raise Gem::Exception, "Unsigned gem" - else - # FIXME: should display warning here (trust policy, but - # either unsigned or badly signed gem file) - end - end - - @tarreader.rewind - @fileops = Gem::FileOperations.new - raise FormatError, "No metadata found!" unless has_meta - end - - # Attempt to YAML-load a gemspec from the given _io_ parameter. Return - # nil if it fails. - def load_gemspec(io) - Gem::Specification.from_yaml(io) - rescue Gem::Exception - nil - end - - def self.open(filename, security_policy = nil, &block) - open_from_io(File.open(filename, "rb"), security_policy, &block) - end - - def self.open_from_io(io, security_policy = nil, &block) - raise "Want a block" unless block_given? - begin - is = new(io, security_policy) - yield is - ensure - is.close if is - end - end - - def each(&block) - @tarreader.each do |entry| - next unless entry.full_name == "data.tar.gz" - is = zipped_stream(entry) - begin - TarReader.new(is) do |inner| - inner.each(&block) - end - ensure - is.close if is - end - end - @tarreader.rewind - end - - # Return an IO stream for the zipped entry. - # - # NOTE: Originally this method used two approaches, Return a GZipReader - # directly, or read the GZipReader into a string and return a StringIO on - # the string. The string IO approach was used for versions of ZLib before - # 1.2.1 to avoid buffer errors on windows machines. Then we found that - # errors happened with 1.2.1 as well, so we changed the condition. Then - # we discovered errors occurred with versions as late as 1.2.3. At this - # point (after some benchmarking to show we weren't seriously crippling - # the unpacking speed) we threw our hands in the air and declared that - # this method would use the String IO approach on all platforms at all - # times. And that's the way it is. - def zipped_stream(entry) - if defined? Rubinius then - zis = Zlib::GzipReader.new entry - dis = zis.read - is = StringIO.new(dis) - else - # This is Jamis Buck's ZLib workaround for some unknown issue - entry.read(10) # skip the gzip header - zis = Zlib::Inflate.new(-Zlib::MAX_WBITS) - is = StringIO.new(zis.inflate(entry.read)) - end - ensure - zis.finish if zis - end - - def extract_entry(destdir, entry, expected_md5sum = nil) - if entry.is_directory? - dest = File.join(destdir, entry.full_name) - if file_class.dir? dest - @fileops.chmod entry.mode, dest, :verbose=>false - else - @fileops.mkdir_p(dest, :mode => entry.mode, :verbose=>false) - end - fsync_dir dest - fsync_dir File.join(dest, "..") - return - end - # it's a file - md5 = Digest::MD5.new if expected_md5sum - destdir = File.join(destdir, File.dirname(entry.full_name)) - @fileops.mkdir_p(destdir, :mode => 0755, :verbose=>false) - destfile = File.join(destdir, File.basename(entry.full_name)) - @fileops.chmod(0600, destfile, :verbose=>false) rescue nil # Errno::ENOENT - file_class.open(destfile, "wb", entry.mode) do |os| - loop do - data = entry.read(4096) - break unless data - md5 << data if expected_md5sum - os.write(data) - end - os.fsync - end - @fileops.chmod(entry.mode, destfile, :verbose=>false) - fsync_dir File.dirname(destfile) - fsync_dir File.join(File.dirname(destfile), "..") - if expected_md5sum && expected_md5sum != md5.hexdigest - raise BadCheckSum - end - end - - def close - @io.close - @tarreader.close - end - - private - - def file_class - File - end - end - - class TarOutput - - class << self; private :new end - - def initialize(io) - @io = io - @external = TarWriter.new @io - end - - def external_handle - @external - end - - def self.open(filename, signer = nil, &block) - io = File.open(filename, "wb") - open_from_io(io, signer, &block) - nil - end - - def self.open_from_io(io, signer = nil, &block) - outputter = new(io) - metadata = nil - set_meta = lambda{|x| metadata = x} - raise "Want a block" unless block_given? - begin - data_sig, meta_sig = nil, nil - - outputter.external_handle.add_file("data.tar.gz", 0644) do |inner| - begin - sio = signer ? StringIO.new : nil - os = Zlib::GzipWriter.new(sio || inner) - - TarWriter.new(os) do |inner_tar_stream| - klass = class << inner_tar_stream; self end - klass.send(:define_method, :metadata=, &set_meta) - block.call inner_tar_stream - end - ensure - os.flush - os.finish - #os.close - - # if we have a signing key, then sign the data - # digest and return the signature - data_sig = nil - if signer - dgst_algo = Gem::Security::OPT[:dgst_algo] - dig = dgst_algo.digest(sio.string) - data_sig = signer.sign(dig) - inner.write(sio.string) - end - end - end - - # if we have a data signature, then write it to the gem too - if data_sig - sig_file = 'data.tar.gz.sig' - outputter.external_handle.add_file(sig_file, 0644) do |os| - os.write(data_sig) - end - end - - outputter.external_handle.add_file("metadata.gz", 0644) do |os| - begin - sio = signer ? StringIO.new : nil - gzos = Zlib::GzipWriter.new(sio || os) - gzos.write metadata - ensure - gzos.flush - gzos.finish - - # if we have a signing key, then sign the metadata - # digest and return the signature - if signer - dgst_algo = Gem::Security::OPT[:dgst_algo] - dig = dgst_algo.digest(sio.string) - meta_sig = signer.sign(dig) - os.write(sio.string) - end - end - end - - # if we have a metadata signature, then write to the gem as - # well - if meta_sig - sig_file = 'metadata.gz.sig' - outputter.external_handle.add_file(sig_file, 0644) do |os| - os.write(meta_sig) - end - end - - ensure - outputter.close - end - nil - end - - def close - @external.close - @io.close - end - - end - - #FIXME: refactor the following 2 methods - - def self.open(dest, mode = "r", signer = nil, &block) - raise "Block needed" unless block_given? - - case mode - when "r" - security_policy = signer - TarInput.open(dest, security_policy, &block) - when "w" - TarOutput.open(dest, signer, &block) - else - raise "Unknown Package open mode" - end - end - - def self.open_from_io(io, mode = "r", signer = nil, &block) - raise "Block needed" unless block_given? - - case mode - when "r" - security_policy = signer - TarInput.open_from_io(io, security_policy, &block) - when "w" - TarOutput.open_from_io(io, signer, &block) - else - raise "Unknown Package open mode" - end + def self.open(io, mode = "r", signer = nil, &block) + tar_type = case mode + when 'r' then TarInput + when 'w' then TarOutput + else + raise "Unknown Package open mode" + end + + tar_type.open(io, signer, &block) end def self.pack(src, destname, signer = nil) @@ -836,19 +83,13 @@ module Gem::Package end end - class << self - def file_class - File - end - - def dir_class - Dir - end - - def find_class # HACK kill me - Find - end - end - end +require 'rubygems/package/f_sync_dir' +require 'rubygems/package/tar_header' +require 'rubygems/package/tar_input' +require 'rubygems/package/tar_output' +require 'rubygems/package/tar_reader' +require 'rubygems/package/tar_reader/entry' +require 'rubygems/package/tar_writer' + |