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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
# frozen_string_literal: true
#++
# Copyright (C) 2004 Mauricio Julio Fernández Pradier
# See LICENSE.txt for additional licensing information.
#--
##
# Class for reading entries out of a tar file
class Gem::Package::TarReader::Entry
##
# Creates a new tar entry for +header+ that will be read from +io+
# If a block is given, the entry is yielded and then closed.
def self.open(header, io, &block)
entry = new header, io
return entry unless block_given?
begin
yield entry
ensure
entry.close
end
end
##
# Header for this tar entry
attr_reader :header
##
# Creates a new tar entry for +header+ that will be read from +io+
def initialize(header, io)
@closed = false
@header = header
@io = io
@orig_pos = @io.pos
@end_pos = @orig_pos + @header.size
@read = 0
end
def check_closed # :nodoc:
raise IOError, "closed #{self.class}" if closed?
end
##
# Number of bytes read out of the tar entry
def bytes_read
@read
end
##
# Closes the tar entry
def close
return if closed?
# Seek to the end of the entry if it wasn't fully read
seek(0, IO::SEEK_END)
# discard trailing zeros
skip = (512 - (@header.size % 512)) % 512
@io.read(skip)
@closed = true
nil
end
##
# Is the tar entry closed?
def closed?
@closed
end
##
# Are we at the end of the tar entry?
def eof?
check_closed
@read >= @header.size
end
##
# Full name of the tar entry
def full_name
if @header.prefix != ""
File.join @header.prefix, @header.name
else
@header.name
end
rescue ArgumentError => e
raise unless e.message == "string contains null byte"
raise Gem::Package::TarInvalidError,
"tar is corrupt, name contains null byte"
end
##
# Read one byte from the tar entry
def getc
check_closed
return nil if @read >= @header.size
ret = @io.getc
@read += 1 if ret
ret
end
##
# Is this tar entry a directory?
def directory?
@header.typeflag == "5"
end
##
# Is this tar entry a file?
def file?
@header.typeflag == "0"
end
##
# Is this tar entry a symlink?
def symlink?
@header.typeflag == "2"
end
##
# The position in the tar entry
def pos
check_closed
bytes_read
end
##
# Seek to the position in the tar entry
def pos=(new_pos)
seek(new_pos, IO::SEEK_SET)
new_pos
end
def size
@header.size
end
alias length size
##
# Reads +len+ bytes from the tar file entry, or the rest of the entry if
# nil
def read(len = nil)
check_closed
len ||= @header.size - @read
return nil if len > 0 && @read >= @header.size
max_read = [len, @header.size - @read].min
ret = @io.read max_read
@read += ret.size
ret
end
def readpartial(maxlen = nil, outbuf = "".b)
check_closed
maxlen ||= @header.size - @read
raise EOFError if maxlen > 0 && @read >= @header.size
max_read = [maxlen, @header.size - @read].min
@io.readpartial(max_read, outbuf)
@read += outbuf.size
outbuf
end
##
# Seeks to +offset+ bytes into the tar file entry
# +whence+ can be IO::SEEK_SET, IO::SEEK_CUR, or IO::SEEK_END
def seek(offset, whence = IO::SEEK_SET)
check_closed
new_pos =
case whence
when IO::SEEK_SET then @orig_pos + offset
when IO::SEEK_CUR then @io.pos + offset
when IO::SEEK_END then @end_pos + offset
else
raise ArgumentError, "invalid whence"
end
if new_pos < @orig_pos
new_pos = @orig_pos
elsif new_pos > @end_pos
new_pos = @end_pos
end
pending = new_pos - @io.pos
if @io.respond_to?(:seek)
begin
# avoid reading if the @io supports seeking
@io.seek new_pos, IO::SEEK_SET
pending = 0
rescue Errno::EINVAL
end
end
# if seeking isn't supported or failed
# negative seek requires that we rewind and read
if pending < 0
@io.rewind
pending = new_pos
end
while pending > 0 do
size_read = @io.read([pending, 4096].min).size
raise UnexpectedEOF if @io.eof?
pending -= size_read
end
@read = @io.pos - @orig_pos
0
end
##
# Rewinds to the beginning of the tar file entry
def rewind
check_closed
seek(0, IO::SEEK_SET)
end
end
|