minitar 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +285 -0
- data/CONTRIBUTING.md +273 -0
- data/CONTRIBUTORS.md +27 -0
- data/LICENCE.md +39 -0
- data/Manifest.txt +29 -6
- data/README.md +70 -0
- data/Rakefile +74 -19
- data/SECURITY.md +64 -0
- data/docs/ruby.txt +3 -3
- data/lib/minitar/input.rb +69 -56
- data/lib/minitar/output.rb +34 -22
- data/lib/minitar/pax_header.rb +111 -0
- data/lib/minitar/posix_header.rb +96 -57
- data/lib/minitar/reader.rb +65 -70
- data/lib/minitar/version.rb +5 -0
- data/lib/minitar/writer.rb +50 -88
- data/lib/minitar.rb +60 -64
- data/licenses/bsdl.txt +20 -0
- data/licenses/dco.txt +34 -0
- data/licenses/ruby.txt +52 -0
- data/test/fixtures/issue_46.tar.gz +0 -0
- data/test/fixtures/issue_62.tar.gz +0 -0
- data/test/fixtures/tar_input.tgz +0 -0
- data/test/fixtures/test_input_non_strict_octal.tgz +0 -0
- data/test/fixtures/test_input_relative.tgz +0 -0
- data/test/fixtures/test_input_space_octal.tgz +0 -0
- data/test/fixtures/test_minitar.tar.gz +0 -0
- data/test/minitest_helper.rb +12 -1
- data/test/support/minitar_test_helpers/fixtures.rb +38 -0
- data/test/support/minitar_test_helpers/header.rb +130 -0
- data/test/support/minitar_test_helpers/tarball.rb +324 -0
- data/test/support/minitar_test_helpers.rb +36 -0
- data/test/test_filename_boundary_conditions.rb +74 -0
- data/test/test_gnu_tar_compatibility.rb +92 -0
- data/test/test_integration_pack_unpack_cycle.rb +38 -0
- data/test/test_issue_46.rb +5 -23
- data/test/test_issue_62.rb +50 -0
- data/test/test_minitar.rb +168 -39
- data/test/test_pax_header.rb +104 -0
- data/test/test_pax_support.rb +66 -0
- data/test/test_tar_header.rb +289 -75
- data/test/test_tar_input.rb +14 -61
- data/test/test_tar_output.rb +7 -9
- data/test/test_tar_reader.rb +17 -18
- data/test/test_tar_writer.rb +105 -126
- metadata +95 -89
- data/Contributing.md +0 -94
- data/History.md +0 -236
- data/Licence.md +0 -15
- data/README.rdoc +0 -92
- data/test/support/tar_test_helpers.rb +0 -134
- /data/{Code-of-Conduct.md → CODE_OF_CONDUCT.md} +0 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Minitar
|
4
|
+
# Implements the PAX Extended Header as a Ruby class. The header consists of following
|
5
|
+
# format:
|
6
|
+
#
|
7
|
+
# <decimal-length><space><ascii-keyword>=<value><newline>
|
8
|
+
#
|
9
|
+
# Where:
|
10
|
+
#
|
11
|
+
# - +decimal-length+ is the total number of bytes for the PaxHeader record using ASCII
|
12
|
+
# decimal values; this includes the terminal newline (0x10).
|
13
|
+
# - +space+ is a single literal ASCII space (0x20).
|
14
|
+
# - +ascii-keyword+ is a PAX Extended Header keyword, which may be any ASCII character
|
15
|
+
# except newline (0x10) or equal sign (0x3D).
|
16
|
+
# - +=+ is the literal ASCII equal sign (0x3D).
|
17
|
+
# - +value+ is any series of bytes except newline (0x10).
|
18
|
+
# - +newline+ is the literal ASCII newline (0x10).
|
19
|
+
#
|
20
|
+
# There are several keywords defined in the POSIX standard; some of them are supported
|
21
|
+
# in this class, but may not be supported by Minitar as a whole.
|
22
|
+
#
|
23
|
+
# Primary support for PAX extended headers is for extracting size information for large
|
24
|
+
# file support. Other features may be added in the future.
|
25
|
+
class PaxHeader
|
26
|
+
BLOCK_SIZE = 512
|
27
|
+
|
28
|
+
attr_reader :attributes
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# Creates a new PaxHeader from a data stream and posix header. Reads the PAX content
|
32
|
+
# based on the size specified in the posix header.
|
33
|
+
def from_stream(stream, posix_header)
|
34
|
+
raise ArgumentError, "Header must be a PAX header" unless posix_header.pax_header?
|
35
|
+
|
36
|
+
pax_block = (posix_header.size / BLOCK_SIZE.to_f).ceil * BLOCK_SIZE
|
37
|
+
pax_content = stream.read(pax_block)
|
38
|
+
|
39
|
+
raise Minitar::InvalidTarStream if pax_content.nil? || pax_content.bytesize < posix_header.size
|
40
|
+
|
41
|
+
actual_content = pax_content[0, posix_header.size]
|
42
|
+
|
43
|
+
from_data(actual_content)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a new PaxHeader from PAX content data.
|
47
|
+
def from_data(content) = new(parse_content(content))
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def parse_content(content)
|
52
|
+
attributes = {}
|
53
|
+
offset = 0
|
54
|
+
|
55
|
+
while offset < content.bytesize
|
56
|
+
space_pos = content.index(" ", offset)
|
57
|
+
break unless space_pos
|
58
|
+
|
59
|
+
length_str = content[offset, space_pos - offset]
|
60
|
+
|
61
|
+
unless length_str.match?(/\A\d+\z/)
|
62
|
+
raise ArgumentError, "Invalid length format in PAX header: '#{length_str}'"
|
63
|
+
end
|
64
|
+
|
65
|
+
length = length_str.to_i
|
66
|
+
if offset + length > content.bytesize
|
67
|
+
raise ArgumentError, "Length beyond PAX header: '#{content[offset..]}'"
|
68
|
+
end
|
69
|
+
record = content[offset, length]
|
70
|
+
|
71
|
+
keyword_value = record[(space_pos - offset + 1)..-2]
|
72
|
+
if keyword_value.include?("=")
|
73
|
+
keyword, value = keyword_value.split("=", 2)
|
74
|
+
attributes[keyword] = value
|
75
|
+
end
|
76
|
+
|
77
|
+
offset += length
|
78
|
+
end
|
79
|
+
attributes
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Creates a new PaxHeader from attributes hash.
|
84
|
+
def initialize(attributes = {})
|
85
|
+
@attributes = attributes.transform_keys(&:to_s)
|
86
|
+
end
|
87
|
+
|
88
|
+
# The size value from PAX attributes
|
89
|
+
def size = @attributes["size"]&.to_i
|
90
|
+
|
91
|
+
# The path value from PAX attributes
|
92
|
+
def path = @attributes["path"]
|
93
|
+
|
94
|
+
# The mtime value from PAX attributes
|
95
|
+
def mtime = @attributes["mtime"]&.to_f
|
96
|
+
|
97
|
+
# Returns a string representation of the PAX header content.
|
98
|
+
def to_s
|
99
|
+
@attributes.map do |keyword, value|
|
100
|
+
keyword_value = " #{keyword}=#{value}\n"
|
101
|
+
record = keyword_value
|
102
|
+
begin
|
103
|
+
length = record.bytesize
|
104
|
+
length_str = length.to_s
|
105
|
+
record = "#{length_str}#{keyword_value}"
|
106
|
+
end while record.size != length # standard:disable Lint/Loop
|
107
|
+
record
|
108
|
+
end.join
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/minitar/posix_header.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Minitar
|
2
4
|
# Implements the POSIX tar header as a Ruby class. The structure of
|
3
5
|
# the POSIX tar header is:
|
4
6
|
#
|
5
7
|
# struct tarfile_entry_posix
|
6
|
-
# { // pack
|
7
|
-
# char name[100]; // ASCII (+ Z unless filled) a100
|
8
|
-
# char mode[8]; // 0 padded, octal, null a8
|
9
|
-
# char uid[8]; // 0 padded, octal, null a8
|
10
|
-
# char gid[8]; // 0 padded, octal, null a8
|
11
|
-
# char size[12]; // 0 padded, octal, null a12
|
12
|
-
# char mtime[12]; // 0 padded, octal, null a12
|
13
|
-
# char checksum[8]; // 0 padded, octal, null, space a8
|
14
|
-
# char typeflag[1]; // see below a
|
15
|
-
# char linkname[100]; // ASCII + (Z unless filled) a100
|
16
|
-
# char magic[6]; // "ustar\0" a6
|
17
|
-
# char version[2]; // "00" a2
|
18
|
-
# char uname[32]; // ASCIIZ a32
|
19
|
-
# char gname[32]; // ASCIIZ a32
|
20
|
-
# char devmajor[8]; // 0 padded, octal, null a8
|
21
|
-
# char devminor[8]; // 0 padded, octal, null a8
|
22
|
-
# char prefix[155]; // ASCII (+ Z unless filled) a155
|
8
|
+
# { // pack unpack
|
9
|
+
# char name[100]; // ASCII (+ Z unless filled) a100 Z100
|
10
|
+
# char mode[8]; // 0 padded, octal, null a8 A8
|
11
|
+
# char uid[8]; // 0 padded, octal, null a8 A8
|
12
|
+
# char gid[8]; // 0 padded, octal, null a8 A8
|
13
|
+
# char size[12]; // 0 padded, octal, null a12 A12
|
14
|
+
# char mtime[12]; // 0 padded, octal, null a12 A12
|
15
|
+
# char checksum[8]; // 0 padded, octal, null, space a8 A8
|
16
|
+
# char typeflag[1]; // see below a a
|
17
|
+
# char linkname[100]; // ASCII + (Z unless filled) a100 Z100
|
18
|
+
# char magic[6]; // "ustar\0" a6 A6
|
19
|
+
# char version[2]; // "00" a2 A2
|
20
|
+
# char uname[32]; // ASCIIZ a32 Z32
|
21
|
+
# char gname[32]; // ASCIIZ a32 Z32
|
22
|
+
# char devmajor[8]; // 0 padded, octal, null a8 A8
|
23
|
+
# char devminor[8]; // 0 padded, octal, null a8 A8
|
24
|
+
# char prefix[155]; // ASCII (+ Z unless filled) a155 Z155
|
23
25
|
# };
|
24
26
|
#
|
25
|
-
# The #typeflag is one of several known values.
|
26
|
-
#
|
27
|
-
# POSIX indicates that "A POSIX-compliant implementation must treat any
|
28
|
-
# unrecognized typeflag value as a regular file."
|
27
|
+
# The #typeflag is one of several known values. POSIX indicates that "A POSIX-compliant
|
28
|
+
# implementation must treat any unrecognized typeflag value as a regular file."
|
29
29
|
class PosixHeader
|
30
30
|
BLOCK_SIZE = 512
|
31
|
-
MAGIC_BYTES = "ustar"
|
31
|
+
MAGIC_BYTES = "ustar"
|
32
32
|
|
33
33
|
GNU_EXT_LONG_LINK = "././@LongLink"
|
34
34
|
|
@@ -36,38 +36,33 @@ class Minitar
|
|
36
36
|
REQUIRED_FIELDS = [:name, :size, :prefix, :mode].freeze
|
37
37
|
# Fields that may be set in a POSIX tar(1) header.
|
38
38
|
OPTIONAL_FIELDS = [
|
39
|
-
:uid, :gid, :mtime, :checksum, :typeflag, :linkname, :magic, :version,
|
40
|
-
:
|
39
|
+
:uid, :gid, :mtime, :checksum, :typeflag, :linkname, :magic, :version, :uname,
|
40
|
+
:gname, :devmajor, :devminor
|
41
41
|
].freeze
|
42
42
|
|
43
43
|
# All fields available in a POSIX tar(1) header.
|
44
44
|
FIELDS = (REQUIRED_FIELDS + OPTIONAL_FIELDS).freeze
|
45
45
|
|
46
46
|
FIELDS.each do |f|
|
47
|
-
attr_reader f.to_sym
|
47
|
+
attr_reader f.to_sym
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
def name=(value)
|
52
|
+
valid_name!(value)
|
53
|
+
@name = value
|
48
54
|
end
|
49
55
|
|
50
|
-
|
51
|
-
# longer (up to BLOCK_SIZE bytes) if using the GNU long name tar extension.
|
52
|
-
attr_accessor :name
|
56
|
+
attr_writer :size
|
53
57
|
|
54
58
|
# The pack format passed to Array#pack for encoding a header.
|
55
|
-
HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155"
|
59
|
+
HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155"
|
56
60
|
# The unpack format passed to String#unpack for decoding a header.
|
57
|
-
HEADER_UNPACK_FORMAT = "
|
61
|
+
HEADER_UNPACK_FORMAT = "Z100A8A8A8a12A12A8aZ100A6A2Z32Z32A8A8Z155"
|
58
62
|
|
59
63
|
class << self
|
60
64
|
# Creates a new PosixHeader from a data stream.
|
61
|
-
def from_stream(stream)
|
62
|
-
from_data(stream.read(BLOCK_SIZE))
|
63
|
-
end
|
64
|
-
|
65
|
-
# Creates a new PosixHeader from a data stream. Deprecated; use
|
66
|
-
# PosixHeader.from_stream instead.
|
67
|
-
def new_from_stream(stream)
|
68
|
-
warn "#{__method__} has been deprecated; use from_stream instead."
|
69
|
-
from_stream(stream)
|
70
|
-
end
|
65
|
+
def from_stream(stream) = from_data(stream.read(BLOCK_SIZE))
|
71
66
|
|
72
67
|
# Creates a new PosixHeader from a BLOCK_SIZE-byte data buffer.
|
73
68
|
def from_data(data)
|
@@ -76,7 +71,7 @@ class Minitar
|
|
76
71
|
mode = fields.shift.oct
|
77
72
|
uid = fields.shift.oct
|
78
73
|
gid = fields.shift.oct
|
79
|
-
size =
|
74
|
+
size = parse_numeric_field(fields.shift)
|
80
75
|
mtime = fields.shift.oct
|
81
76
|
checksum = fields.shift.oct
|
82
77
|
typeflag = fields.shift
|
@@ -114,14 +109,30 @@ class Minitar
|
|
114
109
|
|
115
110
|
private
|
116
111
|
|
117
|
-
def
|
118
|
-
return string.oct if /\A[0-7 ]*\z/.match?(string)
|
119
|
-
|
112
|
+
def parse_numeric_field(string)
|
113
|
+
return string.oct if /\A[0-7 \0]*\z/.match?(string) # \0 appears as a padding
|
114
|
+
return parse_base256(string) if string.bytes.first == 0x80 || string.bytes.first == 0xff
|
115
|
+
raise ArgumentError, "#{string.inspect} is not a valid numeric field"
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_base256(string)
|
119
|
+
# https://www.gnu.org/software/tar/manual/html_node/Extensions.html
|
120
|
+
bytes = string.bytes
|
121
|
+
case bytes.first
|
122
|
+
when 0x80 # Positive number: *non-leading* bytes, number in big-endian order
|
123
|
+
bytes[1..].inject(0) { |r, byte| (r << 8) | byte }
|
124
|
+
when 0xff # Negative number: *all* bytes, two's complement in big-endian order
|
125
|
+
result = bytes.inject(0) { |r, byte| (r << 8) | byte }
|
126
|
+
bit_length = bytes.size * 8
|
127
|
+
result - (1 << bit_length)
|
128
|
+
else
|
129
|
+
raise ArgumentError, "Invalid binary field format"
|
130
|
+
end
|
120
131
|
end
|
121
132
|
end
|
122
133
|
|
123
|
-
# Creates a new PosixHeader. A PosixHeader cannot be created unless
|
124
|
-
# +
|
134
|
+
# Creates a new PosixHeader. A PosixHeader cannot be created unless +name+, +size+,
|
135
|
+
# +prefix+, and +mode+ are provided.
|
125
136
|
def initialize(v)
|
126
137
|
REQUIRED_FIELDS.each do |f|
|
127
138
|
raise ArgumentError, "Field #{f} is required." unless v.key?(f)
|
@@ -138,22 +149,35 @@ class Minitar
|
|
138
149
|
end
|
139
150
|
|
140
151
|
@empty = v[:empty]
|
152
|
+
|
153
|
+
valid_name!(v[:name]) unless v[:empty]
|
141
154
|
end
|
142
155
|
|
143
156
|
# Indicates if the header was an empty header.
|
144
|
-
def empty?
|
145
|
-
@empty
|
146
|
-
end
|
157
|
+
def empty? = @empty
|
147
158
|
|
148
159
|
# Indicates if the header has a valid magic value.
|
149
|
-
def valid?
|
150
|
-
empty? || @magic == MAGIC_BYTES
|
151
|
-
end
|
160
|
+
def valid? = empty? || @magic == MAGIC_BYTES
|
152
161
|
|
153
162
|
# Returns +true+ if the header is a long name special header which indicates
|
154
163
|
# that the next block of data is the filename.
|
155
|
-
def long_name?
|
156
|
-
|
164
|
+
def long_name? = typeflag == "L" && name == GNU_EXT_LONG_LINK
|
165
|
+
|
166
|
+
# Returns +true+ if the header is a PAX extended header which contains
|
167
|
+
# metadata for the next file entry.
|
168
|
+
def pax_header? = typeflag == "x"
|
169
|
+
|
170
|
+
# Sets the +name+ to the +value+ provided and clears +prefix+.
|
171
|
+
#
|
172
|
+
# Used by Minitar::Reader#each_entry to set the long name when processing GNU long
|
173
|
+
# filename extensions.
|
174
|
+
#
|
175
|
+
# The +value+ must be the complete name, including leading directory components.
|
176
|
+
def long_name=(value)
|
177
|
+
valid_name!(value)
|
178
|
+
|
179
|
+
@prefix = ""
|
180
|
+
@name = value
|
157
181
|
end
|
158
182
|
|
159
183
|
# A string representation of the header.
|
@@ -163,6 +187,8 @@ class Minitar
|
|
163
187
|
end
|
164
188
|
alias_method :to_str, :to_s
|
165
189
|
|
190
|
+
# TODO: In Minitar 2, PosixHeader#to_str will be removed.
|
191
|
+
|
166
192
|
# Update the checksum field.
|
167
193
|
def update_checksum
|
168
194
|
hh = header(" " * 8)
|
@@ -171,6 +197,11 @@ class Minitar
|
|
171
197
|
|
172
198
|
private
|
173
199
|
|
200
|
+
def valid_name!(value)
|
201
|
+
return if value.is_a?(String) && !value.empty?
|
202
|
+
raise ArgumentError, "Field name must be a non-empty string"
|
203
|
+
end
|
204
|
+
|
174
205
|
def oct(num, len)
|
175
206
|
if num.nil?
|
176
207
|
"\0" * (len + 1)
|
@@ -180,7 +211,7 @@ class Minitar
|
|
180
211
|
end
|
181
212
|
|
182
213
|
def calculate_checksum(hdr)
|
183
|
-
hdr.unpack("C*").inject
|
214
|
+
hdr.unpack("C*").inject(:+)
|
184
215
|
end
|
185
216
|
|
186
217
|
def header(chksum)
|
@@ -192,7 +223,14 @@ class Minitar
|
|
192
223
|
end
|
193
224
|
|
194
225
|
##
|
195
|
-
# :
|
226
|
+
# :attr_accessor: name
|
227
|
+
# The name of the file. Required.
|
228
|
+
#
|
229
|
+
# By default, limited to 100 bytes, but may be up to BLOCK_SIZE bytes if using the
|
230
|
+
# GNU long name tar extension.
|
231
|
+
|
232
|
+
##
|
233
|
+
# :attr_accessor: size
|
196
234
|
# The size of the file. Required.
|
197
235
|
|
198
236
|
##
|
@@ -243,10 +281,11 @@ class Minitar
|
|
243
281
|
# +5+:: Directory.
|
244
282
|
# +6+:: FIFO node.
|
245
283
|
# +7+:: Reserved.
|
284
|
+
# +L+:: GNU extension for long filenames when #name is <tt>././@LongLink</tt>.
|
246
285
|
|
247
286
|
##
|
248
287
|
# :attr_reader: linkname
|
249
|
-
# The
|
288
|
+
# The target of the symbolic link.
|
250
289
|
|
251
290
|
##
|
252
291
|
# :attr_reader: magic
|
data/lib/minitar/reader.rb
CHANGED
@@ -1,28 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Minitar
|
2
|
-
# The class that reads a tar format archive from a data stream. The data
|
3
|
-
#
|
4
|
-
#
|
4
|
+
# The class that reads a tar format archive from a data stream. The data stream may be
|
5
|
+
# sequential or random access, but certain features only work with random access data
|
6
|
+
# streams.
|
5
7
|
class Reader
|
6
8
|
include Enumerable
|
7
9
|
|
8
|
-
# This marks the EntryStream closed for reading without closing the
|
9
|
-
#
|
10
|
+
# This marks the EntryStream closed for reading without closing the actual data
|
11
|
+
# stream.
|
10
12
|
module InvalidEntryStream
|
11
|
-
def read(*)
|
12
|
-
raise ClosedStream
|
13
|
-
end
|
13
|
+
def read(*) = raise ClosedStream # :nodoc:
|
14
14
|
|
15
|
-
def getc
|
16
|
-
raise ClosedStream
|
17
|
-
end
|
15
|
+
def getc = raise ClosedStream # :nodoc:
|
18
16
|
|
19
|
-
def rewind
|
20
|
-
raise ClosedStream
|
21
|
-
end
|
17
|
+
def rewind = raise ClosedStream # :nodoc:
|
22
18
|
|
23
|
-
def closed?
|
24
|
-
true
|
25
|
-
end
|
19
|
+
def closed? = true # :nodoc:
|
26
20
|
end
|
27
21
|
|
28
22
|
# EntryStreams are pseudo-streams on top of the main data stream.
|
@@ -58,8 +52,8 @@ class Minitar
|
|
58
52
|
end
|
59
53
|
end
|
60
54
|
|
61
|
-
# Reads +len+ bytes (or all remaining data) from the entry. Returns
|
62
|
-
#
|
55
|
+
# Reads +len+ bytes (or all remaining data) from the entry. Returns +nil+ if there
|
56
|
+
# is no more data to read.
|
63
57
|
def read(len = nil)
|
64
58
|
return nil if @read >= @size
|
65
59
|
len ||= @size - @read
|
@@ -69,8 +63,7 @@ class Minitar
|
|
69
63
|
ret
|
70
64
|
end
|
71
65
|
|
72
|
-
# Reads one byte from the entry. Returns +nil+ if there is no more data
|
73
|
-
# to read.
|
66
|
+
# Reads one byte from the entry. Returns +nil+ if there is no more data to read.
|
74
67
|
def getc
|
75
68
|
return nil if @read >= @size
|
76
69
|
ret = @io.getc
|
@@ -84,10 +77,9 @@ class Minitar
|
|
84
77
|
when "5"
|
85
78
|
true
|
86
79
|
when "0", "\0"
|
87
|
-
# If the name ends with a slash, treat it as a directory.
|
88
|
-
#
|
89
|
-
#
|
90
|
-
# and some new ones.
|
80
|
+
# If the name ends with a slash, treat it as a directory. This is what other
|
81
|
+
# major tar implementations do for interoperability and compatibility with older
|
82
|
+
# tar variants and some new ones.
|
91
83
|
@name.end_with?("/")
|
92
84
|
else
|
93
85
|
false
|
@@ -101,16 +93,13 @@ class Minitar
|
|
101
93
|
end
|
102
94
|
alias_method :file, :file?
|
103
95
|
|
104
|
-
# Returns +true+ if the current read pointer is at the end of the
|
105
|
-
|
106
|
-
def eof?
|
107
|
-
@read >= @size
|
108
|
-
end
|
96
|
+
# Returns +true+ if the current read pointer is at the end of the EntryStream data.
|
97
|
+
def eof? = @read >= @size
|
109
98
|
|
110
99
|
# Returns the current read pointer in the EntryStream.
|
111
|
-
def pos
|
112
|
-
|
113
|
-
|
100
|
+
def pos = @read
|
101
|
+
|
102
|
+
alias_method :bytes_read, :pos
|
114
103
|
|
115
104
|
# Sets the current read pointer to the beginning of the EntryStream.
|
116
105
|
def rewind
|
@@ -121,10 +110,6 @@ class Minitar
|
|
121
110
|
@read = 0
|
122
111
|
end
|
123
112
|
|
124
|
-
def bytes_read
|
125
|
-
@read
|
126
|
-
end
|
127
|
-
|
128
113
|
# Returns the full and proper name of the entry.
|
129
114
|
def full_name
|
130
115
|
if @prefix != ""
|
@@ -135,14 +120,10 @@ class Minitar
|
|
135
120
|
end
|
136
121
|
|
137
122
|
# Returns false if the entry stream is valid.
|
138
|
-
def closed?
|
139
|
-
false
|
140
|
-
end
|
123
|
+
def closed? = false
|
141
124
|
|
142
125
|
# Closes the entry.
|
143
|
-
def close
|
144
|
-
invalidate
|
145
|
-
end
|
126
|
+
def close = invalidate
|
146
127
|
|
147
128
|
private
|
148
129
|
|
@@ -151,17 +132,16 @@ class Minitar
|
|
151
132
|
end
|
152
133
|
end
|
153
134
|
|
154
|
-
# With no associated block, +Reader::open+ is a synonym for
|
155
|
-
#
|
156
|
-
# the
|
157
|
-
#
|
158
|
-
# +Reader::open+ returns the value of the block.
|
135
|
+
# With no associated block, +Reader::open+ is a synonym for +Reader::new+. If the
|
136
|
+
# optional code block is given, it will be passed the new _writer_ as an argument and
|
137
|
+
# the Reader object will automatically be closed when the block terminates. In this
|
138
|
+
# instance, +Reader::open+ returns the value of the block.
|
159
139
|
def self.open(io)
|
160
140
|
reader = new(io)
|
161
141
|
return reader unless block_given?
|
162
142
|
|
163
|
-
# This exception context must remain, otherwise the stream closes on open
|
164
|
-
#
|
143
|
+
# This exception context must remain, otherwise the stream closes on open even if
|
144
|
+
# a block is not given.
|
165
145
|
begin
|
166
146
|
yield reader
|
167
147
|
ensure
|
@@ -169,8 +149,7 @@ class Minitar
|
|
169
149
|
end
|
170
150
|
end
|
171
151
|
|
172
|
-
# Iterates over each entry in the provided input. This wraps the common
|
173
|
-
# pattern of:
|
152
|
+
# Iterates over each entry in the provided input. This wraps the common pattern of:
|
174
153
|
#
|
175
154
|
# Minitar::Input.open(io) do |i|
|
176
155
|
# inp.each do |entry|
|
@@ -178,10 +157,9 @@ class Minitar
|
|
178
157
|
# end
|
179
158
|
# end
|
180
159
|
#
|
181
|
-
# If a block is not provided, an enumerator will be created with the same
|
182
|
-
# behaviour.
|
160
|
+
# If a block is not provided, an enumerator will be created with the same behaviour.
|
183
161
|
#
|
184
|
-
# call-seq:
|
162
|
+
# :call-seq:
|
185
163
|
# Minitar::Reader.each_entry(io) -> enumerator
|
186
164
|
# Minitar::Reader.each_entry(io) { |entry| block } -> obj
|
187
165
|
def self.each_entry(io)
|
@@ -204,19 +182,15 @@ class Minitar
|
|
204
182
|
end
|
205
183
|
end
|
206
184
|
|
207
|
-
# Resets the read pointer to the beginning of data stream. Do not call
|
208
|
-
#
|
209
|
-
#
|
185
|
+
# Resets the read pointer to the beginning of data stream. Do not call this during
|
186
|
+
# a #each or #each_entry iteration. This only works with random access data streams
|
187
|
+
# that respond to #rewind and #pos.
|
210
188
|
def rewind
|
211
189
|
if @init_pos.zero?
|
212
|
-
unless Minitar.seekable?(@io, :rewind)
|
213
|
-
raise Minitar::NonSeekableStream
|
214
|
-
end
|
190
|
+
raise Minitar::NonSeekableStream unless Minitar.seekable?(@io, :rewind)
|
215
191
|
@io.rewind
|
216
192
|
else
|
217
|
-
unless Minitar.seekable?(@io, :pos=)
|
218
|
-
raise Minitar::NonSeekableStream
|
219
|
-
end
|
193
|
+
raise Minitar::NonSeekableStream unless Minitar.seekable?(@io, :pos=)
|
220
194
|
@io.pos = @init_pos
|
221
195
|
end
|
222
196
|
end
|
@@ -237,11 +211,18 @@ class Minitar
|
|
237
211
|
if header.long_name?
|
238
212
|
name_block = (header.size / 512.0).ceil * 512
|
239
213
|
|
240
|
-
|
214
|
+
long_name = @io.read(name_block).rstrip
|
241
215
|
header = PosixHeader.from_stream(@io)
|
242
216
|
|
243
217
|
return if header.empty?
|
244
|
-
header.
|
218
|
+
header.long_name = long_name
|
219
|
+
elsif header.pax_header?
|
220
|
+
pax_header = PaxHeader.from_stream(@io, header)
|
221
|
+
|
222
|
+
header = PosixHeader.from_stream(@io)
|
223
|
+
return if header.empty?
|
224
|
+
|
225
|
+
header.size = pax_header.size if pax_header.size
|
245
226
|
end
|
246
227
|
|
247
228
|
entry = EntryStream.new(header, @io)
|
@@ -253,7 +234,7 @@ class Minitar
|
|
253
234
|
|
254
235
|
if Minitar.seekable?(@io, :seek)
|
255
236
|
# avoid reading...
|
256
|
-
|
237
|
+
try_seek(size - entry.bytes_read)
|
257
238
|
else
|
258
239
|
pending = size - entry.bytes_read
|
259
240
|
while pending > 0
|
@@ -271,11 +252,25 @@ class Minitar
|
|
271
252
|
alias_method :each, :each_entry
|
272
253
|
|
273
254
|
# Returns false if the reader is open (it never closes).
|
274
|
-
def closed?
|
275
|
-
false
|
276
|
-
end
|
255
|
+
def closed? = false
|
277
256
|
|
278
257
|
def close
|
279
258
|
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def try_seek(bytes)
|
263
|
+
@io.seek(bytes, IO::SEEK_CUR)
|
264
|
+
rescue RangeError
|
265
|
+
# This happens when skipping the large entry and the skipping entry size exceeds
|
266
|
+
# maximum allowed size (varies by platform and underlying IO object).
|
267
|
+
max = RbConfig::LIMITS.fetch("INT_MAX", 2147483647)
|
268
|
+
skipped = 0
|
269
|
+
while skipped < bytes
|
270
|
+
to_skip = [bytes - skipped, max].min
|
271
|
+
@io.seek(to_skip, IO::SEEK_CUR)
|
272
|
+
skipped += to_skip
|
273
|
+
end
|
274
|
+
end
|
280
275
|
end
|
281
276
|
end
|