2010-02-21 21:52:35 -05:00
|
|
|
|
# -*- coding: iso-8859-1 -*-
|
2008-03-31 18:40:06 -04:00
|
|
|
|
#++
|
|
|
|
|
# Copyright (C) 2004 Mauricio Julio Fern<72>ndez Pradier
|
|
|
|
|
# See LICENSE.txt for additional licensing information.
|
|
|
|
|
#--
|
|
|
|
|
|
2011-01-18 19:08:49 -05:00
|
|
|
|
require 'zlib'
|
|
|
|
|
Gem.load_yaml
|
|
|
|
|
|
2008-03-31 18:40:06 -04:00
|
|
|
|
class Gem::Package::TarInput
|
|
|
|
|
|
|
|
|
|
include Gem::Package::FSyncDir
|
|
|
|
|
include Enumerable
|
|
|
|
|
|
|
|
|
|
attr_reader :metadata
|
|
|
|
|
|
|
|
|
|
private_class_method :new
|
|
|
|
|
|
|
|
|
|
def self.open(io, security_policy = nil, &block)
|
|
|
|
|
is = new io, security_policy
|
|
|
|
|
|
|
|
|
|
yield is
|
|
|
|
|
ensure
|
|
|
|
|
is.close if is
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def initialize(io, security_policy = nil)
|
|
|
|
|
@io = io
|
|
|
|
|
@tarreader = Gem::Package::TarReader.new @io
|
|
|
|
|
has_meta = false
|
|
|
|
|
|
|
|
|
|
data_sig, meta_sig, data_dgst, meta_dgst = nil, nil, nil, nil
|
|
|
|
|
dgst_algo = security_policy ? Gem::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
|
|
|
|
|
Gem.ensure_ssl_available
|
|
|
|
|
sio = StringIO.new(entry.read)
|
|
|
|
|
meta_dgst = dgst_algo.digest(sio.string)
|
|
|
|
|
sio.rewind
|
|
|
|
|
end
|
|
|
|
|
|
2011-08-11 20:15:42 -04:00
|
|
|
|
# Ruby 1.8 doesn't have encoding and YAML is UTF-8
|
|
|
|
|
args = [sio || entry]
|
|
|
|
|
args << { :external_encoding => Encoding::UTF_8 } if
|
|
|
|
|
Object.const_defined?(:Encoding)
|
|
|
|
|
|
|
|
|
|
gzis = Zlib::GzipReader.new(*args)
|
|
|
|
|
|
2008-03-31 18:40:06 -04:00
|
|
|
|
# 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
|
|
|
|
|
Gem.ensure_ssl_available
|
|
|
|
|
data_dgst = dgst_algo.digest(entry.read)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if security_policy then
|
|
|
|
|
Gem.ensure_ssl_available
|
|
|
|
|
|
|
|
|
|
# map trust policy from string to actual class (or a serialized YAML
|
|
|
|
|
# file, if that exists)
|
|
|
|
|
if String === security_policy then
|
2009-06-09 17:38:59 -04:00
|
|
|
|
if Gem::Security::Policies.key? security_policy then
|
2008-03-31 18:40:06 -04:00
|
|
|
|
# load one of the pre-defined security policies
|
2009-06-09 17:38:59 -04:00
|
|
|
|
security_policy = Gem::Security::Policies[security_policy]
|
2008-03-31 18:40:06 -04:00
|
|
|
|
elsif File.exist? security_policy then
|
|
|
|
|
# FIXME: this doesn't work yet
|
|
|
|
|
security_policy = YAML.load File.read(security_policy)
|
|
|
|
|
else
|
|
|
|
|
raise Gem::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 Gem::Exception, "Unsigned gem"
|
|
|
|
|
else
|
|
|
|
|
# FIXME: should display warning here (trust policy, but
|
|
|
|
|
# either unsigned or badly signed gem file)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
@tarreader.rewind
|
|
|
|
|
|
2011-01-31 22:11:34 -05:00
|
|
|
|
unless has_meta then
|
|
|
|
|
path = io.path if io.respond_to? :path
|
|
|
|
|
error = Gem::Package::FormatError.new 'no metadata found', path
|
|
|
|
|
raise error
|
|
|
|
|
end
|
2008-03-31 18:40:06 -04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def close
|
|
|
|
|
@io.close
|
|
|
|
|
@tarreader.close
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def each(&block)
|
|
|
|
|
@tarreader.each do |entry|
|
|
|
|
|
next unless entry.full_name == "data.tar.gz"
|
|
|
|
|
is = zipped_stream entry
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
Gem::Package::TarReader.new is do |inner|
|
|
|
|
|
inner.each(&block)
|
|
|
|
|
end
|
|
|
|
|
ensure
|
|
|
|
|
is.close if is
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
@tarreader.rewind
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def extract_entry(destdir, entry, expected_md5sum = nil)
|
|
|
|
|
if entry.directory? then
|
2009-06-09 17:38:59 -04:00
|
|
|
|
dest = File.join destdir, entry.full_name
|
2008-03-31 18:40:06 -04:00
|
|
|
|
|
2009-06-09 17:38:59 -04:00
|
|
|
|
if File.directory? dest then
|
2011-05-31 23:45:05 -04:00
|
|
|
|
FileUtils.chmod entry.header.mode, dest, :verbose => false
|
2008-03-31 18:40:06 -04:00
|
|
|
|
else
|
2011-05-31 23:45:05 -04:00
|
|
|
|
FileUtils.mkdir_p dest, :mode => entry.header.mode, :verbose => false
|
2008-03-31 18:40:06 -04:00
|
|
|
|
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)
|
2011-05-31 23:45:05 -04:00
|
|
|
|
FileUtils.mkdir_p destdir, :mode => 0755, :verbose => false
|
2008-03-31 18:40:06 -04:00
|
|
|
|
destfile = File.join destdir, File.basename(entry.full_name)
|
2011-05-31 23:45:05 -04:00
|
|
|
|
FileUtils.chmod 0600, destfile, :verbose => false rescue nil # Errno::ENOENT
|
2008-03-31 18:40:06 -04:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2011-05-31 23:45:05 -04:00
|
|
|
|
FileUtils.chmod entry.header.mode, destfile, :verbose => false
|
2008-03-31 18:40:06 -04:00
|
|
|
|
fsync_dir File.dirname(destfile)
|
|
|
|
|
fsync_dir File.join(File.dirname(destfile), "..")
|
|
|
|
|
|
|
|
|
|
if expected_md5sum && expected_md5sum != md5.hexdigest then
|
|
|
|
|
raise Gem::Package::BadCheckSum
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Attempt to YAML-load a gemspec from the given _io_ parameter. Return
|
|
|
|
|
# nil if it fails.
|
|
|
|
|
def load_gemspec(io)
|
|
|
|
|
Gem::Specification.from_yaml io
|
|
|
|
|
rescue Gem::Exception
|
|
|
|
|
nil
|
|
|
|
|
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.
|
2012-04-17 16:50:00 -04:00
|
|
|
|
#
|
|
|
|
|
# Revisited. Here's the beginning of the long story.
|
|
|
|
|
# http://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.
|
2008-03-31 18:40:06 -04:00
|
|
|
|
def zipped_stream(entry)
|
2012-04-17 16:50:00 -04:00
|
|
|
|
Zlib::GzipReader.new entry
|
2008-03-31 18:40:06 -04:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|