rubyzip 1.3.0 → 3.4.0
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 +516 -0
- data/LICENSE.md +24 -0
- data/README.md +213 -51
- data/Rakefile +17 -12
- data/lib/rubyzip.rb +3 -0
- data/lib/zip/central_directory.rb +196 -123
- data/lib/zip/compressor.rb +3 -1
- data/lib/zip/constants.rb +81 -21
- data/lib/zip/crypto/aes_encryption.rb +120 -0
- data/lib/zip/crypto/decrypted_io.rb +46 -0
- data/lib/zip/crypto/encryption.rb +4 -2
- data/lib/zip/crypto/null_encryption.rb +5 -13
- data/lib/zip/crypto/traditional_encryption.rb +22 -16
- data/lib/zip/decompressor.rb +21 -2
- data/lib/zip/deflater.rb +12 -8
- data/lib/zip/dirtyable.rb +32 -0
- data/lib/zip/dos_time.rb +65 -13
- data/lib/zip/entry.rb +433 -249
- data/lib/zip/entry_set.rb +13 -9
- data/lib/zip/errors.rb +152 -15
- data/lib/zip/extra_field/aes.rb +50 -0
- data/lib/zip/extra_field/generic.rb +18 -18
- data/lib/zip/extra_field/ntfs.rb +10 -4
- data/lib/zip/extra_field/old_unix.rb +6 -2
- data/lib/zip/extra_field/universal_time.rb +45 -13
- data/lib/zip/extra_field/unix.rb +7 -3
- data/lib/zip/extra_field/unknown.rb +35 -0
- data/lib/zip/extra_field/zip64.rb +23 -7
- data/lib/zip/extra_field.rb +31 -27
- data/lib/zip/file.rb +225 -241
- data/lib/zip/file_split.rb +91 -0
- data/lib/zip/filesystem/dir.rb +86 -0
- data/lib/zip/filesystem/directory_iterator.rb +48 -0
- data/lib/zip/filesystem/file.rb +263 -0
- data/lib/zip/filesystem/file_stat.rb +110 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +81 -0
- data/lib/zip/filesystem.rb +32 -585
- data/lib/zip/inflater.rb +28 -37
- data/lib/zip/input_stream.rb +113 -54
- data/lib/zip/ioextras/abstract_input_stream.rb +154 -55
- data/lib/zip/ioextras/abstract_output_stream.rb +13 -3
- data/lib/zip/ioextras.rb +8 -8
- data/lib/zip/null_compressor.rb +3 -1
- data/lib/zip/null_decompressor.rb +7 -12
- data/lib/zip/null_input_stream.rb +3 -1
- data/lib/zip/output_stream.rb +69 -50
- data/lib/zip/pass_thru_compressor.rb +5 -3
- data/lib/zip/pass_thru_decompressor.rb +17 -23
- data/lib/zip/streamable_directory.rb +6 -4
- data/lib/zip/streamable_stream.rb +10 -11
- data/lib/zip/version.rb +4 -1
- data/lib/zip.rb +32 -7
- data/rubyzip.gemspec +39 -0
- data/samples/example.rb +9 -4
- data/samples/example_filesystem.rb +4 -3
- data/samples/example_recursive.rb +3 -1
- data/samples/gtk_ruby_zip.rb +23 -21
- data/samples/qtzip.rb +13 -12
- data/samples/write_simple.rb +3 -4
- data/samples/zipfind.rb +24 -22
- metadata +85 -177
- data/TODO +0 -15
- data/lib/zip/extra_field/zip64_placeholder.rb +0 -15
- data/test/basic_zip_file_test.rb +0 -60
- data/test/case_sensitivity_test.rb +0 -69
- data/test/central_directory_entry_test.rb +0 -69
- data/test/central_directory_test.rb +0 -100
- data/test/crypto/null_encryption_test.rb +0 -57
- data/test/crypto/traditional_encryption_test.rb +0 -80
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/file1.txt +0 -46
- data/test/data/file1.txt.deflatedData +0 -0
- data/test/data/file2.txt +0 -1504
- data/test/data/globTest/foo/bar/baz/foo.txt +0 -0
- data/test/data/globTest/foo.txt +0 -0
- data/test/data/globTest/food.txt +0 -0
- data/test/data/globTest.zip +0 -0
- data/test/data/gpbit3stored.zip +0 -0
- data/test/data/mimetype +0 -1
- data/test/data/notzippedruby.rb +0 -7
- data/test/data/ntfs.zip +0 -0
- data/test/data/oddExtraField.zip +0 -0
- data/test/data/path_traversal/Makefile +0 -10
- data/test/data/path_traversal/jwilk/README.md +0 -5
- data/test/data/path_traversal/jwilk/absolute1.zip +0 -0
- data/test/data/path_traversal/jwilk/absolute2.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2a.zip +0 -0
- data/test/data/path_traversal/jwilk/dirsymlink2b.zip +0 -0
- data/test/data/path_traversal/jwilk/relative0.zip +0 -0
- data/test/data/path_traversal/jwilk/relative2.zip +0 -0
- data/test/data/path_traversal/jwilk/symlink.zip +0 -0
- data/test/data/path_traversal/relative1.zip +0 -0
- data/test/data/path_traversal/tilde.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/README.md +0 -3
- data/test/data/path_traversal/tuzovakaoff/absolutepath.zip +0 -0
- data/test/data/path_traversal/tuzovakaoff/symlink.zip +0 -0
- data/test/data/rubycode.zip +0 -0
- data/test/data/rubycode2.zip +0 -0
- data/test/data/test.xls +0 -0
- data/test/data/testDirectory.bin +0 -0
- data/test/data/zip64-sample.zip +0 -0
- data/test/data/zipWithDirs.zip +0 -0
- data/test/data/zipWithEncryption.zip +0 -0
- data/test/deflater_test.rb +0 -65
- data/test/encryption_test.rb +0 -42
- data/test/entry_set_test.rb +0 -163
- data/test/entry_test.rb +0 -154
- data/test/errors_test.rb +0 -35
- data/test/extra_field_test.rb +0 -76
- data/test/file_extract_directory_test.rb +0 -54
- data/test/file_extract_test.rb +0 -145
- data/test/file_permissions_test.rb +0 -65
- data/test/file_split_test.rb +0 -57
- data/test/file_test.rb +0 -666
- data/test/filesystem/dir_iterator_test.rb +0 -58
- data/test/filesystem/directory_test.rb +0 -139
- data/test/filesystem/file_mutating_test.rb +0 -87
- data/test/filesystem/file_nonmutating_test.rb +0 -508
- data/test/filesystem/file_stat_test.rb +0 -64
- data/test/gentestfiles.rb +0 -126
- data/test/inflater_test.rb +0 -14
- data/test/input_stream_test.rb +0 -182
- data/test/ioextras/abstract_input_stream_test.rb +0 -102
- data/test/ioextras/abstract_output_stream_test.rb +0 -106
- data/test/ioextras/fake_io_test.rb +0 -18
- data/test/local_entry_test.rb +0 -154
- data/test/output_stream_test.rb +0 -128
- data/test/pass_thru_compressor_test.rb +0 -30
- data/test/pass_thru_decompressor_test.rb +0 -14
- data/test/path_traversal_test.rb +0 -141
- data/test/samples/example_recursive_test.rb +0 -37
- data/test/settings_test.rb +0 -95
- data/test/test_helper.rb +0 -234
- data/test/unicode_file_names_and_comments_test.rb +0 -62
- data/test/zip64_full_test.rb +0 -51
- data/test/zip64_support_test.rb +0 -14
data/lib/zip/entry.rb
CHANGED
|
@@ -1,21 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'pathname'
|
|
4
|
+
|
|
5
|
+
require_relative 'constants'
|
|
6
|
+
require_relative 'dirtyable'
|
|
7
|
+
|
|
2
8
|
module Zip
|
|
9
|
+
# Zip::Entry represents an entry in a Zip archive.
|
|
3
10
|
class Entry
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
include Dirtyable
|
|
12
|
+
|
|
13
|
+
# Constant used to specify that the entry is stored (i.e., not compressed).
|
|
14
|
+
STORED = ::Zip::COMPRESSION_METHOD_STORE
|
|
15
|
+
|
|
16
|
+
# Constant used to specify that the entry is deflated (i.e., compressed).
|
|
17
|
+
DEFLATED = ::Zip::COMPRESSION_METHOD_DEFLATE
|
|
18
|
+
|
|
6
19
|
# Language encoding flag (EFS) bit
|
|
7
|
-
EFS = 0b100000000000
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
EFS = 0b100000000000 # :nodoc:
|
|
21
|
+
|
|
22
|
+
# Compression level flags (used as part of the gp flags).
|
|
23
|
+
COMPRESSION_LEVEL_SUPERFAST_GPFLAG = 0b110 # :nodoc:
|
|
24
|
+
COMPRESSION_LEVEL_FAST_GPFLAG = 0b100 # :nodoc:
|
|
25
|
+
COMPRESSION_LEVEL_MAX_GPFLAG = 0b010 # :nodoc:
|
|
26
|
+
|
|
27
|
+
attr_accessor :comment, :compressed_size, :follow_symlinks, :name,
|
|
28
|
+
:restore_ownership, :restore_permissions, :restore_times,
|
|
29
|
+
:unix_gid, :unix_perms, :unix_uid
|
|
30
|
+
|
|
31
|
+
attr_accessor :crc, :external_file_attributes, :fstype, :gp_flags,
|
|
32
|
+
:internal_file_attributes, :local_header_offset # :nodoc:
|
|
33
|
+
|
|
34
|
+
attr_reader :extra, :compression_level, :filepath # :nodoc:
|
|
35
|
+
|
|
36
|
+
attr_writer :size # :nodoc:
|
|
37
|
+
|
|
38
|
+
mark_dirty :comment=, :compressed_size=, :external_file_attributes=,
|
|
39
|
+
:fstype=, :gp_flags=, :name=, :size=,
|
|
40
|
+
:unix_gid=, :unix_perms=, :unix_uid=
|
|
41
|
+
|
|
42
|
+
def set_default_vars_values # :nodoc:
|
|
19
43
|
@local_header_offset = 0
|
|
20
44
|
@local_header_size = nil # not known until local entry is created or read
|
|
21
45
|
@internal_file_attributes = 1
|
|
@@ -34,169 +58,257 @@ module Zip
|
|
|
34
58
|
end
|
|
35
59
|
@follow_symlinks = false
|
|
36
60
|
|
|
37
|
-
@restore_times =
|
|
38
|
-
@restore_permissions =
|
|
39
|
-
@restore_ownership =
|
|
61
|
+
@restore_times = DEFAULT_RESTORE_OPTIONS[:restore_times]
|
|
62
|
+
@restore_permissions = DEFAULT_RESTORE_OPTIONS[:restore_permissions]
|
|
63
|
+
@restore_ownership = DEFAULT_RESTORE_OPTIONS[:restore_ownership]
|
|
40
64
|
# BUG: need an extra field to support uid/gid's
|
|
41
65
|
@unix_uid = nil
|
|
42
66
|
@unix_gid = nil
|
|
43
67
|
@unix_perms = nil
|
|
44
|
-
# @posix_acl = nil
|
|
45
|
-
# @ntfs_acl = nil
|
|
46
|
-
@dirty = false
|
|
47
68
|
end
|
|
48
69
|
|
|
49
|
-
def check_name(name)
|
|
50
|
-
|
|
51
|
-
raise
|
|
70
|
+
def check_name(name) # :nodoc:
|
|
71
|
+
raise EntryNameError, name if name.start_with?('/')
|
|
72
|
+
raise EntryNameError if name.length > 65_535
|
|
52
73
|
end
|
|
53
74
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
# Create a new Zip::Entry.
|
|
76
|
+
def initialize(
|
|
77
|
+
zipfile = '', name = '',
|
|
78
|
+
comment: '', size: nil, compressed_size: 0, crc: 0,
|
|
79
|
+
compression_method: DEFLATED,
|
|
80
|
+
compression_level: ::Zip.default_compression,
|
|
81
|
+
time: ::Zip::DOSTime.now, extra: ::Zip::ExtraField.new
|
|
82
|
+
)
|
|
83
|
+
super()
|
|
84
|
+
@name = name
|
|
85
|
+
check_name(@name)
|
|
57
86
|
|
|
58
87
|
set_default_vars_values
|
|
59
88
|
@fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX
|
|
60
89
|
|
|
61
|
-
@zipfile =
|
|
62
|
-
@
|
|
63
|
-
@
|
|
64
|
-
@
|
|
65
|
-
@compressed_size =
|
|
66
|
-
@crc =
|
|
67
|
-
@
|
|
68
|
-
@
|
|
69
|
-
|
|
90
|
+
@zipfile = zipfile
|
|
91
|
+
@comment = comment || ''
|
|
92
|
+
@compression_method = compression_method || DEFLATED
|
|
93
|
+
@compression_level = compression_level || ::Zip.default_compression
|
|
94
|
+
@compressed_size = compressed_size || 0
|
|
95
|
+
@crc = crc || 0
|
|
96
|
+
@size = size
|
|
97
|
+
@time = case time
|
|
98
|
+
when ::Zip::DOSTime
|
|
99
|
+
time
|
|
100
|
+
when Time
|
|
101
|
+
::Zip::DOSTime.from_time(time)
|
|
102
|
+
else
|
|
103
|
+
::Zip::DOSTime.now
|
|
104
|
+
end
|
|
105
|
+
@extra =
|
|
106
|
+
extra.kind_of?(ExtraField) ? extra : ExtraField.new(extra.to_s)
|
|
107
|
+
|
|
108
|
+
set_compression_level_flags
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Is this entry encrypted?
|
|
112
|
+
def encrypted?
|
|
113
|
+
gp_flags & 1 == 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def incomplete? # :nodoc:
|
|
117
|
+
(gp_flags & 8 == 8) && (crc == 0 || size == 0 || compressed_size == 0)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The uncompressed size of the entry.
|
|
121
|
+
def size
|
|
122
|
+
@size || 0
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get a timestamp component of this entry.
|
|
126
|
+
#
|
|
127
|
+
# Returns modification time by default.
|
|
128
|
+
def time(component: :mtime)
|
|
129
|
+
time =
|
|
130
|
+
if @extra[:universaltime]
|
|
131
|
+
@extra[:universaltime].send(component)
|
|
132
|
+
elsif @extra[:ntfs]
|
|
133
|
+
@extra[:ntfs].send(component)
|
|
134
|
+
end
|
|
70
135
|
|
|
71
|
-
|
|
72
|
-
|
|
136
|
+
# Standard time field in central directory has local time
|
|
137
|
+
# under archive creator. Then, we can't get timezone.
|
|
138
|
+
time || (@time if component == :mtime)
|
|
73
139
|
end
|
|
74
140
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
else
|
|
81
|
-
# Standard time field in central directory has local time
|
|
82
|
-
# under archive creator. Then, we can't get timezone.
|
|
83
|
-
@time
|
|
84
|
-
end
|
|
141
|
+
alias mtime time
|
|
142
|
+
|
|
143
|
+
# Get the last access time of this entry, if available.
|
|
144
|
+
def atime
|
|
145
|
+
time(component: :atime)
|
|
85
146
|
end
|
|
86
147
|
|
|
87
|
-
|
|
148
|
+
# Get the creation time of this entry, if available.
|
|
149
|
+
def ctime
|
|
150
|
+
time(component: :ctime)
|
|
151
|
+
end
|
|
88
152
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
153
|
+
# Set a timestamp component of this entry.
|
|
154
|
+
#
|
|
155
|
+
# Sets modification time by default.
|
|
156
|
+
def time=(value, component: :mtime)
|
|
157
|
+
@dirty = true
|
|
158
|
+
unless @extra.member?(:universaltime) || @extra.member?(:ntfs)
|
|
159
|
+
@extra.create(:universaltime)
|
|
92
160
|
end
|
|
93
|
-
|
|
94
|
-
|
|
161
|
+
|
|
162
|
+
value = DOSTime.from_time(value)
|
|
163
|
+
comp = "#{component}=" unless component.to_s.end_with?('=')
|
|
164
|
+
(@extra[:universaltime] || @extra[:ntfs]).send(comp, value)
|
|
165
|
+
@time = value if component == :mtime
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
alias mtime= time=
|
|
169
|
+
|
|
170
|
+
# Set the last access time of this entry.
|
|
171
|
+
def atime=(value)
|
|
172
|
+
send(:time=, value, component: :atime)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Set the creation time of this entry.
|
|
176
|
+
def ctime=(value)
|
|
177
|
+
send(:time=, value, component: :ctime)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Does this entry return time fields with accurate timezone information?
|
|
181
|
+
def absolute_time?
|
|
182
|
+
@extra.member?(:universaltime) || @extra.member?(:ntfs)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Return the compression method for this entry.
|
|
186
|
+
#
|
|
187
|
+
# Returns STORED if the entry is a directory or if the compression
|
|
188
|
+
# level is 0.
|
|
189
|
+
def compression_method
|
|
190
|
+
return STORED if ftype == :directory || @compression_level == 0
|
|
191
|
+
|
|
192
|
+
@compression_method
|
|
95
193
|
end
|
|
96
194
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@
|
|
195
|
+
# Set the compression method for this entry.
|
|
196
|
+
def compression_method=(method)
|
|
197
|
+
@dirty = true
|
|
198
|
+
@compression_method = (ftype == :directory ? STORED : method)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Does this entry use the ZIP64 extensions?
|
|
202
|
+
def zip64?
|
|
203
|
+
!@extra[:zip64].nil?
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Is this entry encrypted with AES encryption?
|
|
207
|
+
def aes?
|
|
208
|
+
!@extra[:aes].nil?
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def file_type_is?(type) # :nodoc:
|
|
212
|
+
ftype == type
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def ftype # :nodoc:
|
|
216
|
+
@ftype ||= name_is_directory? ? :directory : :file
|
|
100
217
|
end
|
|
101
218
|
|
|
102
219
|
# Dynamic checkers
|
|
103
220
|
%w[directory file symlink].each do |k|
|
|
104
|
-
define_method "#{k}?" do
|
|
221
|
+
define_method :"#{k}?" do
|
|
105
222
|
file_type_is?(k.to_sym)
|
|
106
223
|
end
|
|
107
224
|
end
|
|
108
225
|
|
|
109
|
-
def name_is_directory?
|
|
226
|
+
def name_is_directory? # :nodoc:
|
|
110
227
|
@name.end_with?('/')
|
|
111
228
|
end
|
|
112
229
|
|
|
113
230
|
# Is the name a relative path, free of `..` patterns that could lead to
|
|
114
231
|
# path traversal attacks? This does NOT handle symlinks; if the path
|
|
115
232
|
# contains symlinks, this check is NOT enough to guarantee safety.
|
|
116
|
-
def name_safe?
|
|
233
|
+
def name_safe? # :nodoc:
|
|
117
234
|
cleanpath = Pathname.new(@name).cleanpath
|
|
118
235
|
return false unless cleanpath.relative?
|
|
236
|
+
|
|
119
237
|
root = ::File::SEPARATOR
|
|
120
|
-
|
|
121
|
-
|
|
238
|
+
naive = Regexp.escape(::File.join(root, cleanpath.to_s))
|
|
239
|
+
# Allow for Windows drive mappings at the root.
|
|
240
|
+
::File.absolute_path(cleanpath.to_s, root).match?(/([A-Z]:)?#{naive}/i)
|
|
122
241
|
end
|
|
123
242
|
|
|
124
|
-
def local_entry_offset
|
|
243
|
+
def local_entry_offset # :nodoc:
|
|
125
244
|
local_header_offset + @local_header_size
|
|
126
245
|
end
|
|
127
246
|
|
|
128
|
-
def name_size
|
|
247
|
+
def name_size # :nodoc:
|
|
129
248
|
@name ? @name.bytesize : 0
|
|
130
249
|
end
|
|
131
250
|
|
|
132
|
-
def extra_size
|
|
251
|
+
def extra_size # :nodoc:
|
|
133
252
|
@extra ? @extra.local_size : 0
|
|
134
253
|
end
|
|
135
254
|
|
|
136
|
-
def comment_size
|
|
255
|
+
def comment_size # :nodoc:
|
|
137
256
|
@comment ? @comment.bytesize : 0
|
|
138
257
|
end
|
|
139
258
|
|
|
140
|
-
def calculate_local_header_size
|
|
259
|
+
def calculate_local_header_size # :nodoc:
|
|
141
260
|
LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size
|
|
142
261
|
end
|
|
143
262
|
|
|
144
263
|
# check before rewriting an entry (after file sizes are known)
|
|
145
264
|
# that we didn't change the header size (and thus clobber file data or something)
|
|
146
|
-
def verify_local_header_size!
|
|
265
|
+
def verify_local_header_size! # :nodoc:
|
|
147
266
|
return if @local_header_size.nil?
|
|
267
|
+
|
|
148
268
|
new_size = calculate_local_header_size
|
|
149
|
-
|
|
269
|
+
return unless @local_header_size != new_size
|
|
270
|
+
|
|
271
|
+
raise Error,
|
|
272
|
+
"Local header size changed (#{@local_header_size} -> #{new_size})"
|
|
150
273
|
end
|
|
151
274
|
|
|
152
|
-
def cdir_header_size
|
|
275
|
+
def cdir_header_size # :nodoc:
|
|
153
276
|
CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size +
|
|
154
277
|
(@extra ? @extra.c_dir_size : 0) + comment_size
|
|
155
278
|
end
|
|
156
279
|
|
|
157
|
-
def next_header_offset
|
|
158
|
-
local_entry_offset + compressed_size
|
|
280
|
+
def next_header_offset # :nodoc:
|
|
281
|
+
local_entry_offset + compressed_size
|
|
159
282
|
end
|
|
160
283
|
|
|
161
|
-
# Extracts entry to file
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
284
|
+
# Extracts this entry to a file at `entry_path`, with
|
|
285
|
+
# `destination_directory` as the base location in the filesystem.
|
|
286
|
+
#
|
|
287
|
+
# NB: The caller is responsible for making sure `destination_directory` is
|
|
288
|
+
# safe, if it is passed.
|
|
289
|
+
def extract(entry_path = @name, destination_directory: '.', &block)
|
|
290
|
+
dest_dir = ::File.absolute_path(destination_directory || '.')
|
|
291
|
+
extract_path = ::File.absolute_path(::File.join(dest_dir, entry_path))
|
|
292
|
+
|
|
293
|
+
unless extract_path.start_with?(dest_dir + ::File::SEPARATOR) || dest_dir == ::File::SEPARATOR
|
|
294
|
+
warn "WARNING: skipped extracting '#{@name}' to '#{extract_path}' as unsafe."
|
|
167
295
|
return self
|
|
168
296
|
end
|
|
169
297
|
|
|
170
|
-
dest_path ||= @name
|
|
171
298
|
block ||= proc { ::Zip.on_exists_proc }
|
|
172
299
|
|
|
173
|
-
|
|
174
|
-
__send__("create_#{@ftype}", dest_path, &block)
|
|
175
|
-
else
|
|
176
|
-
raise "unknown file type #{inspect}"
|
|
177
|
-
end
|
|
300
|
+
raise "unknown file type #{inspect}" unless directory? || file? || symlink?
|
|
178
301
|
|
|
302
|
+
__send__(:"create_#{ftype}", extract_path, &block)
|
|
179
303
|
self
|
|
180
304
|
end
|
|
181
305
|
|
|
182
|
-
def to_s
|
|
306
|
+
def to_s # :nodoc:
|
|
183
307
|
@name
|
|
184
308
|
end
|
|
185
309
|
|
|
186
310
|
class << self
|
|
187
|
-
def
|
|
188
|
-
io.read(2).unpack('v')[0]
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def read_zip_long(io) # :nodoc:
|
|
192
|
-
io.read(4).unpack('V')[0]
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def read_zip_64_long(io) # :nodoc:
|
|
196
|
-
io.read(8).unpack('Q<')[0]
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def read_c_dir_entry(io) #:nodoc:all
|
|
311
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
200
312
|
path = if io.respond_to?(:path)
|
|
201
313
|
io.path
|
|
202
314
|
else
|
|
@@ -209,18 +321,18 @@ module Zip
|
|
|
209
321
|
nil
|
|
210
322
|
end
|
|
211
323
|
|
|
212
|
-
def read_local_entry(io)
|
|
324
|
+
def read_local_entry(io) # :nodoc:
|
|
213
325
|
entry = new(io)
|
|
214
326
|
entry.read_local_entry(io)
|
|
215
327
|
entry
|
|
328
|
+
rescue SplitArchiveError
|
|
329
|
+
raise
|
|
216
330
|
rescue Error
|
|
217
331
|
nil
|
|
218
332
|
end
|
|
219
333
|
end
|
|
220
334
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def unpack_local_entry(buf)
|
|
335
|
+
def unpack_local_entry(buf) # :nodoc:
|
|
224
336
|
@header_signature,
|
|
225
337
|
@version,
|
|
226
338
|
@fstype,
|
|
@@ -235,61 +347,76 @@ module Zip
|
|
|
235
347
|
@extra_length = buf.unpack('VCCvvvvVVVvv')
|
|
236
348
|
end
|
|
237
349
|
|
|
238
|
-
def read_local_entry(io)
|
|
239
|
-
@
|
|
350
|
+
def read_local_entry(io) # :nodoc:
|
|
351
|
+
@dirty = false # No changes at this point.
|
|
352
|
+
current_offset = io.tell
|
|
240
353
|
|
|
241
|
-
|
|
354
|
+
read_local_header_fields(io)
|
|
242
355
|
|
|
243
|
-
|
|
244
|
-
raise
|
|
245
|
-
end
|
|
356
|
+
if @header_signature == SPLIT_FILE_SIGNATURE
|
|
357
|
+
raise SplitArchiveError if current_offset.zero?
|
|
246
358
|
|
|
247
|
-
|
|
359
|
+
# Rewind, skipping the data descriptor, then try to read the local header again.
|
|
360
|
+
current_offset += 16
|
|
361
|
+
io.seek(current_offset)
|
|
362
|
+
read_local_header_fields(io)
|
|
363
|
+
end
|
|
248
364
|
|
|
249
|
-
unless @header_signature ==
|
|
250
|
-
raise
|
|
365
|
+
unless @header_signature == LOCAL_ENTRY_SIGNATURE
|
|
366
|
+
raise Error, "Zip local header magic not found at location '#{current_offset}'"
|
|
251
367
|
end
|
|
368
|
+
|
|
369
|
+
@local_header_offset = current_offset
|
|
370
|
+
|
|
252
371
|
set_time(@last_mod_date, @last_mod_time)
|
|
253
372
|
|
|
254
373
|
@name = io.read(@name_length)
|
|
255
|
-
extra = io.read(@extra_length)
|
|
256
|
-
|
|
257
|
-
@name.tr!('\\', '/')
|
|
258
374
|
if ::Zip.force_entry_names_encoding
|
|
259
375
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
260
376
|
end
|
|
377
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
261
378
|
|
|
379
|
+
# We need to do this here because `initialize` has so many side-effects.
|
|
380
|
+
# :-(
|
|
381
|
+
@ftype = name_is_directory? ? :directory : :file
|
|
382
|
+
|
|
383
|
+
extra = io.read(@extra_length)
|
|
262
384
|
if extra && extra.bytesize != @extra_length
|
|
263
385
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
|
264
|
-
else
|
|
265
|
-
if @extra.is_a?(::Zip::ExtraField)
|
|
266
|
-
@extra.merge(extra) if extra
|
|
267
|
-
else
|
|
268
|
-
@extra = ::Zip::ExtraField.new(extra)
|
|
269
|
-
end
|
|
270
386
|
end
|
|
387
|
+
|
|
388
|
+
read_extra_field(extra, local: true)
|
|
271
389
|
parse_zip64_extra(true)
|
|
390
|
+
parse_aes_extra
|
|
272
391
|
@local_header_size = calculate_local_header_size
|
|
273
392
|
end
|
|
274
393
|
|
|
275
|
-
def pack_local_entry
|
|
276
|
-
zip64 = @extra[
|
|
394
|
+
def pack_local_entry # :nodoc:
|
|
395
|
+
zip64 = @extra[:zip64]
|
|
277
396
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
|
278
397
|
@version_needed_to_extract, # version needed to extract
|
|
279
398
|
@gp_flags, # @gp_flags
|
|
280
|
-
|
|
399
|
+
compression_method,
|
|
281
400
|
@time.to_binary_dos_time, # @last_mod_time
|
|
282
401
|
@time.to_binary_dos_date, # @last_mod_date
|
|
283
402
|
@crc,
|
|
284
403
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
285
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
404
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
286
405
|
name_size,
|
|
287
406
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
|
288
407
|
end
|
|
289
408
|
|
|
290
|
-
def write_local_entry(io, rewrite
|
|
291
|
-
|
|
292
|
-
|
|
409
|
+
def write_local_entry(io, suppress_extra_fields: false, rewrite: false) # :nodoc:
|
|
410
|
+
prep_local_zip64_extra
|
|
411
|
+
|
|
412
|
+
# If we are rewriting the local header, then we verify that we haven't changed
|
|
413
|
+
# its size. At this point we have to keep extra fields if they are present.
|
|
414
|
+
if rewrite
|
|
415
|
+
verify_local_header_size!
|
|
416
|
+
elsif suppress_extra_fields
|
|
417
|
+
@extra.suppress_fields!(suppress_extra_fields)
|
|
418
|
+
end
|
|
419
|
+
|
|
293
420
|
@local_header_offset = io.tell
|
|
294
421
|
|
|
295
422
|
io << pack_local_entry
|
|
@@ -299,7 +426,7 @@ module Zip
|
|
|
299
426
|
@local_header_size = io.tell - @local_header_offset
|
|
300
427
|
end
|
|
301
428
|
|
|
302
|
-
def unpack_c_dir_entry(buf)
|
|
429
|
+
def unpack_c_dir_entry(buf) # :nodoc:
|
|
303
430
|
@header_signature,
|
|
304
431
|
@version, # version of encoding software
|
|
305
432
|
@fstype, # filesystem type
|
|
@@ -317,13 +444,10 @@ module Zip
|
|
|
317
444
|
_, # diskNumberStart
|
|
318
445
|
@internal_file_attributes,
|
|
319
446
|
@external_file_attributes,
|
|
320
|
-
@local_header_offset
|
|
321
|
-
@name,
|
|
322
|
-
@extra,
|
|
323
|
-
@comment = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
447
|
+
@local_header_offset = buf.unpack('VCCvvvvvVVVvvvvvVV')
|
|
324
448
|
end
|
|
325
449
|
|
|
326
|
-
def set_ftype_from_c_dir_entry
|
|
450
|
+
def set_ftype_from_c_dir_entry # :nodoc:
|
|
327
451
|
@ftype = case @fstype
|
|
328
452
|
when ::Zip::FSTYPE_UNIX
|
|
329
453
|
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
|
@@ -335,8 +459,9 @@ module Zip
|
|
|
335
459
|
when ::Zip::FILE_TYPE_SYMLINK
|
|
336
460
|
:symlink
|
|
337
461
|
else
|
|
338
|
-
#
|
|
339
|
-
# Otherwise this would be set to unknown and that
|
|
462
|
+
# Best case guess for whether it is a file or not.
|
|
463
|
+
# Otherwise this would be set to unknown and that
|
|
464
|
+
# entry would never be able to be extracted.
|
|
340
465
|
if name_is_directory?
|
|
341
466
|
:directory
|
|
342
467
|
else
|
|
@@ -352,44 +477,52 @@ module Zip
|
|
|
352
477
|
end
|
|
353
478
|
end
|
|
354
479
|
|
|
355
|
-
def check_c_dir_entry_static_header_length(buf)
|
|
356
|
-
return
|
|
480
|
+
def check_c_dir_entry_static_header_length(buf) # :nodoc:
|
|
481
|
+
return unless buf.nil? || buf.bytesize != ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH
|
|
482
|
+
|
|
357
483
|
raise Error, 'Premature end of file. Not enough data for zip cdir entry header'
|
|
358
484
|
end
|
|
359
485
|
|
|
360
|
-
def check_c_dir_entry_signature
|
|
361
|
-
return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
|
486
|
+
def check_c_dir_entry_signature # :nodoc:
|
|
487
|
+
return if @header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE
|
|
488
|
+
|
|
362
489
|
raise Error, "Zip local header magic not found at location '#{local_header_offset}'"
|
|
363
490
|
end
|
|
364
491
|
|
|
365
|
-
def check_c_dir_entry_comment_size
|
|
492
|
+
def check_c_dir_entry_comment_size # :nodoc:
|
|
366
493
|
return if @comment && @comment.bytesize == @comment_length
|
|
494
|
+
|
|
367
495
|
raise ::Zip::Error, 'Truncated cdir zip entry header'
|
|
368
496
|
end
|
|
369
497
|
|
|
370
|
-
def
|
|
371
|
-
if @extra.
|
|
372
|
-
@extra.merge(
|
|
498
|
+
def read_extra_field(buf, local: false) # :nodoc:
|
|
499
|
+
if @extra.kind_of?(::Zip::ExtraField)
|
|
500
|
+
@extra.merge(buf, local: local) if buf
|
|
373
501
|
else
|
|
374
|
-
@extra = ::Zip::ExtraField.new(
|
|
502
|
+
@extra = ::Zip::ExtraField.new(buf, local: local)
|
|
375
503
|
end
|
|
376
504
|
end
|
|
377
505
|
|
|
378
|
-
def read_c_dir_entry(io)
|
|
506
|
+
def read_c_dir_entry(io) # :nodoc:
|
|
507
|
+
@dirty = false # No changes at this point.
|
|
379
508
|
static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH)
|
|
380
509
|
check_c_dir_entry_static_header_length(static_sized_fields_buf)
|
|
381
510
|
unpack_c_dir_entry(static_sized_fields_buf)
|
|
382
511
|
check_c_dir_entry_signature
|
|
383
512
|
set_time(@last_mod_date, @last_mod_time)
|
|
513
|
+
|
|
384
514
|
@name = io.read(@name_length)
|
|
385
515
|
if ::Zip.force_entry_names_encoding
|
|
386
516
|
@name.force_encoding(::Zip.force_entry_names_encoding)
|
|
387
517
|
end
|
|
388
|
-
|
|
518
|
+
@name.tr!('\\', '/') # Normalise filepath separators after encoding set.
|
|
519
|
+
|
|
520
|
+
read_extra_field(io.read(@extra_length))
|
|
389
521
|
@comment = io.read(@comment_length)
|
|
390
522
|
check_c_dir_entry_comment_size
|
|
391
523
|
set_ftype_from_c_dir_entry
|
|
392
524
|
parse_zip64_extra(false)
|
|
525
|
+
parse_aes_extra
|
|
393
526
|
end
|
|
394
527
|
|
|
395
528
|
def file_stat(path) # :nodoc:
|
|
@@ -401,64 +534,72 @@ module Zip
|
|
|
401
534
|
end
|
|
402
535
|
|
|
403
536
|
def get_extra_attributes_from_path(path) # :nodoc:
|
|
404
|
-
|
|
405
|
-
|
|
537
|
+
stat = file_stat(path)
|
|
538
|
+
@time = DOSTime.from_time(stat.mtime)
|
|
539
|
+
return if ::Zip::RUNNING_ON_WINDOWS
|
|
540
|
+
|
|
406
541
|
@unix_uid = stat.uid
|
|
407
542
|
@unix_gid = stat.gid
|
|
408
543
|
@unix_perms = stat.mode & 0o7777
|
|
409
544
|
end
|
|
410
545
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
#
|
|
414
|
-
unix_perms_mask = 0o1777
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
546
|
+
# rubocop:disable Style/GuardClause
|
|
547
|
+
def set_unix_attributes_on_path(dest_path) # :nodoc:
|
|
548
|
+
# Ignore setuid/setgid bits by default. Honour if @restore_ownership.
|
|
549
|
+
unix_perms_mask = (@restore_ownership ? 0o7777 : 0o1777)
|
|
550
|
+
if @restore_permissions && @unix_perms
|
|
551
|
+
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path)
|
|
552
|
+
end
|
|
553
|
+
if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
|
554
|
+
::FileUtils.chown(@unix_uid, @unix_gid, dest_path)
|
|
555
|
+
end
|
|
419
556
|
end
|
|
557
|
+
# rubocop:enable Style/GuardClause
|
|
420
558
|
|
|
421
559
|
def set_extra_attributes_on_path(dest_path) # :nodoc:
|
|
422
560
|
return unless file? || directory?
|
|
423
561
|
|
|
424
562
|
case @fstype
|
|
425
563
|
when ::Zip::FSTYPE_UNIX
|
|
426
|
-
|
|
564
|
+
set_unix_attributes_on_path(dest_path)
|
|
427
565
|
end
|
|
566
|
+
|
|
567
|
+
# Restore the timestamp on a file. This will either have come from the
|
|
568
|
+
# original source file that was copied into the archive, or from the
|
|
569
|
+
# creation date of the archive if there was no original source file.
|
|
570
|
+
::FileUtils.touch(dest_path, mtime: time) if @restore_times
|
|
428
571
|
end
|
|
429
572
|
|
|
430
|
-
def pack_c_dir_entry
|
|
431
|
-
zip64 = @extra[
|
|
573
|
+
def pack_c_dir_entry # :nodoc:
|
|
574
|
+
zip64 = @extra[:zip64]
|
|
432
575
|
[
|
|
433
576
|
@header_signature,
|
|
434
577
|
@version, # version of encoding software
|
|
435
578
|
@fstype, # filesystem type
|
|
436
579
|
@version_needed_to_extract, # @versionNeededToExtract
|
|
437
580
|
@gp_flags, # @gp_flags
|
|
438
|
-
|
|
581
|
+
compression_method,
|
|
439
582
|
@time.to_binary_dos_time, # @last_mod_time
|
|
440
583
|
@time.to_binary_dos_date, # @last_mod_date
|
|
441
584
|
@crc,
|
|
442
585
|
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
|
443
|
-
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
|
586
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : (@size || 0),
|
|
444
587
|
name_size,
|
|
445
588
|
@extra ? @extra.c_dir_size : 0,
|
|
446
589
|
comment_size,
|
|
447
590
|
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
|
448
591
|
@internal_file_attributes, # file type (binary=0, text=1)
|
|
449
592
|
@external_file_attributes, # native filesystem attributes
|
|
450
|
-
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
451
|
-
@name,
|
|
452
|
-
@extra,
|
|
453
|
-
@comment
|
|
593
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset
|
|
454
594
|
].pack('VCCvvvvvVVVvvvvvVV')
|
|
455
595
|
end
|
|
456
596
|
|
|
457
|
-
def write_c_dir_entry(io)
|
|
458
|
-
|
|
597
|
+
def write_c_dir_entry(io, suppress_extra_fields: false) # :nodoc:
|
|
598
|
+
prep_cdir_zip64_extra
|
|
599
|
+
|
|
459
600
|
case @fstype
|
|
460
601
|
when ::Zip::FSTYPE_UNIX
|
|
461
|
-
ft = case
|
|
602
|
+
ft = case ftype
|
|
462
603
|
when :file
|
|
463
604
|
@unix_perms ||= 0o644
|
|
464
605
|
::Zip::FILE_TYPE_FILE
|
|
@@ -471,10 +612,11 @@ module Zip
|
|
|
471
612
|
end
|
|
472
613
|
|
|
473
614
|
unless ft.nil?
|
|
474
|
-
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
|
615
|
+
@external_file_attributes = ((ft << 12) | (@unix_perms & 0o7777)) << 16
|
|
475
616
|
end
|
|
476
617
|
end
|
|
477
618
|
|
|
619
|
+
@extra.suppress_fields!(suppress_extra_fields) if suppress_extra_fields
|
|
478
620
|
io << pack_c_dir_entry
|
|
479
621
|
|
|
480
622
|
io << @name
|
|
@@ -482,42 +624,42 @@ module Zip
|
|
|
482
624
|
io << @comment
|
|
483
625
|
end
|
|
484
626
|
|
|
485
|
-
def ==(other)
|
|
627
|
+
def ==(other) # :nodoc:
|
|
486
628
|
return false unless other.class == self.class
|
|
629
|
+
|
|
487
630
|
# Compares contents of local entry and exposed fields
|
|
488
|
-
|
|
631
|
+
%w[compression_method crc compressed_size size name extra filepath time].all? do |k|
|
|
489
632
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
|
490
633
|
end
|
|
491
|
-
keys_equal && time.dos_equals(other.time)
|
|
492
634
|
end
|
|
493
635
|
|
|
494
|
-
def <=>(other)
|
|
636
|
+
def <=>(other) # :nodoc:
|
|
495
637
|
to_s <=> other.to_s
|
|
496
638
|
end
|
|
497
639
|
|
|
498
640
|
# Returns an IO like object for the given ZipEntry.
|
|
499
641
|
# Warning: may behave weird with symlinks.
|
|
500
642
|
def get_input_stream(&block)
|
|
501
|
-
if
|
|
502
|
-
yield ::Zip::NullInputStream if
|
|
643
|
+
if ftype == :directory
|
|
644
|
+
yield ::Zip::NullInputStream if block
|
|
503
645
|
::Zip::NullInputStream
|
|
504
646
|
elsif @filepath
|
|
505
|
-
case
|
|
647
|
+
case ftype
|
|
506
648
|
when :file
|
|
507
649
|
::File.open(@filepath, 'rb', &block)
|
|
508
650
|
when :symlink
|
|
509
651
|
linkpath = ::File.readlink(@filepath)
|
|
510
652
|
stringio = ::StringIO.new(linkpath)
|
|
511
|
-
yield(stringio) if
|
|
653
|
+
yield(stringio) if block
|
|
512
654
|
stringio
|
|
513
655
|
else
|
|
514
|
-
raise "unknown @file_type #{
|
|
656
|
+
raise "unknown @file_type #{ftype}"
|
|
515
657
|
end
|
|
516
658
|
else
|
|
517
|
-
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
|
659
|
+
zis = ::Zip::InputStream.new(@zipfile, offset: local_header_offset)
|
|
518
660
|
zis.instance_variable_set(:@complete_entry, self)
|
|
519
661
|
zis.get_next_entry
|
|
520
|
-
if
|
|
662
|
+
if block
|
|
521
663
|
begin
|
|
522
664
|
yield(zis)
|
|
523
665
|
ensure
|
|
@@ -535,8 +677,8 @@ module Zip
|
|
|
535
677
|
when 'file'
|
|
536
678
|
if name_is_directory?
|
|
537
679
|
raise ArgumentError,
|
|
538
|
-
"entry name '#{
|
|
539
|
-
|
|
680
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
681
|
+
"'#{src_path}' is not a directory"
|
|
540
682
|
end
|
|
541
683
|
:file
|
|
542
684
|
when 'directory'
|
|
@@ -545,8 +687,8 @@ module Zip
|
|
|
545
687
|
when 'link'
|
|
546
688
|
if name_is_directory?
|
|
547
689
|
raise ArgumentError,
|
|
548
|
-
"entry name '#{
|
|
549
|
-
|
|
690
|
+
"entry name '#{@name}' indicates a directory entry, but " \
|
|
691
|
+
"'#{src_path}' is not a directory"
|
|
550
692
|
end
|
|
551
693
|
:symlink
|
|
552
694
|
else
|
|
@@ -554,27 +696,30 @@ module Zip
|
|
|
554
696
|
end
|
|
555
697
|
|
|
556
698
|
@filepath = src_path
|
|
699
|
+
@size = stat.size
|
|
557
700
|
get_extra_attributes_from_path(@filepath)
|
|
558
701
|
end
|
|
559
702
|
|
|
560
|
-
def write_to_zip_output_stream(zip_output_stream)
|
|
561
|
-
if
|
|
562
|
-
zip_output_stream.put_next_entry(self
|
|
703
|
+
def write_to_zip_output_stream(zip_output_stream) # :nodoc:
|
|
704
|
+
if ftype == :directory
|
|
705
|
+
zip_output_stream.put_next_entry(self)
|
|
563
706
|
elsif @filepath
|
|
564
|
-
zip_output_stream.put_next_entry(self
|
|
565
|
-
get_input_stream
|
|
707
|
+
zip_output_stream.put_next_entry(self)
|
|
708
|
+
get_input_stream do |is|
|
|
709
|
+
::Zip::IOExtras.copy_stream(zip_output_stream, is)
|
|
710
|
+
end
|
|
566
711
|
else
|
|
567
712
|
zip_output_stream.copy_raw_entry(self)
|
|
568
713
|
end
|
|
569
714
|
end
|
|
570
715
|
|
|
571
|
-
def parent_as_string
|
|
716
|
+
def parent_as_string # :nodoc:
|
|
572
717
|
entry_name = name.chomp('/')
|
|
573
718
|
slash_index = entry_name.rindex('/')
|
|
574
719
|
slash_index ? entry_name.slice(0, slash_index + 1) : nil
|
|
575
720
|
end
|
|
576
721
|
|
|
577
|
-
def get_raw_input_stream(&block)
|
|
722
|
+
def get_raw_input_stream(&block) # :nodoc:
|
|
578
723
|
if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read)
|
|
579
724
|
yield @zipfile
|
|
580
725
|
else
|
|
@@ -582,58 +727,63 @@ module Zip
|
|
|
582
727
|
end
|
|
583
728
|
end
|
|
584
729
|
|
|
585
|
-
def clean_up
|
|
586
|
-
#
|
|
730
|
+
def clean_up # :nodoc:
|
|
731
|
+
@dirty = false # Any changes are written at this point.
|
|
587
732
|
end
|
|
588
733
|
|
|
589
734
|
private
|
|
590
735
|
|
|
736
|
+
def read_local_header_fields(io) # :nodoc:
|
|
737
|
+
static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || ''
|
|
738
|
+
|
|
739
|
+
unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH
|
|
740
|
+
raise Error, 'Premature end of file. Not enough data for zip entry local header'
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
unpack_local_entry(static_sized_fields_buf)
|
|
744
|
+
end
|
|
745
|
+
|
|
591
746
|
def set_time(binary_dos_date, binary_dos_time)
|
|
592
747
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
|
593
748
|
rescue ArgumentError
|
|
594
|
-
warn '
|
|
749
|
+
warn 'WARNING: invalid date/time in zip entry.' if ::Zip.warn_invalid_date
|
|
595
750
|
end
|
|
596
751
|
|
|
597
752
|
def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
|
598
753
|
if ::File.exist?(dest_path) && !yield(self, dest_path)
|
|
599
|
-
raise
|
|
600
|
-
"Destination '#{dest_path}' already exists"
|
|
754
|
+
raise DestinationExistsError, dest_path
|
|
601
755
|
end
|
|
756
|
+
|
|
602
757
|
::File.open(dest_path, 'wb') do |os|
|
|
603
758
|
get_input_stream do |is|
|
|
604
|
-
set_extra_attributes_on_path(dest_path)
|
|
605
|
-
|
|
606
759
|
bytes_written = 0
|
|
607
760
|
warned = false
|
|
608
|
-
buf =
|
|
609
|
-
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
|
761
|
+
while (buf = is.sysread(Decompressor::CHUNK_SIZE))
|
|
610
762
|
os << buf
|
|
611
763
|
bytes_written += buf.bytesize
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
end
|
|
620
|
-
end
|
|
764
|
+
next unless bytes_written > size && !warned
|
|
765
|
+
|
|
766
|
+
error = EntrySizeError.new(self)
|
|
767
|
+
raise error if Zip.validate_entry_sizes
|
|
768
|
+
|
|
769
|
+
warn "WARNING: #{error.message}"
|
|
770
|
+
warned = true
|
|
621
771
|
end
|
|
622
772
|
end
|
|
623
773
|
end
|
|
774
|
+
|
|
775
|
+
set_extra_attributes_on_path(dest_path)
|
|
624
776
|
end
|
|
625
777
|
|
|
626
778
|
def create_directory(dest_path)
|
|
627
779
|
return if ::File.directory?(dest_path)
|
|
780
|
+
|
|
628
781
|
if ::File.exist?(dest_path)
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
raise ::Zip::DestinationFileExistsError,
|
|
633
|
-
"Cannot create directory '#{dest_path}'. " \
|
|
634
|
-
'A file already exists with that name'
|
|
635
|
-
end
|
|
782
|
+
raise ::Zip::DestinationExistsError, dest_path unless block_given? && yield(self, dest_path)
|
|
783
|
+
|
|
784
|
+
::FileUtils.rm_f dest_path
|
|
636
785
|
end
|
|
786
|
+
|
|
637
787
|
::FileUtils.mkdir_p(dest_path)
|
|
638
788
|
set_extra_attributes_on_path(dest_path)
|
|
639
789
|
end
|
|
@@ -642,56 +792,90 @@ module Zip
|
|
|
642
792
|
def create_symlink(dest_path)
|
|
643
793
|
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
|
644
794
|
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
|
645
|
-
|
|
795
|
+
warn "WARNING: skipped symlink '#{dest_path}'."
|
|
646
796
|
end
|
|
647
797
|
|
|
648
798
|
# apply missing data from the zip64 extra information field, if present
|
|
649
799
|
# (required when file sizes exceed 2**32, but can be used for all files)
|
|
650
|
-
def parse_zip64_extra(for_local_header)
|
|
651
|
-
return
|
|
800
|
+
def parse_zip64_extra(for_local_header) # :nodoc:
|
|
801
|
+
return unless zip64?
|
|
802
|
+
|
|
652
803
|
if for_local_header
|
|
653
|
-
@size, @compressed_size = @extra[
|
|
804
|
+
@size, @compressed_size = @extra[:zip64].parse(@size, @compressed_size)
|
|
654
805
|
else
|
|
655
|
-
@size, @compressed_size, @local_header_offset = @extra[
|
|
806
|
+
@size, @compressed_size, @local_header_offset = @extra[:zip64].parse(
|
|
807
|
+
@size, @compressed_size, @local_header_offset
|
|
808
|
+
)
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
def parse_aes_extra # :nodoc:
|
|
813
|
+
return unless aes?
|
|
814
|
+
|
|
815
|
+
if @extra[:aes].vendor_id != 'AE'
|
|
816
|
+
raise Error, "Unsupported encryption method #{@extra[:aes].vendor_id}"
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
unless ::Zip::AESEncryption::VERSIONS.include? @extra[:aes].vendor_version
|
|
820
|
+
raise Error, "Unsupported encryption style #{@extra[:aes].vendor_version}"
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
@compression_method = @extra[:aes].compression_method if ftype != :directory
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
# For DEFLATED compression *only*: set the general purpose flags 1 and 2 to
|
|
827
|
+
# indicate compression level. This seems to be mainly cosmetic but they are
|
|
828
|
+
# generally set by other tools - including in docx files. It is these flags
|
|
829
|
+
# that are used by commandline tools (and elsewhere) to give an indication
|
|
830
|
+
# of how compressed a file is. See the PKWARE APPNOTE for more information:
|
|
831
|
+
# https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
|
832
|
+
#
|
|
833
|
+
# It's safe to simply OR these flags here as compression_level is read only.
|
|
834
|
+
def set_compression_level_flags
|
|
835
|
+
return unless compression_method == DEFLATED
|
|
836
|
+
|
|
837
|
+
case @compression_level
|
|
838
|
+
when 1
|
|
839
|
+
@gp_flags |= COMPRESSION_LEVEL_SUPERFAST_GPFLAG
|
|
840
|
+
when 2
|
|
841
|
+
@gp_flags |= COMPRESSION_LEVEL_FAST_GPFLAG
|
|
842
|
+
when 8, 9
|
|
843
|
+
@gp_flags |= COMPRESSION_LEVEL_MAX_GPFLAG
|
|
656
844
|
end
|
|
657
845
|
end
|
|
658
846
|
|
|
659
|
-
|
|
660
|
-
|
|
847
|
+
# rubocop:disable Style/GuardClause
|
|
848
|
+
def prep_local_zip64_extra
|
|
849
|
+
return unless ::Zip.write_zip64_support
|
|
850
|
+
return if (!zip64? && @size && @size < 0xFFFFFFFF) || !file?
|
|
851
|
+
|
|
852
|
+
# Might not know size here, so need ZIP64 just in case.
|
|
853
|
+
# If we already have a ZIP64 extra (placeholder) then we must fill it in.
|
|
854
|
+
if zip64? || @size.nil? || @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF
|
|
855
|
+
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
|
856
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
857
|
+
|
|
858
|
+
# Local header always includes size and compressed size.
|
|
859
|
+
zip64.original_size = @size || 0
|
|
860
|
+
zip64.compressed_size = @compressed_size
|
|
861
|
+
end
|
|
661
862
|
end
|
|
662
863
|
|
|
663
|
-
|
|
664
|
-
def prep_zip64_extra(for_local_header) #:nodoc:all
|
|
864
|
+
def prep_cdir_zip64_extra
|
|
665
865
|
return unless ::Zip.write_zip64_support
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
866
|
+
|
|
867
|
+
if (@size && @size >= 0xFFFFFFFF) || @compressed_size >= 0xFFFFFFFF ||
|
|
868
|
+
@local_header_offset >= 0xFFFFFFFF
|
|
669
869
|
@version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64
|
|
670
|
-
@extra.
|
|
671
|
-
zip64 = @extra.create('Zip64')
|
|
672
|
-
if for_local_header
|
|
673
|
-
# local header always includes size and compressed size
|
|
674
|
-
zip64.original_size = @size
|
|
675
|
-
zip64.compressed_size = @compressed_size
|
|
676
|
-
else
|
|
677
|
-
# central directory entry entries include whichever fields are necessary
|
|
678
|
-
zip64.original_size = @size if @size >= 0xFFFFFFFF
|
|
679
|
-
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
|
680
|
-
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
|
681
|
-
end
|
|
682
|
-
else
|
|
683
|
-
@extra.delete('Zip64')
|
|
870
|
+
zip64 = @extra[:zip64] || @extra.create(:zip64)
|
|
684
871
|
|
|
685
|
-
#
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if
|
|
689
|
-
@extra.create('Zip64Placeholder')
|
|
690
|
-
else
|
|
691
|
-
@extra.delete('Zip64Placeholder')
|
|
692
|
-
end
|
|
872
|
+
# Central directory entry entries include whichever fields are necessary.
|
|
873
|
+
zip64.original_size = @size if @size && @size >= 0xFFFFFFFF
|
|
874
|
+
zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF
|
|
875
|
+
zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF
|
|
693
876
|
end
|
|
694
877
|
end
|
|
878
|
+
# rubocop:enable Style/GuardClause
|
|
695
879
|
end
|
|
696
880
|
end
|
|
697
881
|
|