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:     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

[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 126
126:   def close
127:     @io.close
128:     @tarreader.close
129:   end

[Source]

     # 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

[Source]

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

[Source]

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

[Source]

     # File lib/rubygems/package/tar_input.rb, line 230
230:   def zipped_stream(entry)
231:     Zlib::GzipReader.new entry
232:   end

[Validate]