rubyzip 1.1.6 → 1.1.7
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rubyzip might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +66 -13
- data/lib/zip.rb +5 -1
- data/lib/zip/central_directory.rb +1 -1
- data/lib/zip/crypto/encryption.rb +11 -0
- data/lib/zip/crypto/null_encryption.rb +45 -0
- data/lib/zip/crypto/traditional_encryption.rb +99 -0
- data/lib/zip/deflater.rb +6 -3
- data/lib/zip/entry.rb +11 -5
- data/lib/zip/entry_set.rb +5 -6
- data/lib/zip/extra_field.rb +1 -0
- data/lib/zip/extra_field/ntfs.rb +92 -0
- data/lib/zip/file.rb +11 -1
- data/lib/zip/filesystem.rb +4 -0
- data/lib/zip/inflater.rb +4 -3
- data/lib/zip/input_stream.rb +10 -4
- data/lib/zip/output_stream.rb +12 -7
- data/lib/zip/version.rb +1 -1
- data/test/basic_zip_file_test.rb +1 -1
- data/test/central_directory_entry_test.rb +1 -1
- data/test/central_directory_test.rb +5 -1
- data/test/crypto/null_encryption_test.rb +53 -0
- data/test/crypto/traditional_encryption_test.rb +80 -0
- data/test/data/WarnInvalidDate.zip +0 -0
- data/test/data/ntfs.zip +0 -0
- data/test/data/zipWithEncryption.zip +0 -0
- data/test/deflater_test.rb +14 -9
- data/test/encryption_test.rb +42 -0
- data/test/entry_set_test.rb +14 -1
- data/test/entry_test.rb +2 -2
- data/test/errors_test.rb +1 -1
- data/test/extra_field_test.rb +10 -1
- data/test/file_extract_directory_test.rb +3 -2
- data/test/file_extract_test.rb +2 -2
- data/test/file_split_test.rb +2 -2
- data/test/file_test.rb +16 -13
- data/test/filesystem/dir_iterator_test.rb +1 -1
- data/test/filesystem/directory_test.rb +1 -1
- data/test/filesystem/file_mutating_test.rb +1 -1
- data/test/filesystem/file_nonmutating_test.rb +10 -1
- data/test/filesystem/file_stat_test.rb +1 -1
- data/test/inflater_test.rb +1 -1
- data/test/input_stream_test.rb +1 -1
- data/test/ioextras/abstract_input_stream_test.rb +1 -1
- data/test/ioextras/abstract_output_stream_test.rb +1 -1
- data/test/ioextras/fake_io_test.rb +1 -1
- data/test/local_entry_test.rb +14 -11
- data/test/output_stream_test.rb +18 -3
- data/test/pass_thru_compressor_test.rb +2 -2
- data/test/pass_thru_decompressor_test.rb +1 -1
- data/test/settings_test.rb +23 -2
- data/test/test_helper.rb +1 -1
- data/test/unicode_file_names_and_comments_test.rb +16 -4
- data/test/zip64_full_test.rb +5 -1
- data/test/zip64_support_test.rb +1 -1
- metadata +104 -6
- data/test/dummy.txt +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21b11d72a619a00f577f3ac15a65d309d07ab57b
|
4
|
+
data.tar.gz: d2c28c4157e692118e26478c34205094eb2ca055
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 935e9064b3fc98693e8e0eef68f147b9a62ba4dcf25c9f42cf9828a544665d749fd8ff5866b52183a569171fd3aecddb09613d526da1bc43bf34c0b556bcea02
|
7
|
+
data.tar.gz: b9e36842ed0830befe3ca556b954757975291f8c523be419213c5974015742e41c4bb4a4b44c88a746ad22f7656f23162cf5e5d5e7591d917927ae9830ad8dae
|
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# rubyzip
|
2
|
-
[![
|
3
|
-
[![
|
4
|
-
[![
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip)
|
3
|
+
[![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip)
|
5
|
+
[![Coverage Status](https://img.shields.io/coveralls/rubyzip/rubyzip.svg)](https://coveralls.io/r/rubyzip/rubyzip?branch=master)
|
5
6
|
|
6
7
|
rubyzip is a ruby library for reading and writing zip files.
|
7
8
|
|
@@ -9,7 +10,7 @@ rubyzip is a ruby library for reading and writing zip files.
|
|
9
10
|
|
10
11
|
Rubyzip interface changed!!! No need to do `require "zip/zip"` and `Zip` prefix in class names removed.
|
11
12
|
|
12
|
-
If you have issues with any third-party gems
|
13
|
+
If you have issues with any third-party gems that require an old version of rubyzip, you can use this workaround:
|
13
14
|
|
14
15
|
```ruby
|
15
16
|
gem 'rubyzip', '>= 1.0.0' # will load new rubyzip version
|
@@ -58,18 +59,52 @@ end
|
|
58
59
|
```
|
59
60
|
|
60
61
|
### Zipping a directory recursively
|
62
|
+
Copy from [here](https://github.com/rubyzip/rubyzip/blob/05916bf89181e1955118fd3ea059f18acac28cc8/samples/example_recursive.rb )
|
61
63
|
|
62
64
|
```ruby
|
63
65
|
require 'rubygems'
|
64
66
|
require 'zip'
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
67
|
+
# This is a simple example which uses rubyzip to
|
68
|
+
# recursively generate a zip file from the contents of
|
69
|
+
# a specified directory. The directory itself is not
|
70
|
+
# included in the archive, rather just its contents.
|
71
|
+
#
|
72
|
+
# Usage:
|
73
|
+
# require /path/to/the/ZipFileGenerator/Class
|
74
|
+
# directoryToZip = "/tmp/input"
|
75
|
+
# outputFile = "/tmp/out.zip"
|
76
|
+
# zf = ZipFileGenerator.new(directoryToZip, outputFile)
|
77
|
+
# zf.write()
|
78
|
+
|
79
|
+
class ZipFileGenerator
|
80
|
+
# Initialize with the directory to zip and the location of the output archive.
|
81
|
+
def initialize(inputDir, outputFile)
|
82
|
+
@inputDir = inputDir
|
83
|
+
@outputFile = outputFile
|
84
|
+
end
|
85
|
+
# Zip the input directory.
|
86
|
+
def write()
|
87
|
+
entries = Dir.entries(@inputDir); entries.delete("."); entries.delete("..")
|
88
|
+
io = Zip::File.open(@outputFile, Zip::File::CREATE);
|
89
|
+
writeEntries(entries, "", io)
|
90
|
+
io.close();
|
91
|
+
end
|
92
|
+
# A helper method to make the recursion work.
|
93
|
+
private
|
94
|
+
def writeEntries(entries, path, io)
|
95
|
+
entries.each { |e|
|
96
|
+
zipFilePath = path == "" ? e : File.join(path, e)
|
97
|
+
diskFilePath = File.join(@inputDir, zipFilePath)
|
98
|
+
puts "Deflating " + diskFilePath
|
99
|
+
if File.directory?(diskFilePath)
|
100
|
+
io.mkdir(zipFilePath)
|
101
|
+
subdir =Dir.entries(diskFilePath); subdir.delete("."); subdir.delete("..")
|
102
|
+
writeEntries(subdir, zipFilePath, io)
|
103
|
+
else
|
104
|
+
io.get_output_stream(zipFilePath) { |f| f.print(File.open(diskFilePath, "rb").read())}
|
105
|
+
end
|
106
|
+
}
|
107
|
+
end
|
73
108
|
end
|
74
109
|
```
|
75
110
|
|
@@ -111,6 +146,19 @@ Zip::File.open('foo.zip') do |zip_file|
|
|
111
146
|
end
|
112
147
|
```
|
113
148
|
|
149
|
+
### Password Protection (Experimental)
|
150
|
+
|
151
|
+
RubyZip supports reading/writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). AES encryption is not yet supported. It can be used with buffer streams, e.g.:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new('password')) do |out|
|
155
|
+
out.put_next_entry("my_file.txt")
|
156
|
+
out.write my_data
|
157
|
+
end.string
|
158
|
+
```
|
159
|
+
|
160
|
+
This is an experimental feature and the interface for encryption may change in future versions.
|
161
|
+
|
114
162
|
## Known issues
|
115
163
|
|
116
164
|
### Modify docx file with rubyzip
|
@@ -158,12 +206,17 @@ If you want to store non english names and want to open properly file on Windows
|
|
158
206
|
Zip.unicode_names = true
|
159
207
|
```
|
160
208
|
|
209
|
+
In some zip date of files stored in incorrect format. You can hide warning about it by using:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
Zip.warn_invalid_date = false
|
213
|
+
```
|
214
|
+
|
161
215
|
You can set the default compression level like so:
|
162
216
|
|
163
217
|
```ruby
|
164
218
|
Zip.default_compression = Zlib::DEFAULT_COMPRESSION
|
165
219
|
```
|
166
|
-
|
167
220
|
It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION`
|
168
221
|
|
169
222
|
All settings in same time
|
data/lib/zip.rb
CHANGED
@@ -21,6 +21,9 @@ require 'zip/null_compressor'
|
|
21
21
|
require 'zip/null_input_stream'
|
22
22
|
require 'zip/pass_thru_compressor'
|
23
23
|
require 'zip/pass_thru_decompressor'
|
24
|
+
require 'zip/crypto/encryption'
|
25
|
+
require 'zip/crypto/null_encryption'
|
26
|
+
require 'zip/crypto/traditional_encryption'
|
24
27
|
require 'zip/inflater'
|
25
28
|
require 'zip/deflater'
|
26
29
|
require 'zip/streamable_stream'
|
@@ -34,7 +37,7 @@ end
|
|
34
37
|
|
35
38
|
module Zip
|
36
39
|
extend self
|
37
|
-
attr_accessor :unicode_names, :on_exists_proc, :continue_on_exists_proc, :sort_entries, :default_compression, :write_zip64_support
|
40
|
+
attr_accessor :unicode_names, :on_exists_proc, :continue_on_exists_proc, :sort_entries, :default_compression, :write_zip64_support, :warn_invalid_date
|
38
41
|
|
39
42
|
def reset!
|
40
43
|
@_ran_once = false
|
@@ -44,6 +47,7 @@ module Zip
|
|
44
47
|
@sort_entries = false
|
45
48
|
@default_compression = ::Zlib::DEFAULT_COMPRESSION
|
46
49
|
@write_zip64_support = false
|
50
|
+
@warn_invalid_date = true
|
47
51
|
end
|
48
52
|
|
49
53
|
def setup
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Zip
|
2
|
+
module NullEncryption
|
3
|
+
def header_bytesize
|
4
|
+
0
|
5
|
+
end
|
6
|
+
|
7
|
+
def gp_flags
|
8
|
+
0
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class NullEncrypter < Encrypter
|
13
|
+
include NullEncryption
|
14
|
+
|
15
|
+
def header(mtime)
|
16
|
+
''
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypt(data)
|
20
|
+
data
|
21
|
+
end
|
22
|
+
|
23
|
+
def data_descriptor(crc32, compressed_size, uncomprssed_size)
|
24
|
+
''
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class NullDecrypter < Decrypter
|
32
|
+
include NullEncryption
|
33
|
+
|
34
|
+
def decrypt(data)
|
35
|
+
data
|
36
|
+
end
|
37
|
+
|
38
|
+
def reset!(header)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
44
|
+
# rubyzip is free software; you can redistribute it and/or
|
45
|
+
# modify it under the terms of the ruby license.
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Zip
|
2
|
+
module TraditionalEncryption
|
3
|
+
def initialize(password)
|
4
|
+
@password = password
|
5
|
+
reset_keys!
|
6
|
+
end
|
7
|
+
|
8
|
+
def header_bytesize
|
9
|
+
12
|
10
|
+
end
|
11
|
+
|
12
|
+
def gp_flags
|
13
|
+
0x0001 | 0x0008
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def reset_keys!
|
19
|
+
@key0 = 0x12345678
|
20
|
+
@key1 = 0x23456789
|
21
|
+
@key2 = 0x34567890
|
22
|
+
@password.each_byte do |byte|
|
23
|
+
update_keys(byte.chr)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_keys(n)
|
28
|
+
@key0 = ~Zlib.crc32(n, ~@key0)
|
29
|
+
@key1 = ((@key1 + (@key0 & 0xff)) * 134775813 + 1) & 0xffffffff
|
30
|
+
@key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
|
31
|
+
end
|
32
|
+
|
33
|
+
def decrypt_byte
|
34
|
+
temp = (@key2 & 0xffff) | 2
|
35
|
+
((temp * (temp ^ 1)) >> 8) & 0xff
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class TraditionalEncrypter < Encrypter
|
40
|
+
include TraditionalEncryption
|
41
|
+
|
42
|
+
def header(mtime)
|
43
|
+
[].tap do |header|
|
44
|
+
(header_bytesize - 2).times do
|
45
|
+
header << Random.rand(0..255)
|
46
|
+
end
|
47
|
+
header << (mtime.to_binary_dos_time & 0xff)
|
48
|
+
header << (mtime.to_binary_dos_time >> 8)
|
49
|
+
end.map{|x| encode x}.pack("C*")
|
50
|
+
end
|
51
|
+
|
52
|
+
def encrypt(data)
|
53
|
+
data.unpack("C*").map{|x| encode x}.pack("C*")
|
54
|
+
end
|
55
|
+
|
56
|
+
def data_descriptor(crc32, compressed_size, uncomprssed_size)
|
57
|
+
[0x08074b50, crc32, compressed_size, uncomprssed_size].pack("VVVV")
|
58
|
+
end
|
59
|
+
|
60
|
+
def reset!
|
61
|
+
reset_keys!
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def encode(n)
|
67
|
+
t = decrypt_byte
|
68
|
+
update_keys(n.chr)
|
69
|
+
t ^ n
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class TraditionalDecrypter < Decrypter
|
74
|
+
include TraditionalEncryption
|
75
|
+
|
76
|
+
def decrypt(data)
|
77
|
+
data.unpack("C*").map{|x| decode x}.pack("C*")
|
78
|
+
end
|
79
|
+
|
80
|
+
def reset!(header)
|
81
|
+
reset_keys!
|
82
|
+
header.each_byte do |x|
|
83
|
+
decode x
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def decode(n)
|
90
|
+
n ^= decrypt_byte
|
91
|
+
update_keys(n.chr)
|
92
|
+
n
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
98
|
+
# rubyzip is free software; you can redistribute it and/or
|
99
|
+
# modify it under the terms of the ruby license.
|
data/lib/zip/deflater.rb
CHANGED
@@ -1,23 +1,26 @@
|
|
1
1
|
module Zip
|
2
2
|
class Deflater < Compressor #:nodoc:all
|
3
3
|
|
4
|
-
def initialize(output_stream, level = Zip.default_compression)
|
4
|
+
def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new)
|
5
5
|
super()
|
6
6
|
@output_stream = output_stream
|
7
7
|
@zlib_deflater = ::Zlib::Deflate.new(level, -::Zlib::MAX_WBITS)
|
8
8
|
@size = 0
|
9
9
|
@crc = ::Zlib.crc32
|
10
|
+
@encrypter = encrypter
|
11
|
+
@buffer_stream = ::StringIO.new('')
|
10
12
|
end
|
11
13
|
|
12
14
|
def << (data)
|
13
15
|
val = data.to_s
|
14
16
|
@crc = Zlib::crc32(val, @crc)
|
15
17
|
@size += val.bytesize
|
16
|
-
@
|
18
|
+
@buffer_stream << @zlib_deflater.deflate(data)
|
17
19
|
end
|
18
20
|
|
19
21
|
def finish
|
20
|
-
@output_stream << @
|
22
|
+
@output_stream << @encrypter.encrypt(@buffer_stream.string)
|
23
|
+
@output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished?
|
21
24
|
end
|
22
25
|
|
23
26
|
attr_reader :size, :crc
|
data/lib/zip/entry.rb
CHANGED
@@ -74,6 +74,8 @@ module Zip
|
|
74
74
|
def time
|
75
75
|
if @extra['UniversalTime']
|
76
76
|
@extra['UniversalTime'].mtime
|
77
|
+
elsif @extra['NTFS']
|
78
|
+
@extra['NTFS'].mtime
|
77
79
|
else
|
78
80
|
# Standard time field in central directory has local time
|
79
81
|
# under archive creator. Then, we can't get timezone.
|
@@ -84,10 +86,10 @@ module Zip
|
|
84
86
|
alias :mtime :time
|
85
87
|
|
86
88
|
def time=(value)
|
87
|
-
unless @extra.member?('UniversalTime')
|
89
|
+
unless @extra.member?('UniversalTime') || @extra.member?('NTFS')
|
88
90
|
@extra.create('UniversalTime')
|
89
91
|
end
|
90
|
-
@extra['UniversalTime'].mtime = value
|
92
|
+
(@extra['UniversalTime'] || @extra['NTFS']).mtime = value
|
91
93
|
@time = value
|
92
94
|
end
|
93
95
|
|
@@ -141,7 +143,7 @@ module Zip
|
|
141
143
|
end
|
142
144
|
|
143
145
|
def next_header_offset #:nodoc:all
|
144
|
-
local_entry_offset + self.compressed_size
|
146
|
+
local_entry_offset + self.compressed_size + data_descriptor_size
|
145
147
|
end
|
146
148
|
|
147
149
|
# Extracts entry to file dest_path (defaults to @name).
|
@@ -362,7 +364,7 @@ module Zip
|
|
362
364
|
unpack_c_dir_entry(static_sized_fields_buf)
|
363
365
|
check_c_dir_entry_signature
|
364
366
|
set_time(@last_mod_date, @last_mod_time)
|
365
|
-
@name = io.read(@name_length).
|
367
|
+
@name = io.read(@name_length).tr('\\', '/')
|
366
368
|
read_c_dir_extra_field(io)
|
367
369
|
@comment = io.read(@comment_length)
|
368
370
|
check_c_dir_entry_comment_size
|
@@ -569,7 +571,7 @@ module Zip
|
|
569
571
|
def set_time(binary_dos_date, binary_dos_time)
|
570
572
|
@time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time)
|
571
573
|
rescue ArgumentError
|
572
|
-
puts "Invalid date/time in zip entry"
|
574
|
+
puts "Invalid date/time in zip entry" if ::Zip.warn_invalid_date
|
573
575
|
end
|
574
576
|
|
575
577
|
def create_file(dest_path, continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
|
@@ -646,6 +648,10 @@ module Zip
|
|
646
648
|
end
|
647
649
|
end
|
648
650
|
|
651
|
+
def data_descriptor_size
|
652
|
+
(@gp_flags & 0x0008) > 0 ? 16 : 0
|
653
|
+
end
|
654
|
+
|
649
655
|
# create a zip64 extra information field if we need one
|
650
656
|
def prep_zip64_extra(for_local_header) #:nodoc:all
|
651
657
|
return unless ::Zip.write_zip64_support
|
data/lib/zip/entry_set.rb
CHANGED
@@ -38,17 +38,13 @@ module Zip
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def each(&block)
|
41
|
-
@entry_set =
|
41
|
+
@entry_set = sorted_entries.dup.each do |_, value|
|
42
42
|
block.call(value)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
def entries
|
47
|
-
|
48
|
-
@entry_set.values.sort_by{|x| x.name}
|
49
|
-
else
|
50
|
-
@entry_set.values
|
51
|
-
end
|
47
|
+
sorted_entries.values
|
52
48
|
end
|
53
49
|
|
54
50
|
# deep clone
|
@@ -74,6 +70,9 @@ module Zip
|
|
74
70
|
end
|
75
71
|
|
76
72
|
protected
|
73
|
+
def sorted_entries
|
74
|
+
::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set
|
75
|
+
end
|
77
76
|
|
78
77
|
private
|
79
78
|
def to_key(entry)
|
data/lib/zip/extra_field.rb
CHANGED
@@ -94,6 +94,7 @@ require 'zip/extra_field/old_unix'
|
|
94
94
|
require 'zip/extra_field/unix'
|
95
95
|
require 'zip/extra_field/zip64'
|
96
96
|
require 'zip/extra_field/zip64_placeholder'
|
97
|
+
require 'zip/extra_field/ntfs'
|
97
98
|
|
98
99
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
99
100
|
# rubyzip is free software; you can redistribute it and/or
|