rubyzip 3.0.2 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changelog.md +21 -0
- data/README.md +15 -6
- data/Rakefile +4 -6
- data/lib/zip/crypto/aes_encryption.rb +120 -0
- data/lib/zip/crypto/decrypted_io.rb +17 -13
- data/lib/zip/crypto/null_encryption.rb +0 -10
- data/lib/zip/crypto/traditional_encryption.rb +2 -0
- data/lib/zip/dos_time.rb +3 -2
- data/lib/zip/entry.rb +21 -0
- data/lib/zip/extra_field/aes.rb +45 -0
- data/lib/zip/extra_field.rb +1 -0
- data/lib/zip/file.rb +2 -4
- data/lib/zip/filesystem/file.rb +4 -3
- data/lib/zip/inflater.rb +4 -3
- data/lib/zip/input_stream.rb +31 -12
- data/lib/zip/ioextras/abstract_input_stream.rb +3 -2
- data/lib/zip/null_decompressor.rb +3 -2
- data/lib/zip/pass_thru_decompressor.rb +4 -3
- data/lib/zip/streamable_stream.rb +1 -1
- data/lib/zip/version.rb +2 -1
- data/lib/zip.rb +2 -0
- data/rubyzip.gemspec +3 -3
- data/samples/gtk_ruby_zip.rb +1 -1
- data/samples/qtzip.rb +1 -1
- metadata +14 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 869a32fef1374d44c2d01fcf90d92d10760ceb619cbc2ccb545f7753503efee4
|
4
|
+
data.tar.gz: 9eb0b0601572717187029c44fbcfdd7fa3a47005d869d8aea2b36ab465b18de5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cc3d0b1cb58eabdafb91db1cc8e7a2ef3932bcaf993df9a8c21ae261d480ffa43d431111faa11f5e19e52aa59a70e14a4724bbd174a8628a7ffacb48119ccdb
|
7
|
+
data.tar.gz: 1171566d3c91b07b01e3f5fe77e9d098f6c5fa6f94891cf289c1edbaa35bbe2a3a96165bebfcb7b35f2d41eedb268b13e90aab27683cd05175ea0ffbc9a485c6
|
data/Changelog.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# 3.1.1 (2025-09-26)
|
2
|
+
|
3
|
+
- Improve the IO pipeline when decompressing. [#649](https://github.com/rubyzip/rubyzip/pull/649) (which also fixes [#647](https://github.com/rubyzip/rubyzip/issues/647))
|
4
|
+
|
5
|
+
Tooling/internal:
|
6
|
+
|
7
|
+
- Improve the `DecryptedIo` class with various updates and optimizations.
|
8
|
+
- Remove the `NullDecrypter` class.
|
9
|
+
- Properly convert the test suite to use minitest.
|
10
|
+
- Move all test helper code into separate files.
|
11
|
+
- Updates to the Actions CI, including new OS versions.
|
12
|
+
- Update rubocop versions and fix resultant cop failures. [#646](https://github.com/rubyzip/rubyzip/pull/646)
|
13
|
+
|
14
|
+
# 3.1.0 (2025-09-06)
|
15
|
+
|
16
|
+
- Support AES decryption. [#579](https://github.com/rubyzip/rubyzip/pull/579) and [#645](https://github.com/rubyzip/rubyzip/pull/645)
|
17
|
+
|
18
|
+
Tooling/internal:
|
19
|
+
|
20
|
+
- Add various useful zip specification documents to the repo for ease of finding them in the future. These are not included in the gem release.
|
21
|
+
|
1
22
|
# 3.0.2 (2025-08-21)
|
2
23
|
|
3
24
|
- Fix `InputStream#sysread` to handle frozen string literals. [#643](https://github.com/rubyzip/rubyzip/pull/643)
|
data/README.md
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
[](http://badge.fury.io/rb/rubyzip)
|
4
4
|
[](https://github.com/rubyzip/rubyzip/actions/workflows/tests.yml)
|
5
5
|
[](https://github.com/rubyzip/rubyzip/actions/workflows/lint.yml)
|
6
|
-
[](https://github.com/rubocop/rubocop)
|
7
|
+
[](https://qlty.sh/gh/rubyzip/projects/rubyzip)
|
7
8
|
[](https://coveralls.io/r/rubyzip/rubyzip?branch=master)
|
8
9
|
|
9
10
|
Rubyzip is a ruby library for reading and writing zip files.
|
@@ -201,11 +202,11 @@ end
|
|
201
202
|
|
202
203
|
Any attempt to move about in a zip file opened with `Zip::InputStream` could result in the incorrect entry being accessed and/or Zlib buffer errors. If you need random access in a zip file, use `Zip::File`.
|
203
204
|
|
204
|
-
### Password Protection (
|
205
|
+
### Password Protection (experimental)
|
205
206
|
|
206
|
-
Rubyzip supports reading
|
207
|
+
Rubyzip supports reading zip files with AES encryption (version 3.1 and later), and reading and writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). Encryption is currently only available with the stream API, with either files or buffers, e.g.:
|
207
208
|
|
208
|
-
#### Version 2.x
|
209
|
+
#### Version 2.x (ZipCrypto only)
|
209
210
|
|
210
211
|
```ruby
|
211
212
|
# Writing.
|
@@ -224,9 +225,17 @@ Zip::InputStream.open(buffer, 0, dec) do |input|
|
|
224
225
|
end
|
225
226
|
```
|
226
227
|
|
227
|
-
#### Version 3.x
|
228
|
+
#### Version 3.x (AES reading and ZipCrypto read/write)
|
228
229
|
|
229
230
|
```ruby
|
231
|
+
# Reading AES, version 3.1 and later.
|
232
|
+
dec = Zip::AESDecrypter.new('password', Zip::AESEncryption::STRENGTH_256_BIT)
|
233
|
+
Zip::InputStream.open('aes-encrypted-file.zip', decrypter: dec) do |input|
|
234
|
+
entry = input.get_next_entry
|
235
|
+
puts "Contents of '#{entry.name}':"
|
236
|
+
puts input.read
|
237
|
+
end
|
238
|
+
|
230
239
|
# Writing.
|
231
240
|
enc = Zip::TraditionalEncrypter.new('password')
|
232
241
|
buffer = Zip::OutputStream.write_buffer(encrypter: enc) do |output|
|
@@ -243,7 +252,7 @@ Zip::InputStream.open(buffer, decrypter: dec) do |input|
|
|
243
252
|
end
|
244
253
|
```
|
245
254
|
|
246
|
-
_This is an
|
255
|
+
_This is an evolving feature and the interface for encryption may change in future versions._
|
247
256
|
|
248
257
|
## Known issues
|
249
258
|
|
data/Rakefile
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
|
-
require '
|
4
|
+
require 'minitest/test_task'
|
5
5
|
require 'rdoc/task'
|
6
6
|
require 'rubocop/rake_task'
|
7
7
|
|
8
8
|
task default: :test
|
9
9
|
|
10
|
-
|
11
|
-
test.
|
12
|
-
test.
|
13
|
-
test.pattern = 'test/**/*_test.rb'
|
14
|
-
test.verbose = true
|
10
|
+
Minitest::TestTask.create do |test|
|
11
|
+
test.framework = 'require "simplecov"'
|
12
|
+
test.test_globs = 'test/**/*_test.rb'
|
15
13
|
end
|
16
14
|
|
17
15
|
RDoc::Task.new do |rdoc|
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Zip
|
6
|
+
module AESEncryption # :nodoc:
|
7
|
+
VERIFIER_LENGTH = 2
|
8
|
+
BLOCK_SIZE = 16
|
9
|
+
AUTHENTICATION_CODE_LENGTH = 10
|
10
|
+
|
11
|
+
VERSION_AE_1 = 0x01
|
12
|
+
VERSION_AE_2 = 0x02
|
13
|
+
|
14
|
+
VERSIONS = [
|
15
|
+
VERSION_AE_1,
|
16
|
+
VERSION_AE_2
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
STRENGTH_128_BIT = 0x01
|
20
|
+
STRENGTH_192_BIT = 0x02
|
21
|
+
STRENGTH_256_BIT = 0x03
|
22
|
+
|
23
|
+
STRENGTHS = [
|
24
|
+
STRENGTH_128_BIT,
|
25
|
+
STRENGTH_192_BIT,
|
26
|
+
STRENGTH_256_BIT
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
BITS = {
|
30
|
+
STRENGTH_128_BIT => 128,
|
31
|
+
STRENGTH_192_BIT => 192,
|
32
|
+
STRENGTH_256_BIT => 256
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
KEY_LENGTHS = {
|
36
|
+
STRENGTH_128_BIT => 16,
|
37
|
+
STRENGTH_192_BIT => 24,
|
38
|
+
STRENGTH_256_BIT => 32
|
39
|
+
}.freeze
|
40
|
+
|
41
|
+
SALT_LENGTHS = {
|
42
|
+
STRENGTH_128_BIT => 8,
|
43
|
+
STRENGTH_192_BIT => 12,
|
44
|
+
STRENGTH_256_BIT => 16
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
def initialize(password, strength)
|
48
|
+
@password = password
|
49
|
+
@strength = strength
|
50
|
+
@bits = BITS[@strength]
|
51
|
+
@key_length = KEY_LENGTHS[@strength]
|
52
|
+
@salt_length = SALT_LENGTHS[@strength]
|
53
|
+
end
|
54
|
+
|
55
|
+
def header_bytesize
|
56
|
+
@salt_length + VERIFIER_LENGTH
|
57
|
+
end
|
58
|
+
|
59
|
+
def gp_flags
|
60
|
+
0x0001
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class AESDecrypter < Decrypter # :nodoc:
|
65
|
+
include AESEncryption
|
66
|
+
|
67
|
+
def decrypt(encrypted_data)
|
68
|
+
@hmac.update(encrypted_data)
|
69
|
+
|
70
|
+
idx = 0
|
71
|
+
decrypted_data = +''
|
72
|
+
amount_to_read = encrypted_data.size
|
73
|
+
|
74
|
+
while amount_to_read.positive?
|
75
|
+
@cipher.iv = [@counter + 1].pack('Vx12')
|
76
|
+
begin_index = BLOCK_SIZE * idx
|
77
|
+
end_index = begin_index + [BLOCK_SIZE, amount_to_read].min
|
78
|
+
decrypted_data << @cipher.update(encrypted_data[begin_index...end_index])
|
79
|
+
amount_to_read -= BLOCK_SIZE
|
80
|
+
@counter += 1
|
81
|
+
idx += 1
|
82
|
+
end
|
83
|
+
|
84
|
+
# JRuby requires finalization of the cipher. This is a bug, as noted in
|
85
|
+
# jruby/jruby-openssl#182 and jruby/jruby-openssl#183.
|
86
|
+
decrypted_data << @cipher.final if defined?(JRUBY_VERSION)
|
87
|
+
decrypted_data
|
88
|
+
end
|
89
|
+
|
90
|
+
def reset!(header)
|
91
|
+
raise Error, "Unsupported encryption AES-#{@bits}" unless STRENGTHS.include? @strength
|
92
|
+
|
93
|
+
salt = header[0...@salt_length]
|
94
|
+
pwd_verify = header[-VERIFIER_LENGTH..]
|
95
|
+
key_material = OpenSSL::KDF.pbkdf2_hmac(
|
96
|
+
@password,
|
97
|
+
salt: salt,
|
98
|
+
iterations: 1000,
|
99
|
+
length: (2 * @key_length) + VERIFIER_LENGTH,
|
100
|
+
hash: 'sha1'
|
101
|
+
)
|
102
|
+
enc_key = key_material[0...@key_length]
|
103
|
+
enc_hmac_key = key_material[@key_length...(2 * @key_length)]
|
104
|
+
enc_pwd_verify = key_material[-VERIFIER_LENGTH..]
|
105
|
+
|
106
|
+
raise Error, 'Bad password' if enc_pwd_verify != pwd_verify
|
107
|
+
|
108
|
+
@counter = 0
|
109
|
+
@cipher = OpenSSL::Cipher::AES.new(@bits, :CTR)
|
110
|
+
@cipher.decrypt
|
111
|
+
@cipher.key = enc_key
|
112
|
+
@hmac = OpenSSL::HMAC.new(enc_hmac_key, OpenSSL::Digest.new('SHA1'))
|
113
|
+
end
|
114
|
+
|
115
|
+
def check_integrity!(io)
|
116
|
+
auth_code = io.read(AUTHENTICATION_CODE_LENGTH)
|
117
|
+
raise Error, 'Integrity fault' if @hmac.digest[0...AUTHENTICATION_CODE_LENGTH] != auth_code
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -4,39 +4,43 @@ module Zip
|
|
4
4
|
class DecryptedIo # :nodoc:all
|
5
5
|
CHUNK_SIZE = 32_768
|
6
6
|
|
7
|
-
def initialize(io, decrypter)
|
7
|
+
def initialize(io, decrypter, compressed_size)
|
8
8
|
@io = io
|
9
9
|
@decrypter = decrypter
|
10
|
+
@bytes_remaining = compressed_size
|
11
|
+
@buffer = +''
|
10
12
|
end
|
11
13
|
|
12
14
|
def read(length = nil, outbuf = +'')
|
13
|
-
return (length.nil? || length.zero? ? '' : nil) if eof
|
15
|
+
return (length.nil? || length.zero? ? '' : nil) if eof?
|
14
16
|
|
15
|
-
while length.nil? || (buffer.bytesize < length)
|
17
|
+
while length.nil? || (@buffer.bytesize < length)
|
16
18
|
break if input_finished?
|
17
19
|
|
18
|
-
buffer << produce_input
|
20
|
+
@buffer << produce_input
|
19
21
|
end
|
20
22
|
|
21
|
-
|
23
|
+
@decrypter.check_integrity!(@io) if input_finished?
|
24
|
+
|
25
|
+
outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize)))
|
22
26
|
end
|
23
27
|
|
24
28
|
private
|
25
29
|
|
26
|
-
def eof
|
27
|
-
buffer.empty? && input_finished?
|
28
|
-
end
|
29
|
-
|
30
|
-
def buffer
|
31
|
-
@buffer ||= +''
|
30
|
+
def eof?
|
31
|
+
@buffer.empty? && input_finished?
|
32
32
|
end
|
33
33
|
|
34
34
|
def input_finished?
|
35
|
-
|
35
|
+
!@bytes_remaining.positive?
|
36
36
|
end
|
37
37
|
|
38
38
|
def produce_input
|
39
|
-
@
|
39
|
+
chunk_size = [@bytes_remaining, CHUNK_SIZE].min
|
40
|
+
return '' unless chunk_size.positive?
|
41
|
+
|
42
|
+
@bytes_remaining -= chunk_size
|
43
|
+
@decrypter.decrypt(@io.read(chunk_size))
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -28,16 +28,6 @@ module Zip
|
|
28
28
|
|
29
29
|
def reset!; end
|
30
30
|
end
|
31
|
-
|
32
|
-
class NullDecrypter < Decrypter # :nodoc:
|
33
|
-
include NullEncryption
|
34
|
-
|
35
|
-
def decrypt(data)
|
36
|
-
data
|
37
|
-
end
|
38
|
-
|
39
|
-
def reset!(_header); end
|
40
|
-
end
|
41
31
|
end
|
42
32
|
|
43
33
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
data/lib/zip/dos_time.rb
CHANGED
@@ -21,7 +21,7 @@ module Zip
|
|
21
21
|
def absolute_time?
|
22
22
|
# If absolute time is not set, we can assume it is an absolute time
|
23
23
|
# because times do have timezone information by default.
|
24
|
-
@absolute_time.nil?
|
24
|
+
@absolute_time.nil? || @absolute_time
|
25
25
|
end
|
26
26
|
|
27
27
|
def to_binary_dos_time
|
@@ -36,7 +36,8 @@ module Zip
|
|
36
36
|
((year - 1980) << 9)
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
# Deprecated. Remove for version 4.
|
40
|
+
def dos_equals(other) # rubocop:disable Naming/PredicateMethod
|
40
41
|
warn 'Zip::DOSTime#dos_equals is deprecated. Use `==` instead.'
|
41
42
|
self == other
|
42
43
|
end
|
data/lib/zip/entry.rb
CHANGED
@@ -203,6 +203,11 @@ module Zip
|
|
203
203
|
!@extra['Zip64'].nil?
|
204
204
|
end
|
205
205
|
|
206
|
+
# Is this entry encrypted with AES encryption?
|
207
|
+
def aes?
|
208
|
+
!@extra['AES'].nil?
|
209
|
+
end
|
210
|
+
|
206
211
|
def file_type_is?(type) # :nodoc:
|
207
212
|
ftype == type
|
208
213
|
end
|
@@ -382,6 +387,7 @@ module Zip
|
|
382
387
|
|
383
388
|
read_extra_field(extra, local: true)
|
384
389
|
parse_zip64_extra(true)
|
390
|
+
parse_aes_extra
|
385
391
|
@local_header_size = calculate_local_header_size
|
386
392
|
end
|
387
393
|
|
@@ -511,6 +517,7 @@ module Zip
|
|
511
517
|
check_c_dir_entry_comment_size
|
512
518
|
set_ftype_from_c_dir_entry
|
513
519
|
parse_zip64_extra(false)
|
520
|
+
parse_aes_extra
|
514
521
|
end
|
515
522
|
|
516
523
|
def file_stat(path) # :nodoc:
|
@@ -800,6 +807,20 @@ module Zip
|
|
800
807
|
end
|
801
808
|
end
|
802
809
|
|
810
|
+
def parse_aes_extra # :nodoc:
|
811
|
+
return unless aes?
|
812
|
+
|
813
|
+
if @extra['AES'].vendor_id != 'AE'
|
814
|
+
raise Error, "Unsupported encryption method #{@extra['AES'].vendor_id}"
|
815
|
+
end
|
816
|
+
|
817
|
+
unless ::Zip::AESEncryption::VERSIONS.include? @extra['AES'].vendor_version
|
818
|
+
raise Error, "Unsupported encryption style #{@extra['AES'].vendor_version}"
|
819
|
+
end
|
820
|
+
|
821
|
+
@compression_method = @extra['AES'].compression_method if ftype != :directory
|
822
|
+
end
|
823
|
+
|
803
824
|
# For DEFLATED compression *only*: set the general purpose flags 1 and 2 to
|
804
825
|
# indicate compression level. This seems to be mainly cosmetic but they are
|
805
826
|
# generally set by other tools - including in docx files. It is these flags
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zip
|
4
|
+
# Info-ZIP Extra for AES encryption
|
5
|
+
class ExtraField::AES < ExtraField::Generic # :nodoc:
|
6
|
+
attr_reader :vendor_version, :vendor_id, :encryption_strength, :compression_method
|
7
|
+
|
8
|
+
HEADER_ID = [0x9901].pack('v')
|
9
|
+
register_map
|
10
|
+
|
11
|
+
def initialize(binstr = nil)
|
12
|
+
@vendor_version = nil
|
13
|
+
@vendor_id = nil
|
14
|
+
@encryption_strength = nil
|
15
|
+
@compression_method = nil
|
16
|
+
binstr && merge(binstr)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
@vendor_version == other.vendor_version &&
|
21
|
+
@vendor_id == other.vendor_id &&
|
22
|
+
@encryption_strength == other.encryption_strength &&
|
23
|
+
@compression_method == other.compression_method
|
24
|
+
end
|
25
|
+
|
26
|
+
def merge(binstr)
|
27
|
+
return if binstr.empty?
|
28
|
+
|
29
|
+
size, content = initial_parse(binstr)
|
30
|
+
(size && content) || return
|
31
|
+
|
32
|
+
@vendor_version, @vendor_id,
|
33
|
+
@encryption_strength, @compression_method = content.unpack('va2Cv')
|
34
|
+
end
|
35
|
+
|
36
|
+
def pack_for_local
|
37
|
+
[@vendor_version, @vendor_id,
|
38
|
+
@encryption_strength, @compression_method].pack('va2Cv')
|
39
|
+
end
|
40
|
+
|
41
|
+
def pack_for_c_dir
|
42
|
+
pack_for_local
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/zip/extra_field.rb
CHANGED
@@ -91,6 +91,7 @@ require 'zip/extra_field/old_unix'
|
|
91
91
|
require 'zip/extra_field/unix'
|
92
92
|
require 'zip/extra_field/zip64'
|
93
93
|
require 'zip/extra_field/ntfs'
|
94
|
+
require 'zip/extra_field/aes'
|
94
95
|
|
95
96
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
96
97
|
# rubyzip is free software; you can redistribute it and/or
|
data/lib/zip/file.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'fileutils'
|
3
4
|
require 'forwardable'
|
4
5
|
|
5
6
|
require_relative 'file_split'
|
@@ -98,7 +99,6 @@ module Zip
|
|
98
99
|
restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
|
99
100
|
restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
|
100
101
|
compression_level: ::Zip.default_compression)
|
101
|
-
|
102
102
|
zf = ::Zip::File.new(file_name, create: create,
|
103
103
|
restore_ownership: restore_ownership,
|
104
104
|
restore_permissions: restore_permissions,
|
@@ -123,7 +123,6 @@ module Zip
|
|
123
123
|
restore_permissions: DEFAULT_RESTORE_OPTIONS[:restore_permissions],
|
124
124
|
restore_times: DEFAULT_RESTORE_OPTIONS[:restore_times],
|
125
125
|
compression_level: ::Zip.default_compression)
|
126
|
-
|
127
126
|
unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.kind_of?(String)
|
128
127
|
raise 'Zip::File.open_buffer expects a String or IO-like argument' \
|
129
128
|
"(responds to #{IO_METHODS.join(', ')}). Found: #{io.class}"
|
@@ -191,7 +190,6 @@ module Zip
|
|
191
190
|
extra: nil, compressed_size: nil, crc: nil,
|
192
191
|
compression_method: nil, compression_level: nil,
|
193
192
|
size: nil, time: nil, &a_proc)
|
194
|
-
|
195
193
|
new_entry =
|
196
194
|
if entry.kind_of?(Entry)
|
197
195
|
entry
|
@@ -412,7 +410,7 @@ module Zip
|
|
412
410
|
::File.chmod(@file_permissions, name) unless @create
|
413
411
|
end
|
414
412
|
ensure
|
415
|
-
|
413
|
+
FileUtils.rm_f(tmp_filename)
|
416
414
|
end
|
417
415
|
end
|
418
416
|
end
|
data/lib/zip/filesystem/file.rb
CHANGED
@@ -27,7 +27,7 @@ module Zip
|
|
27
27
|
|
28
28
|
def unix_mode_cmp(filename, mode)
|
29
29
|
e = find_entry(filename)
|
30
|
-
e.fstype == FSTYPE_UNIX && (
|
30
|
+
e.fstype == FSTYPE_UNIX && (e.external_file_attributes >> 16).anybits?(mode)
|
31
31
|
rescue Errno::ENOENT
|
32
32
|
false
|
33
33
|
end
|
@@ -102,10 +102,11 @@ module Zip
|
|
102
102
|
@mapped_zip.get_entry(filename).size
|
103
103
|
end
|
104
104
|
|
105
|
-
# Returns nil for not found and nil for directories
|
105
|
+
# Returns nil for not found and nil for directories.
|
106
|
+
# We disable the cop here for compatibility with `::File.size?`.
|
106
107
|
def size?(filename)
|
107
108
|
entry = @mapped_zip.find_entry(filename)
|
108
|
-
entry.nil? || entry.directory? ? nil : entry.size
|
109
|
+
entry.nil? || entry.directory? ? nil : entry.size # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
109
110
|
end
|
110
111
|
|
111
112
|
def chown(owner, group, *filenames)
|
data/lib/zip/inflater.rb
CHANGED
@@ -10,7 +10,7 @@ module Zip
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def read(length = nil, outbuf = +'')
|
13
|
-
return (length.nil? || length.zero? ? '' : nil) if eof
|
13
|
+
return (length.nil? || length.zero? ? '' : nil) if eof?
|
14
14
|
|
15
15
|
while length.nil? || (@buffer.bytesize < length)
|
16
16
|
break if input_finished?
|
@@ -21,11 +21,12 @@ module Zip
|
|
21
21
|
outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize)))
|
22
22
|
end
|
23
23
|
|
24
|
-
def eof
|
24
|
+
def eof?
|
25
25
|
@buffer.empty? && input_finished?
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
# Alias for compatibility. Remove for version 4.
|
29
|
+
alias eof eof?
|
29
30
|
|
30
31
|
private
|
31
32
|
|
data/lib/zip/input_stream.rb
CHANGED
@@ -55,7 +55,7 @@ module Zip
|
|
55
55
|
super()
|
56
56
|
@archive_io = get_io(context, offset)
|
57
57
|
@decompressor = ::Zip::NullDecompressor
|
58
|
-
@decrypter = decrypter
|
58
|
+
@decrypter = decrypter
|
59
59
|
@current_entry = nil
|
60
60
|
@complete_entry = nil
|
61
61
|
end
|
@@ -135,29 +135,48 @@ module Zip
|
|
135
135
|
@current_entry = ::Zip::Entry.read_local_entry(@archive_io)
|
136
136
|
return if @current_entry.nil?
|
137
137
|
|
138
|
-
if @current_entry.encrypted? && @decrypter.kind_of?(NullDecrypter)
|
139
|
-
raise Error,
|
140
|
-
'A password is required to decode this zip file'
|
141
|
-
end
|
142
|
-
|
143
138
|
if @current_entry.incomplete? && @current_entry.compressed_size == 0 && !@complete_entry
|
144
139
|
raise StreamingError, @current_entry
|
145
140
|
end
|
146
141
|
|
147
|
-
@
|
148
|
-
@decompressor = get_decompressor
|
142
|
+
@decompressor = assemble_io
|
149
143
|
flush
|
150
144
|
@current_entry
|
151
145
|
end
|
152
146
|
|
147
|
+
def assemble_io # :nodoc:
|
148
|
+
io = if @current_entry.encrypted?
|
149
|
+
raise Error, 'A password is required to decode this zip file.' if @decrypter.nil?
|
150
|
+
|
151
|
+
get_decrypted_io
|
152
|
+
else
|
153
|
+
@archive_io
|
154
|
+
end
|
155
|
+
|
156
|
+
get_decompressor(io)
|
157
|
+
end
|
158
|
+
|
153
159
|
def get_decrypted_io # :nodoc:
|
154
160
|
header = @archive_io.read(@decrypter.header_bytesize)
|
155
161
|
@decrypter.reset!(header)
|
156
162
|
|
157
|
-
|
163
|
+
compressed_size =
|
164
|
+
if @current_entry.incomplete? && @current_entry.crc == 0 &&
|
165
|
+
@current_entry.compressed_size == 0 && @complete_entry
|
166
|
+
@complete_entry.compressed_size
|
167
|
+
else
|
168
|
+
@current_entry.compressed_size
|
169
|
+
end
|
170
|
+
|
171
|
+
if @decrypter.kind_of?(::Zip::AESDecrypter)
|
172
|
+
compressed_size -= @decrypter.header_bytesize
|
173
|
+
compressed_size -= ::Zip::AESEncryption::AUTHENTICATION_CODE_LENGTH
|
174
|
+
end
|
175
|
+
|
176
|
+
::Zip::DecryptedIo.new(@archive_io, @decrypter, compressed_size)
|
158
177
|
end
|
159
178
|
|
160
|
-
def get_decompressor # :nodoc:
|
179
|
+
def get_decompressor(io) # :nodoc:
|
161
180
|
return ::Zip::NullDecompressor if @current_entry.nil?
|
162
181
|
|
163
182
|
decompressed_size =
|
@@ -175,7 +194,7 @@ module Zip
|
|
175
194
|
raise ::Zip::CompressionMethodError, @current_entry.compression_method
|
176
195
|
end
|
177
196
|
|
178
|
-
decompressor_class.new(
|
197
|
+
decompressor_class.new(io, decompressed_size)
|
179
198
|
end
|
180
199
|
|
181
200
|
def produce_input # :nodoc:
|
@@ -183,7 +202,7 @@ module Zip
|
|
183
202
|
end
|
184
203
|
|
185
204
|
def input_finished? # :nodoc:
|
186
|
-
@decompressor.eof
|
205
|
+
@decompressor.eof?
|
187
206
|
end
|
188
207
|
end
|
189
208
|
end
|
@@ -117,11 +117,12 @@ module Zip
|
|
117
117
|
|
118
118
|
alias each each_line
|
119
119
|
|
120
|
-
def eof
|
120
|
+
def eof?
|
121
121
|
@output_buffer.empty? && input_finished?
|
122
122
|
end
|
123
123
|
|
124
|
-
|
124
|
+
# Alias for compatibility. Remove for version 4.
|
125
|
+
alias eof eof?
|
125
126
|
end
|
126
127
|
end
|
127
128
|
end
|
@@ -8,7 +8,7 @@ module Zip
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def read(length = nil, outbuf = +'')
|
11
|
-
return (length.nil? || length.zero? ? '' : nil) if eof
|
11
|
+
return (length.nil? || length.zero? ? '' : nil) if eof?
|
12
12
|
|
13
13
|
if length.nil? || (@read_so_far + length) > decompressed_size
|
14
14
|
length = decompressed_size - @read_so_far
|
@@ -18,11 +18,12 @@ module Zip
|
|
18
18
|
input_stream.read(length, outbuf)
|
19
19
|
end
|
20
20
|
|
21
|
-
def eof
|
21
|
+
def eof?
|
22
22
|
@read_so_far >= decompressed_size
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
# Alias for compatibility. Remove for version 4.
|
26
|
+
alias eof eof?
|
26
27
|
end
|
27
28
|
|
28
29
|
::Zip::Decompressor.register(::Zip::COMPRESSION_METHOD_STORE, ::Zip::PassThruDecompressor)
|
data/lib/zip/version.rb
CHANGED
data/lib/zip.rb
CHANGED
@@ -30,6 +30,7 @@ require 'zip/crypto/decrypted_io'
|
|
30
30
|
require 'zip/crypto/encryption'
|
31
31
|
require 'zip/crypto/null_encryption'
|
32
32
|
require 'zip/crypto/traditional_encryption'
|
33
|
+
require 'zip/crypto/aes_encryption'
|
33
34
|
require 'zip/inflater'
|
34
35
|
require 'zip/deflater'
|
35
36
|
require 'zip/streamable_stream'
|
@@ -43,6 +44,7 @@ require 'zip/errors'
|
|
43
44
|
# ::Dir APIs then `require 'zip/filesystem'` and see FileSystem.
|
44
45
|
module Zip
|
45
46
|
extend self
|
47
|
+
|
46
48
|
attr_accessor :unicode_names,
|
47
49
|
:on_exists_proc,
|
48
50
|
:continue_on_exists_proc,
|
data/rubyzip.gemspec
CHANGED
@@ -31,9 +31,9 @@ Gem::Specification.new do |s|
|
|
31
31
|
s.add_development_dependency 'minitest', '~> 5.25'
|
32
32
|
s.add_development_dependency 'rake', '~> 13.2'
|
33
33
|
s.add_development_dependency 'rdoc', '~> 6.11'
|
34
|
-
s.add_development_dependency 'rubocop', '~> 1.
|
35
|
-
s.add_development_dependency 'rubocop-performance', '~> 1.
|
36
|
-
s.add_development_dependency 'rubocop-rake', '~> 0.
|
34
|
+
s.add_development_dependency 'rubocop', '~> 1.80.2'
|
35
|
+
s.add_development_dependency 'rubocop-performance', '~> 1.26.0'
|
36
|
+
s.add_development_dependency 'rubocop-rake', '~> 0.7.1'
|
37
37
|
s.add_development_dependency 'simplecov', '~> 0.22.0'
|
38
38
|
s.add_development_dependency 'simplecov-lcov', '~> 0.8'
|
39
39
|
end
|
data/samples/gtk_ruby_zip.rb
CHANGED
data/samples/qtzip.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyzip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Haines
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2025-
|
13
|
+
date: 2025-09-26 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: minitest
|
@@ -60,42 +60,42 @@ dependencies:
|
|
60
60
|
requirements:
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: 1.
|
63
|
+
version: 1.80.2
|
64
64
|
type: :development
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
68
|
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: 1.
|
70
|
+
version: 1.80.2
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
72
|
name: rubocop-performance
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
75
|
- - "~>"
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version: 1.
|
77
|
+
version: 1.26.0
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
82
|
- - "~>"
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
version: 1.
|
84
|
+
version: 1.26.0
|
85
85
|
- !ruby/object:Gem::Dependency
|
86
86
|
name: rubocop-rake
|
87
87
|
requirement: !ruby/object:Gem::Requirement
|
88
88
|
requirements:
|
89
89
|
- - "~>"
|
90
90
|
- !ruby/object:Gem::Version
|
91
|
-
version: 0.
|
91
|
+
version: 0.7.1
|
92
92
|
type: :development
|
93
93
|
prerelease: false
|
94
94
|
version_requirements: !ruby/object:Gem::Requirement
|
95
95
|
requirements:
|
96
96
|
- - "~>"
|
97
97
|
- !ruby/object:Gem::Version
|
98
|
-
version: 0.
|
98
|
+
version: 0.7.1
|
99
99
|
- !ruby/object:Gem::Dependency
|
100
100
|
name: simplecov
|
101
101
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/zip/central_directory.rb
|
142
142
|
- lib/zip/compressor.rb
|
143
143
|
- lib/zip/constants.rb
|
144
|
+
- lib/zip/crypto/aes_encryption.rb
|
144
145
|
- lib/zip/crypto/decrypted_io.rb
|
145
146
|
- lib/zip/crypto/encryption.rb
|
146
147
|
- lib/zip/crypto/null_encryption.rb
|
@@ -153,6 +154,7 @@ files:
|
|
153
154
|
- lib/zip/entry_set.rb
|
154
155
|
- lib/zip/errors.rb
|
155
156
|
- lib/zip/extra_field.rb
|
157
|
+
- lib/zip/extra_field/aes.rb
|
156
158
|
- lib/zip/extra_field/generic.rb
|
157
159
|
- lib/zip/extra_field/ntfs.rb
|
158
160
|
- lib/zip/extra_field/old_unix.rb
|
@@ -195,9 +197,9 @@ licenses:
|
|
195
197
|
- BSD-2-Clause
|
196
198
|
metadata:
|
197
199
|
bug_tracker_uri: https://github.com/rubyzip/rubyzip/issues
|
198
|
-
changelog_uri: https://github.com/rubyzip/rubyzip/blob/v3.
|
199
|
-
documentation_uri: https://www.rubydoc.info/gems/rubyzip/3.
|
200
|
-
source_code_uri: https://github.com/rubyzip/rubyzip/tree/v3.
|
200
|
+
changelog_uri: https://github.com/rubyzip/rubyzip/blob/v3.1.1/Changelog.md
|
201
|
+
documentation_uri: https://www.rubydoc.info/gems/rubyzip/3.1.1
|
202
|
+
source_code_uri: https://github.com/rubyzip/rubyzip/tree/v3.1.1
|
201
203
|
wiki_uri: https://github.com/rubyzip/rubyzip/wiki
|
202
204
|
rubygems_mfa_required: 'true'
|
203
205
|
post_install_message:
|
@@ -215,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
215
217
|
- !ruby/object:Gem::Version
|
216
218
|
version: '0'
|
217
219
|
requirements: []
|
218
|
-
rubygems_version: 3.4.
|
220
|
+
rubygems_version: 3.4.19
|
219
221
|
signing_key:
|
220
222
|
specification_version: 4
|
221
223
|
summary: rubyzip is a ruby module for reading and writing zip files
|