archive-zip 0.12.0 → 0.13.0.pre1
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/LICENSE +1 -1
- data/NEWS.md +5 -0
- data/README.md +1 -1
- data/lib/archive/support/ioextensions.rb +5 -7
- data/lib/archive/support/iowindow.rb +34 -87
- data/lib/archive/support/stringio.rb +27 -0
- data/lib/archive/support/zlib.rb +0 -435
- data/lib/archive/zip/codec/deflate/reader.rb +187 -0
- data/lib/archive/zip/codec/deflate/writer.rb +209 -0
- data/lib/archive/zip/codec/deflate.rb +92 -244
- data/lib/archive/zip/codec/null_encryption.rb +4 -184
- data/lib/archive/zip/codec/store/reader.rb +97 -0
- data/lib/archive/zip/codec/store/writer.rb +78 -0
- data/lib/archive/zip/codec/store.rb +6 -231
- data/lib/archive/zip/codec/traditional_encryption/base.rb +85 -0
- data/lib/archive/zip/codec/traditional_encryption/reader.rb +65 -0
- data/lib/archive/zip/codec/traditional_encryption/writer.rb +71 -0
- data/lib/archive/zip/codec/traditional_encryption.rb +6 -360
- data/lib/archive/zip/dos_time.rb +103 -0
- data/lib/archive/zip/entry.rb +86 -70
- data/lib/archive/zip.rb +2 -5
- metadata +35 -227
- data/.yardopts +0 -1
- data/Rakefile +0 -247
- data/lib/archive/support/binary_stringio.rb +0 -30
- data/lib/archive/support/integer.rb +0 -13
- data/lib/archive/support/io-like.rb +0 -14
- data/lib/archive/support/time.rb +0 -119
- data/lib/archive/zip/version.rb +0 -6
- data/spec/archive/dos_time_spec.rb +0 -113
- data/spec/archive/zip/archive_spec.rb +0 -54
- data/spec/archive/zip/codec/deflate/compress/checksum_spec.rb +0 -44
- data/spec/archive/zip/codec/deflate/compress/close_spec.rb +0 -45
- data/spec/archive/zip/codec/deflate/compress/crc32_spec.rb +0 -23
- data/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb +0 -74
- data/spec/archive/zip/codec/deflate/compress/new_spec.rb +0 -39
- data/spec/archive/zip/codec/deflate/compress/open_spec.rb +0 -48
- data/spec/archive/zip/codec/deflate/compress/write_spec.rb +0 -111
- data/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb +0 -20
- data/spec/archive/zip/codec/deflate/decompress/close_spec.rb +0 -34
- data/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb +0 -20
- data/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb +0 -74
- data/spec/archive/zip/codec/deflate/decompress/new_spec.rb +0 -16
- data/spec/archive/zip/codec/deflate/decompress/open_spec.rb +0 -29
- data/spec/archive/zip/codec/deflate/fixtures/classes.rb +0 -25
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin +0 -1
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/archive/zip/codec/deflate/fixtures/raw_file.txt +0 -10
- data/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb +0 -34
- data/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb +0 -16
- data/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb +0 -29
- data/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb +0 -26
- data/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb +0 -27
- data/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb +0 -59
- data/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb +0 -23
- data/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb +0 -34
- data/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb +0 -16
- data/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb +0 -31
- data/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb +0 -28
- data/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb +0 -52
- data/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb +0 -31
- data/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb +0 -31
- data/spec/archive/zip/codec/null_encryption/fixtures/classes.rb +0 -12
- data/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt +0 -10
- data/spec/archive/zip/codec/store/compress/close_spec.rb +0 -34
- data/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb +0 -77
- data/spec/archive/zip/codec/store/compress/new_spec.rb +0 -16
- data/spec/archive/zip/codec/store/compress/open_spec.rb +0 -29
- data/spec/archive/zip/codec/store/compress/rewind_spec.rb +0 -28
- data/spec/archive/zip/codec/store/compress/seek_spec.rb +0 -52
- data/spec/archive/zip/codec/store/compress/tell_spec.rb +0 -31
- data/spec/archive/zip/codec/store/compress/write_spec.rb +0 -29
- data/spec/archive/zip/codec/store/decompress/close_spec.rb +0 -34
- data/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb +0 -75
- data/spec/archive/zip/codec/store/decompress/new_spec.rb +0 -16
- data/spec/archive/zip/codec/store/decompress/open_spec.rb +0 -29
- data/spec/archive/zip/codec/store/decompress/read_spec.rb +0 -26
- data/spec/archive/zip/codec/store/decompress/rewind_spec.rb +0 -27
- data/spec/archive/zip/codec/store/decompress/seek_spec.rb +0 -59
- data/spec/archive/zip/codec/store/decompress/tell_spec.rb +0 -23
- data/spec/archive/zip/codec/store/fixtures/classes.rb +0 -12
- data/spec/archive/zip/codec/store/fixtures/raw_file.txt +0 -10
- data/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb +0 -53
- data/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb +0 -20
- data/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb +0 -43
- data/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb +0 -127
- data/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb +0 -36
- data/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb +0 -80
- data/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb +0 -27
- data/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb +0 -53
- data/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb +0 -20
- data/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb +0 -41
- data/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb +0 -39
- data/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb +0 -73
- data/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb +0 -40
- data/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb +0 -114
- data/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb +0 -27
- data/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin +0 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt +0 -10
- data/spec/binary_stringio/new_spec.rb +0 -40
- data/spec/binary_stringio/set_encoding_spec.rb +0 -17
- data/spec/ioextensions/read_exactly_spec.rb +0 -52
- data/spec/zlib/fixtures/classes.rb +0 -65
- data/spec/zlib/fixtures/compressed_file.bin +0 -1
- data/spec/zlib/fixtures/compressed_file_gzip.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_huffman.bin +0 -2
- data/spec/zlib/fixtures/compressed_file_minmem.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_minwin.bin +0 -1
- data/spec/zlib/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_raw.bin +0 -1
- data/spec/zlib/fixtures/raw_file.txt +0 -10
- data/spec/zlib/zreader/checksum_spec.rb +0 -42
- data/spec/zlib/zreader/close_spec.rb +0 -16
- data/spec/zlib/zreader/compressed_size_spec.rb +0 -20
- data/spec/zlib/zreader/new_spec.rb +0 -43
- data/spec/zlib/zreader/open_spec.rb +0 -51
- data/spec/zlib/zreader/read_spec.rb +0 -58
- data/spec/zlib/zreader/rewind_spec.rb +0 -25
- data/spec/zlib/zreader/seek_spec.rb +0 -57
- data/spec/zlib/zreader/tell_spec.rb +0 -23
- data/spec/zlib/zreader/uncompressed_size_spec.rb +0 -20
- data/spec/zlib/zwriter/checksum_spec.rb +0 -43
- data/spec/zlib/zwriter/close_spec.rb +0 -16
- data/spec/zlib/zwriter/compressed_size_spec.rb +0 -21
- data/spec/zlib/zwriter/new_spec.rb +0 -66
- data/spec/zlib/zwriter/open_spec.rb +0 -70
- data/spec/zlib/zwriter/rewind_spec.rb +0 -28
- data/spec/zlib/zwriter/seek_spec.rb +0 -52
- data/spec/zlib/zwriter/tell_spec.rb +0 -31
- data/spec/zlib/zwriter/uncompressed_size_spec.rb +0 -21
- data/spec/zlib/zwriter/write_spec.rb +0 -28
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'io/like_helpers/delegated_io'
|
4
|
+
|
5
|
+
require 'archive/support/zlib'
|
6
|
+
|
7
|
+
module Archive; class Zip; module Codec; class TraditionalEncryption
|
8
|
+
# Archive::Zip::Codec::TraditionalEncryption::Base provides some basic
|
9
|
+
# methods which are shared between
|
10
|
+
# Archive::Zip::Codec::TraditionalEncryption::Writer and
|
11
|
+
# Archive::Zip::Codec::TraditionalEncryption::Reader.
|
12
|
+
#
|
13
|
+
# Do not use this class directly.
|
14
|
+
class Base < IO::LikeHelpers::DelegatedIO
|
15
|
+
# Creates a new instance of this class. _delegate must be an IO-like
|
16
|
+
# object to be used as a delegate for IO operations. _password_ should be
|
17
|
+
# the encryption key. _mtime_ must be the last modified time of the entry
|
18
|
+
# to be encrypted/decrypted.
|
19
|
+
def initialize(delegate, password, mtime, autoclose: true)
|
20
|
+
super(delegate, autoclose: autoclose)
|
21
|
+
@password = password
|
22
|
+
@mtime = mtime
|
23
|
+
|
24
|
+
initialize_keys
|
25
|
+
end
|
26
|
+
|
27
|
+
# Allows resetting this object and the delegate object back to the
|
28
|
+
# beginning of the stream or reporting the current position in the stream.
|
29
|
+
#
|
30
|
+
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
31
|
+
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
32
|
+
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
33
|
+
# method.
|
34
|
+
def seek(amount, whence = IO::SEEK_SET)
|
35
|
+
assert_open
|
36
|
+
raise Errno::ESPIPE if amount != 0 || whence == IO::SEEK_END
|
37
|
+
|
38
|
+
case whence
|
39
|
+
when IO::SEEK_SET
|
40
|
+
result = super
|
41
|
+
return result if Symbol === result
|
42
|
+
initialize_keys
|
43
|
+
result
|
44
|
+
when IO::SEEK_CUR
|
45
|
+
@bytes_processed
|
46
|
+
else
|
47
|
+
raise Errno::EINVAL
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Initializes the keys used for encrypting/decrypting data by setting the
|
54
|
+
# keys to well known values and then processing them with the password.
|
55
|
+
def initialize_keys
|
56
|
+
@key0 = 0x12345678
|
57
|
+
@key1 = 0x23456789
|
58
|
+
@key2 = 0x34567890
|
59
|
+
@password.each_char { |char| update_keys(char) }
|
60
|
+
|
61
|
+
@bytes_processed = 0
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Updates the keys following the ZIP specification using _char_, which
|
66
|
+
# must be a single byte String.
|
67
|
+
def update_keys(char)
|
68
|
+
# For some reason not explained in the ZIP specification but discovered
|
69
|
+
# in the source for InfoZIP, the old CRC value must first have its bits
|
70
|
+
# flipped before processing. The new CRC value must have its bits
|
71
|
+
# flipped as well for storage and later use. This applies to the
|
72
|
+
# handling of @key0 and @key2.
|
73
|
+
@key0 = ~Zlib.crc32(char, ~@key0)
|
74
|
+
@key1 = ((@key1 + (@key0 & 0xff)) * 134775813 + 1) & 0xffffffff
|
75
|
+
@key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the next decryption byte based on the current keys.
|
80
|
+
def decrypt_byte
|
81
|
+
temp = (@key2 | 2) & 0x0000ffff
|
82
|
+
((temp * (temp ^ 1)) >> 8) & 0x000000ff
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end; end; end; end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'archive/zip/codec/traditional_encryption/base'
|
4
|
+
|
5
|
+
module Archive; class Zip; module Codec; class TraditionalEncryption
|
6
|
+
# Archive::Zip::Codec::TraditionalEncryption::Reader is a readable, IO-like
|
7
|
+
# object which decrypts data data it reads from a delegate IO object using
|
8
|
+
# the traditional encryption algorithm as documented in the ZIP
|
9
|
+
# specification. A _close_ method is also provided which can optionally
|
10
|
+
# close the delegate object.
|
11
|
+
#
|
12
|
+
# Instances of this class should only be accessed via the
|
13
|
+
# Archive::Zip::Codec::TraditionalEncryption#decompressor method.
|
14
|
+
class Reader < Base
|
15
|
+
# Reads, decrypts, and returns at most _length_ bytes from the delegate IO
|
16
|
+
# object.
|
17
|
+
#
|
18
|
+
# Raises EOFError if there is no data to read.
|
19
|
+
def read(length, buffer: nil, buffer_offset: 0)
|
20
|
+
# This short circuits if the header has already been read.
|
21
|
+
result = read_header
|
22
|
+
return result if Symbol === result
|
23
|
+
|
24
|
+
result = super
|
25
|
+
return result if Symbol === result
|
26
|
+
|
27
|
+
if buffer.nil?
|
28
|
+
buffer = result
|
29
|
+
buffer_offset = 0
|
30
|
+
length = buffer.bytesize
|
31
|
+
else
|
32
|
+
length = result
|
33
|
+
end
|
34
|
+
|
35
|
+
buffer[buffer_offset, length].to_enum(:each_byte).each_with_index do |byte, idx|
|
36
|
+
buffer[idx] = (byte ^ decrypt_byte).chr
|
37
|
+
update_keys(buffer[idx])
|
38
|
+
end
|
39
|
+
@bytes_processed += length
|
40
|
+
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def read_header
|
47
|
+
while @header_bytes_needed > 0 do
|
48
|
+
result = delegate.read(@header_bytes_needed)
|
49
|
+
return result if Symbol === result
|
50
|
+
|
51
|
+
result.each_byte do |byte|
|
52
|
+
update_keys((byte ^ decrypt_byte).chr)
|
53
|
+
end
|
54
|
+
@header_bytes_needed -= result.bytesize
|
55
|
+
end
|
56
|
+
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize_keys
|
61
|
+
super
|
62
|
+
@header_bytes_needed = 12
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end; end; end; end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'archive/zip/codec/traditional_encryption/base'
|
4
|
+
require 'archive/zip/dos_time'
|
5
|
+
|
6
|
+
module Archive; class Zip; module Codec; class TraditionalEncryption
|
7
|
+
# Archive::Zip::Codec::TraditionalEncryption::Writer is a writable, IO-like
|
8
|
+
# object which encrypts data written to it using the traditional encryption
|
9
|
+
# algorithm as documented in the ZIP specification and writes the result to
|
10
|
+
# a delegate IO object. A _close_ method is also provided which can
|
11
|
+
# optionally close the delegate object.
|
12
|
+
#
|
13
|
+
# Instances of this class should only be accessed via the
|
14
|
+
# Archive::Zip::Codec::TraditionalEncryption#compressor method.
|
15
|
+
class Writer < Base
|
16
|
+
def initialize(delegate, password, mtime, autoclose: true)
|
17
|
+
super
|
18
|
+
|
19
|
+
# A 12 byte header to protect the encrypted file data from attack. The
|
20
|
+
# first 10 bytes are random, and the last 2 bytes are the low order word
|
21
|
+
# in little endian byte order of the last modified time of the entry in
|
22
|
+
# DOS format.
|
23
|
+
@header =
|
24
|
+
(10.times.map { |_| rand(256) } + [DOSTime.new(@mtime).to_i].pack('V')[0, 2].bytes)
|
25
|
+
.map do |byte|
|
26
|
+
crypt_char = (byte ^ decrypt_byte).chr
|
27
|
+
update_keys(byte.chr)
|
28
|
+
crypt_char
|
29
|
+
end
|
30
|
+
.join
|
31
|
+
end
|
32
|
+
|
33
|
+
# Encrypts and writes _string_ to the delegate IO object. Returns the
|
34
|
+
# number of bytes of _string_ written.
|
35
|
+
def write(buffer, length: buffer.bytesize)
|
36
|
+
result = write_header
|
37
|
+
return result if Symbol === result
|
38
|
+
|
39
|
+
buffer = buffer[0, length] if length < buffer.bytesize
|
40
|
+
buffer.to_enum(:each_byte).each_with_index do |byte, idx|
|
41
|
+
result = super((byte ^ decrypt_byte).chr)
|
42
|
+
if Symbol === result
|
43
|
+
return idx if idx > 0
|
44
|
+
return result
|
45
|
+
end
|
46
|
+
update_keys(byte.chr)
|
47
|
+
@bytes_processed += 1
|
48
|
+
end
|
49
|
+
|
50
|
+
buffer.bytesize
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def write_header
|
56
|
+
while @header_idx < @header.size do
|
57
|
+
result = delegate.write(@header[@header_idx..-1])
|
58
|
+
return result if Symbol === result
|
59
|
+
|
60
|
+
@header_idx += result
|
61
|
+
end
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize_keys
|
67
|
+
super
|
68
|
+
@header_idx = 0
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end; end; end; end
|
@@ -1,366 +1,12 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require 'archive/
|
4
|
-
require 'archive/
|
5
|
-
require 'archive/support/time'
|
6
|
-
require 'archive/support/zlib'
|
7
|
-
require 'archive/zip/codec'
|
3
|
+
require 'archive/zip/codec/traditional_encryption/reader'
|
4
|
+
require 'archive/zip/codec/traditional_encryption/writer'
|
8
5
|
|
9
6
|
module Archive; class Zip; module Codec
|
10
7
|
# Archive::Zip::Codec::TraditionalEncryption is a handle for the traditional
|
11
8
|
# encryption codec.
|
12
9
|
class TraditionalEncryption
|
13
|
-
# Archive::Zip::Codec::TraditionalEncryption::Base provides some basic
|
14
|
-
# methods which are shared between
|
15
|
-
# Archive::Zip::Codec::TraditionalEncryption::Encrypt and
|
16
|
-
# Archive::Zip::Codec::TraditionalEncryption::Decrypt.
|
17
|
-
#
|
18
|
-
# Do not use this class directly.
|
19
|
-
class Base
|
20
|
-
# Creates a new instance of this class. _io_ must be an IO-like object to
|
21
|
-
# be used as a delegate for IO operations. _password_ should be the
|
22
|
-
# encryption key. _mtime_ must be the last modified time of the entry to
|
23
|
-
# be encrypted/decrypted.
|
24
|
-
def initialize(io, password, mtime)
|
25
|
-
@io = io
|
26
|
-
@password = password.nil? ? '' : password
|
27
|
-
@mtime = mtime
|
28
|
-
initialize_keys
|
29
|
-
end
|
30
|
-
|
31
|
-
protected
|
32
|
-
|
33
|
-
# The delegate IO-like object.
|
34
|
-
attr_reader :io
|
35
|
-
# The encryption key.
|
36
|
-
attr_reader :password
|
37
|
-
# The last modified time of the entry being encrypted. This is used in
|
38
|
-
# the entryption header as a way to check the password.
|
39
|
-
attr_reader :mtime
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
# Initializes the keys used for encrypting/decrypting data by setting the
|
44
|
-
# keys to well known values and then processing them with the password.
|
45
|
-
def initialize_keys
|
46
|
-
@key0 = 0x12345678
|
47
|
-
@key1 = 0x23456789
|
48
|
-
@key2 = 0x34567890
|
49
|
-
@password.each_byte { |byte| update_keys(byte.chr) }
|
50
|
-
nil
|
51
|
-
end
|
52
|
-
|
53
|
-
# Updates the keys following the ZIP specification using _char_, which
|
54
|
-
# must be a single byte String.
|
55
|
-
def update_keys(char)
|
56
|
-
# For some reason not explained in the ZIP specification but discovered
|
57
|
-
# in the source for InfoZIP, the old CRC value must first have its bits
|
58
|
-
# flipped before processing. The new CRC value must have its bits
|
59
|
-
# flipped as well for storage and later use. This applies to the
|
60
|
-
# handling of @key0 and @key2.
|
61
|
-
@key0 = ~Zlib.crc32(char, ~@key0)
|
62
|
-
@key1 = ((@key1 + (@key0 & 0xff)) * 134775813 + 1) & 0xffffffff
|
63
|
-
@key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2)
|
64
|
-
nil
|
65
|
-
end
|
66
|
-
|
67
|
-
# Returns the next decryption byte based on the current keys.
|
68
|
-
def decrypt_byte
|
69
|
-
temp = (@key2 | 2) & 0x0000ffff
|
70
|
-
((temp * (temp ^ 1)) >> 8) & 0x000000ff
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Archive::Zip::Codec::TraditionalEncryption::Encrypt is a writable, IO-like
|
75
|
-
# object which encrypts data written to it using the traditional encryption
|
76
|
-
# algorithm as documented in the ZIP specification and writes the result to
|
77
|
-
# a delegate IO object. A _close_ method is also provided which can
|
78
|
-
# optionally close the delegate object.
|
79
|
-
#
|
80
|
-
# Instances of this class should only be accessed via the
|
81
|
-
# Archive::Zip::Codec::TraditionalEncryption#compressor method.
|
82
|
-
class Encrypt < Base
|
83
|
-
include IO::Like
|
84
|
-
|
85
|
-
# Creates a new instance of this class with the given argument using #new
|
86
|
-
# and then passes the instance to the given block. The #close method is
|
87
|
-
# guaranteed to be called after the block completes.
|
88
|
-
#
|
89
|
-
# Equivalent to #new if no block is given.
|
90
|
-
def self.open(io, password, mtime)
|
91
|
-
encrypt_io = new(io, password, mtime)
|
92
|
-
return encrypt_io unless block_given?
|
93
|
-
|
94
|
-
begin
|
95
|
-
yield(encrypt_io)
|
96
|
-
ensure
|
97
|
-
encrypt_io.close unless encrypt_io.closed?
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Creates a new instance of this class using _io_ as a data sink. _io_
|
102
|
-
# must be writable and must provide a write method as IO does or errors
|
103
|
-
# will be raised when performing write operations. _password_ should be
|
104
|
-
# the encryption key. _mtime_ must be the last modified time of the entry
|
105
|
-
# to be encrypted/decrypted.
|
106
|
-
#
|
107
|
-
# The _flush_size_ attribute is set to <tt>0</tt> by default under the
|
108
|
-
# assumption that _io_ is already buffered.
|
109
|
-
def initialize(io, password, mtime)
|
110
|
-
# Keep track of the total number of bytes written.
|
111
|
-
# Set this here so that the call to #initialize_keys caused by the call
|
112
|
-
# to super below does not cause errors in #unbuffered_write due to this
|
113
|
-
# attribute being uninitialized.
|
114
|
-
@total_bytes_in = 0
|
115
|
-
|
116
|
-
# This buffer is used to hold the encrypted version of the string most
|
117
|
-
# recently sent to #unbuffered_write.
|
118
|
-
@encrypt_buffer = ''
|
119
|
-
|
120
|
-
super(io, password, mtime)
|
121
|
-
|
122
|
-
# Assume that the delegate IO object is already buffered.
|
123
|
-
self.flush_size = 0
|
124
|
-
end
|
125
|
-
|
126
|
-
# Closes the stream after flushing the encryption buffer to the delegate.
|
127
|
-
# If _close_delegate_ is +true+, the delegate object used as a data sink
|
128
|
-
# will also be closed using its close method.
|
129
|
-
#
|
130
|
-
# Raises IOError if called more than once.
|
131
|
-
def close(close_delegate = true)
|
132
|
-
flush()
|
133
|
-
begin
|
134
|
-
until @encrypt_buffer.empty? do
|
135
|
-
@encrypt_buffer.slice!(0, io.write(@encrypt_buffer))
|
136
|
-
end
|
137
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
138
|
-
retry if write_ready?
|
139
|
-
end
|
140
|
-
|
141
|
-
super()
|
142
|
-
io.close if close_delegate
|
143
|
-
nil
|
144
|
-
end
|
145
|
-
|
146
|
-
private
|
147
|
-
|
148
|
-
# Extend the inherited initialize_keys method to further initialize the
|
149
|
-
# keys by encrypting and writing a 12 byte header to the delegate IO
|
150
|
-
# object.
|
151
|
-
def initialize_keys
|
152
|
-
super
|
153
|
-
|
154
|
-
# Create and encrypt a 12 byte header to protect the encrypted file data
|
155
|
-
# from attack. The first 10 bytes are random, and the last 2 bytes are
|
156
|
-
# the low order word in little endian byte order of the last modified
|
157
|
-
# time of the entry in DOS format.
|
158
|
-
header = ''
|
159
|
-
10.times do
|
160
|
-
header << rand(256).chr
|
161
|
-
end
|
162
|
-
header << mtime.to_dos_time.pack[0, 2]
|
163
|
-
|
164
|
-
# Take care to ensure that all bytes in the header are written.
|
165
|
-
while header.size > 0 do
|
166
|
-
begin
|
167
|
-
header.slice!(0, unbuffered_write(header))
|
168
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
169
|
-
sleep(1)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
# Reset the total bytes written in order to disregard the header.
|
174
|
-
@total_bytes_in = 0
|
175
|
-
|
176
|
-
nil
|
177
|
-
end
|
178
|
-
|
179
|
-
# Allows resetting this object and the delegate object back to the
|
180
|
-
# beginning of the stream or reporting the current position in the stream.
|
181
|
-
#
|
182
|
-
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
183
|
-
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
184
|
-
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
185
|
-
# method.
|
186
|
-
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
187
|
-
unless offset == 0 &&
|
188
|
-
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
189
|
-
whence == IO::SEEK_CUR) then
|
190
|
-
raise Errno::EINVAL
|
191
|
-
end
|
192
|
-
|
193
|
-
case whence
|
194
|
-
when IO::SEEK_SET
|
195
|
-
io.rewind
|
196
|
-
@encrypt_buffer = ''
|
197
|
-
initialize_keys
|
198
|
-
@total_bytes_in = 0
|
199
|
-
when IO::SEEK_CUR
|
200
|
-
@total_bytes_in
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
# Encrypts and writes _string_ to the delegate IO object. Returns the
|
205
|
-
# number of bytes of _string_ written.
|
206
|
-
def unbuffered_write(string)
|
207
|
-
# First try to write out the contents of the encrypt buffer because if
|
208
|
-
# that raises a failure we can let that pass up the call stack without
|
209
|
-
# having polluted the encryption state.
|
210
|
-
until @encrypt_buffer.empty? do
|
211
|
-
@encrypt_buffer.slice!(0, io.write(@encrypt_buffer))
|
212
|
-
end
|
213
|
-
# At this point we can encrypt the given string into a new buffer and
|
214
|
-
# behave as if it was written.
|
215
|
-
string.each_byte do |byte|
|
216
|
-
temp = decrypt_byte
|
217
|
-
@encrypt_buffer << (byte ^ temp).chr
|
218
|
-
update_keys(byte.chr)
|
219
|
-
end
|
220
|
-
@total_bytes_in += string.length
|
221
|
-
string.length
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
# Archive::Zip::Codec::TraditionalEncryption::Decrypt is a readable, IO-like
|
226
|
-
# object which decrypts data data it reads from a delegate IO object using
|
227
|
-
# the traditional encryption algorithm as documented in the ZIP
|
228
|
-
# specification. A _close_ method is also provided which can optionally
|
229
|
-
# close the delegate object.
|
230
|
-
#
|
231
|
-
# Instances of this class should only be accessed via the
|
232
|
-
# Archive::Zip::Codec::TraditionalEncryption#decompressor method.
|
233
|
-
class Decrypt < Base
|
234
|
-
include IO::Like
|
235
|
-
|
236
|
-
# Creates a new instance of this class with the given argument using #new
|
237
|
-
# and then passes the instance to the given block. The #close method is
|
238
|
-
# guaranteed to be called after the block completes.
|
239
|
-
#
|
240
|
-
# Equivalent to #new if no block is given.
|
241
|
-
def self.open(io, password, mtime)
|
242
|
-
decrypt_io = new(io, password, mtime)
|
243
|
-
return decrypt_io unless block_given?
|
244
|
-
|
245
|
-
begin
|
246
|
-
yield(decrypt_io)
|
247
|
-
ensure
|
248
|
-
decrypt_io.close unless decrypt_io.closed?
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
# Creates a new instance of this class using _io_ as a data source. _io_
|
253
|
-
# must be readable and provide a _read_ method as an IO instance would or
|
254
|
-
# errors will be raised when performing read operations. _password_
|
255
|
-
# should be the encryption key. _mtime_ must be the last modified time of
|
256
|
-
# the entry to be encrypted/decrypted.
|
257
|
-
#
|
258
|
-
# This class has extremely limited seek capabilities. It is possible to
|
259
|
-
# seek with an offset of <tt>0</tt> and a whence of <tt>IO::SEEK_CUR</tt>.
|
260
|
-
# As a result, the _pos_ and _tell_ methods also work as expected.
|
261
|
-
#
|
262
|
-
# Due to certain optimizations within IO::Like#seek and if there is data
|
263
|
-
# in the read buffer, the _seek_ method can be used to seek forward from
|
264
|
-
# the current stream position up to the end of the buffer. Unless it is
|
265
|
-
# known definitively how much data is in the buffer, it is best to avoid
|
266
|
-
# relying on this behavior.
|
267
|
-
#
|
268
|
-
# If _io_ also responds to _rewind_, then the _rewind_ method of this
|
269
|
-
# class can be used to reset the whole stream back to the beginning. Using
|
270
|
-
# _seek_ of this class to seek directly to offset <tt>0</tt> using
|
271
|
-
# <tt>IO::SEEK_SET</tt> for whence will also work in this case.
|
272
|
-
#
|
273
|
-
# Any other seeking attempts, will raise Errno::EINVAL exceptions.
|
274
|
-
#
|
275
|
-
# The _fill_size_ attribute is set to <tt>0</tt> by default under the
|
276
|
-
# assumption that _io_ is already buffered.
|
277
|
-
def initialize(io, password, mtime)
|
278
|
-
# Keep track of the total number of bytes read.
|
279
|
-
# Set this here so that the call to #initialize_keys caused by the call
|
280
|
-
# to super below does not cause errors in #unbuffered_read due to this
|
281
|
-
# attribute being uninitialized.
|
282
|
-
@total_bytes_out = 0
|
283
|
-
|
284
|
-
super(io, password, mtime)
|
285
|
-
|
286
|
-
# Assume that the delegate IO object is already buffered.
|
287
|
-
self.fill_size = 0
|
288
|
-
end
|
289
|
-
|
290
|
-
# Closes this object so that further write operations will fail. If
|
291
|
-
# _close_delegate_ is +true+, the delegate object used as a data source
|
292
|
-
# will also be closed using its close method.
|
293
|
-
def close(close_delegate = true)
|
294
|
-
super()
|
295
|
-
io.close if close_delegate
|
296
|
-
end
|
297
|
-
|
298
|
-
private
|
299
|
-
|
300
|
-
# Extend the inherited initialize_keys method to further initialize the
|
301
|
-
# keys by encrypting and writing a 12 byte header to the delegate IO
|
302
|
-
# object.
|
303
|
-
def initialize_keys
|
304
|
-
super
|
305
|
-
|
306
|
-
# Load the 12 byte header taking care to ensure that all bytes are read.
|
307
|
-
bytes_needed = 12
|
308
|
-
while bytes_needed > 0 do
|
309
|
-
begin
|
310
|
-
bytes_read = unbuffered_read(bytes_needed)
|
311
|
-
bytes_needed -= bytes_read.size
|
312
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
313
|
-
sleep(1)
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
# Reset the total bytes read in order to disregard the header.
|
318
|
-
@total_bytes_out = 0
|
319
|
-
|
320
|
-
nil
|
321
|
-
end
|
322
|
-
|
323
|
-
# Reads, decrypts, and returns at most _length_ bytes from the delegate IO
|
324
|
-
# object.
|
325
|
-
#
|
326
|
-
# Raises EOFError if there is no data to read.
|
327
|
-
def unbuffered_read(length)
|
328
|
-
buffer = io.read(length)
|
329
|
-
raise EOFError, 'end of file reached' if buffer.nil?
|
330
|
-
@total_bytes_out += buffer.length
|
331
|
-
|
332
|
-
0.upto(buffer.length - 1) do |i|
|
333
|
-
buffer[i] = (buffer[i].ord ^ decrypt_byte).chr
|
334
|
-
update_keys(buffer[i].chr)
|
335
|
-
end
|
336
|
-
buffer
|
337
|
-
end
|
338
|
-
|
339
|
-
# Allows resetting this object and the delegate object back to the
|
340
|
-
# beginning of the stream or reporting the current position in the stream.
|
341
|
-
#
|
342
|
-
# Raises Errno::EINVAL unless _offset_ is <tt>0</tt> and _whence_ is
|
343
|
-
# either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_
|
344
|
-
# is IO::SEEK_SEK and the delegate object does not respond to the _rewind_
|
345
|
-
# method.
|
346
|
-
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
347
|
-
unless offset == 0 &&
|
348
|
-
((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) ||
|
349
|
-
whence == IO::SEEK_CUR) then
|
350
|
-
raise Errno::EINVAL
|
351
|
-
end
|
352
|
-
|
353
|
-
case whence
|
354
|
-
when IO::SEEK_SET
|
355
|
-
io.rewind
|
356
|
-
initialize_keys
|
357
|
-
@total_bytes_out = 0
|
358
|
-
when IO::SEEK_CUR
|
359
|
-
@total_bytes_out
|
360
|
-
end
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
10
|
# The last modified time of the entry to be processed. Set this before
|
365
11
|
# calling #encryptor or #decryptor.
|
366
12
|
attr_accessor :mtime
|
@@ -369,20 +15,20 @@ module Archive; class Zip; module Codec
|
|
369
15
|
# Archive::Zip::Entry for encryption codec objects.
|
370
16
|
#
|
371
17
|
# A convenience method for creating an
|
372
|
-
# Archive::Zip::Codec::TraditionalEncryption::
|
18
|
+
# Archive::Zip::Codec::TraditionalEncryption::Writer object using that
|
373
19
|
# class' open method.
|
374
20
|
def encryptor(io, password, &b)
|
375
|
-
|
21
|
+
Writer.open(io, password, mtime, &b)
|
376
22
|
end
|
377
23
|
|
378
24
|
# This method signature is part of the interface contract expected by
|
379
25
|
# Archive::Zip::Entry for encryption codec objects.
|
380
26
|
#
|
381
27
|
# A convenience method for creating an
|
382
|
-
# Archive::Zip::Codec::TraditionalEncryption::
|
28
|
+
# Archive::Zip::Codec::TraditionalEncryption::Reader object using that
|
383
29
|
# class' open method.
|
384
30
|
def decryptor(io, password, &b)
|
385
|
-
|
31
|
+
Reader.open(io, password, mtime, &b)
|
386
32
|
end
|
387
33
|
|
388
34
|
# This method signature is part of the interface contract expected by
|