rubyzip 1.2.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +64 -23
  3. data/lib/zip.rb +5 -3
  4. data/lib/zip/constants.rb +52 -0
  5. data/lib/zip/crypto/decrypted_io.rb +39 -0
  6. data/lib/zip/decompressor.rb +19 -1
  7. data/lib/zip/dos_time.rb +5 -0
  8. data/lib/zip/entry.rb +34 -10
  9. data/lib/zip/errors.rb +2 -0
  10. data/lib/zip/extra_field/generic.rb +1 -1
  11. data/lib/zip/extra_field/universal_time.rb +39 -12
  12. data/lib/zip/file.rb +68 -34
  13. data/lib/zip/inflater.rb +22 -36
  14. data/lib/zip/input_stream.rb +28 -24
  15. data/lib/zip/ioextras/abstract_input_stream.rb +6 -0
  16. data/lib/zip/null_decompressor.rb +1 -9
  17. data/lib/zip/pass_thru_decompressor.rb +13 -22
  18. data/lib/zip/streamable_stream.rb +1 -6
  19. data/lib/zip/version.rb +1 -1
  20. metadata +12 -154
  21. data/test/basic_zip_file_test.rb +0 -60
  22. data/test/case_sensitivity_test.rb +0 -69
  23. data/test/central_directory_entry_test.rb +0 -69
  24. data/test/central_directory_test.rb +0 -100
  25. data/test/crypto/null_encryption_test.rb +0 -57
  26. data/test/crypto/traditional_encryption_test.rb +0 -80
  27. data/test/data/WarnInvalidDate.zip +0 -0
  28. data/test/data/file1.txt +0 -46
  29. data/test/data/file1.txt.deflatedData +0 -0
  30. data/test/data/file2.txt +0 -1504
  31. data/test/data/globTest.zip +0 -0
  32. data/test/data/globTest/foo.txt +0 -0
  33. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  34. data/test/data/globTest/food.txt +0 -0
  35. data/test/data/gpbit3stored.zip +0 -0
  36. data/test/data/mimetype +0 -1
  37. data/test/data/notzippedruby.rb +0 -7
  38. data/test/data/ntfs.zip +0 -0
  39. data/test/data/oddExtraField.zip +0 -0
  40. data/test/data/path_traversal/Makefile +0 -10
  41. data/test/data/path_traversal/jwilk/README.md +0 -5
  42. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  43. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  44. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  45. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  46. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  47. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  48. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  49. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  50. data/test/data/path_traversal/relative1.zip +0 -0
  51. data/test/data/path_traversal/tilde.zip +0 -0
  52. data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
  53. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  54. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  55. data/test/data/rubycode.zip +0 -0
  56. data/test/data/rubycode2.zip +0 -0
  57. data/test/data/test.xls +0 -0
  58. data/test/data/testDirectory.bin +0 -0
  59. data/test/data/zip64-sample.zip +0 -0
  60. data/test/data/zipWithDirs.zip +0 -0
  61. data/test/data/zipWithEncryption.zip +0 -0
  62. data/test/deflater_test.rb +0 -65
  63. data/test/encryption_test.rb +0 -42
  64. data/test/entry_set_test.rb +0 -163
  65. data/test/entry_test.rb +0 -154
  66. data/test/errors_test.rb +0 -35
  67. data/test/extra_field_test.rb +0 -76
  68. data/test/file_extract_directory_test.rb +0 -54
  69. data/test/file_extract_test.rb +0 -83
  70. data/test/file_permissions_test.rb +0 -65
  71. data/test/file_split_test.rb +0 -57
  72. data/test/file_test.rb +0 -601
  73. data/test/filesystem/dir_iterator_test.rb +0 -58
  74. data/test/filesystem/directory_test.rb +0 -139
  75. data/test/filesystem/file_mutating_test.rb +0 -87
  76. data/test/filesystem/file_nonmutating_test.rb +0 -508
  77. data/test/filesystem/file_stat_test.rb +0 -64
  78. data/test/gentestfiles.rb +0 -126
  79. data/test/inflater_test.rb +0 -14
  80. data/test/input_stream_test.rb +0 -182
  81. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  82. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  83. data/test/ioextras/fake_io_test.rb +0 -18
  84. data/test/local_entry_test.rb +0 -154
  85. data/test/output_stream_test.rb +0 -128
  86. data/test/pass_thru_compressor_test.rb +0 -30
  87. data/test/pass_thru_decompressor_test.rb +0 -14
  88. data/test/path_traversal_test.rb +0 -141
  89. data/test/samples/example_recursive_test.rb +0 -37
  90. data/test/settings_test.rb +0 -95
  91. data/test/test_helper.rb +0 -234
  92. data/test/unicode_file_names_and_comments_test.rb +0 -62
  93. data/test/zip64_full_test.rb +0 -51
  94. data/test/zip64_support_test.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bed33b4d4b864f1ad4d1a3483022c13b8079a607b2e4afc5b3828b9267f04c8
4
- data.tar.gz: 0acb47d50691a266b1abc45e9b9c2abe5ac2310f38706ef5905bf53723e1f26d
3
+ metadata.gz: cba65b486ec6325e6623957990539a225398a2b9b665b5814a5c06ba70ea1744
4
+ data.tar.gz: 37dc716e5d98048e1b0c72165c16deb6a6bfef02556890f783cdc62ff5021f4e
5
5
  SHA512:
6
- metadata.gz: 64da2a44d5a0b167ad81023554552f7cf101a6e5eb380ef356abaeb3c97d40e6dc6a5013b3fbfa615823833106bb31c8766e38b81d378b685a4eb680f5cc4ab5
7
- data.tar.gz: 7f8157731ecfbb4497e97dcaff38bb6f259c5b18f070e04d5706170a999c7e0d41a32487fbf0e297836f60669f6d0a3e0cbbead7f5c2a06b56f651285786163e
6
+ metadata.gz: 45de19fa6e2c549c07830243686d622adb937509a942eeb1412a05ea6d1f4a1bde5d60429446ff64f607f3a55400a38c9ccd2d30380825fb4b6e55618702d802
7
+ data.tar.gz: 8d22f9bc464d950e9ede9cfc1da1056ba664ede954b93ae12d0af68e1d859600d8cd63ba5768553f3d810a693d38ce912c0c2d0f3b3d130da973c8bd833fb3bc
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,10 +1,10 @@
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'
7
+ require 'zip/constants'
8
8
  require 'zip/dos_time'
9
9
  require 'zip/ioextras'
10
10
  require 'rbconfig'
@@ -22,6 +22,7 @@ require 'zip/null_compressor'
22
22
  require 'zip/null_input_stream'
23
23
  require 'zip/pass_thru_compressor'
24
24
  require 'zip/pass_thru_decompressor'
25
+ require 'zip/crypto/decrypted_io'
25
26
  require 'zip/crypto/encryption'
26
27
  require 'zip/crypto/null_encryption'
27
28
  require 'zip/crypto/traditional_encryption'
@@ -29,7 +30,6 @@ require 'zip/inflater'
29
30
  require 'zip/deflater'
30
31
  require 'zip/streamable_stream'
31
32
  require 'zip/streamable_directory'
32
- require 'zip/constants'
33
33
  require 'zip/errors'
34
34
 
35
35
  module Zip
@@ -42,7 +42,8 @@ module Zip
42
42
  :write_zip64_support,
43
43
  :warn_invalid_date,
44
44
  :case_insensitive_match,
45
- :force_entry_names_encoding
45
+ :force_entry_names_encoding,
46
+ :validate_entry_sizes
46
47
 
47
48
  def reset!
48
49
  @_ran_once = false
@@ -54,6 +55,7 @@ module Zip
54
55
  @write_zip64_support = false
55
56
  @warn_invalid_date = true
56
57
  @case_insensitive_match = false
58
+ @validate_entry_sizes = true
57
59
  end
58
60
 
59
61
  def setup
@@ -60,4 +60,56 @@ module Zip
60
60
  FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze,
61
61
  FSTYPE_ATHEOS => 'AtheOS'.freeze
62
62
  }.freeze
63
+
64
+ COMPRESSION_METHOD_STORE = 0
65
+ COMPRESSION_METHOD_SHRINK = 1
66
+ COMPRESSION_METHOD_REDUCE_1 = 2
67
+ COMPRESSION_METHOD_REDUCE_2 = 3
68
+ COMPRESSION_METHOD_REDUCE_3 = 4
69
+ COMPRESSION_METHOD_REDUCE_4 = 5
70
+ COMPRESSION_METHOD_IMPLODE = 6
71
+ # RESERVED = 7
72
+ COMPRESSION_METHOD_DEFLATE = 8
73
+ COMPRESSION_METHOD_DEFLATE_64 = 9
74
+ COMPRESSION_METHOD_PKWARE_DCLI = 10
75
+ # RESERVED = 11
76
+ COMPRESSION_METHOD_BZIP2 = 12
77
+ # RESERVED = 13
78
+ COMPRESSION_METHOD_LZMA = 14
79
+ # RESERVED = 15
80
+ COMPRESSION_METHOD_IBM_CMPSC = 16
81
+ # RESERVED = 17
82
+ COMPRESSION_METHOD_IBM_TERSE = 18
83
+ COMPRESSION_METHOD_IBM_LZ77 = 19
84
+ COMPRESSION_METHOD_JPEG = 96
85
+ COMPRESSION_METHOD_WAVPACK = 97
86
+ COMPRESSION_METHOD_PPMD = 98
87
+ COMPRESSION_METHOD_AES = 99
88
+
89
+ COMPRESSION_METHODS = {
90
+ COMPRESSION_METHOD_STORE => 'Store (no compression)',
91
+ COMPRESSION_METHOD_SHRINK => 'Shrink',
92
+ COMPRESSION_METHOD_REDUCE_1 => 'Reduce with compression factor 1',
93
+ COMPRESSION_METHOD_REDUCE_2 => 'Reduce with compression factor 2',
94
+ COMPRESSION_METHOD_REDUCE_3 => 'Reduce with compression factor 3',
95
+ COMPRESSION_METHOD_REDUCE_4 => 'Reduce with compression factor 4',
96
+ COMPRESSION_METHOD_IMPLODE => 'Implode',
97
+ # RESERVED = 7
98
+ COMPRESSION_METHOD_DEFLATE => 'Deflate',
99
+ COMPRESSION_METHOD_DEFLATE_64 => 'Deflate64(tm)',
100
+ COMPRESSION_METHOD_PKWARE_DCLI => 'PKWARE Data Compression Library Imploding (old IBM TERSE)',
101
+ # RESERVED = 11
102
+ COMPRESSION_METHOD_BZIP2 => 'BZIP2',
103
+ # RESERVED = 13
104
+ COMPRESSION_METHOD_LZMA => 'LZMA',
105
+ # RESERVED = 15
106
+ COMPRESSION_METHOD_IBM_CMPSC => 'IBM z/OS CMPSC Compression',
107
+ # RESERVED = 17
108
+ COMPRESSION_METHOD_IBM_TERSE => 'IBM TERSE (new)',
109
+ COMPRESSION_METHOD_IBM_LZ77 => 'IBM LZ77 z Architecture (PFS)',
110
+ COMPRESSION_METHOD_JPEG => 'JPEG variant',
111
+ COMPRESSION_METHOD_WAVPACK => 'WavPack compressed data',
112
+ COMPRESSION_METHOD_PPMD => 'PPMd version I, Rev 1',
113
+ COMPRESSION_METHOD_AES => 'AES encryption',
114
+ }.freeze
63
115
  end
@@ -0,0 +1,39 @@
1
+ module Zip
2
+ class DecryptedIo #:nodoc:all
3
+ CHUNK_SIZE = 32_768
4
+
5
+ def initialize(io, decrypter)
6
+ @io = io
7
+ @decrypter = decrypter
8
+ end
9
+
10
+ def read(length = nil, outbuf = '')
11
+ return ((length.nil? || length.zero?) ? "" : nil) if eof
12
+
13
+ while length.nil? || (buffer.bytesize < length)
14
+ break if input_finished?
15
+ buffer << produce_input
16
+ end
17
+
18
+ outbuf.replace(buffer.slice!(0...(length || output_buffer.bytesize)))
19
+ end
20
+
21
+ private
22
+
23
+ def eof
24
+ buffer.empty? && input_finished?
25
+ end
26
+
27
+ def buffer
28
+ @buffer ||= ''.dup
29
+ end
30
+
31
+ def input_finished?
32
+ @io.eof
33
+ end
34
+
35
+ def produce_input
36
+ @decrypter.decrypt(@io.read(CHUNK_SIZE))
37
+ end
38
+ end
39
+ end
@@ -1,9 +1,27 @@
1
1
  module Zip
2
2
  class Decompressor #:nodoc:all
3
3
  CHUNK_SIZE = 32_768
4
- def initialize(input_stream)
4
+
5
+ def self.decompressor_classes
6
+ @decompressor_classes ||= {}
7
+ end
8
+
9
+ def self.register(compression_method, decompressor_class)
10
+ decompressor_classes[compression_method] = decompressor_class
11
+ end
12
+
13
+ def self.find_by_compression_method(compression_method)
14
+ decompressor_classes[compression_method]
15
+ end
16
+
17
+ attr_reader :input_stream
18
+ attr_reader :decompressed_size
19
+
20
+ def initialize(input_stream, decompressed_size = nil)
5
21
  super()
22
+
6
23
  @input_stream = input_stream
24
+ @decompressed_size = decompressed_size
7
25
  end
8
26
  end
9
27
  end
@@ -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
@@ -34,7 +34,7 @@ module Zip
34
34
  end
35
35
  @follow_symlinks = false
36
36
 
37
- @restore_times = true
37
+ @restore_times = false
38
38
  @restore_permissions = false
39
39
  @restore_ownership = false
40
40
  # BUG: need an extra field to support uid/gid's
@@ -72,6 +72,14 @@ module Zip
72
72
  @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
73
73
  end
74
74
 
75
+ def encrypted?
76
+ gp_flags & 1 == 1
77
+ end
78
+
79
+ def incomplete?
80
+ gp_flags & 8 == 8
81
+ end
82
+
75
83
  def time
76
84
  if @extra['UniversalTime']
77
85
  @extra['UniversalTime'].mtime
@@ -163,7 +171,7 @@ module Zip
163
171
  # is passed.
164
172
  def extract(dest_path = nil, &block)
165
173
  if dest_path.nil? && !name_safe?
166
- puts "WARNING: skipped #{@name} as unsafe"
174
+ warn "WARNING: skipped '#{@name}' as unsafe."
167
175
  return self
168
176
  end
169
177
 
@@ -406,16 +414,20 @@ module Zip
406
414
  @unix_uid = stat.uid
407
415
  @unix_gid = stat.gid
408
416
  @unix_perms = stat.mode & 0o7777
417
+ @time = ::Zip::DOSTime.from_time(stat.mtime)
409
418
  end
410
419
 
411
- def set_unix_permissions_on_path(dest_path)
412
- # BUG: does not update timestamps into account
420
+ def set_unix_attributes_on_path(dest_path)
413
421
  # ignore setuid/setgid bits by default. honor if @restore_ownership
414
422
  unix_perms_mask = 0o1777
415
423
  unix_perms_mask = 0o7777 if @restore_ownership
416
424
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
417
425
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
418
- # File::utimes()
426
+
427
+ # Restore the timestamp on a file. This will either have come from the
428
+ # original source file that was copied into the archive, or from the
429
+ # creation date of the archive if there was no original source file.
430
+ ::FileUtils.touch(dest_path, mtime: time) if @restore_times
419
431
  end
420
432
 
421
433
  def set_extra_attributes_on_path(dest_path) # :nodoc:
@@ -423,7 +435,7 @@ module Zip
423
435
 
424
436
  case @fstype
425
437
  when ::Zip::FSTYPE_UNIX
426
- set_unix_permissions_on_path(dest_path)
438
+ set_unix_attributes_on_path(dest_path)
427
439
  end
428
440
  end
429
441
 
@@ -591,7 +603,7 @@ module Zip
591
603
  def set_time(binary_dos_date, binary_dos_time)
592
604
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
593
605
  rescue ArgumentError
594
- warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
606
+ warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
595
607
  end
596
608
 
597
609
  def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
@@ -601,14 +613,26 @@ module Zip
601
613
  end
602
614
  ::File.open(dest_path, 'wb') do |os|
603
615
  get_input_stream do |is|
604
- set_extra_attributes_on_path(dest_path)
605
-
616
+ bytes_written = 0
617
+ warned = false
606
618
  buf = ''.dup
607
619
  while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
608
620
  os << buf
621
+ bytes_written += buf.bytesize
622
+ if bytes_written > size && !warned
623
+ message = "entry '#{name}' should be #{size}B, but is larger when inflated."
624
+ if ::Zip.validate_entry_sizes
625
+ raise ::Zip::EntrySizeError, message
626
+ else
627
+ warn "WARNING: #{message}"
628
+ warned = true
629
+ end
630
+ end
609
631
  end
610
632
  end
611
633
  end
634
+
635
+ set_extra_attributes_on_path(dest_path)
612
636
  end
613
637
 
614
638
  def create_directory(dest_path)
@@ -630,7 +654,7 @@ module Zip
630
654
  def create_symlink(dest_path)
631
655
  # TODO: Symlinks pose security challenges. Symlink support temporarily
632
656
  # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
633
- puts "WARNING: skipped symlink #{dest_path}"
657
+ warn "WARNING: skipped symlink '#{dest_path}'."
634
658
  end
635
659
 
636
660
  # apply missing data from the zip64 extra information field, if present