summaryrefslogtreecommitdiff
path: root/trunk/lib/rubygems/package/tar_writer.rb
blob: 6e11440e22364c574c58dfcea75e2afe78c2047f (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
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
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--

require 'rubygems/package'

class Gem::Package::TarWriter

  class FileOverflow < StandardError; end

  class BoundedStream

    attr_reader :limit, :written

    def initialize(io, limit)
      @io = io
      @limit = limit
      @written = 0
    end

    def write(data)
      if data.size + @written > @limit
        raise FileOverflow, "You tried to feed more data than fits in the file."
      end
      @io.write data
      @written += data.size
      data.size
    end

  end

  class RestrictedStream

    def initialize(io)
      @io = io
    end

    def write(data)
      @io.write data
    end

  end

  def self.new(io)
    writer = super

    return writer unless block_given?

    begin
      yield writer
    ensure
      writer.close
    end

    nil
  end

  def initialize(io)
    @io = io
    @closed = false
  end

  def add_file(name, mode)
    check_closed

    raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos=

    name, prefix = split_name name

    init_pos = @io.pos
    @io.write "\0" * 512 # placeholder for the header

    yield RestrictedStream.new(@io) if block_given?

    size = @io.pos - init_pos - 512

    remainder = (512 - (size % 512)) % 512
    @io.write "\0" * remainder

    final_pos = @io.pos
    @io.pos = init_pos

    header = Gem::Package::TarHeader.new :name => name, :mode => mode,
                                         :size => size, :prefix => prefix

    @io.write header
    @io.pos = final_pos

    self
  end

  def add_file_simple(name, mode, size)
    check_closed

    name, prefix = split_name name

    header = Gem::Package::TarHeader.new(:name => name, :mode => mode,
                                         :size => size, :prefix => prefix).to_s

    @io.write header
    os = BoundedStream.new @io, size

    yield os if block_given?

    min_padding = size - os.written
    @io.write("\0" * min_padding)

    remainder = (512 - (size % 512)) % 512
    @io.write("\0" * remainder)

    self
  end

  def check_closed
    raise IOError, "closed #{self.class}" if closed?
  end

  def close
    check_closed

    @io.write "\0" * 1024
    flush

    @closed = true
  end

  def closed?
    @closed
  end

  def flush
    check_closed

    @io.flush if @io.respond_to? :flush
  end

  def mkdir(name, mode)
    check_closed

    name, prefix = split_name(name)

    header = Gem::Package::TarHeader.new :name => name, :mode => mode,
                                         :typeflag => "5", :size => 0,
                                         :prefix => prefix

    @io.write header

    self
  end

  def split_name(name) # :nodoc:
    raise Gem::Package::TooLongFileName if name.size > 256

    if name.size <= 100 then
      prefix = ""
    else
      parts = name.split(/\//)
      newname = parts.pop
      nxt = ""

      loop do
        nxt = parts.pop
        break if newname.size + 1 + nxt.size > 100
        newname = nxt + "/" + newname
      end

      prefix = (parts + [nxt]).join "/"
      name = newname

      if name.size > 100 or prefix.size > 155 then
        raise Gem::Package::TooLongFileName 
      end
    end

    return name, prefix
  end

end