2 # Copyright (C) 2004 Mauricio Julio Fernández Pradier
3 # See LICENSE.txt for additional licensing information.
6 require 'rubygems/package'
8 class Gem::Package::TarInput
10 include Gem::Package::FSyncDir
15 private_class_method :new
17 def self.open(io, security_policy = nil, &block)
18 is = new io, security_policy
25 def initialize(io, security_policy = nil)
27 @tarreader = Gem::Package::TarReader.new @io
30 data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil
31 dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil
33 @tarreader.each do |entry|
36 @metadata = load_gemspec entry.read
40 # if we have a security_policy, then pre-read the metadata file
41 # and calculate it's digest
44 Gem.ensure_ssl_available
45 sio = StringIO.new(entry.read)
46 meta_dgst = dgst_algo.digest(sio.string)
50 gzis = Zlib::GzipReader.new(sio || entry)
51 # YAML wants an instance of IO
52 @metadata = load_gemspec(gzis)
55 gzis.close unless gzis.nil?
57 when 'metadata.gz.sig'
59 when 'data.tar.gz.sig'
63 Gem.ensure_ssl_available
64 data_dgst = dgst_algo.digest(entry.read)
69 if security_policy then
70 Gem.ensure_ssl_available
72 # map trust policy from string to actual class (or a serialized YAML
73 # file, if that exists)
74 if String === security_policy then
75 if Gem::Security::Policy.key? security_policy then
76 # load one of the pre-defined security policies
77 security_policy = Gem::Security::Policy[security_policy]
78 elsif File.exist? security_policy then
79 # FIXME: this doesn't work yet
80 security_policy = YAML.load File.read(security_policy)
82 raise Gem::Exception, "Unknown trust policy '#{security_policy}'"
86 if data_sig && data_dgst && meta_sig && meta_dgst then
87 # the user has a trust policy, and we have a signed gem
88 # file, so use the trust policy to verify the gem signature
91 security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain)
93 raise "Couldn't verify data signature: #{e}"
97 security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain)
99 raise "Couldn't verify metadata signature: #{e}"
101 elsif security_policy.only_signed
102 raise Gem::Exception, "Unsigned gem"
104 # FIXME: should display warning here (trust policy, but
105 # either unsigned or badly signed gem file)
110 @fileops = Gem::FileOperations.new
112 raise Gem::Package::FormatError, "No metadata found!" unless has_meta
121 @tarreader.each do |entry|
122 next unless entry.full_name == "data.tar.gz"
123 is = zipped_stream entry
126 Gem::Package::TarReader.new is do |inner|
137 def extract_entry(destdir, entry, expected_md5sum = nil)
138 if entry.directory? then
139 dest = File.join(destdir, entry.full_name)
141 if File.dir? dest then
142 @fileops.chmod entry.header.mode, dest, :verbose=>false
144 @fileops.mkdir_p dest, :mode => entry.header.mode, :verbose => false
148 fsync_dir File.join(dest, "..")
154 md5 = Digest::MD5.new if expected_md5sum
155 destdir = File.join destdir, File.dirname(entry.full_name)
156 @fileops.mkdir_p destdir, :mode => 0755, :verbose => false
157 destfile = File.join destdir, File.basename(entry.full_name)
158 @fileops.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT
160 open destfile, "wb", entry.header.mode do |os|
162 data = entry.read 4096
164 # HACK shouldn't we check the MD5 before writing to disk?
165 md5 << data if expected_md5sum
172 @fileops.chmod entry.header.mode, destfile, :verbose => false
173 fsync_dir File.dirname(destfile)
174 fsync_dir File.join(File.dirname(destfile), "..")
176 if expected_md5sum && expected_md5sum != md5.hexdigest then
177 raise Gem::Package::BadCheckSum
181 # Attempt to YAML-load a gemspec from the given _io_ parameter. Return
184 Gem::Specification.from_yaml io
185 rescue Gem::Exception
190 # Return an IO stream for the zipped entry.
192 # NOTE: Originally this method used two approaches, Return a GZipReader
193 # directly, or read the GZipReader into a string and return a StringIO on
194 # the string. The string IO approach was used for versions of ZLib before
195 # 1.2.1 to avoid buffer errors on windows machines. Then we found that
196 # errors happened with 1.2.1 as well, so we changed the condition. Then
197 # we discovered errors occurred with versions as late as 1.2.3. At this
198 # point (after some benchmarking to show we weren't seriously crippling
199 # the unpacking speed) we threw our hands in the air and declared that
200 # this method would use the String IO approach on all platforms at all
201 # times. And that's the way it is.
203 def zipped_stream(entry)
204 if defined? Rubinius then
205 zis = Zlib::GzipReader.new entry
207 is = StringIO.new(dis)
209 # This is Jamis Buck's Zlib workaround for some unknown issue
210 entry.read(10) # skip the gzip header
211 zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)
212 is = StringIO.new(zis.inflate(entry.read))