rubyzip 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +95 -43
- data/lib/zip.rb +11 -1
- data/lib/zip/central_directory.rb +3 -3
- data/lib/zip/compressor.rb +1 -2
- data/lib/zip/constants.rb +3 -3
- data/lib/zip/crypto/null_encryption.rb +2 -4
- data/lib/zip/decompressor.rb +1 -1
- data/lib/zip/dos_time.rb +1 -1
- data/lib/zip/entry.rb +70 -54
- data/lib/zip/entry_set.rb +4 -4
- data/lib/zip/errors.rb +1 -0
- data/lib/zip/extra_field.rb +2 -2
- data/lib/zip/extra_field/generic.rb +1 -1
- data/lib/zip/extra_field/zip64_placeholder.rb +1 -2
- data/lib/zip/file.rb +62 -51
- data/lib/zip/filesystem.rb +17 -13
- data/lib/zip/inflater.rb +2 -2
- data/lib/zip/input_stream.rb +10 -7
- data/lib/zip/ioextras/abstract_input_stream.rb +1 -1
- data/lib/zip/ioextras/abstract_output_stream.rb +3 -3
- data/lib/zip/output_stream.rb +5 -5
- data/lib/zip/pass_thru_decompressor.rb +1 -1
- data/lib/zip/streamable_stream.rb +1 -1
- data/lib/zip/version.rb +1 -1
- data/samples/example_recursive.rb +15 -18
- data/samples/gtk_ruby_zip.rb +1 -1
- data/samples/qtzip.rb +1 -1
- data/samples/zipfind.rb +2 -2
- data/test/central_directory_entry_test.rb +2 -2
- data/test/crypto/null_encryption_test.rb +6 -2
- data/test/data/gpbit3stored.zip +0 -0
- data/test/data/path_traversal/Makefile +10 -0
- data/test/data/path_traversal/jwilk/README.md +5 -0
- 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 +3 -0
- 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/entry_set_test.rb +13 -2
- data/test/entry_test.rb +3 -12
- data/test/errors_test.rb +1 -0
- data/test/file_extract_test.rb +62 -0
- data/test/file_permissions_test.rb +39 -43
- data/test/file_test.rb +115 -12
- data/test/filesystem/dir_iterator_test.rb +1 -1
- data/test/filesystem/directory_test.rb +29 -11
- data/test/filesystem/file_mutating_test.rb +3 -4
- data/test/filesystem/file_nonmutating_test.rb +34 -34
- data/test/filesystem/file_stat_test.rb +5 -5
- data/test/gentestfiles.rb +17 -13
- data/test/input_stream_test.rb +10 -10
- data/test/ioextras/abstract_input_stream_test.rb +1 -1
- data/test/ioextras/abstract_output_stream_test.rb +2 -2
- data/test/ioextras/fake_io_test.rb +1 -1
- data/test/local_entry_test.rb +1 -1
- data/test/path_traversal_test.rb +141 -0
- data/test/test_helper.rb +16 -3
- data/test/unicode_file_names_and_comments_test.rb +12 -0
- data/test/zip64_full_test.rb +2 -2
- metadata +103 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7861f60d52fbe54891831d9239df3e3e927a33f1f2d00abcb7b2693a8c01097d
|
4
|
+
data.tar.gz: c04844369dbc75f40583f3d8aab34cbf2cfcce293b3c30570fa76208755a3157
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9fa8a9f92c4789f06a1691eee6201918a753f1356eb6f2ef99fe9c777d46d661b7a8f96ce71b7a18406a4e1a4098f81b8a282babe2a7284ae0c8c04d03cf758
|
7
|
+
data.tar.gz: e4650b6f572ef4bcdb33cff4bd0c7ddbd399b04278bd644d11ea29defe057510706d15d0b8ac918df28280d13f4bdfec28526aaad258ff52136ce209f29a35dd
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# rubyzip
|
2
|
+
|
2
3
|
[![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip)
|
3
4
|
[![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip)
|
4
5
|
[![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip)
|
@@ -19,9 +20,10 @@ gem 'zip-zip' # will load compatibility for old rubyzip API.
|
|
19
20
|
|
20
21
|
## Requirements
|
21
22
|
|
22
|
-
|
23
|
+
- Ruby 1.9.2 or greater
|
23
24
|
|
24
25
|
## Installation
|
26
|
+
|
25
27
|
Rubyzip is available on RubyGems:
|
26
28
|
|
27
29
|
```
|
@@ -52,30 +54,29 @@ Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
|
|
52
54
|
# Two arguments:
|
53
55
|
# - The name of the file as it will appear in the archive
|
54
56
|
# - The original file, including the path to find it
|
55
|
-
zipfile.add(filename, folder
|
57
|
+
zipfile.add(filename, File.join(folder, filename))
|
56
58
|
end
|
57
|
-
zipfile.get_output_stream("myFile") { |
|
59
|
+
zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" }
|
58
60
|
end
|
59
61
|
```
|
60
62
|
|
61
63
|
### Zipping a directory recursively
|
62
|
-
|
64
|
+
|
65
|
+
Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb)
|
63
66
|
|
64
67
|
```ruby
|
65
|
-
require 'rubygems'
|
66
68
|
require 'zip'
|
69
|
+
|
67
70
|
# This is a simple example which uses rubyzip to
|
68
71
|
# recursively generate a zip file from the contents of
|
69
72
|
# a specified directory. The directory itself is not
|
70
73
|
# included in the archive, rather just its contents.
|
71
74
|
#
|
72
75
|
# Usage:
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# zf.write()
|
78
|
-
|
76
|
+
# directory_to_zip = "/tmp/input"
|
77
|
+
# output_file = "/tmp/out.zip"
|
78
|
+
# zf = ZipFileGenerator.new(directory_to_zip, output_file)
|
79
|
+
# zf.write()
|
79
80
|
class ZipFileGenerator
|
80
81
|
# Initialize with the directory to zip and the location of the output archive.
|
81
82
|
def initialize(input_dir, output_file)
|
@@ -85,40 +86,37 @@ class ZipFileGenerator
|
|
85
86
|
|
86
87
|
# Zip the input directory.
|
87
88
|
def write
|
88
|
-
entries = Dir.entries(@input_dir) - %w
|
89
|
+
entries = Dir.entries(@input_dir) - %w[. ..]
|
89
90
|
|
90
|
-
::Zip::File.open(@output_file, ::Zip::File::CREATE) do |
|
91
|
-
write_entries entries, '',
|
91
|
+
::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
|
92
|
+
write_entries entries, '', zipfile
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
95
96
|
private
|
96
97
|
|
97
98
|
# A helper method to make the recursion work.
|
98
|
-
def write_entries(entries, path,
|
99
|
+
def write_entries(entries, path, zipfile)
|
99
100
|
entries.each do |e|
|
100
|
-
|
101
|
-
disk_file_path = File.join(@input_dir,
|
102
|
-
puts "Deflating #{disk_file_path}"
|
101
|
+
zipfile_path = path == '' ? e : File.join(path, e)
|
102
|
+
disk_file_path = File.join(@input_dir, zipfile_path)
|
103
103
|
|
104
104
|
if File.directory? disk_file_path
|
105
|
-
recursively_deflate_directory(disk_file_path,
|
105
|
+
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
106
106
|
else
|
107
|
-
put_into_archive(disk_file_path,
|
107
|
+
put_into_archive(disk_file_path, zipfile, zipfile_path)
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
def recursively_deflate_directory(disk_file_path,
|
113
|
-
|
114
|
-
subdir = Dir.entries(disk_file_path) - %w
|
115
|
-
write_entries subdir,
|
112
|
+
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
113
|
+
zipfile.mkdir zipfile_path
|
114
|
+
subdir = Dir.entries(disk_file_path) - %w[. ..]
|
115
|
+
write_entries subdir, zipfile_path, zipfile
|
116
116
|
end
|
117
117
|
|
118
|
-
def put_into_archive(disk_file_path,
|
119
|
-
|
120
|
-
f.puts(File.open(disk_file_path, 'rb').read)
|
121
|
-
end
|
118
|
+
def put_into_archive(disk_file_path, zipfile, zipfile_path)
|
119
|
+
zipfile.add(zipfile_path, disk_file_path)
|
122
120
|
end
|
123
121
|
end
|
124
122
|
```
|
@@ -154,12 +152,15 @@ When modifying a zip archive the file permissions of the archive are preserved.
|
|
154
152
|
### Reading a Zip file
|
155
153
|
|
156
154
|
```ruby
|
155
|
+
MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this)
|
157
156
|
Zip::File.open('foo.zip') do |zip_file|
|
158
157
|
# Handle entries one by one
|
159
158
|
zip_file.each do |entry|
|
160
|
-
# Extract to file/directory/symlink
|
161
159
|
puts "Extracting #{entry.name}"
|
162
|
-
entry.
|
160
|
+
raise 'File too large when extracted' if entry.size > MAX_SIZE
|
161
|
+
|
162
|
+
# Extract to file or directory based on name in the archive
|
163
|
+
entry.extract
|
163
164
|
|
164
165
|
# Read into memory
|
165
166
|
content = entry.get_input_stream.read
|
@@ -167,6 +168,7 @@ Zip::File.open('foo.zip') do |zip_file|
|
|
167
168
|
|
168
169
|
# Find specific entry
|
169
170
|
entry = zip_file.glob('*.csv').first
|
171
|
+
raise 'File too large when extracted' if entry.size > MAX_SIZE
|
170
172
|
puts entry.get_input_stream.read
|
171
173
|
end
|
172
174
|
```
|
@@ -177,9 +179,7 @@ end
|
|
177
179
|
|
178
180
|
But there is one exception when it is not working - General Purpose Flag Bit 3.
|
179
181
|
|
180
|
-
|
181
|
-
If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data
|
182
|
-
```
|
182
|
+
> If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data
|
183
183
|
|
184
184
|
If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception.
|
185
185
|
|
@@ -218,12 +218,14 @@ buffer = Zip::OutputStream.write_buffer do |out|
|
|
218
218
|
out.write rels.to_xml(:indent => 0).gsub("\n","")
|
219
219
|
end
|
220
220
|
|
221
|
-
File.open(new_path, "
|
221
|
+
File.open(new_path, "wb") {|f| f.write(buffer.string) }
|
222
222
|
```
|
223
223
|
|
224
224
|
## Configuration
|
225
225
|
|
226
|
-
|
226
|
+
### Existing Files
|
227
|
+
|
228
|
+
By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so:
|
227
229
|
|
228
230
|
```ruby
|
229
231
|
Zip.on_exists_proc = true
|
@@ -237,25 +239,83 @@ Additionally, if you want to configure rubyzip to overwrite existing files while
|
|
237
239
|
Zip.continue_on_exists_proc = true
|
238
240
|
```
|
239
241
|
|
242
|
+
### Non-ASCII Names
|
243
|
+
|
240
244
|
If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option:
|
241
245
|
|
242
246
|
```ruby
|
243
247
|
Zip.unicode_names = true
|
244
248
|
```
|
245
249
|
|
250
|
+
Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so:
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
Zip.force_entry_names_encoding = 'UTF-8'
|
254
|
+
```
|
255
|
+
|
256
|
+
Allowed encoding names are the same as accepted by `String#force_encoding`
|
257
|
+
|
258
|
+
### Date Validation
|
259
|
+
|
246
260
|
Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting:
|
247
261
|
|
248
262
|
```ruby
|
249
263
|
Zip.warn_invalid_date = false
|
250
264
|
```
|
251
265
|
|
266
|
+
### Size Validation
|
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:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
MAX_FILE_SIZE = 10 * 1024**2 # 10MiB
|
278
|
+
MAX_FILES = 100
|
279
|
+
Zip::File.open('foo.zip') do |zip_file|
|
280
|
+
num_files = 0
|
281
|
+
zip_file.each do |entry|
|
282
|
+
num_files += 1 if entry.file?
|
283
|
+
raise 'Too many extracted files' if num_files > MAX_FILES
|
284
|
+
raise 'File too large when extracted' if entry.size > MAX_FILE_SIZE
|
285
|
+
entry.extract
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
If you need to extract zip files that report incorrect uncompressed sizes and you really trust them not too be too large, you can disable this setting with
|
291
|
+
```ruby
|
292
|
+
Zip.validate_entry_sizes = false
|
293
|
+
```
|
294
|
+
|
295
|
+
Note that if you use the lower level `Zip::InputStream` interface, `rubyzip` does *not* check the entry `size`s. In this case, the caller is responsible for making sure it does not read more data than expected from the input stream.
|
296
|
+
|
297
|
+
### Default Compression
|
298
|
+
|
252
299
|
You can set the default compression level like so:
|
253
300
|
|
254
301
|
```ruby
|
255
302
|
Zip.default_compression = Zlib::DEFAULT_COMPRESSION
|
256
303
|
```
|
304
|
+
|
257
305
|
It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION`
|
258
306
|
|
307
|
+
### Zip64 Support
|
308
|
+
|
309
|
+
By default, Zip64 support is disabled for writing. To enable it do this:
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
Zip.write_zip64_support = true
|
313
|
+
```
|
314
|
+
|
315
|
+
_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
|
316
|
+
|
317
|
+
### Block Form
|
318
|
+
|
259
319
|
You can set multiple settings at the same time by using a block:
|
260
320
|
|
261
321
|
```ruby
|
@@ -267,14 +327,6 @@ You can set multiple settings at the same time by using a block:
|
|
267
327
|
end
|
268
328
|
```
|
269
329
|
|
270
|
-
By default, Zip64 support is disabled for writing. To enable it do this:
|
271
|
-
|
272
|
-
```ruby
|
273
|
-
Zip.write_zip64_support = true
|
274
|
-
```
|
275
|
-
|
276
|
-
_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive.
|
277
|
-
|
278
330
|
## Developing
|
279
331
|
|
280
332
|
To run the test you need to do this:
|
data/lib/zip.rb
CHANGED
@@ -34,7 +34,16 @@ require 'zip/errors'
|
|
34
34
|
|
35
35
|
module Zip
|
36
36
|
extend self
|
37
|
-
attr_accessor :unicode_names,
|
37
|
+
attr_accessor :unicode_names,
|
38
|
+
:on_exists_proc,
|
39
|
+
:continue_on_exists_proc,
|
40
|
+
:sort_entries,
|
41
|
+
:default_compression,
|
42
|
+
:write_zip64_support,
|
43
|
+
:warn_invalid_date,
|
44
|
+
:case_insensitive_match,
|
45
|
+
:force_entry_names_encoding,
|
46
|
+
:validate_entry_sizes
|
38
47
|
|
39
48
|
def reset!
|
40
49
|
@_ran_once = false
|
@@ -46,6 +55,7 @@ module Zip
|
|
46
55
|
@write_zip64_support = false
|
47
56
|
@warn_invalid_date = true
|
48
57
|
@case_insensitive_match = false
|
58
|
+
@validate_entry_sizes = false
|
49
59
|
end
|
50
60
|
|
51
61
|
def setup
|
@@ -96,7 +96,7 @@ module Zip
|
|
96
96
|
@size_in_bytes = Entry.read_zip_64_long(buf)
|
97
97
|
@cdir_offset = Entry.read_zip_64_long(buf)
|
98
98
|
@zip_64_extensible = buf.slice!(0, buf.bytesize)
|
99
|
-
raise Error, 'Zip consistency problem while reading eocd structure' unless buf.
|
99
|
+
raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty?
|
100
100
|
end
|
101
101
|
|
102
102
|
def read_e_o_c_d(buf) #:nodoc:
|
@@ -113,7 +113,7 @@ module Zip
|
|
113
113
|
else
|
114
114
|
buf.read(comment_length)
|
115
115
|
end
|
116
|
-
raise Error, 'Zip consistency problem while reading eocd structure' unless buf.
|
116
|
+
raise Error, 'Zip consistency problem while reading eocd structure' unless buf.empty?
|
117
117
|
end
|
118
118
|
|
119
119
|
def read_central_directory_entries(io) #:nodoc:
|
@@ -130,7 +130,7 @@ module Zip
|
|
130
130
|
|
131
131
|
def read_from_stream(io) #:nodoc:
|
132
132
|
buf = start_buf(io)
|
133
|
-
if
|
133
|
+
if zip64_file?(buf)
|
134
134
|
read_64_e_o_c_d(buf)
|
135
135
|
else
|
136
136
|
read_e_o_c_d(buf)
|
data/lib/zip/compressor.rb
CHANGED
data/lib/zip/constants.rb
CHANGED
@@ -11,9 +11,9 @@ module Zip
|
|
11
11
|
VERSION_NEEDED_TO_EXTRACT = 20
|
12
12
|
VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45
|
13
13
|
|
14
|
-
FILE_TYPE_FILE =
|
15
|
-
FILE_TYPE_DIR =
|
16
|
-
FILE_TYPE_SYMLINK =
|
14
|
+
FILE_TYPE_FILE = 0o10
|
15
|
+
FILE_TYPE_DIR = 0o04
|
16
|
+
FILE_TYPE_SYMLINK = 0o12
|
17
17
|
|
18
18
|
FSTYPE_FAT = 0
|
19
19
|
FSTYPE_AMIGA = 1
|
data/lib/zip/decompressor.rb
CHANGED
data/lib/zip/dos_time.rb
CHANGED
data/lib/zip/entry.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'pathname'
|
1
2
|
module Zip
|
2
3
|
class Entry
|
3
4
|
STORED = 0
|
@@ -7,6 +8,7 @@ module Zip
|
|
7
8
|
|
8
9
|
attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
|
9
10
|
:name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes,
|
11
|
+
:internal_file_attributes,
|
10
12
|
:gp_flags, :header_signature, :follow_symlinks,
|
11
13
|
:restore_times, :restore_permissions, :restore_ownership,
|
12
14
|
:unix_uid, :unix_gid, :unix_perms,
|
@@ -98,7 +100,7 @@ module Zip
|
|
98
100
|
end
|
99
101
|
|
100
102
|
# Dynamic checkers
|
101
|
-
%w
|
103
|
+
%w[directory file symlink].each do |k|
|
102
104
|
define_method "#{k}?" do
|
103
105
|
file_type_is?(k.to_sym)
|
104
106
|
end
|
@@ -108,6 +110,17 @@ module Zip
|
|
108
110
|
@name.end_with?('/')
|
109
111
|
end
|
110
112
|
|
113
|
+
# Is the name a relative path, free of `..` patterns that could lead to
|
114
|
+
# path traversal attacks? This does NOT handle symlinks; if the path
|
115
|
+
# contains symlinks, this check is NOT enough to guarantee safety.
|
116
|
+
def name_safe?
|
117
|
+
cleanpath = Pathname.new(@name).cleanpath
|
118
|
+
return false unless cleanpath.relative?
|
119
|
+
root = ::File::SEPARATOR
|
120
|
+
naive_expanded_path = ::File.join(root, cleanpath.to_s)
|
121
|
+
::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path
|
122
|
+
end
|
123
|
+
|
111
124
|
def local_entry_offset #:nodoc:all
|
112
125
|
local_header_offset + @local_header_size
|
113
126
|
end
|
@@ -146,7 +159,15 @@ module Zip
|
|
146
159
|
end
|
147
160
|
|
148
161
|
# Extracts entry to file dest_path (defaults to @name).
|
149
|
-
|
162
|
+
# NB: The caller is responsible for making sure dest_path is safe, if it
|
163
|
+
# is passed.
|
164
|
+
def extract(dest_path = nil, &block)
|
165
|
+
if dest_path.nil? && !name_safe?
|
166
|
+
puts "WARNING: skipped #{@name} as unsafe"
|
167
|
+
return self
|
168
|
+
end
|
169
|
+
|
170
|
+
dest_path ||= @name
|
150
171
|
block ||= proc { ::Zip.on_exists_proc }
|
151
172
|
|
152
173
|
if directory? || file? || symlink?
|
@@ -233,7 +254,10 @@ module Zip
|
|
233
254
|
@name = io.read(@name_length)
|
234
255
|
extra = io.read(@extra_length)
|
235
256
|
|
236
|
-
@name.
|
257
|
+
@name.tr!('\\', '/')
|
258
|
+
if ::Zip.force_entry_names_encoding
|
259
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
260
|
+
end
|
237
261
|
|
238
262
|
if extra && extra.bytesize != @extra_length
|
239
263
|
raise ::Zip::Error, 'Truncated local zip entry header'
|
@@ -252,13 +276,13 @@ module Zip
|
|
252
276
|
zip64 = @extra['Zip64']
|
253
277
|
[::Zip::LOCAL_ENTRY_SIGNATURE,
|
254
278
|
@version_needed_to_extract, # version needed to extract
|
255
|
-
@gp_flags, # @gp_flags
|
279
|
+
@gp_flags, # @gp_flags
|
256
280
|
@compression_method,
|
257
|
-
@time.to_binary_dos_time, # @last_mod_time
|
258
|
-
@time.to_binary_dos_date, # @last_mod_date
|
281
|
+
@time.to_binary_dos_time, # @last_mod_time
|
282
|
+
@time.to_binary_dos_date, # @last_mod_date
|
259
283
|
@crc,
|
260
|
-
|
261
|
-
|
284
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
285
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
262
286
|
name_size,
|
263
287
|
@extra ? @extra.local_size : 0].pack('VvvvvvVVVvv')
|
264
288
|
end
|
@@ -302,7 +326,7 @@ module Zip
|
|
302
326
|
def set_ftype_from_c_dir_entry
|
303
327
|
@ftype = case @fstype
|
304
328
|
when ::Zip::FSTYPE_UNIX
|
305
|
-
@unix_perms = (@external_file_attributes >> 16) &
|
329
|
+
@unix_perms = (@external_file_attributes >> 16) & 0o7777
|
306
330
|
case (@external_file_attributes >> 28)
|
307
331
|
when ::Zip::FILE_TYPE_DIR
|
308
332
|
:directory
|
@@ -357,7 +381,10 @@ module Zip
|
|
357
381
|
unpack_c_dir_entry(static_sized_fields_buf)
|
358
382
|
check_c_dir_entry_signature
|
359
383
|
set_time(@last_mod_date, @last_mod_time)
|
360
|
-
@name = io.read(@name_length)
|
384
|
+
@name = io.read(@name_length)
|
385
|
+
if ::Zip.force_entry_names_encoding
|
386
|
+
@name.force_encoding(::Zip.force_entry_names_encoding)
|
387
|
+
end
|
361
388
|
read_c_dir_extra_field(io)
|
362
389
|
@comment = io.read(@comment_length)
|
363
390
|
check_c_dir_entry_comment_size
|
@@ -378,14 +405,14 @@ module Zip
|
|
378
405
|
stat = file_stat(path)
|
379
406
|
@unix_uid = stat.uid
|
380
407
|
@unix_gid = stat.gid
|
381
|
-
@unix_perms = stat.mode &
|
408
|
+
@unix_perms = stat.mode & 0o7777
|
382
409
|
end
|
383
410
|
|
384
411
|
def set_unix_permissions_on_path(dest_path)
|
385
412
|
# BUG: does not update timestamps into account
|
386
413
|
# ignore setuid/setgid bits by default. honor if @restore_ownership
|
387
|
-
unix_perms_mask =
|
388
|
-
unix_perms_mask =
|
414
|
+
unix_perms_mask = 0o1777
|
415
|
+
unix_perms_mask = 0o7777 if @restore_ownership
|
389
416
|
::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms
|
390
417
|
::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0
|
391
418
|
# File::utimes()
|
@@ -406,21 +433,21 @@ module Zip
|
|
406
433
|
@header_signature,
|
407
434
|
@version, # version of encoding software
|
408
435
|
@fstype, # filesystem type
|
409
|
-
@version_needed_to_extract, # @versionNeededToExtract
|
410
|
-
@gp_flags, # @gp_flags
|
436
|
+
@version_needed_to_extract, # @versionNeededToExtract
|
437
|
+
@gp_flags, # @gp_flags
|
411
438
|
@compression_method,
|
412
|
-
@time.to_binary_dos_time, # @last_mod_time
|
413
|
-
@time.to_binary_dos_date, # @last_mod_date
|
439
|
+
@time.to_binary_dos_time, # @last_mod_time
|
440
|
+
@time.to_binary_dos_date, # @last_mod_date
|
414
441
|
@crc,
|
415
|
-
|
416
|
-
|
442
|
+
zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size,
|
443
|
+
zip64 && zip64.original_size ? 0xFFFFFFFF : @size,
|
417
444
|
name_size,
|
418
445
|
@extra ? @extra.c_dir_size : 0,
|
419
446
|
comment_size,
|
420
|
-
|
447
|
+
zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start
|
421
448
|
@internal_file_attributes, # file type (binary=0, text=1)
|
422
449
|
@external_file_attributes, # native filesystem attributes
|
423
|
-
|
450
|
+
zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset,
|
424
451
|
@name,
|
425
452
|
@extra,
|
426
453
|
@comment
|
@@ -433,18 +460,18 @@ module Zip
|
|
433
460
|
when ::Zip::FSTYPE_UNIX
|
434
461
|
ft = case @ftype
|
435
462
|
when :file
|
436
|
-
@unix_perms ||=
|
463
|
+
@unix_perms ||= 0o644
|
437
464
|
::Zip::FILE_TYPE_FILE
|
438
465
|
when :directory
|
439
|
-
@unix_perms ||=
|
466
|
+
@unix_perms ||= 0o755
|
440
467
|
::Zip::FILE_TYPE_DIR
|
441
468
|
when :symlink
|
442
|
-
@unix_perms ||=
|
469
|
+
@unix_perms ||= 0o755
|
443
470
|
::Zip::FILE_TYPE_SYMLINK
|
444
471
|
end
|
445
472
|
|
446
473
|
unless ft.nil?
|
447
|
-
@external_file_attributes = (ft << 12 | (@unix_perms &
|
474
|
+
@external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16
|
448
475
|
end
|
449
476
|
end
|
450
477
|
|
@@ -458,7 +485,7 @@ module Zip
|
|
458
485
|
def ==(other)
|
459
486
|
return false unless other.class == self.class
|
460
487
|
# Compares contents of local entry and exposed fields
|
461
|
-
keys_equal = %w
|
488
|
+
keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k|
|
462
489
|
other.__send__(k.to_sym) == __send__(k.to_sym)
|
463
490
|
end
|
464
491
|
keys_equal && time.dos_equals(other.time)
|
@@ -488,7 +515,7 @@ module Zip
|
|
488
515
|
end
|
489
516
|
else
|
490
517
|
zis = ::Zip::InputStream.new(@zipfile, local_header_offset)
|
491
|
-
zis.instance_variable_set(:@
|
518
|
+
zis.instance_variable_set(:@complete_entry, self)
|
492
519
|
zis.get_next_entry
|
493
520
|
if block_given?
|
494
521
|
begin
|
@@ -576,9 +603,21 @@ module Zip
|
|
576
603
|
get_input_stream do |is|
|
577
604
|
set_extra_attributes_on_path(dest_path)
|
578
605
|
|
579
|
-
|
606
|
+
bytes_written = 0
|
607
|
+
warned = false
|
608
|
+
buf = ''.dup
|
580
609
|
while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
|
581
610
|
os << buf
|
611
|
+
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
|
582
621
|
end
|
583
622
|
end
|
584
623
|
end
|
@@ -601,32 +640,9 @@ module Zip
|
|
601
640
|
|
602
641
|
# BUG: create_symlink() does not use &block
|
603
642
|
def create_symlink(dest_path)
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
rescue Errno::ENOENT
|
608
|
-
end
|
609
|
-
|
610
|
-
io = get_input_stream
|
611
|
-
linkto = io.read
|
612
|
-
|
613
|
-
if stat
|
614
|
-
if stat.symlink?
|
615
|
-
if ::File.readlink(dest_path) == linkto
|
616
|
-
return
|
617
|
-
else
|
618
|
-
raise ::Zip::DestinationFileExistsError,
|
619
|
-
"Cannot create symlink '#{dest_path}'. " \
|
620
|
-
'A symlink already exists with that name'
|
621
|
-
end
|
622
|
-
else
|
623
|
-
raise ::Zip::DestinationFileExistsError,
|
624
|
-
"Cannot create symlink '#{dest_path}'. " \
|
625
|
-
'A file already exists with that name'
|
626
|
-
end
|
627
|
-
end
|
628
|
-
|
629
|
-
::File.symlink(linkto, dest_path)
|
643
|
+
# TODO: Symlinks pose security challenges. Symlink support temporarily
|
644
|
+
# removed in view of https://github.com/rubyzip/rubyzip/issues/369 .
|
645
|
+
puts "WARNING: skipped symlink #{dest_path}"
|
630
646
|
end
|
631
647
|
|
632
648
|
# apply missing data from the zip64 extra information field, if present
|