rubyzip 2.4.1 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +419 -0
  3. data/LICENSE.md +24 -0
  4. data/README.md +137 -37
  5. data/Rakefile +11 -7
  6. data/lib/zip/central_directory.rb +169 -123
  7. data/lib/zip/compressor.rb +3 -1
  8. data/lib/zip/constants.rb +29 -21
  9. data/lib/zip/crypto/decrypted_io.rb +4 -2
  10. data/lib/zip/crypto/encryption.rb +4 -2
  11. data/lib/zip/crypto/null_encryption.rb +6 -4
  12. data/lib/zip/crypto/traditional_encryption.rb +8 -6
  13. data/lib/zip/decompressor.rb +4 -3
  14. data/lib/zip/deflater.rb +10 -8
  15. data/lib/zip/dirtyable.rb +32 -0
  16. data/lib/zip/dos_time.rb +43 -4
  17. data/lib/zip/entry.rb +333 -242
  18. data/lib/zip/entry_set.rb +11 -9
  19. data/lib/zip/errors.rb +136 -16
  20. data/lib/zip/extra_field/generic.rb +6 -13
  21. data/lib/zip/extra_field/ntfs.rb +6 -4
  22. data/lib/zip/extra_field/old_unix.rb +3 -1
  23. data/lib/zip/extra_field/universal_time.rb +3 -1
  24. data/lib/zip/extra_field/unix.rb +5 -3
  25. data/lib/zip/extra_field/unknown.rb +33 -0
  26. data/lib/zip/extra_field/zip64.rb +12 -5
  27. data/lib/zip/extra_field.rb +16 -22
  28. data/lib/zip/file.rb +166 -264
  29. data/lib/zip/file_split.rb +91 -0
  30. data/lib/zip/filesystem/dir.rb +86 -0
  31. data/lib/zip/filesystem/directory_iterator.rb +48 -0
  32. data/lib/zip/filesystem/file.rb +262 -0
  33. data/lib/zip/filesystem/file_stat.rb +110 -0
  34. data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
  35. data/lib/zip/filesystem.rb +27 -596
  36. data/lib/zip/inflater.rb +7 -5
  37. data/lib/zip/input_stream.rb +50 -50
  38. data/lib/zip/ioextras/abstract_input_stream.rb +16 -11
  39. data/lib/zip/ioextras/abstract_output_stream.rb +5 -3
  40. data/lib/zip/ioextras.rb +7 -7
  41. data/lib/zip/null_compressor.rb +3 -1
  42. data/lib/zip/null_decompressor.rb +3 -1
  43. data/lib/zip/null_input_stream.rb +3 -1
  44. data/lib/zip/output_stream.rb +55 -56
  45. data/lib/zip/pass_thru_compressor.rb +3 -1
  46. data/lib/zip/pass_thru_decompressor.rb +4 -2
  47. data/lib/zip/streamable_directory.rb +3 -1
  48. data/lib/zip/streamable_stream.rb +3 -0
  49. data/lib/zip/version.rb +3 -1
  50. data/lib/zip.rb +18 -22
  51. data/rubyzip.gemspec +39 -0
  52. data/samples/example.rb +8 -3
  53. data/samples/example_filesystem.rb +3 -2
  54. data/samples/example_recursive.rb +3 -1
  55. data/samples/gtk_ruby_zip.rb +4 -2
  56. data/samples/qtzip.rb +6 -5
  57. data/samples/write_simple.rb +2 -1
  58. data/samples/zipfind.rb +1 -0
  59. metadata +87 -51
  60. data/TODO +0 -15
  61. data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
data/lib/zip/file.rb CHANGED
@@ -1,132 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require_relative 'file_split'
6
+
1
7
  module Zip
2
- # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
3
- # The most important methods are those inherited from
4
- # ZipCentralDirectory for accessing information about the entries in
5
- # the archive and methods such as get_input_stream and
6
- # get_output_stream for reading from and writing entries to the
8
+ # Zip::File is modeled after java.util.zip.ZipFile from the Java SDK.
9
+ # The most important methods are those for accessing information about
10
+ # the entries in
11
+ # the archive and methods such as `get_input_stream` and
12
+ # `get_output_stream` for reading from and writing entries to the
7
13
  # archive. The class includes a few convenience methods such as
8
- # #extract for extracting entries to the filesystem, and #remove,
9
- # #replace, #rename and #mkdir for making simple modifications to
14
+ # `extract` for extracting entries to the filesystem, and `remove`,
15
+ # `replace`, `rename` and `mkdir` for making simple modifications to
10
16
  # the archive.
11
17
  #
12
- # Modifications to a zip archive are not committed until #commit or
13
- # #close is called. The method #open accepts a block following
14
- # the pattern from File.open offering a simple way to
18
+ # Modifications to a zip archive are not committed until `commit` or
19
+ # `close` is called. The method `open` accepts a block following
20
+ # the pattern from ::File.open offering a simple way to
15
21
  # automatically close the archive when the block returns.
16
22
  #
17
- # The following example opens zip archive <code>my.zip</code>
23
+ # The following example opens zip archive `my.zip`
18
24
  # (creating it if it doesn't exist) and adds an entry
19
- # <code>first.txt</code> and a directory entry <code>a_dir</code>
25
+ # `first.txt` and a directory entry `a_dir`
20
26
  # to it.
21
27
  #
22
- # require 'zip'
28
+ # ```
29
+ # require 'zip'
23
30
  #
24
- # Zip::File.open("my.zip", Zip::File::CREATE) {
25
- # |zipfile|
26
- # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
27
- # zipfile.mkdir("a_dir")
28
- # }
31
+ # Zip::File.open('my.zip', create: true) do |zipfile|
32
+ # zipfile.get_output_stream('first.txt') { |f| f.puts 'Hello from Zip::File' }
33
+ # zipfile.mkdir('a_dir')
34
+ # end
35
+ # ```
29
36
  #
30
- # The next example reopens <code>my.zip</code> writes the contents of
31
- # <code>first.txt</code> to standard out and deletes the entry from
37
+ # The next example reopens `my.zip`, writes the contents of
38
+ # `first.txt` to standard out and deletes the entry from
32
39
  # the archive.
33
40
  #
34
- # require 'zip'
41
+ # ```
42
+ # require 'zip'
35
43
  #
36
- # Zip::File.open("my.zip", Zip::File::CREATE) {
37
- # |zipfile|
38
- # puts zipfile.read("first.txt")
39
- # zipfile.remove("first.txt")
40
- # }
44
+ # Zip::File.open('my.zip', create: true) do |zipfile|
45
+ # puts zipfile.read('first.txt')
46
+ # zipfile.remove('first.txt')
47
+ # end
41
48
  #
42
- # ZipFileSystem offers an alternative API that emulates ruby's
43
- # interface for accessing the filesystem, ie. the File and Dir classes.
44
-
45
- class File < CentralDirectory
46
- CREATE = true
47
- SPLIT_SIGNATURE = 0x08074b50
48
- ZIP64_EOCD_SIGNATURE = 0x06064b50
49
- MAX_SEGMENT_SIZE = 3_221_225_472
50
- MIN_SEGMENT_SIZE = 65_536
51
- DATA_BUFFER_SIZE = 8192
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
49
+ # Zip::FileSystem offers an alternative API that emulates ruby's
50
+ # interface for accessing the filesystem, ie. the ::File and ::Dir classes.
51
+ class File
52
+ extend Forwardable
53
+ extend FileSplit
59
54
 
55
+ IO_METHODS = [:tell, :seek, :read, :eof, :close].freeze # :nodoc:
56
+
57
+ # The name of this zip archive.
60
58
  attr_reader :name
61
59
 
62
60
  # default -> false.
63
61
  attr_accessor :restore_ownership
64
62
 
65
- # default -> false, but will be set to true in a future version.
63
+ # default -> true.
66
64
  attr_accessor :restore_permissions
67
65
 
68
- # default -> false, but will be set to true in a future version.
66
+ # default -> true.
69
67
  attr_accessor :restore_times
70
68
 
71
- # Returns the zip files comment, if it has one
72
- attr_accessor :comment
69
+ def_delegators :@cdir, :comment, :comment=, :each, :entries, :glob, :size
73
70
 
74
- # Opens a zip archive. Pass true as the second parameter to create
71
+ # Opens a zip archive. Pass create: true to create
75
72
  # a new archive if it doesn't exist already.
76
- def initialize(path_or_io, dep_create = false, dep_buffer = false,
77
- create: false, buffer: false, **options)
73
+ def initialize(path_or_io, create: false, buffer: false,
74
+ restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
75
+ restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
76
+ restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
77
+ compression_level: ::Zip.default_compression)
78
78
  super()
79
79
 
80
- Zip.warn_about_v3_api('File#new') if dep_create || dep_buffer
81
-
82
- options = DEFAULT_OPTIONS.merge(options)
83
80
  @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io
84
- @comment = ''
85
- @create = create || dep_create ? true : false # allow any truthy value to mean true
86
- buffer ||= dep_buffer
81
+ @create = create ? true : false # allow any truthy value to mean true
87
82
 
88
- if ::File.size?(@name.to_s)
89
- # There is a file, which exists, that is associated with this zip.
90
- @create = false
91
- @file_permissions = ::File.stat(@name).mode
83
+ initialize_cdir(path_or_io, buffer: buffer)
92
84
 
93
- if buffer
94
- read_from_stream(path_or_io)
95
- else
96
- ::File.open(@name, 'rb') do |f|
97
- read_from_stream(f)
98
- end
99
- end
100
- elsif buffer && path_or_io.size > 0
101
- # This zip is probably a non-empty StringIO.
102
- @create = false
103
- read_from_stream(path_or_io)
104
- elsif @create
105
- # This zip is completely new/empty and is to be created.
106
- @entry_set = EntrySet.new
107
- elsif ::File.zero?(@name)
108
- # A file exists, but it is empty.
109
- raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
110
- else
111
- # Everything is wrong.
112
- raise Error, "File #{@name} not found"
113
- end
114
-
115
- @stored_entries = @entry_set.dup
116
- @stored_comment = @comment
117
- @restore_ownership = options[:restore_ownership]
118
- @restore_permissions = options[:restore_permissions]
119
- @restore_times = options[:restore_times]
85
+ @restore_ownership = restore_ownership
86
+ @restore_permissions = restore_permissions
87
+ @restore_times = restore_times
88
+ @compression_level = compression_level
120
89
  end
121
90
 
122
91
  class << self
123
92
  # Similar to ::new. If a block is passed the Zip::File object is passed
124
93
  # to the block and is automatically closed afterwards, just as with
125
94
  # ruby's builtin File::open method.
126
- def open(file_name, dep_create = false, create: false, **options)
127
- Zip.warn_about_v3_api('Zip::File.open') if dep_create
95
+ def open(file_name, create: false,
96
+ restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
97
+ restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
98
+ restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
99
+ compression_level: ::Zip.default_compression)
100
+
101
+ zf = ::Zip::File.new(file_name, create: create,
102
+ restore_ownership: restore_ownership,
103
+ restore_permissions: restore_permissions,
104
+ restore_times: restore_times,
105
+ compression_level: compression_level)
128
106
 
129
- zf = ::Zip::File.new(file_name, create: (dep_create || create), buffer: false, **options)
130
107
  return zf unless block_given?
131
108
 
132
109
  begin
@@ -136,31 +113,29 @@ module Zip
136
113
  end
137
114
  end
138
115
 
139
- # Same as #open. But outputs data to a buffer instead of a file
140
- def add_buffer
141
- Zip.warn_about_v3_api('Zip::File.add_buffer')
142
-
143
- io = ::StringIO.new
144
- zf = ::Zip::File.new(io, true, true)
145
- yield zf
146
- zf.write_buffer(io)
147
- end
148
-
149
116
  # Like #open, but reads zip archive contents from a String or open IO
150
117
  # stream, and outputs data to a buffer.
151
118
  # (This can be used to extract data from a
152
119
  # downloaded zip archive without first saving it to disk.)
153
- def open_buffer(io, **options)
120
+ def open_buffer(io = ::StringIO.new, create: false,
121
+ restore_ownership: DEFAULT_RESTORE_OPTIONS[:restore_ownership],
122
+ restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
123
+ restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
124
+ compression_level: ::Zip.default_compression)
125
+
154
126
  unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
155
- raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
127
+ raise 'Zip::File.open_buffer expects a String or IO-like argument' \
128
+ "(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
156
129
  end
157
130
 
158
131
  io = ::StringIO.new(io) if io.kind_of?(::String)
159
132
 
160
- # https://github.com/rubyzip/rubyzip/issues/119
161
- io.binmode if io.respond_to?(:binmode)
133
+ zf = ::Zip::File.new(io, create: create, buffer: true,
134
+ restore_ownership: restore_ownership,
135
+ restore_permissions: restore_permissions,
136
+ restore_times: restore_times,
137
+ compression_level: compression_level)
162
138
 
163
- zf = ::Zip::File.new(io, create: true, buffer: true, **options)
164
139
  return zf unless block_given?
165
140
 
166
141
  yield zf
@@ -184,88 +159,18 @@ module Zip
184
159
  end
185
160
  end
186
161
 
187
- def get_segment_size_for_split(segment_size)
188
- if MIN_SEGMENT_SIZE > segment_size
189
- MIN_SEGMENT_SIZE
190
- elsif MAX_SEGMENT_SIZE < segment_size
191
- MAX_SEGMENT_SIZE
192
- else
193
- segment_size
194
- end
195
- end
196
-
197
- def get_partial_zip_file_name(zip_file_name, partial_zip_file_name)
198
- unless partial_zip_file_name.nil?
199
- partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/,
200
- partial_zip_file_name + ::File.extname(zip_file_name))
201
- end
202
- partial_zip_file_name ||= zip_file_name
203
- partial_zip_file_name
204
- end
205
-
206
- def get_segment_count_for_split(zip_file_size, segment_size)
207
- (zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1)
208
- end
209
-
210
- def put_split_signature(szip_file, segment_size)
211
- signature_packed = [SPLIT_SIGNATURE].pack('V')
212
- szip_file << signature_packed
213
- segment_size - signature_packed.size
214
- end
215
-
216
- #
217
- # TODO: Make the code more understandable
218
- #
219
- def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
220
- ssegment_size = zip_file_size - zip_file.pos
221
- ssegment_size = segment_size if ssegment_size > segment_size
222
- szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}"
223
- ::File.open(szip_file_name, 'wb') do |szip_file|
224
- if szip_file_index == 1
225
- ssegment_size = put_split_signature(szip_file, segment_size)
226
- end
227
- chunk_bytes = 0
228
- until ssegment_size == chunk_bytes || zip_file.eof?
229
- segment_bytes_left = ssegment_size - chunk_bytes
230
- buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE
231
- chunk = zip_file.read(buffer_size)
232
- chunk_bytes += buffer_size
233
- szip_file << chunk
234
- # Info for track splitting
235
- yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given?
236
- end
237
- end
238
- end
239
-
240
- # Splits an archive into parts with segment size
241
- def split(zip_file_name,
242
- dep_segment_size = MAX_SEGMENT_SIZE, dep_delete_zip_file = true, dep_partial_zip_file_name = nil,
243
- segment_size: MAX_SEGMENT_SIZE, delete_zip_file: nil, partial_zip_file_name: nil)
244
- raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name)
245
- raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name)
246
-
247
- if dep_segment_size != MAX_SEGMENT_SIZE || !dep_delete_zip_file || dep_partial_zip_file_name
248
- Zip.warn_about_v3_api('Zip::File.split')
249
- end
162
+ # Count the entries in a zip archive without reading the whole set of
163
+ # entry data into memory.
164
+ def count_entries(path_or_io)
165
+ cdir = ::Zip::CentralDirectory.new
250
166
 
251
- zip_file_size = ::File.size(zip_file_name)
252
- segment_size = get_segment_size_for_split(segment_size || dep_segment_size)
253
- return if zip_file_size <= segment_size
254
-
255
- segment_count = get_segment_count_for_split(zip_file_size, segment_size)
256
- # Checking for correct zip structure
257
- ::Zip::File.open(zip_file_name) {}
258
- partial_zip_file_name = get_partial_zip_file_name(zip_file_name, (partial_zip_file_name || dep_partial_zip_file_name))
259
- szip_file_index = 0
260
- ::File.open(zip_file_name, 'rb') do |zip_file|
261
- until zip_file.eof?
262
- szip_file_index += 1
263
- save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count)
167
+ if path_or_io.kind_of?(String)
168
+ ::File.open(path_or_io, 'rb') do |f|
169
+ cdir.count_entries(f)
264
170
  end
171
+ else
172
+ cdir.count_entries(path_or_io)
265
173
  end
266
- delete_zip_file = delete_zip_file.nil? ? dep_delete_zip_file : delete_zip_file
267
- ::File.delete(zip_file_name) if delete_zip_file
268
- szip_file_index
269
174
  end
270
175
  end
271
176
 
@@ -281,45 +186,31 @@ module Zip
281
186
  # specified. If a block is passed the stream object is passed to the block and
282
187
  # the stream is automatically closed afterwards just as with ruby's builtin
283
188
  # File.open method.
284
- # rubocop:disable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
285
- def get_output_stream(entry,
286
- dep_permission_int = nil, dep_comment = nil,
287
- dep_extra = nil, dep_compressed_size = nil, dep_crc = nil,
288
- dep_compression_method = nil, dep_size = nil, dep_time = nil,
289
- permission_int: nil, comment: nil,
189
+ def get_output_stream(entry, permissions: nil, comment: nil,
290
190
  extra: nil, compressed_size: nil, crc: nil,
291
- compression_method: nil, size: nil, time: nil,
292
- &a_proc)
293
-
294
- unless dep_permission_int.nil? && dep_comment.nil? && dep_extra.nil? &&
295
- dep_compressed_size.nil? && dep_crc.nil? && dep_compression_method.nil? &&
296
- dep_size.nil? && dep_time.nil?
297
- Zip.warn_about_v3_api('Zip::File#get_output_stream')
298
- end
191
+ compression_method: nil, compression_level: nil,
192
+ size: nil, time: nil, &a_proc)
299
193
 
300
194
  new_entry =
301
195
  if entry.kind_of?(Entry)
302
196
  entry
303
197
  else
304
- Entry.new(@name, entry.to_s,
305
- comment: (comment || dep_comment),
306
- extra: (extra || dep_extra),
307
- compressed_size: (compressed_size || dep_compressed_size),
308
- crc: (crc || dep_crc),
309
- compression_method: (compression_method || dep_compression_method),
310
- size: (size || dep_size),
311
- time: (time || dep_time))
198
+ Entry.new(
199
+ @name, entry.to_s, comment: comment, extra: extra,
200
+ compressed_size: compressed_size, crc: crc, size: size,
201
+ compression_method: compression_method,
202
+ compression_level: compression_level, time: time
203
+ )
312
204
  end
313
205
  if new_entry.directory?
314
206
  raise ArgumentError,
315
207
  "cannot open stream to directory entry - '#{new_entry}'"
316
208
  end
317
- new_entry.unix_perms = (permission_int || dep_permission_int)
209
+ new_entry.unix_perms = permissions
318
210
  zip_streamable_entry = StreamableStream.new(new_entry)
319
- @entry_set << zip_streamable_entry
211
+ @cdir << zip_streamable_entry
320
212
  zip_streamable_entry.get_output_stream(&a_proc)
321
213
  end
322
- # rubocop:enable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
323
214
 
324
215
  # Returns the name of the zip archive
325
216
  def to_s
@@ -335,31 +226,39 @@ module Zip
335
226
  def add(entry, src_path, &continue_on_exists_proc)
336
227
  continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc }
337
228
  check_entry_exists(entry, continue_on_exists_proc, 'add')
338
- new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s)
229
+ new_entry = if entry.kind_of?(::Zip::Entry)
230
+ entry
231
+ else
232
+ ::Zip::Entry.new(
233
+ @name, entry.to_s,
234
+ compression_level: @compression_level
235
+ )
236
+ end
339
237
  new_entry.gather_fileinfo_from_srcpath(src_path)
340
- new_entry.dirty = true
341
- @entry_set << new_entry
238
+ @cdir << new_entry
342
239
  end
343
240
 
344
241
  # Convenience method for adding the contents of a file to the archive
345
242
  # in Stored format (uncompressed)
346
243
  def add_stored(entry, src_path, &continue_on_exists_proc)
347
- entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED)
244
+ entry = ::Zip::Entry.new(
245
+ @name, entry.to_s, compression_method: ::Zip::Entry::STORED
246
+ )
348
247
  add(entry, src_path, &continue_on_exists_proc)
349
248
  end
350
249
 
351
250
  # Removes the specified entry.
352
251
  def remove(entry)
353
- @entry_set.delete(get_entry(entry))
252
+ @cdir.delete(get_entry(entry))
354
253
  end
355
254
 
356
255
  # Renames the specified entry.
357
256
  def rename(entry, new_name, &continue_on_exists_proc)
358
257
  found_entry = get_entry(entry)
359
258
  check_entry_exists(new_name, continue_on_exists_proc, 'rename')
360
- @entry_set.delete(found_entry)
259
+ @cdir.delete(found_entry)
361
260
  found_entry.name = new_name
362
- @entry_set << found_entry
261
+ @cdir << found_entry
363
262
  end
364
263
 
365
264
  # Replaces the specified entry with the contents of src_path (from
@@ -370,25 +269,16 @@ module Zip
370
269
  add(entry, src_path)
371
270
  end
372
271
 
373
- # Extracts entry to file dest_path.
374
- def extract(entry, dest_path, &block)
375
- Zip.warn_about_v3_api('Zip::File#extract')
376
-
377
- block ||= proc { ::Zip.on_exists_proc }
378
- found_entry = get_entry(entry)
379
- found_entry.extract(dest_path, &block)
380
- end
381
-
382
272
  # Extracts `entry` to a file at `entry_path`, with `destination_directory`
383
273
  # as the base location in the filesystem.
384
274
  #
385
275
  # NB: The caller is responsible for making sure `destination_directory` is
386
276
  # safe, if it is passed.
387
- def extract_v3(entry, entry_path = nil, destination_directory: '.', &block)
277
+ def extract(entry, entry_path = nil, destination_directory: '.', &block)
388
278
  block ||= proc { ::Zip.on_exists_proc }
389
279
  found_entry = get_entry(entry)
390
280
  entry_path ||= found_entry.name
391
- found_entry.extract_v3(entry_path, destination_directory: destination_directory, &block)
281
+ found_entry.extract(entry_path, destination_directory: destination_directory, &block)
392
282
  end
393
283
 
394
284
  # Commits changes that has been made since the previous commit to
@@ -398,16 +288,15 @@ module Zip
398
288
 
399
289
  on_success_replace do |tmp_file|
400
290
  ::Zip::OutputStream.open(tmp_file) do |zos|
401
- @entry_set.each do |e|
291
+ @cdir.each do |e|
402
292
  e.write_to_zip_output_stream(zos)
403
- e.dirty = false
404
293
  e.clean_up
405
294
  end
406
295
  zos.comment = comment
407
296
  end
408
297
  true
409
298
  end
410
- initialize(name)
299
+ initialize_cdir(@name)
411
300
  end
412
301
 
413
302
  # Write buffer write changes to buffer and return
@@ -415,7 +304,7 @@ module Zip
415
304
  return io unless commit_required?
416
305
 
417
306
  ::Zip::OutputStream.write_buffer(io) do |zos|
418
- @entry_set.each { |e| e.write_to_zip_output_stream(zos) }
307
+ @cdir.each { |e| e.write_to_zip_output_stream(zos) }
419
308
  zos.comment = comment
420
309
  end
421
310
  end
@@ -428,16 +317,19 @@ module Zip
428
317
  # Returns true if any changes has been made to this archive since
429
318
  # the previous commit
430
319
  def commit_required?
431
- @entry_set.each do |e|
432
- return true if e.dirty
320
+ return true if @create || @cdir.dirty?
321
+
322
+ @cdir.each do |e|
323
+ return true if e.dirty?
433
324
  end
434
- @comment != @stored_comment || @entry_set != @stored_entries || @create
325
+
326
+ false
435
327
  end
436
328
 
437
329
  # Searches for entry with the specified name. Returns nil if
438
330
  # no entry is found. See also get_entry
439
331
  def find_entry(entry_name)
440
- selected_entry = @entry_set.find_entry(entry_name)
332
+ selected_entry = @cdir.find_entry(entry_name)
441
333
  return if selected_entry.nil?
442
334
 
443
335
  selected_entry.restore_ownership = @restore_ownership
@@ -446,11 +338,6 @@ module Zip
446
338
  selected_entry
447
339
  end
448
340
 
449
- # Searches for entries given a glob
450
- def glob(*args, &block)
451
- @entry_set.glob(*args, &block)
452
- end
453
-
454
341
  # Searches for an entry just as find_entry, but throws Errno::ENOENT
455
342
  # if no entry is found.
456
343
  def get_entry(entry)
@@ -466,33 +353,50 @@ module Zip
466
353
 
467
354
  entry_name = entry_name.dup.to_s
468
355
  entry_name << '/' unless entry_name.end_with?('/')
469
- @entry_set << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
356
+ @cdir << ::Zip::StreamableDirectory.new(@name, entry_name, nil, permission)
470
357
  end
471
358
 
472
359
  private
473
360
 
474
- def directory?(new_entry, src_path)
475
- path_is_directory = ::File.directory?(src_path)
476
- if new_entry.directory? && !path_is_directory
477
- raise ArgumentError,
478
- "entry name '#{new_entry}' indicates directory entry, but " \
479
- "'#{src_path}' is not a directory"
480
- elsif !new_entry.directory? && path_is_directory
481
- new_entry.name += '/'
361
+ def initialize_cdir(path_or_io, buffer: false)
362
+ @cdir = ::Zip::CentralDirectory.new
363
+
364
+ if ::File.size?(@name.to_s)
365
+ # There is a file, which exists, that is associated with this zip.
366
+ @create = false
367
+ @file_permissions = ::File.stat(@name).mode
368
+
369
+ if buffer
370
+ # https://github.com/rubyzip/rubyzip/issues/119
371
+ path_or_io.binmode if path_or_io.respond_to?(:binmode)
372
+ @cdir.read_from_stream(path_or_io)
373
+ else
374
+ ::File.open(@name, 'rb') do |f|
375
+ @cdir.read_from_stream(f)
376
+ end
377
+ end
378
+ elsif buffer && path_or_io.size > 0
379
+ # This zip is probably a non-empty StringIO.
380
+ @create = false
381
+ @cdir.read_from_stream(path_or_io)
382
+ elsif !@create && ::File.empty?(@name)
383
+ # A file exists, but it is empty, and we've said we're
384
+ # NOT creating a new zip.
385
+ raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?"
386
+ elsif !@create
387
+ # If we get here, and we're not creating a new zip, then
388
+ # everything is wrong.
389
+ raise Error, "File #{@name} not found"
482
390
  end
483
- new_entry.directory? && path_is_directory
484
391
  end
485
392
 
486
393
  def check_entry_exists(entry_name, continue_on_exists_proc, proc_name)
394
+ return unless @cdir.include?(entry_name)
395
+
487
396
  continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc }
488
- return unless @entry_set.include?(entry_name)
397
+ raise ::Zip::EntryExistsError.new proc_name, entry_name unless continue_on_exists_proc.call
489
398
 
490
- if continue_on_exists_proc.call
491
- remove get_entry(entry_name)
492
- else
493
- raise ::Zip::EntryExistsError,
494
- proc_name + " failed. Entry #{entry_name} already exists"
495
- end
399
+ remove get_entry(entry_name)
496
400
  end
497
401
 
498
402
  def check_file(path)
@@ -502,14 +406,12 @@ module Zip
502
406
  def on_success_replace
503
407
  dirname, basename = ::File.split(name)
504
408
  ::Dir::Tmpname.create(basename, dirname) do |tmp_filename|
505
- begin
506
- if yield tmp_filename
507
- ::File.rename(tmp_filename, name)
508
- ::File.chmod(@file_permissions, name) unless @create
509
- end
510
- ensure
511
- ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
409
+ if yield tmp_filename
410
+ ::File.rename(tmp_filename, name)
411
+ ::File.chmod(@file_permissions, name) unless @create
512
412
  end
413
+ ensure
414
+ ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename)
513
415
  end
514
416
  end
515
417
  end