minitar 0.8 → 0.12

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.
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'archive/tar/minitar/reader'
3
+ require "archive/tar/minitar/reader"
4
4
 
5
5
  module Archive::Tar::Minitar
6
6
  # Wraps a Archive::Tar::Minitar::Reader with convenience methods and wrapped
@@ -19,14 +19,17 @@ module Archive::Tar::Minitar
19
19
  # Archive::Tar::Minitar::Input.open(io) { |input| block } -> obj
20
20
  def self.open(input)
21
21
  stream = new(input)
22
- return stream unless block_given?
23
-
24
- # This exception context must remain, otherwise the stream closes on open
25
- # even if a block is not given.
26
- begin
27
- yield stream
28
- ensure
29
- stream.close
22
+
23
+ if block_given?
24
+ # This exception context must remain, otherwise the stream closes on
25
+ # open even if a block is not given.
26
+ begin
27
+ yield stream
28
+ ensure
29
+ stream.close
30
+ end
31
+ else
32
+ stream
30
33
  end
31
34
  end
32
35
 
@@ -48,7 +51,7 @@ module Archive::Tar::Minitar
48
51
  def self.each_entry(input)
49
52
  return to_enum(__method__, input) unless block_given?
50
53
 
51
- open(input) do |stream|
54
+ Input.open(input) do |stream|
52
55
  stream.each do |entry|
53
56
  yield entry
54
57
  end
@@ -68,10 +71,10 @@ module Archive::Tar::Minitar
68
71
  # Archive::Tar::Minitar::Input.new(path) -> input
69
72
  def initialize(input)
70
73
  @io = if input.respond_to?(:read)
71
- input
72
- else
73
- ::Kernel.open(input, 'rb')
74
- end
74
+ input
75
+ else
76
+ ::Kernel.open(input, "rb")
77
+ end
75
78
 
76
79
  unless Archive::Tar::Minitar.seekable?(@io, :rewind)
77
80
  raise Archive::Tar::Minitar::NonSeekableStream
@@ -93,7 +96,7 @@ module Archive::Tar::Minitar
93
96
  ensure
94
97
  @tar.rewind
95
98
  end
96
- alias each each_entry
99
+ alias_method :each, :each_entry
97
100
 
98
101
  # Extracts the current +entry+ to +destdir+. If a block is provided, it
99
102
  # yields an +action+ Symbol, the full name of the file being extracted
@@ -114,11 +117,11 @@ module Archive::Tar::Minitar
114
117
  # cycle.
115
118
  # <tt>:entry</tt>:: The entry being extracted; this is a
116
119
  # Reader::EntryStream, with all methods thereof.
117
- def extract_entry(destdir, entry) # :yields action, name, stats:
120
+ def extract_entry(destdir, entry, options = {}, &block) # :yields action, name, stats:
118
121
  stats = {
119
- :current => 0,
120
- :currinc => 0,
121
- :entry => entry
122
+ :current => 0,
123
+ :currinc => 0,
124
+ :entry => entry
122
125
  }
123
126
 
124
127
  # extract_entry is not vulnerable to prefix '/' vulnerabilities, but it
@@ -130,66 +133,16 @@ module Archive::Tar::Minitar
130
133
  # leave +destdir+.
131
134
  #
132
135
  # However, squeeze consecutive '/' characters together.
133
- full_name = entry.full_name.squeeze('/')
136
+ full_name = entry.full_name.squeeze("/")
134
137
 
135
- if full_name =~ /\.{2}(?:\/|\z)/
136
- raise SecureRelativePathError, %q(Path contains '..')
138
+ if /\.{2}(?:\/|\z)/.match?(full_name)
139
+ raise SecureRelativePathError, "Path contains '..'"
137
140
  end
138
141
 
139
142
  if entry.directory?
140
- dest = File.join(destdir, full_name)
141
-
142
- yield :dir, full_name, stats if block_given?
143
-
144
- if Archive::Tar::Minitar.dir?(dest)
145
- begin
146
- FileUtils.chmod(entry.mode, dest)
147
- rescue
148
- nil
149
- end
150
- else
151
- File.unlink(dest.chomp('/')) if File.symlink?(dest.chomp('/'))
152
-
153
- FileUtils.mkdir_p(dest, :mode => entry.mode)
154
- FileUtils.chmod(entry.mode, dest)
155
- end
156
-
157
- fsync_dir(dest)
158
- fsync_dir(File.join(dest, '..'))
159
- return
143
+ extract_directory(destdir, full_name, entry, stats, options, &block)
160
144
  else # it's a file
161
- destdir = File.join(destdir, File.dirname(full_name))
162
- FileUtils.mkdir_p(destdir, :mode => 0o755)
163
-
164
- destfile = File.join(destdir, File.basename(full_name))
165
-
166
- File.unlink(destfile) if File.symlink?(destfile)
167
-
168
- # Errno::ENOENT
169
- # rubocop:disable Style/RescueModifier
170
- FileUtils.chmod(0o600, destfile) rescue nil
171
- # rubocop:enable Style/RescueModifier
172
-
173
- yield :file_start, full_name, stats if block_given?
174
-
175
- File.open(destfile, 'wb', entry.mode) do |os|
176
- loop do
177
- data = entry.read(4096)
178
- break unless data
179
-
180
- stats[:currinc] = os.write(data)
181
- stats[:current] += stats[:currinc]
182
-
183
- yield :file_progress, full_name, stats if block_given?
184
- end
185
- os.fsync
186
- end
187
-
188
- FileUtils.chmod(entry.mode, destfile)
189
- fsync_dir(File.dirname(destfile))
190
- fsync_dir(File.join(File.dirname(destfile), '..'))
191
-
192
- yield :file_done, full_name, stats if block_given?
145
+ extract_file(destdir, full_name, entry, stats, options, &block)
193
146
  end
194
147
  end
195
148
 
@@ -211,12 +164,80 @@ module Archive::Tar::Minitar
211
164
 
212
165
  def fsync_dir(dirname)
213
166
  # make sure this hits the disc
214
- dir = open(dirname, 'rb')
167
+ dir = IO.open(dirname, "rb")
215
168
  dir.fsync
216
169
  rescue # ignore IOError if it's an unpatched (old) Ruby
217
170
  nil
218
171
  ensure
219
172
  dir.close if dir rescue nil # rubocop:disable Style/RescueModifier
220
173
  end
174
+
175
+ def extract_directory(destdir, full_name, entry, stats, options)
176
+ dest = File.join(destdir, full_name)
177
+
178
+ yield :dir, full_name, stats if block_given?
179
+
180
+ if Archive::Tar::Minitar.dir?(dest)
181
+ begin
182
+ FileUtils.chmod(entry.mode, dest)
183
+ rescue
184
+ nil
185
+ end
186
+ else
187
+ File.unlink(dest.chomp("/")) if File.symlink?(dest.chomp("/"))
188
+
189
+ FileUtils.mkdir_p(dest, :mode => entry.mode)
190
+ FileUtils.chmod(entry.mode, dest)
191
+ end
192
+
193
+ if options.fetch(:fsync, true)
194
+ fsync_dir(dest)
195
+ fsync_dir(File.join(dest, ".."))
196
+ end
197
+ end
198
+
199
+ def extract_file(destdir, full_name, entry, stats, options)
200
+ destdir = File.join(destdir, File.dirname(full_name))
201
+ FileUtils.mkdir_p(destdir, :mode => 0o755)
202
+
203
+ destfile = File.join(destdir, File.basename(full_name))
204
+
205
+ File.unlink(destfile) if File.symlink?(destfile)
206
+
207
+ # Errno::ENOENT
208
+ # rubocop:disable Style/RescueModifier
209
+ FileUtils.chmod(0o600, destfile) rescue nil
210
+ # rubocop:enable Style/RescueModifier
211
+
212
+ yield :file_start, full_name, stats if block_given?
213
+
214
+ File.open(destfile, "wb", entry.mode) do |os|
215
+ loop do
216
+ data = entry.read(4096)
217
+ break unless data
218
+
219
+ stats[:currinc] = os.write(data)
220
+ stats[:current] += stats[:currinc]
221
+
222
+ yield :file_progress, full_name, stats if block_given?
223
+ end
224
+
225
+ if options.fetch(:fsync, true)
226
+ yield :file_fsync, full_name, stats if block_given?
227
+ os.fsync
228
+ end
229
+ end
230
+
231
+ FileUtils.chmod(entry.mode, destfile)
232
+
233
+ if options.fetch(:fsync, true)
234
+ yield :dir_fsync, full_name, stats if block_given?
235
+
236
+ fsync_dir(File.dirname(destfile))
237
+ fsync_dir(File.join(File.dirname(destfile), ".."))
238
+ end
239
+
240
+ yield :file_done, full_name, stats if block_given?
241
+ end
221
242
  end
222
243
  end
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'archive/tar/minitar/writer'
3
+ require "archive/tar/minitar/writer"
4
4
 
5
5
  module Archive::Tar::Minitar
6
6
  # Wraps a Archive::Tar::Minitar::Writer with convenience methods and wrapped
@@ -40,7 +40,7 @@ module Archive::Tar::Minitar
40
40
  def self.tar(output)
41
41
  return to_enum(__method__, output) unless block_given?
42
42
 
43
- open(output) do |stream|
43
+ Output.open(output) do |stream|
44
44
  yield stream.tar
45
45
  end
46
46
  end
@@ -55,10 +55,10 @@ module Archive::Tar::Minitar
55
55
  # Archive::Tar::Minitar::Output.new(path) -> output
56
56
  def initialize(output)
57
57
  @io = if output.respond_to?(:write)
58
- output
59
- else
60
- ::Kernel.open(output, 'wb')
61
- end
58
+ output
59
+ else
60
+ ::Kernel.open(output, "wb")
61
+ end
62
62
  @tar = Archive::Tar::Minitar::Writer.new(@io)
63
63
  end
64
64
 
@@ -2,8 +2,10 @@
2
2
 
3
3
  ##
4
4
  module Archive; end
5
+
5
6
  ##
6
7
  module Archive::Tar; end
8
+
7
9
  ##
8
10
  module Archive::Tar::Minitar; end
9
11
 
@@ -36,12 +38,12 @@ module Archive::Tar::Minitar; end
36
38
  # unrecognized typeflag value as a regular file."
37
39
  class Archive::Tar::Minitar::PosixHeader
38
40
  BLOCK_SIZE = 512
39
- MAGIC_BYTES = 'ustar'.freeze
41
+ MAGIC_BYTES = "ustar".freeze
40
42
 
41
- GNU_EXT_LONG_LINK = '././@LongLink'
43
+ GNU_EXT_LONG_LINK = "././@LongLink"
42
44
 
43
45
  # Fields that must be set in a POSIX tar(1) header.
44
- REQUIRED_FIELDS = [ :name, :size, :prefix, :mode ].freeze
46
+ REQUIRED_FIELDS = [:name, :size, :prefix, :mode].freeze
45
47
  # Fields that may be set in a POSIX tar(1) header.
46
48
  OPTIONAL_FIELDS = [
47
49
  :uid, :gid, :mtime, :checksum, :typeflag, :linkname, :magic, :version,
@@ -60,9 +62,9 @@ class Archive::Tar::Minitar::PosixHeader
60
62
  attr_accessor :name
61
63
 
62
64
  # The pack format passed to Array#pack for encoding a header.
63
- HEADER_PACK_FORMAT = 'a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155'.freeze
65
+ HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155".freeze
64
66
  # The unpack format passed to String#unpack for decoding a header.
65
- HEADER_UNPACK_FORMAT = 'Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155'.freeze
67
+ HEADER_UNPACK_FORMAT = "Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155".freeze
66
68
 
67
69
  class << self
68
70
  # Creates a new PosixHeader from a data stream.
@@ -79,23 +81,23 @@ class Archive::Tar::Minitar::PosixHeader
79
81
 
80
82
  # Creates a new PosixHeader from a BLOCK_SIZE-byte data buffer.
81
83
  def from_data(data)
82
- fields = data.unpack(HEADER_UNPACK_FORMAT)
83
- name = fields.shift
84
- mode = fields.shift.oct
85
- uid = fields.shift.oct
86
- gid = fields.shift.oct
87
- size = strict_oct(fields.shift)
88
- mtime = fields.shift.oct
89
- checksum = fields.shift.oct
90
- typeflag = fields.shift
91
- linkname = fields.shift
92
- magic = fields.shift
93
- version = fields.shift.oct
94
- uname = fields.shift
95
- gname = fields.shift
96
- devmajor = fields.shift.oct
97
- devminor = fields.shift.oct
98
- prefix = fields.shift
84
+ fields = data.unpack(HEADER_UNPACK_FORMAT)
85
+ name = fields.shift
86
+ mode = fields.shift.oct
87
+ uid = fields.shift.oct
88
+ gid = fields.shift.oct
89
+ size = strict_oct(fields.shift)
90
+ mtime = fields.shift.oct
91
+ checksum = fields.shift.oct
92
+ typeflag = fields.shift
93
+ linkname = fields.shift
94
+ magic = fields.shift
95
+ version = fields.shift.oct
96
+ uname = fields.shift
97
+ gname = fields.shift
98
+ devmajor = fields.shift.oct
99
+ devminor = fields.shift.oct
100
+ prefix = fields.shift
99
101
 
100
102
  empty = !data.each_byte.any?(&:nonzero?)
101
103
 
@@ -123,7 +125,7 @@ class Archive::Tar::Minitar::PosixHeader
123
125
  private
124
126
 
125
127
  def strict_oct(string)
126
- return string.oct if string =~ /\A[0-7 ]*\z/
128
+ return string.oct if /\A[0-7 ]*\z/.match?(string)
127
129
  raise ArgumentError, "#{string.inspect} is not a valid octal string"
128
130
  end
129
131
  end
@@ -136,13 +138,13 @@ class Archive::Tar::Minitar::PosixHeader
136
138
  end
137
139
 
138
140
  v[:mtime] = v[:mtime].to_i
139
- v[:checksum] ||= ''
140
- v[:typeflag] ||= '0'
141
- v[:magic] ||= MAGIC_BYTES
142
- v[:version] ||= '00'
141
+ v[:checksum] ||= ""
142
+ v[:typeflag] ||= "0"
143
+ v[:magic] ||= MAGIC_BYTES
144
+ v[:version] ||= "00"
143
145
 
144
146
  FIELDS.each do |f|
145
- instance_variable_set("@#{f}", v[f])
147
+ instance_variable_set(:"@#{f}", v[f])
146
148
  end
147
149
 
148
150
  @empty = v[:empty]
@@ -161,7 +163,7 @@ class Archive::Tar::Minitar::PosixHeader
161
163
  # Returns +true+ if the header is a long name special header which indicates
162
164
  # that the next block of data is the filename.
163
165
  def long_name?
164
- typeflag == 'L' && name == GNU_EXT_LONG_LINK
166
+ typeflag == "L" && name == GNU_EXT_LONG_LINK
165
167
  end
166
168
 
167
169
  # A string representation of the header.
@@ -169,11 +171,11 @@ class Archive::Tar::Minitar::PosixHeader
169
171
  update_checksum
170
172
  header(@checksum)
171
173
  end
172
- alias to_str to_s
174
+ alias_method :to_str, :to_s
173
175
 
174
176
  # Update the checksum field.
175
177
  def update_checksum
176
- hh = header(' ' * 8)
178
+ hh = header(" " * 8)
177
179
  @checksum = oct(calculate_checksum(hh), 6)
178
180
  end
179
181
 
@@ -190,13 +192,13 @@ class Archive::Tar::Minitar::PosixHeader
190
192
  end
191
193
 
192
194
  def calculate_checksum(hdr)
193
- hdr.unpack('C*').inject { |a, e| a + e }
195
+ hdr.unpack("C*").inject { |a, e| a + e }
194
196
  end
195
197
 
196
198
  def header(chksum)
197
199
  arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11),
198
- oct(mtime, 11), chksum, ' ', typeflag, linkname, magic, version,
199
- uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix]
200
+ oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version,
201
+ uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix]
200
202
  str = arr.pack(HEADER_PACK_FORMAT)
201
203
  str + "\0" * ((BLOCK_SIZE - bytesize(str)) % BLOCK_SIZE)
202
204
  end
@@ -11,14 +11,21 @@ module Archive::Tar::Minitar
11
11
  # This marks the EntryStream closed for reading without closing the
12
12
  # actual data stream.
13
13
  module InvalidEntryStream
14
- # rubocop:disable Style/SingleLineMethods
15
- # rubocop:disable Style/EmptyLineBetweenDefs
16
- def read(*); raise ClosedStream; end
17
- def getc; raise ClosedStream; end
18
- def rewind; raise ClosedStream; end
19
- def closed?; true; end
20
- # rubocop:enable Style/EmptyLineBetweenDefs
21
- # rubocop:enable Style/SingleLineMethods Style/EmptyLineBetweenDefs
14
+ def read(*)
15
+ raise ClosedStream
16
+ end
17
+
18
+ def getc
19
+ raise ClosedStream
20
+ end
21
+
22
+ def rewind
23
+ raise ClosedStream
24
+ end
25
+
26
+ def closed?
27
+ true
28
+ end
22
29
  end
23
30
 
24
31
  # EntryStreams are pseudo-streams on top of the main data stream.
@@ -30,24 +37,24 @@ module Archive::Tar::Minitar
30
37
  end
31
38
 
32
39
  def initialize(header, io)
33
- @io = io
34
- @name = header.name
35
- @mode = header.mode
36
- @uid = header.uid
37
- @gid = header.gid
38
- @size = header.size
39
- @mtime = header.mtime
40
+ @io = io
41
+ @name = header.name
42
+ @mode = header.mode
43
+ @uid = header.uid
44
+ @gid = header.gid
45
+ @size = header.size
46
+ @mtime = header.mtime
40
47
  @checksum = header.checksum
41
48
  @typeflag = header.typeflag
42
49
  @linkname = header.linkname
43
- @magic = header.magic
44
- @version = header.version
45
- @uname = header.uname
46
- @gname = header.gname
50
+ @magic = header.magic
51
+ @version = header.version
52
+ @uname = header.uname
53
+ @gname = header.gname
47
54
  @devmajor = header.devmajor
48
55
  @devminor = header.devminor
49
- @prefix = header.prefix
50
- @read = 0
56
+ @prefix = header.prefix
57
+ @read = 0
51
58
  @orig_pos =
52
59
  if Archive::Tar::Minitar.seekable?(@io)
53
60
  @io.pos
@@ -79,25 +86,25 @@ module Archive::Tar::Minitar
79
86
  # Returns +true+ if the entry represents a directory.
80
87
  def directory?
81
88
  case @typeflag
82
- when '5'
89
+ when "5"
83
90
  true
84
- when '0', "\0"
91
+ when "0", "\0"
85
92
  # If the name ends with a slash, treat it as a directory.
86
93
  # This is what other major tar implementations do for
87
94
  # interoperability and compatibility with older tar variants
88
95
  # and some new ones.
89
- @name.end_with?('/')
96
+ @name.end_with?("/")
90
97
  else
91
98
  false
92
99
  end
93
100
  end
94
- alias directory directory?
101
+ alias_method :directory, :directory?
95
102
 
96
103
  # Returns +true+ if the entry represents a plain file.
97
104
  def file?
98
- (@typeflag == '0' || @typeflag == "\0") && !@name.end_with?('/')
105
+ (@typeflag == "0" || @typeflag == "\0") && !@name.end_with?("/")
99
106
  end
100
- alias file file?
107
+ alias_method :file, :file?
101
108
 
102
109
  # Returns +true+ if the current read pointer is at the end of the
103
110
  # EntryStream data.
@@ -125,7 +132,7 @@ module Archive::Tar::Minitar
125
132
 
126
133
  # Returns the full and proper name of the entry.
127
134
  def full_name
128
- if @prefix != ''
135
+ if @prefix != ""
129
136
  File.join(@prefix, @name)
130
137
  else
131
138
  @name
@@ -185,7 +192,7 @@ module Archive::Tar::Minitar
185
192
  def self.each_entry(io)
186
193
  return to_enum(__method__, io) unless block_given?
187
194
 
188
- open(io) do |reader|
195
+ Input.open(io) do |reader|
189
196
  reader.each_entry do |entry|
190
197
  yield entry
191
198
  end
@@ -195,7 +202,11 @@ module Archive::Tar::Minitar
195
202
  # Creates and returns a new Reader object.
196
203
  def initialize(io)
197
204
  @io = io
198
- @init_pos = io.pos rescue nil
205
+ @init_pos = begin
206
+ io.pos
207
+ rescue
208
+ nil
209
+ end
199
210
  end
200
211
 
201
212
  # Resets the read pointer to the beginning of data stream. Do not call
@@ -229,14 +240,17 @@ module Archive::Tar::Minitar
229
240
  raise Archive::Tar::Minitar::InvalidTarStream if header.size < 0
230
241
 
231
242
  if header.long_name?
232
- name = @io.read(512).rstrip
243
+ name_block = (header.size / 512.0).ceil * 512
244
+
245
+ name = @io.read(name_block).rstrip
233
246
  header = PosixHeader.from_stream(@io)
247
+
234
248
  return if header.empty?
235
249
  header.name = name
236
250
  end
237
251
 
238
252
  entry = EntryStream.new(header, @io)
239
- size = entry.size
253
+ size = entry.size
240
254
 
241
255
  yield entry
242
256
 
@@ -259,7 +273,7 @@ module Archive::Tar::Minitar
259
273
  entry.close
260
274
  end
261
275
  end
262
- alias each each_entry
276
+ alias_method :each, :each_entry
263
277
 
264
278
  # Returns false if the reader is open (it never closes).
265
279
  def closed?