관리-도구
편집 파일: tar_input.rb
# -*- coding: iso-8859-1 -*- #++ # Copyright (C) 2004 Mauricio Julio Fern�ndez Pradier # See LICENSE.txt for additional licensing information. #-- require 'zlib' class Gem::Package::TarInput include Gem::Package::FSyncDir include Enumerable attr_reader :metadata private_class_method :new def self.open(io, security_policy = nil, &block) is = new io, security_policy yield is ensure is.close if is end def initialize(io, security_policy = nil) @io = io @tarreader = Gem::Package::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 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 # Ruby 1.8 doesn't have encoding and YAML is UTF-8 args = [sio || entry] args << { :external_encoding => Encoding::UTF_8 } if Object.const_defined?(:Encoding) gzis = Zlib::GzipReader.new(*args) # 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::Policies.key? security_policy then # load one of the pre-defined security policies security_policy = Gem::Security::Policies[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 unless has_meta then path = io.path if io.respond_to? :path error = Gem::Package::FormatError.new 'no metadata found', path raise error end end def close @io.close @tarreader.close end def each(&block) @tarreader.each do |entry| next unless entry.full_name == "data.tar.gz" is = zipped_stream entry begin Gem::Package::TarReader.new is do |inner| inner.each(&block) end ensure is.close if is end end @tarreader.rewind end def extract_entry(destdir, entry, expected_md5sum = nil) if entry.directory? then dest = File.join destdir, entry.full_name if File.directory? dest then FileUtils.chmod entry.header.mode, dest, :verbose => false else FileUtils.mkdir_p dest, :mode => entry.header.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) FileUtils.mkdir_p destdir, :mode => 0755, :verbose => false destfile = File.join destdir, File.basename(entry.full_name) FileUtils.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT open destfile, "wb", entry.header.mode do |os| loop do data = entry.read 4096 break unless data # HACK shouldn't we check the MD5 before writing to disk? md5 << data if expected_md5sum os.write(data) end os.fsync end FileUtils.chmod entry.header.mode, destfile, :verbose => false fsync_dir File.dirname(destfile) fsync_dir File.join(File.dirname(destfile), "..") if expected_md5sum && expected_md5sum != md5.hexdigest then raise Gem::Package::BadCheckSum end 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 ## # 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. # # Revisited. Here's the beginning of the long story. # http://osdir.com/ml/lang.ruby.gems.devel/2007-06/msg00045.html # # StringIO wraping has never worked as a workaround by definition. Skipping # initial 10 bytes and passing -MAX_WBITS to Zlib::Inflate luckily works as # gzip reader, but it only works if the GZip header is 10 bytes long (see # below) and it does not check inflated stream consistency (CRC value in the # Gzip trailer.) # # RubyGems generated Gzip Header: 10 bytes # magic(2) + method(1) + flag(1) + mtime(4) + exflag(1) + os(1) + # orig_name(0) + comment(0) # # Ideally, it must return a GZipReader without meaningless buffering. We # have lots of CRuby committers around so let's fix windows build when we # received an error. def zipped_stream(entry) Zlib::GzipReader.new entry end end