summaryrefslogtreecommitdiff
path: root/lib/rubygems/package.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/package.rb')
-rw-r--r--lib/rubygems/package.rb464
1 files changed, 302 insertions, 162 deletions
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index 77811ed5ec..7e41b18f66 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -1,14 +1,22 @@
-# -*- coding: utf-8 -*-
# frozen_string_literal: true
-#--
+
+# rubocop:disable Style/AsciiComments
+
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
-#++
-#
+
+# rubocop:enable Style/AsciiComments
+
+require_relative "win_platform"
+require_relative "security"
+require_relative "user_interaction"
+
+##
# Example using a Gem::Package
#
# Builds a .gem file given a Gem::Specification. A .gem file is a tarball
-# which contains a data.tar.gz and metadata.gz, and possibly signatures.
+# which contains a data.tar.gz, metadata.gz, checksums.yaml.gz and possibly
+# signatures.
#
# require 'rubygems'
# require 'rubygems/package'
@@ -41,13 +49,7 @@
# #files are the files in the .gem tar file, not the Ruby files in the gem
# #extract_files and #contents automatically call #verify
-require 'rubygems/security'
-require 'rubygems/specification'
-require 'rubygems/user_interaction'
-require 'zlib'
-
class Gem::Package
-
include Gem::UserInteraction
class Error < Gem::Exception; end
@@ -55,22 +57,26 @@ class Gem::Package
class FormatError < Error
attr_reader :path
- def initialize message, source = nil
+ def initialize(message, source = nil)
if source
- @path = source.path
+ @path = source.is_a?(String) ? source : source.path
- message = message + " in #{path}" if path
+ message += " in #{path}" if path
end
super message
end
-
end
class PathError < Error
- def initialize destination, destination_dir
- super "installing into parent path %s of %s is not allowed" %
- [destination, destination_dir]
+ def initialize(destination, destination_dir)
+ super format("installing into parent path %s of %s is not allowed", destination, destination_dir)
+ end
+ end
+
+ class SymlinkError < Error
+ def initialize(name, destination, destination_dir)
+ super format("installing symlink '%s' pointing to parent path %s of %s is not allowed", name, destination, destination_dir)
end
end
@@ -83,7 +89,6 @@ class Gem::Package
class TarInvalidError < Error; end
-
attr_accessor :build_time # :nodoc:
##
@@ -98,6 +103,11 @@ class Gem::Package
attr_reader :files
##
+ # Reference to the gem being packaged.
+
+ attr_reader :gem
+
+ ##
# The security policy used for verifying the contents of this package.
attr_accessor :security_policy
@@ -107,12 +117,24 @@ class Gem::Package
attr_writer :spec
- def self.build spec, skip_validation=false
- gem_file = spec.file_name
+ ##
+ # Permission for directories
+ attr_accessor :dir_mode
+
+ ##
+ # Permission for program files
+ attr_accessor :prog_mode
+
+ ##
+ # Permission for other files
+ attr_accessor :data_mode
+
+ def self.build(spec, skip_validation = false, strict_validation = false, file_name = nil)
+ gem_file = file_name || spec.file_name
package = new gem_file
package.spec = spec
- package.build skip_validation
+ package.build skip_validation, strict_validation
gem_file
end
@@ -124,34 +146,62 @@ class Gem::Package
# If +gem+ is an existing file in the old format a Gem::Package::Old will be
# returned.
- def self.new gem, security_policy = nil
+ def self.new(gem, security_policy = nil)
gem = if gem.is_a?(Gem::Package::Source)
- gem
- elsif gem.respond_to? :read
- Gem::Package::IOSource.new gem
- else
- Gem::Package::FileSource.new gem
- end
+ gem
+ elsif gem.respond_to? :read
+ Gem::Package::IOSource.new gem
+ else
+ Gem::Package::FileSource.new gem
+ end
- return super unless Gem::Package == self
+ return super unless self == Gem::Package
return super unless gem.present?
return super unless gem.start
- return super unless gem.start.include? 'MD5SUM ='
+ return super unless gem.start.include? "MD5SUM ="
Gem::Package::Old.new gem
end
##
+ # Extracts the Gem::Specification and raw metadata from the .gem file at
+ # +path+.
+ #--
+
+ def self.raw_spec(path, security_policy = nil)
+ format = new(path, security_policy)
+ spec = format.spec
+
+ metadata = nil
+
+ File.open path, Gem.binary_mode do |io|
+ tar = Gem::Package::TarReader.new io
+ tar.each_entry do |entry|
+ case entry.full_name
+ when "metadata" then
+ metadata = entry.read
+ when "metadata.gz" then
+ metadata = Gem::Util.gunzip entry.read
+ end
+ end
+ end
+
+ [spec, metadata]
+ end
+
+ ##
# Creates a new package that will read or write to the file +gem+.
- def initialize gem, security_policy # :notnew:
+ def initialize(gem, security_policy) # :notnew:
+ require "zlib"
+
@gem = gem
- @build_time = Time.now
+ @build_time = Gem.source_date_epoch
@checksums = {}
@contents = nil
- @digests = Hash.new { |h, algorithm| h[algorithm] = {} }
+ @digests = Hash.new {|h, algorithm| h[algorithm] = {} }
@files = nil
@security_policy = security_policy
@signatures = {}
@@ -162,17 +212,17 @@ class Gem::Package
##
# Copies this package to +path+ (if possible)
- def copy_to path
+ def copy_to(path)
FileUtils.cp @gem.path, path unless File.exist? path
end
##
# Adds a checksum for each entry in the gem to checksums.yaml.gz.
- def add_checksums tar
+ def add_checksums(tar)
Gem.load_yaml
- checksums_by_algorithm = Hash.new { |h, algorithm| h[algorithm] = {} }
+ checksums_by_algorithm = Hash.new {|h, algorithm| h[algorithm] = {} }
@checksums.each do |name, digests|
digests.each do |algorithm, digest|
@@ -180,9 +230,13 @@ class Gem::Package
end
end
- tar.add_file_signed 'checksums.yaml.gz', 0444, @signer do |io|
+ tar.add_file_signed "checksums.yaml.gz", 0o444, @signer do |io|
gzip_to io do |gz_io|
- YAML.dump checksums_by_algorithm, gz_io
+ if Gem.use_psych?
+ Psych.dump checksums_by_algorithm, gz_io
+ else
+ gz_io.write Gem::YAMLSerializer.dump(checksums_by_algorithm)
+ end
end
end
end
@@ -191,8 +245,8 @@ class Gem::Package
# Adds the files listed in the packages's Gem::Specification to data.tar.gz
# and adds this file to the +tar+.
- def add_contents tar # :nodoc:
- digests = tar.add_file_signed 'data.tar.gz', 0444, @signer do |io|
+ def add_contents(tar) # :nodoc:
+ digests = tar.add_file_signed "data.tar.gz", 0o444, @signer do |io|
gzip_to io do |gz_io|
Gem::Package::TarWriter.new gz_io do |data_tar|
add_files data_tar
@@ -200,27 +254,25 @@ class Gem::Package
end
end
- @checksums['data.tar.gz'] = digests
+ @checksums["data.tar.gz"] = digests
end
##
# Adds files included the package's Gem::Specification to the +tar+ file
- def add_files tar # :nodoc:
+ def add_files(tar) # :nodoc:
@spec.files.each do |file|
stat = File.lstat file
if stat.symlink?
- relative_dir = File.dirname(file).sub("#{Dir.pwd}/", '')
- target_path = File.join(relative_dir, File.readlink(file))
- tar.add_symlink file, target_path, stat.mode
+ tar.add_symlink file, File.readlink(file), stat.mode
end
next unless stat.file?
tar.add_file_simple file, stat.mode, stat.size do |dst_io|
- open file, 'rb' do |src_io|
- dst_io.write src_io.read 16384 until src_io.eof?
+ File.open file, "rb" do |src_io|
+ copy_stream(src_io, dst_io, stat.size)
end
end
end
@@ -229,27 +281,31 @@ class Gem::Package
##
# Adds the package's Gem::Specification to the +tar+ file
- def add_metadata tar # :nodoc:
- digests = tar.add_file_signed 'metadata.gz', 0444, @signer do |io|
+ def add_metadata(tar) # :nodoc:
+ digests = tar.add_file_signed "metadata.gz", 0o444, @signer do |io|
gzip_to io do |gz_io|
gz_io.write @spec.to_yaml
end
end
- @checksums['metadata.gz'] = digests
+ @checksums["metadata.gz"] = digests
end
##
# Builds this package based on the specification set by #spec=
- def build skip_validation = false
+ def build(skip_validation = false, strict_validation = false)
+ raise ArgumentError, "skip_validation = true and strict_validation = true are incompatible" if skip_validation && strict_validation
+
Gem.load_yaml
- require 'rubygems/security'
- @spec.mark_version
- @spec.validate unless skip_validation
+ @spec.validate true, strict_validation unless skip_validation
- setup_signer
+ setup_signer(
+ signer_options: {
+ expiration_length_days: Gem.configuration.cert_expiration_length_days,
+ }
+ )
@gem.with_write_io do |gem_io|
Gem::Package::TarWriter.new gem_io do |gem|
@@ -263,7 +319,7 @@ class Gem::Package
Successfully built RubyGem
Name: #{@spec.name}
Version: #{@spec.version}
- File: #{File.basename @spec.cache_file}
+ File: #{File.basename @gem.path}
EOM
ensure
@signer = nil
@@ -283,7 +339,7 @@ EOM
gem_tar = Gem::Package::TarReader.new io
gem_tar.each do |entry|
- next unless entry.full_name == 'data.tar.gz'
+ next unless entry.full_name == "data.tar.gz"
open_tar_gz entry do |pkg_tar|
pkg_tar.each do |contents_entry|
@@ -294,31 +350,31 @@ EOM
return @contents
end
end
+ rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e
+ raise Gem::Package::FormatError.new e.message, @gem
end
##
# Creates a digest of the TarEntry +entry+ from the digest algorithm set by
# the security policy.
- def digest entry # :nodoc:
- algorithms = if @checksums then
- @checksums.keys
- else
- [Gem::Security::DIGEST_NAME].compact
- end
-
- algorithms.each do |algorithm|
- digester =
- if defined?(OpenSSL::Digest) then
- OpenSSL::Digest.new algorithm
- else
- Digest.const_get(algorithm).new
- end
+ def digest(entry) # :nodoc:
+ algorithms = if @checksums
+ @checksums.to_h {|algorithm, _| [algorithm, Gem::Security.create_digest(algorithm)] }
+ elsif Gem::Security::DIGEST_NAME
+ { Gem::Security::DIGEST_NAME => Gem::Security.create_digest(Gem::Security::DIGEST_NAME) }
+ end
- digester << entry.read(16384) until entry.eof?
+ return @digests if algorithms.nil? || algorithms.empty?
- entry.rewind
+ buf = String.new(capacity: 16_384, encoding: Encoding::BINARY)
+ until entry.eof?
+ entry.readpartial(16_384, buf)
+ algorithms.each_value {|digester| digester << buf }
+ end
+ entry.rewind
+ algorithms.each do |algorithm, digester|
@digests[algorithm][entry.full_name] = digester
end
@@ -331,22 +387,24 @@ EOM
# If +pattern+ is specified, only entries matching that glob will be
# extracted.
- def extract_files destination_dir, pattern = "*"
+ def extract_files(destination_dir, pattern = "*")
verify unless @spec
- FileUtils.mkdir_p destination_dir
+ FileUtils.mkdir_p destination_dir, mode: dir_mode && 0o755
@gem.with_read_io do |io|
reader = Gem::Package::TarReader.new io
reader.each do |entry|
- next unless entry.full_name == 'data.tar.gz'
+ next unless entry.full_name == "data.tar.gz"
extract_tar_gz entry, destination_dir, pattern
- return # ignore further entries
+ break # ignore further entries
end
end
+ rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e
+ raise Gem::Package::FormatError.new e.message, @gem
end
##
@@ -360,36 +418,81 @@ EOM
# If +pattern+ is specified, only entries matching that glob will be
# extracted.
- def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc:
+ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
+ destination_dir = File.realpath(destination_dir)
+
+ directories = []
+ symlinks = []
+
open_tar_gz io do |tar|
tar.each do |entry|
- next unless File.fnmatch pattern, entry.full_name, File::FNM_DOTMATCH
+ full_name = entry.full_name
+ next unless File.fnmatch pattern, full_name, File::FNM_DOTMATCH
+
+ destination = install_location full_name, destination_dir
- destination = install_location entry.full_name, destination_dir
+ if entry.symlink?
+ link_target = entry.header.linkname
+ real_destination = link_target.start_with?("/") ? link_target : File.expand_path(link_target, File.dirname(destination))
- FileUtils.rm_rf destination
+ raise Gem::Package::SymlinkError.new(full_name, real_destination, destination_dir) unless
+ normalize_path(real_destination).start_with? normalize_path(destination_dir + "/")
+
+ symlinks << [full_name, link_target, destination, real_destination]
+ end
- mkdir_options = {}
- mkdir_options[:mode] = entry.header.mode if entry.directory?
mkdir =
- if entry.directory? then
+ if entry.directory?
destination
else
File.dirname destination
end
- FileUtils.mkdir_p mkdir, mkdir_options
+ unless directories.include?(mkdir)
+ FileUtils.mkdir_p mkdir, mode: dir_mode ? 0o755 : (entry.header.mode if entry.directory?)
+ directories << mkdir
+ end
- open destination, 'wb' do |out|
- out.write entry.read
- FileUtils.chmod entry.header.mode, destination
- end if entry.file?
+ real_mkdir = File.realpath(mkdir)
+ unless real_mkdir == destination_dir || normalize_path(real_mkdir).start_with?(normalize_path(destination_dir + "/"))
+ raise Gem::Package::PathError.new(real_mkdir, destination_dir)
+ end
- File.symlink(entry.header.linkname, destination) if entry.symlink?
+ if entry.file?
+ File.open(destination, "wb") do |out|
+ copy_stream(tar.io, out, entry.size)
+ # Flush needs to happen before chmod because there could be data
+ # in the IO buffer that needs to be written, and that could be
+ # written after the chmod (on close) which would mess up the perms
+ out.flush
+ out.chmod file_mode(entry.header.mode) & ~File.umask
+ end
+ end
verbose destination
end
end
+
+ symlinks.each do |name, target, destination, real_destination|
+ if File.exist?(real_destination)
+ create_symlink(target, destination)
+ else
+ alert_warning "#{@spec.full_name} ships with a dangling symlink named #{name} pointing to missing #{target} file. Ignoring"
+ end
+ end
+
+ if dir_mode
+ File.chmod(dir_mode, *directories)
+ end
+ end
+
+ def file_mode(mode) # :nodoc:
+ ((mode & 0o111).zero? ? data_mode : prog_mode) ||
+ # If we're not using one of the default modes, then we're going to fall
+ # back to the mode from the tarball. In this case we need to mask it down
+ # to fit into 2^16 bits (the maximum value for a mode in CRuby since it
+ # gets put into an unsigned short).
+ (mode & ((1 << 16) - 1))
end
##
@@ -398,7 +501,7 @@ EOM
# Also sets the gzip modification time to the package build time to ease
# testing.
- def gzip_to io # :yields: gz_io
+ def gzip_to(io) # :yields: gz_io
gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION
gz_io.mtime = @build_time
@@ -412,39 +515,40 @@ EOM
#
# If +filename+ is not inside +destination_dir+ an exception is raised.
- def install_location filename, destination_dir # :nodoc:
+ def install_location(filename, destination_dir) # :nodoc:
raise Gem::Package::PathError.new(filename, destination_dir) if
- filename.start_with? '/'
+ filename.start_with? "/"
- destination_dir = File.realpath destination_dir if
- File.respond_to? :realpath
- destination_dir = File.expand_path destination_dir
-
- destination = File.join destination_dir, filename
- destination = File.expand_path destination
+ destination_dir = File.realpath(destination_dir)
+ destination = File.expand_path(filename, destination_dir)
raise Gem::Package::PathError.new(destination, destination_dir) unless
- destination.start_with? destination_dir
+ normalize_path(destination).start_with? normalize_path(destination_dir + "/")
- destination.untaint
destination
end
+ if Gem.win_platform?
+ def normalize_path(pathname) # :nodoc:
+ pathname.downcase
+ end
+ else
+ def normalize_path(pathname) # :nodoc:
+ pathname
+ end
+ end
+
##
# Loads a Gem::Specification from the TarEntry +entry+
- def load_spec entry # :nodoc:
+ def load_spec_from_metadata(entry) # :nodoc:
+ limit = 10 * 1024 * 1024
case entry.full_name
- when 'metadata' then
- @spec = Gem::Specification.from_yaml entry.read
- when 'metadata.gz' then
- args = [entry]
- args << { :external_encoding => Encoding::UTF_8 } if
- Object.const_defined?(:Encoding) &&
- Zlib::GzipReader.method(:wrap).arity != 1
-
- Zlib::GzipReader.wrap(*args) do |gzio|
- @spec = Gem::Specification.from_yaml gzio.read
+ when "metadata" then
+ @spec = Gem::Specification.from_yaml limit_read(entry, "metadata", limit)
+ when "metadata.gz" then
+ Zlib::GzipReader.wrap(entry, external_encoding: Encoding::UTF_8) do |gzio|
+ @spec = Gem::Specification.from_yaml limit_read(gzio, "metadata.gz", limit)
end
end
end
@@ -452,23 +556,32 @@ EOM
##
# Opens +io+ as a gzipped tar archive
- def open_tar_gz io # :nodoc:
+ def open_tar_gz(io) # :nodoc:
Zlib::GzipReader.wrap io do |gzio|
tar = Gem::Package::TarReader.new gzio
yield tar
+ ensure
+ # Consume remaining gzip data to prevent the
+ # "attempt to close unfinished zstream; reset forced" warning
+ # when the GzipReader is closed with unconsumed compressed data.
+ begin
+ IO.copy_stream(gzio, IO::NULL)
+ rescue Zlib::GzipFile::Error, IOError
+ nil
+ end
end
end
##
# Reads and loads checksums.yaml.gz from the tar file +gem+
- def read_checksums gem
+ def read_checksums(gem)
Gem.load_yaml
- @checksums = gem.seek 'checksums.yaml.gz' do |entry|
+ @checksums = gem.seek "checksums.yaml.gz" do |entry|
Zlib::GzipReader.wrap entry do |gz_io|
- Gem::SafeYAML.safe_load gz_io.read
+ Gem::SafeYAML.safe_load limit_read(gz_io, "checksums.yaml.gz", 10 * 1024 * 1024)
end
end
end
@@ -477,15 +590,22 @@ EOM
# Prepares the gem for signing and checksum generation. If a signing
# certificate and key are not present only checksum generation is set up.
- def setup_signer
- passphrase = ENV['GEM_PRIVATE_KEY_PASSPHRASE']
- if @spec.signing_key then
- @signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain, passphrase
+ def setup_signer(signer_options: {})
+ passphrase = ENV["GEM_PRIVATE_KEY_PASSPHRASE"]
+ if @spec.signing_key
+ @signer =
+ Gem::Security::Signer.new(
+ @spec.signing_key,
+ @spec.cert_chain,
+ passphrase,
+ signer_options
+ )
+
@spec.signing_key = nil
- @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s }
+ @spec.cert_chain = @signer.cert_chain.map(&:to_s)
else
@signer = Gem::Security::Signer.new nil, nil, passphrase
- @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_pem } if
+ @spec.cert_chain = @signer.cert_chain.map(&:to_pem) if
@signer.cert_chain
end
end
@@ -527,8 +647,7 @@ EOM
verify_checksums @digests, @checksums
- @security_policy.verify_signatures @spec, @digests, @signatures if
- @security_policy
+ @security_policy&.verify_signatures @spec, @digests, @signatures
true
rescue Gem::Security::Exception
@@ -537,22 +656,24 @@ EOM
raise
rescue Errno::ENOENT => e
raise Gem::Package::FormatError.new e.message
- rescue Gem::Package::TarInvalidError => e
+ rescue Zlib::GzipFile::Error, EOFError, Gem::Package::TarInvalidError => e
raise Gem::Package::FormatError.new e.message, @gem
end
+ private
+
##
# Verifies the +checksums+ against the +digests+. This check is not
# cryptographically secure. Missing checksums are ignored.
- def verify_checksums digests, checksums # :nodoc:
+ def verify_checksums(digests, checksums) # :nodoc:
return unless checksums
checksums.sort.each do |algorithm, gem_digests|
gem_digests.sort.each do |file_name, gem_hexdigest|
computed_digest = digests[algorithm][file_name]
- unless computed_digest.hexdigest == gem_hexdigest then
+ unless computed_digest.hexdigest == gem_hexdigest
raise Gem::Package::FormatError.new \
"#{algorithm} checksum mismatch for #{file_name}", @gem
end
@@ -563,67 +684,86 @@ EOM
##
# Verifies +entry+ in a .gem file.
- def verify_entry entry
+ def verify_entry(entry)
file_name = entry.full_name
@files << file_name
case file_name
when /\.sig$/ then
- @signatures[$`] = entry.read if @security_policy
+ @signatures[$`] = limit_read(entry, file_name, 1024 * 1024) if @security_policy
return
else
digest entry
end
- case file_name
- when /^metadata(.gz)?$/ then
- load_spec entry
- when 'data.tar.gz' then
- verify_gz entry
- end
- rescue => e
- message = "package is corrupt, exception while verifying: " +
- "#{e.message} (#{e.class})"
- raise Gem::Package::FormatError.new message, @gem
+ load_spec_from_metadata entry
+ rescue StandardError
+ warn "Exception while verifying #{@gem.path}"
+ raise
end
##
# Verifies the files of the +gem+
- def verify_files gem
+ def verify_files(gem)
gem.each do |entry|
verify_entry entry
end
- unless @spec then
- raise Gem::Package::FormatError.new 'package metadata is missing', @gem
+ unless @spec
+ raise Gem::Package::FormatError.new "package metadata is missing", @gem
end
- unless @files.include? 'data.tar.gz' then
+ unless @files.include? "data.tar.gz"
raise Gem::Package::FormatError.new \
- 'package content (data.tar.gz) is missing', @gem
+ "package content (data.tar.gz) is missing", @gem
end
- end
- ##
- # Verifies that +entry+ is a valid gzipped file.
+ if (duplicates = @files.group_by {|f| f }.select {|_k,v| v.size > 1 }.map(&:first)) && duplicates.any?
+ raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(", ")})"
+ end
+ end
- def verify_gz entry # :nodoc:
- Zlib::GzipReader.wrap entry do |gzio|
- gzio.read 16384 until gzio.eof? # gzip checksum verification
+ if RUBY_ENGINE == "truffleruby"
+ def copy_stream(src, dst, size) # :nodoc:
+ dst.write src.read(size)
+ end
+ else
+ def copy_stream(src, dst, size) # :nodoc:
+ IO.copy_stream(src, dst, size)
end
- rescue Zlib::GzipFile::Error => e
- raise Gem::Package::FormatError.new(e.message, entry.full_name)
end
+ def limit_read(io, name, limit)
+ bytes = io.read(limit + 1)
+ raise Gem::Package::FormatError, "#{name} is too big (over #{limit} bytes)" if bytes.size > limit
+ bytes
+ end
+
+ if Gem.win_platform?
+ # Create a symlink and fallback to copy the file or directory on Windows,
+ # where symlink creation needs special privileges in form of the Developer Mode.
+ # JRuby on Windows raises TypeError from the wincode path-conversion helper
+ # when it cannot create the symlink, so fall back to copy in that case too.
+ def create_symlink(old_name, new_name)
+ File.symlink(old_name, new_name)
+ rescue Errno::EACCES, TypeError
+ from = File.expand_path(old_name, File.dirname(new_name))
+ FileUtils.cp_r(from, new_name)
+ end
+ else
+ def create_symlink(old_name, new_name)
+ File.symlink(old_name, new_name)
+ end
+ end
end
-require 'rubygems/package/digest_io'
-require 'rubygems/package/source'
-require 'rubygems/package/file_source'
-require 'rubygems/package/io_source'
-require 'rubygems/package/old'
-require 'rubygems/package/tar_header'
-require 'rubygems/package/tar_reader'
-require 'rubygems/package/tar_reader/entry'
-require 'rubygems/package/tar_writer'
+require_relative "package/digest_io"
+require_relative "package/source"
+require_relative "package/file_source"
+require_relative "package/io_source"
+require_relative "package/old"
+require_relative "package/tar_header"
+require_relative "package/tar_reader"
+require_relative "package/tar_reader/entry"
+require_relative "package/tar_writer"