rubyzip 1.2.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +64 -23
  3. data/lib/zip.rb +3 -2
  4. data/lib/zip/dos_time.rb +5 -0
  5. data/lib/zip/entry.rb +36 -19
  6. data/lib/zip/errors.rb +1 -0
  7. data/lib/zip/extra_field.rb +1 -1
  8. data/lib/zip/extra_field/generic.rb +1 -1
  9. data/lib/zip/extra_field/universal_time.rb +39 -12
  10. data/lib/zip/file.rb +67 -33
  11. data/lib/zip/inflater.rb +1 -1
  12. data/lib/zip/input_stream.rb +1 -1
  13. data/lib/zip/streamable_stream.rb +1 -6
  14. data/lib/zip/version.rb +1 -1
  15. metadata +11 -153
  16. data/test/basic_zip_file_test.rb +0 -60
  17. data/test/case_sensitivity_test.rb +0 -69
  18. data/test/central_directory_entry_test.rb +0 -69
  19. data/test/central_directory_test.rb +0 -100
  20. data/test/crypto/null_encryption_test.rb +0 -57
  21. data/test/crypto/traditional_encryption_test.rb +0 -80
  22. data/test/data/WarnInvalidDate.zip +0 -0
  23. data/test/data/file1.txt +0 -46
  24. data/test/data/file1.txt.deflatedData +0 -0
  25. data/test/data/file2.txt +0 -1504
  26. data/test/data/globTest.zip +0 -0
  27. data/test/data/globTest/foo.txt +0 -0
  28. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  29. data/test/data/globTest/food.txt +0 -0
  30. data/test/data/gpbit3stored.zip +0 -0
  31. data/test/data/mimetype +0 -1
  32. data/test/data/notzippedruby.rb +0 -7
  33. data/test/data/ntfs.zip +0 -0
  34. data/test/data/oddExtraField.zip +0 -0
  35. data/test/data/path_traversal/Makefile +0 -10
  36. data/test/data/path_traversal/jwilk/README.md +0 -5
  37. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  38. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  39. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  40. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  41. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  42. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  43. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  44. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  45. data/test/data/path_traversal/relative1.zip +0 -0
  46. data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
  47. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  48. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  49. data/test/data/rubycode.zip +0 -0
  50. data/test/data/rubycode2.zip +0 -0
  51. data/test/data/test.xls +0 -0
  52. data/test/data/testDirectory.bin +0 -0
  53. data/test/data/zip64-sample.zip +0 -0
  54. data/test/data/zipWithDirs.zip +0 -0
  55. data/test/data/zipWithEncryption.zip +0 -0
  56. data/test/deflater_test.rb +0 -65
  57. data/test/encryption_test.rb +0 -42
  58. data/test/entry_set_test.rb +0 -163
  59. data/test/entry_test.rb +0 -154
  60. data/test/errors_test.rb +0 -35
  61. data/test/extra_field_test.rb +0 -76
  62. data/test/file_extract_directory_test.rb +0 -54
  63. data/test/file_extract_test.rb +0 -83
  64. data/test/file_permissions_test.rb +0 -65
  65. data/test/file_split_test.rb +0 -57
  66. data/test/file_test.rb +0 -601
  67. data/test/filesystem/dir_iterator_test.rb +0 -58
  68. data/test/filesystem/directory_test.rb +0 -139
  69. data/test/filesystem/file_mutating_test.rb +0 -87
  70. data/test/filesystem/file_nonmutating_test.rb +0 -508
  71. data/test/filesystem/file_stat_test.rb +0 -64
  72. data/test/gentestfiles.rb +0 -126
  73. data/test/inflater_test.rb +0 -14
  74. data/test/input_stream_test.rb +0 -182
  75. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  76. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  77. data/test/ioextras/fake_io_test.rb +0 -18
  78. data/test/local_entry_test.rb +0 -154
  79. data/test/output_stream_test.rb +0 -128
  80. data/test/pass_thru_compressor_test.rb +0 -30
  81. data/test/pass_thru_decompressor_test.rb +0 -14
  82. data/test/path_traversal_test.rb +0 -134
  83. data/test/samples/example_recursive_test.rb +0 -37
  84. data/test/settings_test.rb +0 -95
  85. data/test/test_helper.rb +0 -234
  86. data/test/unicode_file_names_and_comments_test.rb +0 -62
  87. data/test/zip64_full_test.rb +0 -51
  88. data/test/zip64_support_test.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 898367588fc593bddd565ee8626c75cfbcddfce2
4
- data.tar.gz: 24d90fd344a11d8cf3b8098c459eb2c765e933e7
2
+ SHA256:
3
+ metadata.gz: 7befb1c4855935534788418759d90641e37bd31c53428b5ba04c6c8c4bc4a544
4
+ data.tar.gz: 9b5669fc4b3c8a03cc39c73c7ae0e26ece01a2e51a401274f737a43798592b5b
5
5
  SHA512:
6
- metadata.gz: 4f00c8c74720ba3bf103596601400f57e89d03cdd90b4423debd4c96ed6150a3db0fa8f7407201657cf6a7ee0b2e6586c7a284e1398dd1cf44c523799e1b25be
7
- data.tar.gz: 3a434114b84d7b109e26aec4821b395ad100efda591ad004656c6b5fd8cdba3740e50c171409c726a9770996e0815ef0324c87063d975024642c6f89ba56377e
6
+ metadata.gz: a537f87d0051073f2a31ced8f732d0b3b5c7376e792d02d3c1a52b597133f0f4be16b6c2c2564624104b179e501b7016ec6fecdde3681709d9730b5fb124dc72
7
+ data.tar.gz: 37ed030e2ffbb243bc639e969d050d21836448f746c1d47a49b8f77417b286438bee60dd80a6ba1e333642db218a56d32908ccbce4b3b6873082c42a709c6c77
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # rubyzip
2
+
2
3
  [![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip)
3
4
  [![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip)
4
5
  [![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip)
@@ -19,9 +20,10 @@ gem 'zip-zip' # will load compatibility for old rubyzip API.
19
20
 
20
21
  ## Requirements
21
22
 
22
- * Ruby 1.9.2 or greater
23
+ - Ruby 2.4 or greater (for rubyzip 2.0; use 1.x for older rubies)
23
24
 
24
25
  ## Installation
26
+
25
27
  Rubyzip is available on RubyGems:
26
28
 
27
29
  ```
@@ -59,7 +61,8 @@ end
59
61
  ```
60
62
 
61
63
  ### Zipping a directory recursively
62
- Copy from [here](https://github.com/rubyzip/rubyzip/blob/05916bf89181e1955118fd3ea059f18acac28cc8/samples/example_recursive.rb )
64
+
65
+ Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb)
63
66
 
64
67
  ```ruby
65
68
  require 'zip'
@@ -83,7 +86,7 @@ class ZipFileGenerator
83
86
 
84
87
  # Zip the input directory.
85
88
  def write
86
- entries = Dir.entries(@input_dir) - %w(. ..)
89
+ entries = Dir.entries(@input_dir) - %w[. ..]
87
90
 
88
91
  ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
89
92
  write_entries entries, '', zipfile
@@ -97,7 +100,6 @@ class ZipFileGenerator
97
100
  entries.each do |e|
98
101
  zipfile_path = path == '' ? e : File.join(path, e)
99
102
  disk_file_path = File.join(@input_dir, zipfile_path)
100
- puts "Deflating #{disk_file_path}"
101
103
 
102
104
  if File.directory? disk_file_path
103
105
  recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
@@ -109,14 +111,12 @@ class ZipFileGenerator
109
111
 
110
112
  def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
111
113
  zipfile.mkdir zipfile_path
112
- subdir = Dir.entries(disk_file_path) - %w(. ..)
114
+ subdir = Dir.entries(disk_file_path) - %w[. ..]
113
115
  write_entries subdir, zipfile_path, zipfile
114
116
  end
115
117
 
116
118
  def put_into_archive(disk_file_path, zipfile, zipfile_path)
117
- zipfile.get_output_stream(zipfile_path) do |f|
118
- f.write(File.open(disk_file_path, 'rb').read)
119
- end
119
+ zipfile.add(zipfile_path, disk_file_path)
120
120
  end
121
121
  end
122
122
  ```
@@ -152,12 +152,15 @@ When modifying a zip archive the file permissions of the archive are preserved.
152
152
  ### Reading a Zip file
153
153
 
154
154
  ```ruby
155
+ MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this)
155
156
  Zip::File.open('foo.zip') do |zip_file|
156
157
  # Handle entries one by one
157
158
  zip_file.each do |entry|
158
- # Extract to file/directory/symlink
159
159
  puts "Extracting #{entry.name}"
160
- entry.extract(dest_file)
160
+ raise 'File too large when extracted' if entry.size > MAX_SIZE
161
+
162
+ # Extract to file or directory based on name in the archive
163
+ entry.extract
161
164
 
162
165
  # Read into memory
163
166
  content = entry.get_input_stream.read
@@ -165,6 +168,7 @@ Zip::File.open('foo.zip') do |zip_file|
165
168
 
166
169
  # Find specific entry
167
170
  entry = zip_file.glob('*.csv').first
171
+ raise 'File too large when extracted' if entry.size > MAX_SIZE
168
172
  puts entry.get_input_stream.read
169
173
  end
170
174
  ```
@@ -177,7 +181,6 @@ But there is one exception when it is not working - General Purpose Flag Bit 3.
177
181
 
178
182
  > If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data
179
183
 
180
-
181
184
  If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception.
182
185
 
183
186
  ### Password Protection (Experimental)
@@ -220,7 +223,9 @@ File.open(new_path, "wb") {|f| f.write(buffer.string) }
220
223
 
221
224
  ## Configuration
222
225
 
223
- By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so:
226
+ ### Existing Files
227
+
228
+ By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so:
224
229
 
225
230
  ```ruby
226
231
  Zip.on_exists_proc = true
@@ -234,32 +239,76 @@ Additionally, if you want to configure rubyzip to overwrite existing files while
234
239
  Zip.continue_on_exists_proc = true
235
240
  ```
236
241
 
242
+ ### Non-ASCII Names
243
+
237
244
  If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option:
238
245
 
239
246
  ```ruby
240
247
  Zip.unicode_names = true
241
248
  ```
242
249
 
250
+ Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so:
251
+
252
+ ```ruby
253
+ Zip.force_entry_names_encoding = 'UTF-8'
254
+ ```
255
+
256
+ Allowed encoding names are the same as accepted by `String#force_encoding`
257
+
258
+ ### Date Validation
259
+
243
260
  Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting:
244
261
 
245
262
  ```ruby
246
263
  Zip.warn_invalid_date = false
247
264
  ```
248
265
 
266
+ ### Size Validation
267
+
268
+ By default (in rubyzip >= 2.0), rubyzip's `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like:
269
+
270
+ ```ruby
271
+ MAX_FILE_SIZE = 10 * 1024**2 # 10MiB
272
+ MAX_FILES = 100
273
+ Zip::File.open('foo.zip') do |zip_file|
274
+ num_files = 0
275
+ zip_file.each do |entry|
276
+ num_files += 1 if entry.file?
277
+ raise 'Too many extracted files' if num_files > MAX_FILES
278
+ raise 'File too large when extracted' if entry.size > MAX_FILE_SIZE
279
+ entry.extract
280
+ end
281
+ end
282
+ ```
283
+
284
+ If you need to extract zip files that report incorrect uncompressed sizes and you really trust them not too be too large, you can disable this setting with
285
+ ```ruby
286
+ Zip.validate_entry_sizes = false
287
+ ```
288
+
289
+ Note that if you use the lower level `Zip::InputStream` interface, `rubyzip` does *not* check the entry `size`s. In this case, the caller is responsible for making sure it does not read more data than expected from the input stream.
290
+
291
+ ### Default Compression
292
+
249
293
  You can set the default compression level like so:
250
294
 
251
295
  ```ruby
252
296
  Zip.default_compression = Zlib::DEFAULT_COMPRESSION
253
297
  ```
298
+
254
299
  It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION`
255
300
 
256
- Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so:
301
+ ### Zip64 Support
302
+
303
+ By default, Zip64 support is disabled for writing. To enable it do this:
257
304
 
258
305
  ```ruby
259
- Zip.force_entry_names_encoding = 'UTF-8'
306
+ Zip.write_zip64_support = true
260
307
  ```
261
308
 
262
- Allowed encoding names are the same as accepted by `String#force_encoding`
309
+ _NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
310
+
311
+ ### Block Form
263
312
 
264
313
  You can set multiple settings at the same time by using a block:
265
314
 
@@ -272,14 +321,6 @@ You can set multiple settings at the same time by using a block:
272
321
  end
273
322
  ```
274
323
 
275
- By default, Zip64 support is disabled for writing. To enable it do this:
276
-
277
- ```ruby
278
- Zip.write_zip64_support = true
279
- ```
280
-
281
- _NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
282
-
283
324
  ## Developing
284
325
 
285
326
  To run the test you need to do this:
data/lib/zip.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'delegate'
2
2
  require 'singleton'
3
3
  require 'tempfile'
4
- require 'tmpdir'
5
4
  require 'fileutils'
6
5
  require 'stringio'
7
6
  require 'zlib'
@@ -42,7 +41,8 @@ module Zip
42
41
  :write_zip64_support,
43
42
  :warn_invalid_date,
44
43
  :case_insensitive_match,
45
- :force_entry_names_encoding
44
+ :force_entry_names_encoding,
45
+ :validate_entry_sizes
46
46
 
47
47
  def reset!
48
48
  @_ran_once = false
@@ -54,6 +54,7 @@ module Zip
54
54
  @write_zip64_support = false
55
55
  @warn_invalid_date = true
56
56
  @case_insensitive_match = false
57
+ @validate_entry_sizes = true
57
58
  end
58
59
 
59
60
  def setup
@@ -29,6 +29,11 @@ module Zip
29
29
  to_i / 2 == other.to_i / 2
30
30
  end
31
31
 
32
+ # Create a DOSTime instance from a vanilla Time instance.
33
+ def self.from_time(time)
34
+ local(time.year, time.month, time.day, time.hour, time.min, time.sec)
35
+ end
36
+
32
37
  def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
33
38
  second = 2 * (0b11111 & binaryDosTime)
34
39
  minute = (0b11111100000 & binaryDosTime) >> 5
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  module Zip
2
3
  class Entry
3
4
  STORED = 0
@@ -33,7 +34,7 @@ module Zip
33
34
  end
34
35
  @follow_symlinks = false
35
36
 
36
- @restore_times = true
37
+ @restore_times = false
37
38
  @restore_permissions = false
38
39
  @restore_ownership = false
39
40
  # BUG: need an extra field to support uid/gid's
@@ -117,7 +118,7 @@ module Zip
117
118
  return false unless cleanpath.relative?
118
119
  root = ::File::SEPARATOR
119
120
  naive_expanded_path = ::File.join(root, cleanpath.to_s)
120
- cleanpath.expand_path(root).to_s == naive_expanded_path
121
+ ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
121
122
  end
122
123
 
123
124
  def local_entry_offset #:nodoc:all
@@ -162,7 +163,7 @@ module Zip
162
163
  # is passed.
163
164
  def extract(dest_path = nil, &block)
164
165
  if dest_path.nil? && !name_safe?
165
- puts "WARNING: skipped #{@name} as unsafe"
166
+ warn "WARNING: skipped '#{@name}' as unsafe."
166
167
  return self
167
168
  end
168
169
 
@@ -275,10 +276,10 @@ module Zip
275
276
  zip64 = @extra['Zip64']
276
277
  [::Zip::LOCAL_ENTRY_SIGNATURE,
277
278
  @version_needed_to_extract, # version needed to extract
278
- @gp_flags, # @gp_flags ,
279
+ @gp_flags, # @gp_flags
279
280
  @compression_method,
280
- @time.to_binary_dos_time, # @last_mod_time ,
281
- @time.to_binary_dos_date, # @last_mod_date ,
281
+ @time.to_binary_dos_time, # @last_mod_time
282
+ @time.to_binary_dos_date, # @last_mod_date
282
283
  @crc,
283
284
  zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
284
285
  zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
@@ -405,16 +406,20 @@ module Zip
405
406
  @unix_uid = stat.uid
406
407
  @unix_gid = stat.gid
407
408
  @unix_perms = stat.mode & 0o7777
409
+ @time = ::Zip::DOSTime.from_time(stat.mtime)
408
410
  end
409
411
 
410
- def set_unix_permissions_on_path(dest_path)
411
- # BUG: does not update timestamps into account
412
+ def set_unix_attributes_on_path(dest_path)
412
413
  # ignore setuid/setgid bits by default. honor if @restore_ownership
413
414
  unix_perms_mask = 0o1777
414
415
  unix_perms_mask = 0o7777 if @restore_ownership
415
416
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
416
417
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
417
- # File::utimes()
418
+
419
+ # Restore the timestamp on a file. This will either have come from the
420
+ # original source file that was copied into the archive, or from the
421
+ # creation date of the archive if there was no original source file.
422
+ ::FileUtils.touch(dest_path, mtime: time) if @restore_times
418
423
  end
419
424
 
420
425
  def set_extra_attributes_on_path(dest_path) # :nodoc:
@@ -422,7 +427,7 @@ module Zip
422
427
 
423
428
  case @fstype
424
429
  when ::Zip::FSTYPE_UNIX
425
- set_unix_permissions_on_path(dest_path)
430
+ set_unix_attributes_on_path(dest_path)
426
431
  end
427
432
  end
428
433
 
@@ -432,11 +437,11 @@ module Zip
432
437
  @header_signature,
433
438
  @version, # version of encoding software
434
439
  @fstype, # filesystem type
435
- @version_needed_to_extract, # @versionNeededToExtract ,
436
- @gp_flags, # @gp_flags ,
440
+ @version_needed_to_extract, # @versionNeededToExtract
441
+ @gp_flags, # @gp_flags
437
442
  @compression_method,
438
- @time.to_binary_dos_time, # @last_mod_time ,
439
- @time.to_binary_dos_date, # @last_mod_date ,
443
+ @time.to_binary_dos_time, # @last_mod_time
444
+ @time.to_binary_dos_date, # @last_mod_date
440
445
  @crc,
441
446
  zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
442
447
  zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
@@ -590,7 +595,7 @@ module Zip
590
595
  def set_time(binary_dos_date, binary_dos_time)
591
596
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
592
597
  rescue ArgumentError
593
- warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
598
+ warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
594
599
  end
595
600
 
596
601
  def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
@@ -600,14 +605,26 @@ module Zip
600
605
  end
601
606
  ::File.open(dest_path, 'wb') do |os|
602
607
  get_input_stream do |is|
603
- set_extra_attributes_on_path(dest_path)
604
-
605
- buf = ''
608
+ bytes_written = 0
609
+ warned = false
610
+ buf = ''.dup
606
611
  while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
607
612
  os << buf
613
+ bytes_written += buf.bytesize
614
+ if bytes_written > size && !warned
615
+ message = "entry '#{name}' should be #{size}B, but is larger when inflated."
616
+ if ::Zip.validate_entry_sizes
617
+ raise ::Zip::EntrySizeError, message
618
+ else
619
+ warn "WARNING: #{message}"
620
+ warned = true
621
+ end
622
+ end
608
623
  end
609
624
  end
610
625
  end
626
+
627
+ set_extra_attributes_on_path(dest_path)
611
628
  end
612
629
 
613
630
  def create_directory(dest_path)
@@ -629,7 +646,7 @@ module Zip
629
646
  def create_symlink(dest_path)
630
647
  # TODO: Symlinks pose security challenges. Symlink support temporarily
631
648
  # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
632
- puts "WARNING: skipped symlink #{dest_path}"
649
+ warn "WARNING: skipped symlink '#{dest_path}'."
633
650
  end
634
651
 
635
652
  # apply missing data from the zip64 extra information field, if present
@@ -4,6 +4,7 @@ module Zip
4
4
  class DestinationFileExistsError < Error; end
5
5
  class CompressionMethodError < Error; end
6
6
  class EntryNameError < Error; end
7
+ class EntrySizeError < Error; end
7
8
  class InternalError < Error; end
8
9
  class GPFBit3Error < Error; end
9
10
 
@@ -26,7 +26,7 @@ module Zip
26
26
  end
27
27
 
28
28
  def create_unknown_item
29
- s = ''
29
+ s = ''.dup
30
30
  class << s
31
31
  alias_method :to_c_dir_bin, :to_s
32
32
  alias_method :to_local_bin, :to_s
@@ -16,7 +16,7 @@ module Zip
16
16
  # If nil, start with empty.
17
17
  return false
18
18
  elsif binstr[0, 2] != self.class.const_get(:HEADER_ID)
19
- $stderr.puts 'Warning: weired extra feild header ID. skip parsing'
19
+ warn 'WARNING: weird extra field header ID. Skip parsing it.'
20
20
  return false
21
21
  end
22
22
  [binstr[2, 2].unpack('v')[0], binstr[4..-1]]
@@ -4,24 +4,51 @@ 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
13
25
  end
14
26
 
15
- attr_accessor :atime, :ctime, :mtime, :flag
27
+ def ctime=(time)
28
+ @flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
29
+ @ctime = time
30
+ end
31
+
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
+ @mtime ||= ::Zip::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
48
+ return if times[0].nil?
49
+ @atime ||= ::Zip::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
50
+ return if times[0].nil?
51
+ @ctime ||= ::Zip::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
25
52
  end
26
53
 
27
54
  def ==(other)
@@ -32,15 +59,15 @@ module Zip
32
59
 
33
60
  def pack_for_local
34
61
  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')
62
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
63
+ s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
64
+ s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
38
65
  s
39
66
  end
40
67
 
41
68
  def pack_for_c_dir
42
69
  s = [@flag].pack('C')
43
- @flag & 1 == 1 && s << [@mtime.to_i].pack('V')
70
+ s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
44
71
  s
45
72
  end
46
73
  end