Class: LibGems::Package::TarInput

Inherits:
Object
  • Object
show all
Includes:
Enumerable, FSyncDir
Defined in:
lib/libgems/package/tar_input.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io, security_policy = nil) ⇒ TarInput

Returns a new instance of TarInput.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/libgems/package/tar_input.rb', line 27

def initialize(io, security_policy = nil)
  @io = io
  @tarreader = LibGems::Package::TarReader.new @io
  has_meta = false

  data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil
  dgst_algo = security_policy ? LibGems::Security::OPT[:dgst_algo] : nil

  @tarreader.each do |entry|
    case entry.full_name
    when "metadata"
      @metadata = load_gemspec entry.read
      has_meta = true
    when "metadata.gz"
      begin
        # if we have a security_policy, then pre-read the metadata file
        # and calculate it's digest
        sio = nil
        if security_policy
          LibGems.ensure_ssl_available
          sio = StringIO.new(entry.read)
          meta_dgst = dgst_algo.digest(sio.string)
          sio.rewind
        end

        gzis = Zlib::GzipReader.new(sio || entry)
        # YAML wants an instance of IO
        @metadata = load_gemspec(gzis)
        has_meta = true
      ensure
        gzis.close unless gzis.nil?
      end
    when 'metadata.gz.sig'
      meta_sig = entry.read
    when 'data.tar.gz.sig'
      data_sig = entry.read
    when 'data.tar.gz'
      if security_policy
        LibGems.ensure_ssl_available
        data_dgst = dgst_algo.digest(entry.read)
      end
    end
  end

  if security_policy then
    LibGems.ensure_ssl_available

    # map trust policy from string to actual class (or a serialized YAML
    # file, if that exists)
    if String === security_policy then
      if LibGems::Security::Policies.key? security_policy then
        # load one of the pre-defined security policies
        security_policy = LibGems::Security::Policies[security_policy]
      elsif File.exist? security_policy then
        # FIXME: this doesn't work yet
        security_policy = YAML.load File.read(security_policy)
      else
        raise LibGems::Exception, "Unknown trust policy '#{security_policy}'"
      end
    end

    if data_sig && data_dgst && meta_sig && meta_dgst then
      # the user has a trust policy, and we have a signed gem
      # file, so use the trust policy to verify the gem signature

      begin
        security_policy.verify_gem(data_sig, data_dgst, @metadata.cert_chain)
      rescue Exception => e
        raise "Couldn't verify data signature: #{e}"
      end

      begin
        security_policy.verify_gem(meta_sig, meta_dgst, @metadata.cert_chain)
      rescue Exception => e
        raise "Couldn't verify metadata signature: #{e}"
      end
    elsif security_policy.only_signed
      raise LibGems::Exception, "Unsigned gem"
    else
      # FIXME: should display warning here (trust policy, but
      # either unsigned or badly signed gem file)
    end
  end

  @tarreader.rewind
  @fileops = LibGems::FileOperations.new

  raise LibGems::Package::FormatError, "No metadata found!" unless has_meta
end

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata.



15
16
17
# File 'lib/libgems/package/tar_input.rb', line 15

def 
  @metadata
end

Class Method Details

.open(io, security_policy = nil, &block) ⇒ Object



19
20
21
22
23
24
25
# File 'lib/libgems/package/tar_input.rb', line 19

def self.open(io, security_policy = nil,  &block)
  is = new io, security_policy

  yield is
ensure
  is.close if is
end

Instance Method Details

#closeObject



117
118
119
120
# File 'lib/libgems/package/tar_input.rb', line 117

def close
  @io.close
  @tarreader.close
end

#each(&block) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/libgems/package/tar_input.rb', line 122

def each(&block)
  @tarreader.each do |entry|
    next unless entry.full_name == "data.tar.gz"
    is = zipped_stream entry

    begin
      LibGems::Package::TarReader.new is do |inner|
        inner.each(&block)
      end
    ensure
      is.close if is
    end
  end

  @tarreader.rewind
end

#extract_entry(destdir, entry, expected_md5sum = nil) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/libgems/package/tar_input.rb', line 139

def extract_entry(destdir, entry, expected_md5sum = nil)
  if entry.directory? then
    dest = File.join destdir, entry.full_name

    if File.directory? dest then
      @fileops.chmod entry.header.mode, dest, :verbose => false
    else
      @fileops.mkdir_p dest, :mode => entry.header.mode, :verbose => false
    end

    fsync_dir dest
    fsync_dir File.join(dest, "..")

    return
  end

  # it's a file
  md5 = Digest::MD5.new if expected_md5sum
  destdir = File.join destdir, File.dirname(entry.full_name)
  @fileops.mkdir_p destdir, :mode => 0755, :verbose => false
  destfile = File.join destdir, File.basename(entry.full_name)
  @fileops.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT

  open destfile, "wb", entry.header.mode do |os|
    loop do
      data = entry.read 4096
      break unless data
      # HACK shouldn't we check the MD5 before writing to disk?
      md5 << data if expected_md5sum
      os.write(data)
    end

    os.fsync
  end

  @fileops.chmod entry.header.mode, destfile, :verbose => false
  fsync_dir File.dirname(destfile)
  fsync_dir File.join(File.dirname(destfile), "..")

  if expected_md5sum && expected_md5sum != md5.hexdigest then
    raise LibGems::Package::BadCheckSum
  end
end

#load_gemspec(io) ⇒ Object

Attempt to YAML-load a gemspec from the given io parameter. Return nil if it fails.



185
186
187
188
189
# File 'lib/libgems/package/tar_input.rb', line 185

def load_gemspec(io)
  LibGems::Specification.from_yaml io
rescue LibGems::Exception
  nil
end

#zipped_stream(entry) ⇒ Object

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.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/libgems/package/tar_input.rb', line 205

def zipped_stream(entry)
  if defined? Rubinius or defined? Maglev then
    # these implementations have working Zlib
    zis = Zlib::GzipReader.new entry
    dis = zis.read
    is = StringIO.new(dis)
  else
    # This is Jamis Buck's Zlib workaround for some unknown issue
    entry.read(10) # skip the gzip header
    zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)
    is = StringIO.new(zis.inflate(entry.read))
  end
ensure
  zis.finish if zis
end