OSDN Git Service

ruby-1.9.1-rc1
[splhack/AndroidRuby.git] / lib / ruby-1.9.1-rc1 / lib / rubygems / package / tar_input.rb
1 #++
2 # Copyright (C) 2004 Mauricio Julio Fernández Pradier
3 # See LICENSE.txt for additional licensing information.
4 #--
5
6 require 'rubygems/package'
7
8 class Gem::Package::TarInput
9
10   include Gem::Package::FSyncDir
11   include Enumerable
12
13   attr_reader :metadata
14
15   private_class_method :new
16
17   def self.open(io, security_policy = nil,  &block)
18     is = new io, security_policy
19
20     yield is
21   ensure
22     is.close if is
23   end
24
25   def initialize(io, security_policy = nil)
26     @io = io
27     @tarreader = Gem::Package::TarReader.new @io
28     has_meta = false
29
30     data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil
31     dgst_algo = security_policy ? Gem::Security::OPT[:dgst_algo] : nil
32
33     @tarreader.each do |entry|
34       case entry.full_name
35       when "metadata"
36         @metadata = load_gemspec entry.read
37         has_meta = true
38       when "metadata.gz"
39         begin
40           # if we have a security_policy, then pre-read the metadata file
41           # and calculate it's digest
42           sio = nil
43           if security_policy
44             Gem.ensure_ssl_available
45             sio = StringIO.new(entry.read)
46             meta_dgst = dgst_algo.digest(sio.string)
47             sio.rewind
48           end
49
50           gzis = Zlib::GzipReader.new(sio || entry)
51           # YAML wants an instance of IO
52           @metadata = load_gemspec(gzis)
53           has_meta = true
54         ensure
55           gzis.close unless gzis.nil?
56         end
57       when 'metadata.gz.sig'
58         meta_sig = entry.read
59       when 'data.tar.gz.sig'
60         data_sig = entry.read
61       when 'data.tar.gz'
62         if security_policy
63           Gem.ensure_ssl_available
64           data_dgst = dgst_algo.digest(entry.read)
65         end
66       end
67     end
68
69     if security_policy then
70       Gem.ensure_ssl_available
71
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)
81         else
82           raise Gem::Exception, "Unknown trust policy '#{security_policy}'"
83         end
84       end
85
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
89
90         begin
91           security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain)
92         rescue Exception => e
93           raise "Couldn't verify data signature: #{e}"
94         end
95
96         begin
97           security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain)
98         rescue Exception => e
99           raise "Couldn't verify metadata signature: #{e}"
100         end
101       elsif security_policy.only_signed
102         raise Gem::Exception, "Unsigned gem"
103       else
104         # FIXME: should display warning here (trust policy, but
105         # either unsigned or badly signed gem file)
106       end
107     end
108
109     @tarreader.rewind
110     @fileops = Gem::FileOperations.new
111
112     raise Gem::Package::FormatError, "No metadata found!" unless has_meta
113   end
114
115   def close
116     @io.close
117     @tarreader.close
118   end
119
120   def each(&block)
121     @tarreader.each do |entry|
122       next unless entry.full_name == "data.tar.gz"
123       is = zipped_stream entry
124
125       begin
126         Gem::Package::TarReader.new is do |inner|
127           inner.each(&block)
128         end
129       ensure
130         is.close if is
131       end
132     end
133
134     @tarreader.rewind
135   end
136
137   def extract_entry(destdir, entry, expected_md5sum = nil)
138     if entry.directory? then
139       dest = File.join(destdir, entry.full_name)
140
141       if File.dir? dest then
142         @fileops.chmod entry.header.mode, dest, :verbose=>false
143       else
144         @fileops.mkdir_p dest, :mode => entry.header.mode, :verbose => false
145       end
146
147       fsync_dir dest
148       fsync_dir File.join(dest, "..")
149
150       return
151     end
152
153     # it's a file
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
159
160     open destfile, "wb", entry.header.mode do |os|
161       loop do
162         data = entry.read 4096
163         break unless data
164         # HACK shouldn't we check the MD5 before writing to disk?
165         md5 << data if expected_md5sum
166         os.write(data)
167       end
168
169       os.fsync
170     end
171
172     @fileops.chmod entry.header.mode, destfile, :verbose => false
173     fsync_dir File.dirname(destfile)
174     fsync_dir File.join(File.dirname(destfile), "..")
175
176     if expected_md5sum && expected_md5sum != md5.hexdigest then
177       raise Gem::Package::BadCheckSum
178     end
179   end
180
181   # Attempt to YAML-load a gemspec from the given _io_ parameter.  Return
182   # nil if it fails.
183   def load_gemspec(io)
184     Gem::Specification.from_yaml io
185   rescue Gem::Exception
186     nil
187   end
188
189   ##
190   # Return an IO stream for the zipped entry.
191   #
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.
202
203   def zipped_stream(entry)
204     if defined? Rubinius then
205       zis = Zlib::GzipReader.new entry
206       dis = zis.read
207       is = StringIO.new(dis)
208     else
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))
213     end
214   ensure
215     zis.finish if zis
216   end
217
218 end
219