rubyzip 1.3.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -8
  3. data/Rakefile +3 -0
  4. data/lib/zip.rb +4 -3
  5. data/lib/zip/central_directory.rb +9 -5
  6. data/lib/zip/constants.rb +52 -0
  7. data/lib/zip/crypto/decrypted_io.rb +40 -0
  8. data/lib/zip/crypto/traditional_encryption.rb +9 -9
  9. data/lib/zip/decompressor.rb +19 -1
  10. data/lib/zip/dos_time.rb +12 -7
  11. data/lib/zip/entry.rb +57 -38
  12. data/lib/zip/entry_set.rb +2 -0
  13. data/lib/zip/errors.rb +1 -0
  14. data/lib/zip/extra_field.rb +11 -9
  15. data/lib/zip/extra_field/generic.rb +10 -9
  16. data/lib/zip/extra_field/ntfs.rb +4 -0
  17. data/lib/zip/extra_field/old_unix.rb +3 -1
  18. data/lib/zip/extra_field/universal_time.rb +42 -12
  19. data/lib/zip/extra_field/unix.rb +3 -1
  20. data/lib/zip/extra_field/zip64.rb +4 -2
  21. data/lib/zip/file.rb +79 -54
  22. data/lib/zip/filesystem.rb +193 -177
  23. data/lib/zip/inflater.rb +24 -36
  24. data/lib/zip/input_stream.rb +33 -26
  25. data/lib/zip/ioextras.rb +1 -1
  26. data/lib/zip/ioextras/abstract_input_stream.rb +19 -8
  27. data/lib/zip/ioextras/abstract_output_stream.rb +1 -1
  28. data/lib/zip/null_decompressor.rb +1 -9
  29. data/lib/zip/output_stream.rb +14 -5
  30. data/lib/zip/pass_thru_compressor.rb +2 -2
  31. data/lib/zip/pass_thru_decompressor.rb +13 -22
  32. data/lib/zip/streamable_directory.rb +3 -3
  33. data/lib/zip/streamable_stream.rb +6 -10
  34. data/lib/zip/version.rb +1 -1
  35. data/samples/example.rb +2 -2
  36. data/samples/example_filesystem.rb +1 -1
  37. data/samples/gtk_ruby_zip.rb +19 -19
  38. data/samples/qtzip.rb +6 -6
  39. data/samples/write_simple.rb +2 -4
  40. data/samples/zipfind.rb +23 -22
  41. metadata +28 -169
  42. data/test/basic_zip_file_test.rb +0 -60
  43. data/test/case_sensitivity_test.rb +0 -69
  44. data/test/central_directory_entry_test.rb +0 -69
  45. data/test/central_directory_test.rb +0 -100
  46. data/test/crypto/null_encryption_test.rb +0 -57
  47. data/test/crypto/traditional_encryption_test.rb +0 -80
  48. data/test/data/WarnInvalidDate.zip +0 -0
  49. data/test/data/file1.txt +0 -46
  50. data/test/data/file1.txt.deflatedData +0 -0
  51. data/test/data/file2.txt +0 -1504
  52. data/test/data/globTest.zip +0 -0
  53. data/test/data/globTest/foo.txt +0 -0
  54. data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
  55. data/test/data/globTest/food.txt +0 -0
  56. data/test/data/gpbit3stored.zip +0 -0
  57. data/test/data/mimetype +0 -1
  58. data/test/data/notzippedruby.rb +0 -7
  59. data/test/data/ntfs.zip +0 -0
  60. data/test/data/oddExtraField.zip +0 -0
  61. data/test/data/path_traversal/Makefile +0 -10
  62. data/test/data/path_traversal/jwilk/README.md +0 -5
  63. data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
  64. data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
  65. data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
  66. data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
  67. data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
  68. data/test/data/path_traversal/jwilk/relative0.zip +0 -0
  69. data/test/data/path_traversal/jwilk/relative2.zip +0 -0
  70. data/test/data/path_traversal/jwilk/symlink.zip +0 -0
  71. data/test/data/path_traversal/relative1.zip +0 -0
  72. data/test/data/path_traversal/tilde.zip +0 -0
  73. data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
  74. data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
  75. data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
  76. data/test/data/rubycode.zip +0 -0
  77. data/test/data/rubycode2.zip +0 -0
  78. data/test/data/test.xls +0 -0
  79. data/test/data/testDirectory.bin +0 -0
  80. data/test/data/zip64-sample.zip +0 -0
  81. data/test/data/zipWithDirs.zip +0 -0
  82. data/test/data/zipWithEncryption.zip +0 -0
  83. data/test/deflater_test.rb +0 -65
  84. data/test/encryption_test.rb +0 -42
  85. data/test/entry_set_test.rb +0 -163
  86. data/test/entry_test.rb +0 -154
  87. data/test/errors_test.rb +0 -35
  88. data/test/extra_field_test.rb +0 -76
  89. data/test/file_extract_directory_test.rb +0 -54
  90. data/test/file_extract_test.rb +0 -145
  91. data/test/file_permissions_test.rb +0 -65
  92. data/test/file_split_test.rb +0 -57
  93. data/test/file_test.rb +0 -666
  94. data/test/filesystem/dir_iterator_test.rb +0 -58
  95. data/test/filesystem/directory_test.rb +0 -139
  96. data/test/filesystem/file_mutating_test.rb +0 -87
  97. data/test/filesystem/file_nonmutating_test.rb +0 -508
  98. data/test/filesystem/file_stat_test.rb +0 -64
  99. data/test/gentestfiles.rb +0 -126
  100. data/test/inflater_test.rb +0 -14
  101. data/test/input_stream_test.rb +0 -182
  102. data/test/ioextras/abstract_input_stream_test.rb +0 -102
  103. data/test/ioextras/abstract_output_stream_test.rb +0 -106
  104. data/test/ioextras/fake_io_test.rb +0 -18
  105. data/test/local_entry_test.rb +0 -154
  106. data/test/output_stream_test.rb +0 -128
  107. data/test/pass_thru_compressor_test.rb +0 -30
  108. data/test/pass_thru_decompressor_test.rb +0 -14
  109. data/test/path_traversal_test.rb +0 -141
  110. data/test/samples/example_recursive_test.rb +0 -37
  111. data/test/settings_test.rb +0 -95
  112. data/test/test_helper.rb +0 -234
  113. data/test/unicode_file_names_and_comments_test.rb +0 -62
  114. data/test/zip64_full_test.rb +0 -51
  115. data/test/zip64_support_test.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7861f60d52fbe54891831d9239df3e3e927a33f1f2d00abcb7b2693a8c01097d
4
- data.tar.gz: c04844369dbc75f40583f3d8aab34cbf2cfcce293b3c30570fa76208755a3157
3
+ metadata.gz: 8ffab0e42187d7bbd5b732ff0b9395a6215587a18ce5dd9d64da46d1c628a69c
4
+ data.tar.gz: 3032ae5d4f62644c1a19d5de25d515844ecb8da0f59c774fcaa6b16904f496b7
5
5
  SHA512:
6
- metadata.gz: f9fa8a9f92c4789f06a1691eee6201918a753f1356eb6f2ef99fe9c777d46d661b7a8f96ce71b7a18406a4e1a4098f81b8a282babe2a7284ae0c8c04d03cf758
7
- data.tar.gz: e4650b6f572ef4bcdb33cff4bd0c7ddbd399b04278bd644d11ea29defe057510706d15d0b8ac918df28280d13f4bdfec28526aaad258ff52136ce209f29a35dd
6
+ metadata.gz: 218f77b0c4ae423f2baed702bf56bb598e2c3d22a01fc9af8c2fa33432580e2706a820474fa1222480370517a9f28bda692a2f296d89620459d9c6239fcfff37
7
+ data.tar.gz: a4ffa820bb2272b07bd918827d03751261f87d694966ef6c88f74f4ec569c94b0420c9288a92e5370653d00790478bdd85d3e89a9ef42ccd94834facb622c336
data/README.md CHANGED
@@ -20,7 +20,7 @@ gem 'zip-zip' # will load compatibility for old rubyzip API.
20
20
 
21
21
  ## Requirements
22
22
 
23
- - Ruby 1.9.2 or greater
23
+ - Ruby 2.4 or greater (for rubyzip 2.0; use 1.x for older rubies)
24
24
 
25
25
  ## Installation
26
26
 
@@ -265,13 +265,7 @@ Zip.warn_invalid_date = false
265
265
 
266
266
  ### Size Validation
267
267
 
268
- **This setting defaults to `false` in rubyzip 1.3 for backward compatibility, but it will default to `true` in rubyzip 2.0.**
269
-
270
- If you set
271
- ```
272
- Zip.validate_entry_sizes = true
273
- ```
274
- then `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:
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:
275
269
 
276
270
  ```ruby
277
271
  MAX_FILE_SIZE = 10 * 1024**2 # 10MiB
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
+ require 'rubocop/rake_task'
3
4
 
4
5
  task default: :test
5
6
 
@@ -10,6 +11,8 @@ Rake::TestTask.new(:test) do |test|
10
11
  test.verbose = true
11
12
  end
12
13
 
14
+ RuboCop::RakeTask.new
15
+
13
16
  # Rake::TestTask.new(:zip64_full_test) do |test|
14
17
  # test.libs << File.join(File.dirname(__FILE__), 'lib')
15
18
  # test.libs << File.join(File.dirname(__FILE__), 'test')
data/lib/zip.rb CHANGED
@@ -1,10 +1,11 @@
1
+ require 'English'
1
2
  require 'delegate'
2
3
  require 'singleton'
3
4
  require 'tempfile'
4
- require 'tmpdir'
5
5
  require 'fileutils'
6
6
  require 'stringio'
7
7
  require 'zlib'
8
+ require 'zip/constants'
8
9
  require 'zip/dos_time'
9
10
  require 'zip/ioextras'
10
11
  require 'rbconfig'
@@ -22,6 +23,7 @@ require 'zip/null_compressor'
22
23
  require 'zip/null_input_stream'
23
24
  require 'zip/pass_thru_compressor'
24
25
  require 'zip/pass_thru_decompressor'
26
+ require 'zip/crypto/decrypted_io'
25
27
  require 'zip/crypto/encryption'
26
28
  require 'zip/crypto/null_encryption'
27
29
  require 'zip/crypto/traditional_encryption'
@@ -29,7 +31,6 @@ require 'zip/inflater'
29
31
  require 'zip/deflater'
30
32
  require 'zip/streamable_stream'
31
33
  require 'zip/streamable_directory'
32
- require 'zip/constants'
33
34
  require 'zip/errors'
34
35
 
35
36
  module Zip
@@ -55,7 +56,7 @@ module Zip
55
56
  @write_zip64_support = false
56
57
  @warn_invalid_date = true
57
58
  @case_insensitive_match = false
58
- @validate_entry_sizes = false
59
+ @validate_entry_sizes = true
59
60
  end
60
61
 
61
62
  def setup
@@ -65,7 +65,7 @@ module Zip
65
65
  @entry_set ? @entry_set.size : 0, # number of entries on this disk
66
66
  @entry_set ? @entry_set.size : 0, # number of entries total
67
67
  cdir_size, # size of central directory
68
- offset, # offset of start of central directory in its disk
68
+ offset # offset of start of central directory in its disk
69
69
  ]
70
70
  io << tmp.pack('VQ<vvVVQ<Q<Q<Q<')
71
71
  end
@@ -141,6 +141,7 @@ module Zip
141
141
  def get_e_o_c_d(buf) #:nodoc:
142
142
  sig_index = buf.rindex([END_OF_CDS].pack('V'))
143
143
  raise Error, 'Zip end of central directory signature not found' unless sig_index
144
+
144
145
  buf = buf.slice!((sig_index + 4)..(buf.bytesize))
145
146
 
146
147
  def buf.read(count)
@@ -166,8 +167,10 @@ module Zip
166
167
  def get_64_e_o_c_d(buf) #:nodoc:
167
168
  zip_64_start = buf.rindex([ZIP64_END_OF_CDS].pack('V'))
168
169
  raise Error, 'Zip64 end of central directory signature not found' unless zip_64_start
170
+
169
171
  zip_64_locator = buf.rindex([ZIP64_EOCD_LOCATOR].pack('V'))
170
172
  raise Error, 'Zip64 end of central directory signature locator not found' unless zip_64_locator
173
+
171
174
  buf = buf.slice!((zip_64_start + 4)..zip_64_locator)
172
175
 
173
176
  def buf.read(count)
@@ -178,8 +181,8 @@ module Zip
178
181
  end
179
182
 
180
183
  # For iterating over the entries.
181
- def each(&proc)
182
- @entry_set.each(&proc)
184
+ def each(&a_proc)
185
+ @entry_set.each(&a_proc)
183
186
  end
184
187
 
185
188
  # Returns the number of entries in the central directory (and
@@ -191,13 +194,14 @@ module Zip
191
194
  def self.read_from_stream(io) #:nodoc:
192
195
  cdir = new
193
196
  cdir.read_from_stream(io)
194
- return cdir
197
+ cdir
195
198
  rescue Error
196
- return nil
199
+ nil
197
200
  end
198
201
 
199
202
  def ==(other) #:nodoc:
200
203
  return false unless other.kind_of?(CentralDirectory)
204
+
201
205
  @entry_set.entries.sort == other.entries.sort && comment == other.comment
202
206
  end
203
207
  end
data/lib/zip/constants.rb CHANGED
@@ -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,40 @@
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
+
16
+ buffer << produce_input
17
+ end
18
+
19
+ outbuf.replace(buffer.slice!(0...(length || output_buffer.bytesize)))
20
+ end
21
+
22
+ private
23
+
24
+ def eof
25
+ buffer.empty? && input_finished?
26
+ end
27
+
28
+ def buffer
29
+ @buffer ||= +''
30
+ end
31
+
32
+ def input_finished?
33
+ @io.eof
34
+ end
35
+
36
+ def produce_input
37
+ @decrypter.decrypt(@io.read(CHUNK_SIZE))
38
+ end
39
+ end
40
+ end
@@ -24,8 +24,8 @@ module Zip
24
24
  end
25
25
  end
26
26
 
27
- def update_keys(n)
28
- @key0 = ~Zlib.crc32(n, ~@key0)
27
+ def update_keys(num)
28
+ @key0 = ~Zlib.crc32(num, ~@key0)
29
29
  @key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff
30
30
  @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
31
31
  end
@@ -63,10 +63,10 @@ module Zip
63
63
 
64
64
  private
65
65
 
66
- def encode(n)
66
+ def encode(num)
67
67
  t = decrypt_byte
68
- update_keys(n.chr)
69
- t ^ n
68
+ update_keys(num.chr)
69
+ t ^ num
70
70
  end
71
71
  end
72
72
 
@@ -86,10 +86,10 @@ module Zip
86
86
 
87
87
  private
88
88
 
89
- def decode(n)
90
- n ^= decrypt_byte
91
- update_keys(n.chr)
92
- n
89
+ def decode(num)
90
+ num ^= decrypt_byte
91
+ update_keys(num.chr)
92
+ num
93
93
  end
94
94
  end
95
95
  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
data/lib/zip/dos_time.rb CHANGED
@@ -29,13 +29,18 @@ module Zip
29
29
  to_i / 2 == other.to_i / 2
30
30
  end
31
31
 
32
- def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
33
- second = 2 * (0b11111 & binaryDosTime)
34
- minute = (0b11111100000 & binaryDosTime) >> 5
35
- hour = (0b1111100000000000 & binaryDosTime) >> 11
36
- day = (0b11111 & binaryDosDate)
37
- month = (0b111100000 & binaryDosDate) >> 5
38
- year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
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
+
37
+ def self.parse_binary_dos_format(bin_dos_date, bin_dos_time)
38
+ second = 2 * (0b11111 & bin_dos_time)
39
+ minute = (0b11111100000 & bin_dos_time) >> 5
40
+ hour = (0b1111100000000000 & bin_dos_time) >> 11
41
+ day = (0b11111 & bin_dos_date)
42
+ month = (0b111100000 & bin_dos_date) >> 5
43
+ year = ((0b1111111000000000 & bin_dos_date) >> 9) + 1980
39
44
  begin
40
45
  local(year, month, day, hour, minute, second)
41
46
  end
data/lib/zip/entry.rb CHANGED
@@ -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
@@ -48,6 +48,7 @@ module Zip
48
48
 
49
49
  def check_name(name)
50
50
  return unless name.start_with?('/')
51
+
51
52
  raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
52
53
  end
53
54
 
@@ -69,7 +70,15 @@ module Zip
69
70
  @time = args[8] || ::Zip::DOSTime.now
70
71
 
71
72
  @ftype = name_is_directory? ? :directory : :file
72
- @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField)
73
+ @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.kind_of?(::Zip::ExtraField)
74
+ end
75
+
76
+ def encrypted?
77
+ gp_flags & 1 == 1
78
+ end
79
+
80
+ def incomplete?
81
+ gp_flags & 8 == 8
73
82
  end
74
83
 
75
84
  def time
@@ -91,11 +100,12 @@ module Zip
91
100
  @extra.create('UniversalTime')
92
101
  end
93
102
  (@extra['UniversalTime'] || @extra['NTFS']).mtime = value
94
- @time = value
103
+ @time = value
95
104
  end
96
105
 
97
106
  def file_type_is?(type)
98
107
  raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype
108
+
99
109
  @ftype == type
100
110
  end
101
111
 
@@ -116,6 +126,7 @@ module Zip
116
126
  def name_safe?
117
127
  cleanpath = Pathname.new(@name).cleanpath
118
128
  return false unless cleanpath.relative?
129
+
119
130
  root = ::File::SEPARATOR
120
131
  naive_expanded_path = ::File.join(root, cleanpath.to_s)
121
132
  ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
@@ -145,6 +156,7 @@ module Zip
145
156
  # that we didn't change the header size (and thus clobber file data or something)
146
157
  def verify_local_header_size!
147
158
  return if @local_header_size.nil?
159
+
148
160
  new_size = calculate_local_header_size
149
161
  raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size
150
162
  end
@@ -163,19 +175,16 @@ module Zip
163
175
  # is passed.
164
176
  def extract(dest_path = nil, &block)
165
177
  if dest_path.nil? && !name_safe?
166
- puts "WARNING: skipped #{@name} as unsafe"
178
+ warn "WARNING: skipped '#{@name}' as unsafe."
167
179
  return self
168
180
  end
169
181
 
170
182
  dest_path ||= @name
171
183
  block ||= proc { ::Zip.on_exists_proc }
172
184
 
173
- if directory? || file? || symlink?
174
- __send__("create_#{@ftype}", dest_path, &block)
175
- else
176
- raise "unknown file type #{inspect}"
177
- end
185
+ raise "unknown file type #{inspect}" unless directory? || file? || symlink?
178
186
 
187
+ __send__("create_#{@ftype}", dest_path, &block)
179
188
  self
180
189
  end
181
190
 
@@ -185,15 +194,15 @@ module Zip
185
194
 
186
195
  class << self
187
196
  def read_zip_short(io) # :nodoc:
188
- io.read(2).unpack('v')[0]
197
+ io.read(2).unpack1('v')
189
198
  end
190
199
 
191
200
  def read_zip_long(io) # :nodoc:
192
- io.read(4).unpack('V')[0]
201
+ io.read(4).unpack1('V')
193
202
  end
194
203
 
195
204
  def read_zip_64_long(io) # :nodoc:
196
- io.read(8).unpack('Q<')[0]
205
+ io.read(8).unpack1('Q<')
197
206
  end
198
207
 
199
208
  def read_c_dir_entry(io) #:nodoc:all
@@ -218,8 +227,6 @@ module Zip
218
227
  end
219
228
  end
220
229
 
221
- public
222
-
223
230
  def unpack_local_entry(buf)
224
231
  @header_signature,
225
232
  @version,
@@ -249,6 +256,7 @@ module Zip
249
256
  unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE
250
257
  raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'"
251
258
  end
259
+
252
260
  set_time(@last_mod_date, @last_mod_time)
253
261
 
254
262
  @name = io.read(@name_length)
@@ -261,13 +269,14 @@ module Zip
261
269
 
262
270
  if extra && extra.bytesize != @extra_length
263
271
  raise ::Zip::Error, 'Truncated local zip entry header'
272
+ end
273
+
274
+ if @extra.kind_of?(::Zip::ExtraField)
275
+ @extra.merge(extra) if extra
264
276
  else
265
- if @extra.is_a?(::Zip::ExtraField)
266
- @extra.merge(extra) if extra
267
- else
268
- @extra = ::Zip::ExtraField.new(extra)
269
- end
277
+ @extra = ::Zip::ExtraField.new(extra)
270
278
  end
279
+
271
280
  parse_zip64_extra(true)
272
281
  @local_header_size = calculate_local_header_size
273
282
  end
@@ -354,21 +363,24 @@ module Zip
354
363
 
355
364
  def check_c_dir_entry_static_header_length(buf)
356
365
  return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
366
+
357
367
  raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
358
368
  end
359
369
 
360
370
  def check_c_dir_entry_signature
361
371
  return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
372
+
362
373
  raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
363
374
  end
364
375
 
365
376
  def check_c_dir_entry_comment_size
366
377
  return if @comment && @comment.bytesize == @comment_length
378
+
367
379
  raise ::Zip::Error, 'Truncated cdir zip entry header'
368
380
  end
369
381
 
370
382
  def read_c_dir_extra_field(io)
371
- if @extra.is_a?(::Zip::ExtraField)
383
+ if @extra.kind_of?(::Zip::ExtraField)
372
384
  @extra.merge(io.read(@extra_length))
373
385
  else
374
386
  @extra = ::Zip::ExtraField.new(io.read(@extra_length))
@@ -402,20 +414,25 @@ module Zip
402
414
 
403
415
  def get_extra_attributes_from_path(path) # :nodoc:
404
416
  return if Zip::RUNNING_ON_WINDOWS
417
+
405
418
  stat = file_stat(path)
406
419
  @unix_uid = stat.uid
407
420
  @unix_gid = stat.gid
408
421
  @unix_perms = stat.mode & 0o7777
422
+ @time = ::Zip::DOSTime.from_time(stat.mtime)
409
423
  end
410
424
 
411
- def set_unix_permissions_on_path(dest_path)
412
- # BUG: does not update timestamps into account
425
+ def set_unix_attributes_on_path(dest_path)
413
426
  # ignore setuid/setgid bits by default. honor if @restore_ownership
414
427
  unix_perms_mask = 0o1777
415
428
  unix_perms_mask = 0o7777 if @restore_ownership
416
429
  ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
417
430
  ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
418
- # File::utimes()
431
+
432
+ # Restore the timestamp on a file. This will either have come from the
433
+ # original source file that was copied into the archive, or from the
434
+ # creation date of the archive if there was no original source file.
435
+ ::FileUtils.touch(dest_path, mtime: time) if @restore_times
419
436
  end
420
437
 
421
438
  def set_extra_attributes_on_path(dest_path) # :nodoc:
@@ -423,7 +440,7 @@ module Zip
423
440
 
424
441
  case @fstype
425
442
  when ::Zip::FSTYPE_UNIX
426
- set_unix_permissions_on_path(dest_path)
443
+ set_unix_attributes_on_path(dest_path)
427
444
  end
428
445
  end
429
446
 
@@ -484,6 +501,7 @@ module Zip
484
501
 
485
502
  def ==(other)
486
503
  return false unless other.class == self.class
504
+
487
505
  # Compares contents of local entry and exposed fields
488
506
  keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
489
507
  other.__send__(k.to_sym) == __send__(k.to_sym)
@@ -591,7 +609,7 @@ module Zip
591
609
  def set_time(binary_dos_date, binary_dos_time)
592
610
  @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
593
611
  rescue ArgumentError
594
- warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date
612
+ warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
595
613
  end
596
614
 
597
615
  def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
@@ -601,30 +619,29 @@ module Zip
601
619
  end
602
620
  ::File.open(dest_path, 'wb') do |os|
603
621
  get_input_stream do |is|
604
- set_extra_attributes_on_path(dest_path)
605
-
606
622
  bytes_written = 0
607
623
  warned = false
608
- buf = ''.dup
624
+ buf = +''
609
625
  while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
610
626
  os << buf
611
627
  bytes_written += buf.bytesize
612
- if bytes_written > size && !warned
613
- message = "Entry #{name} should be #{size}B but is larger when inflated"
614
- if ::Zip.validate_entry_sizes
615
- raise ::Zip::EntrySizeError, message
616
- else
617
- puts "WARNING: #{message}"
618
- warned = true
619
- end
620
- end
628
+ next unless bytes_written > size && !warned
629
+
630
+ message = "entry '#{name}' should be #{size}B, but is larger when inflated."
631
+ raise ::Zip::EntrySizeError, message if ::Zip.validate_entry_sizes
632
+
633
+ warn "WARNING: #{message}"
634
+ warned = true
621
635
  end
622
636
  end
623
637
  end
638
+
639
+ set_extra_attributes_on_path(dest_path)
624
640
  end
625
641
 
626
642
  def create_directory(dest_path)
627
643
  return if ::File.directory?(dest_path)
644
+
628
645
  if ::File.exist?(dest_path)
629
646
  if block_given? && yield(self, dest_path)
630
647
  ::FileUtils.rm_f dest_path
@@ -642,13 +659,14 @@ module Zip
642
659
  def create_symlink(dest_path)
643
660
  # TODO: Symlinks pose security challenges. Symlink support temporarily
644
661
  # removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
645
- puts "WARNING: skipped symlink #{dest_path}"
662
+ warn "WARNING: skipped symlink '#{dest_path}'."
646
663
  end
647
664
 
648
665
  # apply missing data from the zip64 extra information field, if present
649
666
  # (required when file sizes exceed 2**32, but can be used for all files)
650
667
  def parse_zip64_extra(for_local_header) #:nodoc:all
651
668
  return if @extra['Zip64'].nil?
669
+
652
670
  if for_local_header
653
671
  @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size)
654
672
  else
@@ -663,6 +681,7 @@ module Zip
663
681
  # create a zip64 extra information field if we need one
664
682
  def prep_zip64_extra(for_local_header) #:nodoc:all
665
683
  return unless ::Zip.write_zip64_support
684
+
666
685
  need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
667
686
  need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header
668
687
  if need_zip64