Class | Gem::Package::TarInput |
In: |
lib/rubygems/package/tar_input.rb
|
Parent: | Object |
metadata | [R] |
# File lib/rubygems/package/tar_input.rb, line 27 27: def initialize(io, security_policy = nil) 28: @io = io 29: @tarreader = Gem::Package::TarReader.new @io 30: @files = [] 31: has_meta = false 32: 33: data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil 34: dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil 35: 36: @tarreader.each do |entry| 37: file_name = entry.full_name 38: @files << file_name 39: case entry.full_name 40: when "metadata" 41: @metadata = load_gemspec entry.read 42: has_meta = true 43: when "metadata.gz" 44: begin 45: # if we have a security_policy, then pre-read the metadata file 46: # and calculate it's digest 47: sio = nil 48: if security_policy 49: Gem.ensure_ssl_available 50: sio = StringIO.new(entry.read) 51: meta_dgst = dgst_algo.digest(sio.string) 52: sio.rewind 53: end 54: 55: # Ruby 1.8 doesn't have encoding and YAML is UTF-8 56: args = [sio || entry] 57: args << { :external_encoding => Encoding::UTF_8 } if 58: Object.const_defined?(:Encoding) 59: 60: gzis = Zlib::GzipReader.new(*args) 61: 62: # YAML wants an instance of IO 63: @metadata = load_gemspec(gzis) 64: has_meta = true 65: ensure 66: gzis.close unless gzis.nil? 67: end 68: when 'metadata.gz.sig' 69: meta_sig = entry.read 70: when 'data.tar.gz.sig' 71: data_sig = entry.read 72: when 'data.tar.gz' 73: if security_policy 74: Gem.ensure_ssl_available 75: data_dgst = dgst_algo.digest(entry.read) 76: end 77: end 78: end 79: 80: if security_policy then 81: Gem.ensure_ssl_available 82: 83: # map trust policy from string to actual class (or a serialized YAML 84: # file, if that exists) 85: if String === security_policy then 86: if Gem::Security::Policies.key? security_policy then 87: # load one of the pre-defined security policies 88: security_policy = Gem::Security::Policies[security_policy] 89: elsif File.exist? security_policy then 90: # FIXME: this doesn't work yet 91: security_policy = YAML.load File.read(security_policy) 92: else 93: raise Gem::Exception, "Unknown trust policy '#{security_policy}'" 94: end 95: end 96: 97: if data_sig && data_dgst && meta_sig && meta_dgst then 98: # the user has a trust policy, and we have a signed gem 99: # file, so use the trust policy to verify the gem signature 100: 101: begin 102: security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain) 103: rescue Exception => e 104: raise "Couldn't verify data signature: #{e}" 105: end 106: 107: begin 108: security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain) 109: rescue Exception => e 110: raise "Couldn't verify metadata signature: #{e}" 111: end 112: elsif security_policy.only_signed 113: raise Gem::Exception, "Unsigned gem" 114: else 115: # FIXME: should display warning here (trust policy, but 116: # either unsigned or badly signed gem file) 117: end 118: end 119: 120: @tarreader.rewind 121: 122: unless has_meta then 123: path = io.path if io.respond_to? :path 124: error = Gem::Package::FormatError.new 'no metadata found', path 125: raise error 126: end 127: 128: if duplicates = @files.group_by {|f| f }.select {|k,v| v.size > 1 }.map(&:first) and duplicates.any? 129: raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(', ')})" 130: end 131: end
# File lib/rubygems/package/tar_input.rb, line 19 19: def self.open(io, security_policy = nil, &block) 20: is = new io, security_policy 21: 22: yield is 23: ensure 24: is.close if is 25: end
# File lib/rubygems/package/tar_input.rb, line 133 133: def close 134: @io.close 135: @tarreader.close 136: end
# File lib/rubygems/package/tar_input.rb, line 138 138: def each(&block) 139: @tarreader.each do |entry| 140: next unless entry.full_name == "data.tar.gz" 141: is = zipped_stream entry 142: 143: begin 144: Gem::Package::TarReader.new is do |inner| 145: inner.each(&block) 146: end 147: ensure 148: is.close if is 149: end 150: end 151: 152: @tarreader.rewind 153: end
# File lib/rubygems/package/tar_input.rb, line 155 155: def extract_entry(destdir, entry, expected_md5sum = nil) 156: if entry.directory? then 157: dest = File.join destdir, entry.full_name 158: 159: if File.directory? dest then 160: FileUtils.chmod entry.header.mode, dest, :verbose => false 161: else 162: FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false 163: end 164: 165: fsync_dir dest 166: fsync_dir File.join(dest, "..") 167: 168: return 169: end 170: 171: # it's a file 172: md5 = Digest::MD5.new if expected_md5sum 173: destdir = File.join destdir, File.dirname(entry.full_name) 174: FileUtils.mkdir_p destdir, :mode => 0755, :verbose => false 175: destfile = File.join destdir, File.basename(entry.full_name) 176: FileUtils.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT 177: 178: open destfile, "wb", entry.header.mode do |os| 179: loop do 180: data = entry.read 4096 181: break unless data 182: # HACK shouldn't we check the MD5 before writing to disk? 183: md5 << data if expected_md5sum 184: os.write(data) 185: end 186: 187: os.fsync 188: end 189: 190: FileUtils.chmod entry.header.mode, destfile, :verbose => false 191: fsync_dir File.dirname(destfile) 192: fsync_dir File.join(File.dirname(destfile), "..") 193: 194: if expected_md5sum && expected_md5sum != md5.hexdigest then 195: raise Gem::Package::BadCheckSum 196: end 197: end
Attempt to YAML-load a gemspec from the given io parameter. Return nil if it fails.
# File lib/rubygems/package/tar_input.rb, line 201 201: def load_gemspec(io) 202: Gem::Specification.from_yaml io 203: rescue Gem::Exception 204: nil 205: 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. 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.
# File lib/rubygems/package/tar_input.rb, line 237 237: def zipped_stream(entry) 238: Zlib::GzipReader.new entry 239: end