Class Gem::Package::TarInput
In: lib/rubygems/package/tar_input.rb
Parent: Object

Methods

close   each   extract_entry   load_gemspec   new   open   zipped_stream  

Included Modules

Gem::Package::FSyncDir Enumerable

Attributes

metadata  [R] 

Public Class methods

[Source]

     # 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

[Source]

    # 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

Public Instance methods

[Source]

     # File lib/rubygems/package/tar_input.rb, line 133
133:   def close
134:     @io.close
135:     @tarreader.close
136:   end

[Source]

     # 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

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # File lib/rubygems/package/tar_input.rb, line 237
237:   def zipped_stream(entry)
238:     Zlib::GzipReader.new entry
239:   end

[Validate]