rubyzip 2.0.0 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/zip/entry_set.rb CHANGED
@@ -50,6 +50,7 @@ module Zip
50
50
 
51
51
  def ==(other)
52
52
  return false unless other.kind_of?(EntrySet)
53
+
53
54
  @entry_set.values == other.entry_set.values
54
55
  end
55
56
 
@@ -60,6 +61,7 @@ module Zip
60
61
  def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
61
62
  entries.map do |entry|
62
63
  next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
64
+
63
65
  yield(entry) if block_given?
64
66
  entry
65
67
  end.compact
data/lib/zip/errors.rb CHANGED
@@ -7,6 +7,7 @@ module Zip
7
7
  class EntrySizeError < Error; end
8
8
  class InternalError < Error; end
9
9
  class GPFBit3Error < Error; end
10
+ class DecompressionError < Error; end
10
11
 
11
12
  # Backwards compatibility with v1 (delete in v2)
12
13
  ZipError = Error
@@ -6,27 +6,27 @@ module Zip
6
6
  merge(binstr) if binstr
7
7
  end
8
8
 
9
- def extra_field_type_exist(binstr, id, len, i)
9
+ def extra_field_type_exist(binstr, id, len, index)
10
10
  field_name = ID_MAP[id].name
11
11
  if member?(field_name)
12
- self[field_name].merge(binstr[i, len + 4])
12
+ self[field_name].merge(binstr[index, len + 4])
13
13
  else
14
- field_obj = ID_MAP[id].new(binstr[i, len + 4])
14
+ field_obj = ID_MAP[id].new(binstr[index, len + 4])
15
15
  self[field_name] = field_obj
16
16
  end
17
17
  end
18
18
 
19
- def extra_field_type_unknown(binstr, len, i)
19
+ def extra_field_type_unknown(binstr, len, index)
20
20
  create_unknown_item unless self['Unknown']
21
- if !len || len + 4 > binstr[i..-1].bytesize
22
- self['Unknown'] << binstr[i..-1]
21
+ if !len || len + 4 > binstr[index..-1].bytesize
22
+ self['Unknown'] << binstr[index..-1]
23
23
  return
24
24
  end
25
- self['Unknown'] << binstr[i, len + 4]
25
+ self['Unknown'] << binstr[index, len + 4]
26
26
  end
27
27
 
28
28
  def create_unknown_item
29
- s = ''.dup
29
+ s = +''
30
30
  class << s
31
31
  alias_method :to_c_dir_bin, :to_s
32
32
  alias_method :to_local_bin, :to_s
@@ -36,10 +36,11 @@ module Zip
36
36
 
37
37
  def merge(binstr)
38
38
  return if binstr.empty?
39
+
39
40
  i = 0
40
41
  while i < binstr.bytesize
41
42
  id = binstr[i, 2]
42
- len = binstr[i + 2, 2].to_s.unpack('v').first
43
+ len = binstr[i + 2, 2].to_s.unpack1('v')
43
44
  if id && ID_MAP.member?(id)
44
45
  extra_field_type_exist(binstr, id, len, i)
45
46
  elsif id
@@ -54,6 +55,7 @@ module Zip
54
55
  unless (field_class = ID_MAP.values.find { |k| k.name == name })
55
56
  raise Error, "Unknown extra field '#{name}'"
56
57
  end
58
+
57
59
  self[name] = field_class.new
58
60
  end
59
61
 
@@ -1,9 +1,9 @@
1
1
  module Zip
2
2
  class ExtraField::Generic
3
3
  def self.register_map
4
- if const_defined?(:HEADER_ID)
5
- ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
6
- end
4
+ return unless const_defined?(:HEADER_ID)
5
+
6
+ ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
7
7
  end
8
8
 
9
9
  def self.name
@@ -12,18 +12,19 @@ module Zip
12
12
 
13
13
  # return field [size, content] or false
14
14
  def initial_parse(binstr)
15
- if !binstr
16
- # If nil, start with empty.
17
- return false
18
- elsif binstr[0, 2] != self.class.const_get(:HEADER_ID)
19
- $stderr.puts 'Warning: weired extra feild header ID. skip parsing'
15
+ return false unless binstr
16
+
17
+ if binstr[0, 2] != self.class.const_get(:HEADER_ID)
18
+ warn 'WARNING: weird extra field header ID. Skip parsing it.'
20
19
  return false
21
20
  end
22
- [binstr[2, 2].unpack('v')[0], binstr[4..-1]]
21
+
22
+ [binstr[2, 2].unpack1('v'), binstr[4..-1]]
23
23
  end
24
24
 
25
25
  def ==(other)
26
26
  return false if self.class != other.class
27
+
27
28
  each do |k, v|
28
29
  return false if v != other[k]
29
30
  end
@@ -19,6 +19,7 @@ module Zip
19
19
 
20
20
  def merge(binstr)
21
21
  return if binstr.empty?
22
+
22
23
  size, content = initial_parse(binstr)
23
24
  (size && content) || return
24
25
 
@@ -27,6 +28,7 @@ module Zip
27
28
 
28
29
  tag1 = tags[1]
29
30
  return unless tag1
31
+
30
32
  ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
31
33
  ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
32
34
  ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
@@ -65,12 +67,14 @@ module Zip
65
67
 
66
68
  def parse_tags(content)
67
69
  return {} if content.nil?
70
+
68
71
  tags = {}
69
72
  i = 0
70
73
  while i < content.bytesize
71
74
  tag, size = content[i, 4].unpack('vv')
72
75
  i += 4
73
76
  break unless tag && size
77
+
74
78
  value = content[i, size]
75
79
  i += size
76
80
  tags[tag] = value
@@ -16,14 +16,16 @@ module Zip
16
16
 
17
17
  def merge(binstr)
18
18
  return if binstr.empty?
19
+
19
20
  size, content = initial_parse(binstr)
20
21
  # size: 0 for central directory. 4 for local header
21
22
  return if !size || size == 0
23
+
22
24
  atime, mtime, uid, gid = content.unpack('VVvv')
23
25
  @uid ||= uid
24
26
  @gid ||= gid
25
27
  @atime ||= atime
26
- @mtime ||= mtime
28
+ @mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName
27
29
  end
28
30
 
29
31
  def ==(other)
@@ -4,24 +4,54 @@ module Zip
4
4
  HEADER_ID = 'UT'
5
5
  register_map
6
6
 
7
+ ATIME_MASK = 0b010
8
+ CTIME_MASK = 0b100
9
+ MTIME_MASK = 0b001
10
+
7
11
  def initialize(binstr = nil)
8
12
  @ctime = nil
9
13
  @mtime = nil
10
14
  @atime = nil
11
- @flag = nil
12
- binstr && merge(binstr)
15
+ @flag = 0
16
+
17
+ merge(binstr) unless binstr.nil?
18
+ end
19
+
20
+ attr_reader :atime, :ctime, :mtime, :flag
21
+
22
+ def atime=(time)
23
+ @flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK
24
+ @atime = time
25
+ end
26
+
27
+ def ctime=(time)
28
+ @flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
29
+ @ctime = time
13
30
  end
14
31
 
15
- attr_accessor :atime, :ctime, :mtime, :flag
32
+ def mtime=(time)
33
+ @flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK
34
+ @mtime = time
35
+ end
16
36
 
17
37
  def merge(binstr)
18
38
  return if binstr.empty?
39
+
19
40
  size, content = initial_parse(binstr)
20
- size || return
21
- @flag, mtime, atime, ctime = content.unpack('CVVV')
22
- mtime && @mtime ||= ::Zip::DOSTime.at(mtime)
23
- atime && @atime ||= ::Zip::DOSTime.at(atime)
24
- ctime && @ctime ||= ::Zip::DOSTime.at(ctime)
41
+ return if !size || size <= 0
42
+
43
+ @flag, *times = content.unpack('Cl<l<l<')
44
+
45
+ # Parse the timestamps, in order, based on which flags are set.
46
+ return if times[0].nil?
47
+
48
+ @mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
49
+ return if times[0].nil?
50
+
51
+ @atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
52
+ return if times[0].nil?
53
+
54
+ @ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
25
55
  end
26
56
 
27
57
  def ==(other)
@@ -32,15 +62,15 @@ module Zip
32
62
 
33
63
  def pack_for_local
34
64
  s = [@flag].pack('C')
35
- @flag & 1 != 0 && s << [@mtime.to_i].pack('V')
36
- @flag & 2 != 0 && s << [@atime.to_i].pack('V')
37
- @flag & 4 != 0 && s << [@ctime.to_i].pack('V')
65
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
66
+ s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
67
+ s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
38
68
  s
39
69
  end
40
70
 
41
71
  def pack_for_c_dir
42
72
  s = [@flag].pack('C')
43
- @flag & 1 == 1 && s << [@mtime.to_i].pack('V')
73
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
44
74
  s
45
75
  end
46
76
  end
@@ -14,12 +14,14 @@ module Zip
14
14
 
15
15
  def merge(binstr)
16
16
  return if binstr.empty?
17
+
17
18
  size, content = initial_parse(binstr)
18
19
  # size: 0 for central directory. 4 for local header
19
20
  return if !size || size == 0
21
+
20
22
  uid, gid = content.unpack('vv')
21
23
  @uid ||= uid
22
- @gid ||= gid
24
+ @gid ||= gid # rubocop:disable Naming/MemoizedInstanceVariableName
23
25
  end
24
26
 
25
27
  def ==(other)
@@ -9,7 +9,7 @@ module Zip
9
9
  # unparsed binary; we don't actually know what this contains
10
10
  # without looking for FFs in the associated file header
11
11
  # call parse after initializing with a binary string
12
- @content = nil
12
+ @content = nil
13
13
  @original_size = nil
14
14
  @compressed_size = nil
15
15
  @relative_header_offset = nil
@@ -26,6 +26,7 @@ module Zip
26
26
 
27
27
  def merge(binstr)
28
28
  return if binstr.empty?
29
+
29
30
  _, @content = initial_parse(binstr)
30
31
  end
31
32
 
@@ -45,13 +46,14 @@ module Zip
45
46
  end
46
47
 
47
48
  def extract(size, format)
48
- @content.slice!(0, size).unpack(format)[0]
49
+ @content.slice!(0, size).unpack1(format)
49
50
  end
50
51
  private :extract
51
52
 
52
53
  def pack_for_local
53
54
  # local header entries must contain original size and compressed size; other fields do not apply
54
55
  return '' unless @original_size && @compressed_size
56
+
55
57
  [@original_size, @compressed_size].pack('Q<Q<')
56
58
  end
57
59
 
data/lib/zip/file.rb CHANGED
@@ -49,16 +49,25 @@ module Zip
49
49
  MAX_SEGMENT_SIZE = 3_221_225_472
50
50
  MIN_SEGMENT_SIZE = 65_536
51
51
  DATA_BUFFER_SIZE = 8192
52
- IO_METHODS = [:tell, :seek, :read, :close]
52
+ IO_METHODS = [:tell, :seek, :read, :eof, :close]
53
+
54
+ DEFAULT_OPTIONS = {
55
+ restore_ownership: false,
56
+ restore_permissions: false,
57
+ restore_times: false
58
+ }.freeze
53
59
 
54
60
  attr_reader :name
55
61
 
56
- # default -> false
62
+ # default -> false.
57
63
  attr_accessor :restore_ownership
58
- # default -> false
64
+
65
+ # default -> false, but will be set to true in a future version.
59
66
  attr_accessor :restore_permissions
60
- # default -> true
67
+
68
+ # default -> false, but will be set to true in a future version.
61
69
  attr_accessor :restore_times
70
+
62
71
  # Returns the zip files comment, if it has one
63
72
  attr_accessor :comment
64
73
 
@@ -66,6 +75,7 @@ module Zip
66
75
  # a new archive if it doesn't exist already.
67
76
  def initialize(path_or_io, create = false, buffer = false, options = {})
68
77
  super()
78
+ options = DEFAULT_OPTIONS.merge(options)
69
79
  @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
70
80
  @comment = ''
71
81
  @create = create ? true : false # allow any truthy value to mean true
@@ -98,18 +108,19 @@ module Zip
98
108
 
99
109
  @stored_entries = @entry_set.dup
100
110
  @stored_comment = @comment
101
- @restore_ownership = options[:restore_ownership] || false
102
- @restore_permissions = options[:restore_permissions] || true
103
- @restore_times = options[:restore_times] || true
111
+ @restore_ownership = options[:restore_ownership]
112
+ @restore_permissions = options[:restore_permissions]
113
+ @restore_times = options[:restore_times]
104
114
  end
105
115
 
106
116
  class << self
107
- # Same as #new. If a block is passed the ZipFile object is passed
108
- # to the block and is automatically closed afterwards just as with
109
- # ruby's builtin File.open method.
110
- def open(file_name, create = false)
111
- zf = ::Zip::File.new(file_name, create)
117
+ # Similar to ::new. If a block is passed the Zip::File object is passed
118
+ # to the block and is automatically closed afterwards, just as with
119
+ # ruby's builtin File::open method.
120
+ def open(file_name, create = false, options = {})
121
+ zf = ::Zip::File.new(file_name, create, false, options)
112
122
  return zf unless block_given?
123
+
113
124
  begin
114
125
  yield zf
115
126
  ensure
@@ -130,17 +141,18 @@ module Zip
130
141
  # (This can be used to extract data from a
131
142
  # downloaded zip archive without first saving it to disk.)
132
143
  def open_buffer(io, options = {})
133
- unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String)
144
+ unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
134
145
  raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
135
146
  end
136
147
 
137
- io = ::StringIO.new(io) if io.is_a?(::String)
148
+ io = ::StringIO.new(io) if io.kind_of?(::String)
138
149
 
139
150
  # https://github.com/rubyzip/rubyzip/issues/119
140
151
  io.binmode if io.respond_to?(:binmode)
141
152
 
142
153
  zf = ::Zip::File.new(io, true, true, options)
143
154
  return zf unless block_given?
155
+
144
156
  yield zf
145
157
 
146
158
  begin
@@ -156,9 +168,9 @@ module Zip
156
168
  # whereas ZipInputStream jumps through the entire archive accessing the
157
169
  # local entry headers (which contain the same information as the
158
170
  # central directory).
159
- def foreach(aZipFileName, &block)
160
- open(aZipFileName) do |zipFile|
161
- zipFile.each(&block)
171
+ def foreach(zip_file_name, &block)
172
+ ::Zip::File.open(zip_file_name) do |zip_file|
173
+ zip_file.each(&block)
162
174
  end
163
175
  end
164
176
 
@@ -219,12 +231,14 @@ module Zip
219
231
  def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil)
220
232
  raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
221
233
  raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
234
+
222
235
  zip_file_size = ::File.size(zip_file_name)
223
236
  segment_size = get_segment_size_for_split(segment_size)
224
237
  return if zip_file_size <= segment_size
238
+
225
239
  segment_count = get_segment_count_for_split(zip_file_size, segment_size)
226
240
  # Checking for correct zip structure
227
- open(zip_file_name) {}
241
+ ::Zip::File.open(zip_file_name) {}
228
242
  partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
229
243
  szip_file_index = 0
230
244
  ::File.open(zip_file_name, 'rb') do |zip_file|
@@ -241,8 +255,8 @@ module Zip
241
255
  # Returns an input stream to the specified entry. If a block is passed
242
256
  # the stream object is passed to the block and the stream is automatically
243
257
  # closed afterwards just as with ruby's builtin File.open method.
244
- def get_input_stream(entry, &aProc)
245
- get_entry(entry).get_input_stream(&aProc)
258
+ def get_input_stream(entry, &a_proc)
259
+ get_entry(entry).get_input_stream(&a_proc)
246
260
  end
247
261
 
248
262
  # Returns an output stream to the specified entry. If entry is not an instance
@@ -250,7 +264,11 @@ module Zip
250
264
  # specified. If a block is passed the stream object is passed to the block and
251
265
  # the stream is automatically closed afterwards just as with ruby's builtin
252
266
  # File.open method.
253
- def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc)
267
+ def get_output_stream(entry, permission_int = nil, comment = nil,
268
+ extra = nil, compressed_size = nil, crc = nil,
269
+ compression_method = nil, size = nil, time = nil,
270
+ &a_proc)
271
+
254
272
  new_entry =
255
273
  if entry.kind_of?(Entry)
256
274
  entry
@@ -264,7 +282,7 @@ module Zip
264
282
  new_entry.unix_perms = permission_int
265
283
  zip_streamable_entry = StreamableStream.new(new_entry)
266
284
  @entry_set << zip_streamable_entry
267
- zip_streamable_entry.get_output_stream(&aProc)
285
+ zip_streamable_entry.get_output_stream(&a_proc)
268
286
  end
269
287
 
270
288
  # Returns the name of the zip archive
@@ -274,7 +292,7 @@ module Zip
274
292
 
275
293
  # Returns a string containing the contents of the specified entry
276
294
  def read(entry)
277
- get_input_stream(entry) { |is| is.read }
295
+ get_input_stream(entry, &:read)
278
296
  end
279
297
 
280
298
  # Convenience method for adding the contents of a file to the archive
@@ -301,19 +319,19 @@ module Zip
301
319
 
302
320
  # Renames the specified entry.
303
321
  def rename(entry, new_name, &continue_on_exists_proc)
304
- foundEntry = get_entry(entry)
322
+ found_entry = get_entry(entry)
305
323
  check_entry_exists(new_name, continue_on_exists_proc, 'rename')
306
- @entry_set.delete(foundEntry)
307
- foundEntry.name = new_name
308
- @entry_set << foundEntry
324
+ @entry_set.delete(found_entry)
325
+ found_entry.name = new_name
326
+ @entry_set << found_entry
309
327
  end
310
328
 
311
- # Replaces the specified entry with the contents of srcPath (from
329
+ # Replaces the specified entry with the contents of src_path (from
312
330
  # the file system).
313
- def replace(entry, srcPath)
314
- check_file(srcPath)
331
+ def replace(entry, src_path)
332
+ check_file(src_path)
315
333
  remove(entry)
316
- add(entry, srcPath)
334
+ add(entry, src_path)
317
335
  end
318
336
 
319
337
  # Extracts entry to file dest_path.
@@ -326,7 +344,8 @@ module Zip
326
344
  # Commits changes that has been made since the previous commit to
327
345
  # the zip archive.
328
346
  def commit
329
- return if name.is_a?(StringIO) || !commit_required?
347
+ return if name.kind_of?(StringIO) || !commit_required?
348
+
330
349
  on_success_replace do |tmp_file|
331
350
  ::Zip::OutputStream.open(tmp_file) do |zos|
332
351
  @entry_set.each do |e|
@@ -366,7 +385,13 @@ module Zip
366
385
  # Searches for entry with the specified name. Returns nil if
367
386
  # no entry is found. See also get_entry
368
387
  def find_entry(entry_name)
369
- @entry_set.find_entry(entry_name)
388
+ selected_entry = @entry_set.find_entry(entry_name)
389
+ return if selected_entry.nil?
390
+
391
+ selected_entry.restore_ownership = @restore_ownership
392
+ selected_entry.restore_permissions = @restore_permissions
393
+ selected_entry.restore_times = @restore_times
394
+ selected_entry
370
395
  end
371
396
 
372
397
  # Searches for entries given a glob
@@ -378,43 +403,43 @@ module Zip
378
403
  # if no entry is found.
379
404
  def get_entry(entry)
380
405
  selected_entry = find_entry(entry)
381
- raise Errno::ENOENT, entry unless selected_entry
382
- selected_entry.restore_ownership = @restore_ownership
383
- selected_entry.restore_permissions = @restore_permissions
384
- selected_entry.restore_times = @restore_times
406
+ raise Errno::ENOENT, entry if selected_entry.nil?
407
+
385
408
  selected_entry
386
409
  end
387
410
 
388
411
  # Creates a directory
389
- def mkdir(entryName, permissionInt = 0o755)
390
- raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName)
391
- entryName = entryName.dup.to_s
392
- entryName << '/' unless entryName.end_with?('/')
393
- @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt)
412
+ def mkdir(entry_name, permission = 0o755)
413
+ raise Errno::EEXIST, "File exists - #{entry_name}" if find_entry(entry_name)
414
+
415
+ entry_name = entry_name.dup.to_s
416
+ entry_name << '/' unless entry_name.end_with?('/')
417
+ @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
394
418
  end
395
419
 
396
420
  private
397
421
 
398
- def directory?(newEntry, srcPath)
399
- srcPathIsDirectory = ::File.directory?(srcPath)
400
- if newEntry.directory? && !srcPathIsDirectory
422
+ def directory?(new_entry, src_path)
423
+ path_is_directory = ::File.directory?(src_path)
424
+ if new_entry.directory? && !path_is_directory
401
425
  raise ArgumentError,
402
- "entry name '#{newEntry}' indicates directory entry, but " \
403
- "'#{srcPath}' is not a directory"
404
- elsif !newEntry.directory? && srcPathIsDirectory
405
- newEntry.name += '/'
426
+ "entry name '#{new_entry}' indicates directory entry, but " \
427
+ "'#{src_path}' is not a directory"
428
+ elsif !new_entry.directory? && path_is_directory
429
+ new_entry.name += '/'
406
430
  end
407
- newEntry.directory? && srcPathIsDirectory
431
+ new_entry.directory? && path_is_directory
408
432
  end
409
433
 
410
- def check_entry_exists(entryName, continue_on_exists_proc, procedureName)
434
+ def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
411
435
  continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
412
- return unless @entry_set.include?(entryName)
436
+ return unless @entry_set.include?(entry_name)
437
+
413
438
  if continue_on_exists_proc.call
414
- remove get_entry(entryName)
439
+ remove get_entry(entry_name)
415
440
  else
416
441
  raise ::Zip::EntryExistsError,
417
- procedureName + " failed. Entry #{entryName} already exists"
442
+ proc_name + " failed. Entry #{entry_name} already exists"
418
443
  end
419
444
  end
420
445