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