summaryrefslogtreecommitdiff
path: root/lib/rubygems/package/old.rb
blob: 552a5f3591100d1cdbcf6a1268da54f590537fb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++

##
# The format class knows the guts of the ancient .gem file format and provides
# the capability to read such ancient gems.
#
# Please pretend this doesn't exist.

class Gem::Package::Old < Gem::Package

  undef_method :spec=

  ##
  # Creates a new old-format package reader for +gem+.  Old-format packages
  # cannot be written.

  def initialize gem
    require 'fileutils'
    require 'zlib'
    Gem.load_yaml

    @gem      = gem
    @contents = nil
    @spec     = nil
  end

  ##
  # A list of file names contained in this gem

  def contents
    return @contents if @contents

    open @gem, 'rb' do |io|
      read_until_dashes io # spec
      header = file_list io

      @contents = header.map { |file| file['path'] }
    end
  end

  ##
  # Extracts the files in this package into +destination_dir+

  def extract_files destination_dir
    errstr = "Error reading files from gem"

    open @gem, 'rb' do |io|
      read_until_dashes io # spec
      header = file_list io
      raise Gem::Exception, errstr unless header

      header.each do |entry|
        full_name = entry['path']

        destination = install_location full_name, destination_dir

        file_data = ''

        read_until_dashes io do |line|
          file_data << line
        end

        file_data = file_data.strip.unpack("m")[0]
        file_data = Zlib::Inflate.inflate file_data

        raise Gem::Package::FormatError, "#{full_name} in #{@gem} is corrupt" if
          file_data.length != entry['size'].to_i

        FileUtils.rm_rf destination

        FileUtils.mkdir_p File.dirname destination

        open destination, 'wb', entry['mode'] do |out|
          out.write file_data
        end

        say destination if Gem.configuration.really_verbose
      end
    end
  rescue Zlib::DataError
    raise Gem::Exception, errstr
  end

  ##
  # Reads the file list section from the old-format gem +io+

  def file_list io # :nodoc:
    header = ''

    read_until_dashes io do |line|
      header << line
    end

    YAML.load header
  end

  ##
  # Reads lines until a "---" separator is found

  def read_until_dashes io # :nodoc:
    while (line = io.gets) && line.chomp.strip != "---" do
      yield line if block_given?
    end
  end

  ##
  # Skips the Ruby self-install header in +io+.

  def skip_ruby io # :nodoc:
    loop do
      line = io.gets

      return if line.chomp == '__END__'
      break unless line
    end

    raise Gem::Exception, "Failed to find end of ruby script while reading gem"
  end

  ##
  # The specification for this gem

  def spec
    return @spec if @spec

    yaml = ''

    open @gem, 'rb' do |io|
      skip_ruby io
      read_until_dashes io do |line|
        yaml << line
      end
    end

    @spec = Gem::Specification.from_yaml yaml
  rescue YAML::SyntaxError => e
    raise Gem::Exception, "Failed to parse gem specification out of gem file"
  rescue ArgumentError => e
    raise Gem::Exception, "Failed to parse gem specification out of gem file"
  end

end